diff --git a/TEST_MAPPING b/TEST_MAPPING index e66bca07abaebe10e7ecfa95844a4792abd751f8..cd8f3cdcc24c52501bc12d978664ed56cc76b4fe 100644 --- a/TEST_MAPPING +++ b/TEST_MAPPING @@ -4,55 +4,10 @@ "name": "SurfaceFlinger_test", "options": [ { - "include-filter": "*CredentialsTest.*" + "include-filter": "*" }, { - "include-filter": "*SurfaceFlingerStress.*" - }, - { - "include-filter": "*SurfaceInterceptorTest.*" - }, - { - "include-filter": "*LayerTransactionTest.*" - }, - { - "include-filter": "*LayerTypeTransactionTest.*" - }, - { - "include-filter": "*LayerUpdateTest.*" - }, - { - "include-filter": "*GeometryLatchingTest.*" - }, - { - "include-filter": "*CropLatchingTest.*" - }, - { - "include-filter": "*ScreenCaptureTest.*" - }, - { - "include-filter": "*ScreenCaptureChildOnlyTest.*" - }, - { - "include-filter": "*DereferenceSurfaceControlTest.*" - }, - { - "include-filter": "*BoundlessLayerTest.*" - }, - { - "include-filter": "*MultiDisplayLayerBoundsTest.*" - }, - { - "include-filter": "*InvalidHandleTest.*" - }, - { - "include-filter": "*VirtualDisplayTest.*" - }, - { - "include-filter": "*RelativeZTest.*" - }, - { - "include-filter": "*RefreshRateOverlayTest.*" + "exclude-filter": "*ChildLayerTest#ChildrenSurviveParentDestruction" } ] }, @@ -70,58 +25,7 @@ "name": "SurfaceFlinger_test", "options": [ { - "include-filter": "*CredentialsTest.*" - }, - { - "include-filter": "*SurfaceFlingerStress.*" - }, - { - "include-filter": "*SurfaceInterceptorTest.*" - }, - { - "include-filter": "*LayerTransactionTest.*" - }, - { - "include-filter": "*LayerTypeTransactionTest.*" - }, - { - "include-filter": "*LayerUpdateTest.*" - }, - { - "include-filter": "*GeometryLatchingTest.*" - }, - { - "include-filter": "*CropLatchingTest.*" - }, - { - "include-filter": "*ChildLayerTest.*" - }, - { - "include-filter": "*ScreenCaptureTest.*" - }, - { - "include-filter": "*ScreenCaptureChildOnlyTest.*" - }, - { - "include-filter": "*DereferenceSurfaceControlTest.*" - }, - { - "include-filter": "*BoundlessLayerTest.*" - }, - { - "include-filter": "*MultiDisplayLayerBoundsTest.*" - }, - { - "include-filter": "*InvalidHandleTest.*" - }, - { - "include-filter": "*VirtualDisplayTest.*" - }, - { - "include-filter": "*RelativeZTest.*" - }, - { - "include-filter": "*RefreshRateOverlayTest.*" + "include-filter": "*" } ] } diff --git a/cmds/dumpstate/Android.bp b/cmds/dumpstate/Android.bp index a62bd01a5bde68af544ac1e21f52b3260e91acda..860a2d82e64a0502400e0d5a0bdd8e7386c83533 100644 --- a/cmds/dumpstate/Android.bp +++ b/cmds/dumpstate/Android.bp @@ -155,7 +155,10 @@ cc_test { "dumpstate.cpp", "tests/dumpstate_test.cpp", ], - static_libs: ["libgmock"], + static_libs: [ + "libc++fs", + "libgmock", + ], test_config: "dumpstate_test.xml", data: [ ":dumpstate_test_fixture", diff --git a/cmds/dumpstate/DumpstateService.cpp b/cmds/dumpstate/DumpstateService.cpp index e42ee0540bdad5140f1758462296bb5c06dd55a8..a7bc018ff644e231b22368af63f0525e39a65217 100644 --- a/cmds/dumpstate/DumpstateService.cpp +++ b/cmds/dumpstate/DumpstateService.cpp @@ -51,13 +51,20 @@ static binder::Status exception(uint32_t code, const std::string& msg, // Creates a bugreport and exits, thus preserving the oneshot nature of the service. // Note: takes ownership of data. -[[noreturn]] static void* dumpstate_thread_main(void* data) { +[[noreturn]] static void* dumpstate_thread_bugreport(void* data) { std::unique_ptr ds_info(static_cast(data)); ds_info->ds->Run(ds_info->calling_uid, ds_info->calling_package); MYLOGD("Finished taking a bugreport. Exiting.\n"); exit(0); } +[[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); + MYLOGD("Finished retrieving a bugreport. Exiting.\n"); + exit(0); +} + [[noreturn]] static void signalErrorAndExit(sp listener, int error_code) { listener->onError(error_code); exit(0); @@ -84,11 +91,28 @@ status_t DumpstateService::Start() { return android::OK; } +binder::Status DumpstateService::preDumpUiData(const std::string&) { + std::lock_guard lock(lock_); + MYLOGI("preDumpUiData()"); + + if (ds_ != nullptr) { + MYLOGE("Error! DumpstateService is currently already being used. Returning."); + return exception(binder::Status::EX_SERVICE_SPECIFIC, + "DumpstateService is already being used"); + } + + ds_ = &(Dumpstate::GetInstance()); + ds_->PreDumpUiData(); + + return binder::Status::ok(); +} + binder::Status DumpstateService::startBugreport(int32_t calling_uid, const std::string& calling_package, 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) { MYLOGI("startBugreport() with mode: %d\n", bugreport_mode); @@ -96,12 +120,12 @@ binder::Status DumpstateService::startBugreport(int32_t calling_uid, // Ensure there is only one bugreport in progress at a time. std::lock_guard lock(lock_); if (ds_ != nullptr) { - MYLOGE("Error! There is already a bugreport in progress. Returning."); + MYLOGE("Error! DumpstateService is currently already being used. Returning."); if (listener != nullptr) { listener->onError(IDumpstateListener::BUGREPORT_ERROR_ANOTHER_REPORT_IN_PROGRESS); } return exception(binder::Status::EX_SERVICE_SPECIFIC, - "There is already a bugreport in progress"); + "DumpstateService is already being used"); } // From here on, all conditions that indicate we are done with this incoming request should @@ -123,8 +147,8 @@ binder::Status DumpstateService::startBugreport(int32_t calling_uid, } std::unique_ptr options = std::make_unique(); - options->Initialize(static_cast(bugreport_mode), bugreport_fd, - screenshot_fd, is_screenshot_requested); + options->Initialize(static_cast(bugreport_mode), bugreport_flags, + bugreport_fd, screenshot_fd, is_screenshot_requested); if (bugreport_fd.get() == -1 || (options->do_screenshot && screenshot_fd.get() == -1)) { MYLOGE("Invalid filedescriptor"); @@ -148,10 +172,9 @@ binder::Status DumpstateService::startBugreport(int32_t calling_uid, pthread_t thread; // Initialize dumpstate ds_->Initialize(); - status_t err = pthread_create(&thread, nullptr, dumpstate_thread_main, ds_info); + status_t err = pthread_create(&thread, nullptr, dumpstate_thread_bugreport, ds_info); if (err != 0) { delete ds_info; - ds_info = nullptr; MYLOGE("Could not create a thread"); signalErrorAndExit(listener, IDumpstateListener::BUGREPORT_ERROR_RUNTIME_ERROR); } @@ -176,6 +199,41 @@ binder::Status DumpstateService::cancelBugreport(int32_t calling_uid, return binder::Status::ok(); } +binder::Status DumpstateService::retrieveBugreport( + int32_t calling_uid, const std::string& calling_package, + android::base::unique_fd bugreport_fd, + const std::string& bugreport_file, + const sp& listener) { + + ds_ = &(Dumpstate::GetInstance()); + DumpstateInfo* ds_info = new DumpstateInfo(); + ds_info->ds = ds_; + ds_info->calling_uid = calling_uid; + ds_info->calling_package = calling_package; + ds_->listener_ = listener; + std::unique_ptr options = std::make_unique(); + // Use a /dev/null FD when initializing options since none is provided. + android::base::unique_fd devnull_fd( + TEMP_FAILURE_RETRY(open("/dev/null", O_WRONLY | O_CLOEXEC))); + + options->Initialize(Dumpstate::BugreportMode::BUGREPORT_DEFAULT, + 0, bugreport_fd, devnull_fd, false); + + if (bugreport_fd.get() == -1) { + MYLOGE("Invalid filedescriptor"); + signalErrorAndExit(listener, IDumpstateListener::BUGREPORT_ERROR_INVALID_INPUT); + } + ds_->SetOptions(std::move(options)); + ds_->path_ = bugreport_file; + pthread_t thread; + status_t err = pthread_create(&thread, nullptr, dumpstate_thread_retrieve, ds_info); + if (err != 0) { + MYLOGE("Could not create a thread"); + signalErrorAndExit(listener, IDumpstateListener::BUGREPORT_ERROR_RUNTIME_ERROR); + } + return binder::Status::ok(); +} + status_t DumpstateService::dump(int fd, const Vector&) { std::lock_guard lock(lock_); if (ds_ == nullptr) { diff --git a/cmds/dumpstate/DumpstateService.h b/cmds/dumpstate/DumpstateService.h index 3ec8471c045c8b56a613339c79b05dadba3d35e6..dd7331932c61ef99f28da9976f51ecb36c16f198 100644 --- a/cmds/dumpstate/DumpstateService.h +++ b/cmds/dumpstate/DumpstateService.h @@ -38,13 +38,23 @@ class DumpstateService : public BinderService, public BnDumpst status_t dump(int fd, const Vector& args) override; + binder::Status preDumpUiData(const std::string& callingPackage) override; + binder::Status startBugreport(int32_t calling_uid, const std::string& calling_package, android::base::unique_fd bugreport_fd, android::base::unique_fd screenshot_fd, int bugreport_mode, - const sp& listener, + int bugreport_flags, const sp& listener, bool is_screenshot_requested) override; - binder::Status cancelBugreport(int32_t calling_uid, const std::string& calling_package); + binder::Status retrieveBugreport(int32_t calling_uid, + const std::string& calling_package, + android::base::unique_fd bugreport_fd, + const std::string& bugreport_file, + const sp& listener) + override; + + binder::Status cancelBugreport(int32_t calling_uid, + const std::string& calling_package) override; private: // Dumpstate object which contains all the bugreporting logic. diff --git a/cmds/dumpstate/binder/android/os/IDumpstate.aidl b/cmds/dumpstate/binder/android/os/IDumpstate.aidl index 0793f0b95ff1ad7d21f12a1e50ba7fca62498f39..0dc8f5ad64600682abdc0fb5b8d13399e65c62be 100644 --- a/cmds/dumpstate/binder/android/os/IDumpstate.aidl +++ b/cmds/dumpstate/binder/android/os/IDumpstate.aidl @@ -49,6 +49,26 @@ interface IDumpstate { // Default mode. const int BUGREPORT_MODE_DEFAULT = 6; + // Use pre-dumped data. + const int BUGREPORT_FLAG_USE_PREDUMPED_UI_DATA = 0x1; + + // Defer user consent. + const int BUGREPORT_FLAG_DEFER_CONSENT = 0x2; + + /** + * Speculatively pre-dumps UI data for a bugreport request that might come later. + * + *

Triggers the dump of certain critical UI data, e.g. traces stored in short + * ring buffers that might get lost by the time the actual bugreport is requested. + * + *

{@code startBugreport} will then pick the pre-dumped data if: + * - {@link BUGREPORT_FLAG_USE_PREDUMPED_UI_DATA} is specified. + * - {@code preDumpUiData} and {@code startBugreport} were called by the same UID. + * + * @param callingPackage package of the original application that requested the report. + */ + void preDumpUiData(@utf8InCpp String callingPackage); + /** * Starts a bugreport in the background. * @@ -63,13 +83,14 @@ interface IDumpstate { * @param bugreportFd the file to which the zipped bugreport should be written * @param screenshotFd the file to which screenshot should be written * @param bugreportMode the mode that specifies other run time options; must be one of above + * @param bugreportFlags flags to customize the bugreport generation * @param listener callback for updates; optional * @param isScreenshotRequested indicates screenshot is requested or not */ void startBugreport(int callingUid, @utf8InCpp String callingPackage, FileDescriptor bugreportFd, FileDescriptor screenshotFd, - int bugreportMode, IDumpstateListener listener, - boolean isScreenshotRequested); + int bugreportMode, int bugreportFlags, + IDumpstateListener listener, boolean isScreenshotRequested); /** * Cancels the bugreport currently in progress. @@ -82,4 +103,22 @@ interface IDumpstate { * @param callingPackage package of the original application that requested the cancellation. */ void cancelBugreport(int callingUid, @utf8InCpp String callingPackage); + + /** + * Retrieves a previously generated bugreport. + * + *

The caller must have previously generated a bugreport using + * {@link #startBugreport} with the {@link BUGREPORT_FLAG_DEFER_CONSENT} + * flag set. + * + * @param callingUid UID of the original application that requested the report. + * @param callingPackage package of the original application that requested the report. + * @param bugreportFd the file to which the zipped bugreport should be written + * @param bugreportFile the path of the bugreport file + * @param listener callback for updates; optional + */ + void retrieveBugreport(int callingUid, @utf8InCpp String callingPackage, + FileDescriptor bugreportFd, + @utf8InCpp String bugreportFile, + IDumpstateListener listener); } diff --git a/cmds/dumpstate/binder/android/os/IDumpstateListener.aidl b/cmds/dumpstate/binder/android/os/IDumpstateListener.aidl index 50c1624dc20e0a253450c359839b25776f83823d..e8891d3236cc2f76288c9f29ca6c477ded792bc2 100644 --- a/cmds/dumpstate/binder/android/os/IDumpstateListener.aidl +++ b/cmds/dumpstate/binder/android/os/IDumpstateListener.aidl @@ -50,6 +50,9 @@ interface IDumpstateListener { /* There is currently a bugreport running. The caller should try again later. */ const int BUGREPORT_ERROR_ANOTHER_REPORT_IN_PROGRESS = 5; + /* There is no bugreport to retrieve for the given caller. */ + const int BUGREPORT_ERROR_NO_BUGREPORT_TO_RETRIEVE = 6; + /** * Called on an error condition with one of the error codes listed above. */ @@ -57,8 +60,10 @@ interface IDumpstateListener { /** * Called when taking bugreport finishes successfully. + * + * @param bugreportFile The location of the bugreport file */ - oneway void onFinished(); + oneway void onFinished(@utf8InCpp String bugreportFile); /** * Called when screenshot is taken. diff --git a/cmds/dumpstate/dumpstate.cpp b/cmds/dumpstate/dumpstate.cpp index e18c8e380dc209a8add8e3db3cf060240fd0e12d..6fdf1c5233aae4fb0b58fcfe04d65623daba8e16 100644 --- a/cmds/dumpstate/dumpstate.cpp +++ b/cmds/dumpstate/dumpstate.cpp @@ -74,6 +74,7 @@ #include #include #include +#include #include #include #include @@ -243,6 +244,7 @@ static const std::string DUMP_NETSTATS_PROTO_TASK = "DUMP NETSTATS PROTO"; static const std::string DUMP_HALS_TASK = "DUMP HALS"; static const std::string DUMP_BOARD_TASK = "dumpstate_board()"; static const std::string DUMP_CHECKINS_TASK = "DUMP CHECKINS"; +static const std::string POST_PROCESS_UI_TRACES_TASK = "POST-PROCESS UI TRACES"; namespace android { namespace os { @@ -1072,7 +1074,7 @@ static void DumpNetstatsProto() { return; } RunCommandToFd(fd, "", {"dumpsys", "netstats", "--proto"}, - CommandOptions::WithTimeout(120).Build()); + CommandOptions::WithTimeout(5).Build()); bool empty = 0 == lseek(fd, 0, SEEK_END); if (!empty) { ds.EnqueueAddZipEntryAndCleanupIfNeeded(kProtoPath + "netstats" + kProtoExt, @@ -1628,16 +1630,16 @@ static void DumpAppInfos(int out_fd = STDOUT_FILENO) { // via the consent they are shown. Ignores other errors that occur while running various // commands. The consent checking is currently done around long running tasks, which happen to // be distributed fairly evenly throughout the function. -static Dumpstate::RunStatus dumpstate() { +Dumpstate::RunStatus Dumpstate::dumpstate() { DurationReporter duration_reporter("DUMPSTATE"); // Enqueue slow functions into the thread pool, if the parallel run is enabled. std::future dump_hals, dump_incident_report, dump_board, dump_checkins, - dump_netstats_report; + dump_netstats_report, post_process_ui_traces; if (ds.dump_pool_) { // Pool was shutdown in DumpstateDefaultAfterCritical method in order to - // drop root user. Restarts it with two threads for the parallel run. - ds.dump_pool_->start(/* thread_counts = */2); + // drop root user. Restarts it. + ds.dump_pool_->start(/* thread_counts = */3); dump_hals = ds.dump_pool_->enqueueTaskWithFd(DUMP_HALS_TASK, &DumpHals, _1); dump_incident_report = ds.dump_pool_->enqueueTask( @@ -1647,6 +1649,8 @@ static Dumpstate::RunStatus dumpstate() { dump_board = ds.dump_pool_->enqueueTaskWithFd( DUMP_BOARD_TASK, &Dumpstate::DumpstateBoard, &ds, _1); dump_checkins = ds.dump_pool_->enqueueTaskWithFd(DUMP_CHECKINS_TASK, &DumpCheckins, _1); + post_process_ui_traces = ds.dump_pool_->enqueueTask( + POST_PROCESS_UI_TRACES_TASK, &Dumpstate::MaybePostProcessUiTraces, &ds); } // Dump various things. Note that anything that takes "long" (i.e. several seconds) should @@ -1777,11 +1781,6 @@ static Dumpstate::RunStatus dumpstate() { DumpFile("BINDER STATS", binder_logs_dir + "/stats"); DumpFile("BINDER STATE", binder_logs_dir + "/state"); - /* Add window and surface trace files. */ - if (!PropertiesHelper::IsUserBuild()) { - ds.AddDir(WMTRACE_DATA_DIR, false); - } - ds.AddDir(SNAPSHOTCTL_LOG_DIR, false); if (ds.dump_pool_) { @@ -1861,6 +1860,14 @@ static Dumpstate::RunStatus dumpstate() { DumpIncidentReport); } + if (ds.dump_pool_) { + WaitForTask(std::move(post_process_ui_traces)); + } else { + RUN_SLOW_FUNCTION_AND_LOG(POST_PROCESS_UI_TRACES_TASK, MaybePostProcessUiTraces); + } + + MaybeAddUiTracesToZip(); + return Dumpstate::RunStatus::OK; } @@ -2664,10 +2671,13 @@ bool Dumpstate::FinishZipFile() { return true; } -static void SendBroadcast(const std::string& action, const std::vector& args) { +static void SendBroadcast(const std::string& action, + const std::vector& args, + int32_t user_id) { // clang-format off - std::vector am = {"/system/bin/cmd", "activity", "broadcast", "--user", "0", - "--receiver-foreground", "--receiver-include-background", "-a", action}; + std::vector am = {"/system/bin/cmd", "activity", "broadcast", "--user", + std::to_string(user_id), "--receiver-foreground", + "--receiver-include-background", "-a", action}; // clang-format on am.insert(am.end(), args.begin(), args.end()); @@ -2802,6 +2812,11 @@ static inline const char* ModeToString(Dumpstate::BugreportMode mode) { } } +static bool IsConsentlessBugreportAllowed(const Dumpstate::DumpOptions& options) { + // only BUGREPORT_TELEPHONY does not allow using consentless bugreport + return !options.telephony_only; +} + static void SetOptionsFromMode(Dumpstate::BugreportMode mode, Dumpstate::DumpOptions* options, bool is_screenshot_requested) { // Modify com.android.shell.BugreportProgressService#isDefaultScreenshotRequired as well for @@ -2857,9 +2872,12 @@ static void LogDumpOptions(const Dumpstate::DumpOptions& options) { } 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) { + this->use_predumped_ui_data = bugreport_flags & BugreportFlag::BUGREPORT_USE_PREDUMPED_UI_DATA; + this->is_consent_deferred = bugreport_flags & BugreportFlag::BUGREPORT_FLAG_DEFER_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)); @@ -2942,10 +2960,64 @@ void Dumpstate::Initialize() { Dumpstate::RunStatus Dumpstate::Run(int32_t calling_uid, const std::string& calling_package) { Dumpstate::RunStatus status = RunInternal(calling_uid, calling_package); - if (listener_ != nullptr) { + HandleRunStatus(status); + return status; +} + +Dumpstate::RunStatus Dumpstate::Retrieve(int32_t calling_uid, const std::string& calling_package) { + Dumpstate::RunStatus status = RetrieveInternal(calling_uid, calling_package); + HandleRunStatus(status); + return status; +} + +Dumpstate::RunStatus Dumpstate::RetrieveInternal(int32_t calling_uid, + const std::string& calling_package) { + 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 = + android::os::CopyFileToFd(path_, options_->bugreport_fd.get()); + if (copy_succeeded) { + android::os::UnlinkAndLogOnError(path_); + } + return copy_succeeded ? Dumpstate::RunStatus::OK + : Dumpstate::RunStatus::ERROR; +} + +void Dumpstate::HandleRunStatus(Dumpstate::RunStatus status) { + if (listener_ != nullptr) { switch (status) { case Dumpstate::RunStatus::OK: - listener_->onFinished(); + listener_->onFinished(path_.c_str()); break; case Dumpstate::RunStatus::HELP: break; @@ -2963,9 +3035,7 @@ Dumpstate::RunStatus Dumpstate::Run(int32_t calling_uid, const std::string& call break; } } - return status; } - void Dumpstate::Cancel() { CleanupTmpFiles(); android::os::UnlinkAndLogOnError(log_path_); @@ -2987,6 +3057,10 @@ void Dumpstate::Cancel() { } } +void Dumpstate::PreDumpUiData() { + MaybeSnapshotUiTraces(); +} + /* * Dumps relevant information to a bugreport based on the given options. * @@ -3103,7 +3177,8 @@ Dumpstate::RunStatus Dumpstate::RunInternal(int32_t calling_uid, }; // clang-format on // Send STARTED broadcast for apps that listen to bugreport generation events - SendBroadcast("com.android.internal.intent.action.BUGREPORT_STARTED", am_args); + SendBroadcast("com.android.internal.intent.action.BUGREPORT_STARTED", + am_args, multiuser_get_user_id(calling_uid)); if (options_->progress_updates_to_socket) { dprintf(control_socket_fd_, "BEGIN:%s\n", path_.c_str()); } @@ -3184,9 +3259,9 @@ Dumpstate::RunStatus Dumpstate::RunInternal(int32_t calling_uid, // The trace file is added to the zip by MaybeAddSystemTraceToZip(). MaybeSnapshotSystemTrace(); - // If a winscope trace is running, snapshot it now. It will be pulled into bugreport later - // from WMTRACE_DATA_DIR. - MaybeSnapshotWinTrace(); + // Snapshot the UI traces now (if running). + // The trace files will be added to bugreport later. + MaybeSnapshotUiTraces(); } onUiIntensiveBugreportDumpsFinished(calling_uid); MaybeCheckUserConsent(calling_uid, calling_package); @@ -3217,7 +3292,7 @@ Dumpstate::RunStatus Dumpstate::RunInternal(int32_t calling_uid, // Share the final file with the caller if the user has consented or Shell is the caller. Dumpstate::RunStatus status = Dumpstate::RunStatus::OK; - if (CalledByApi()) { + if (CalledByApi() && !options_->is_consent_deferred) { status = CopyBugreportIfUserConsented(calling_uid); if (status != Dumpstate::RunStatus::OK && status != Dumpstate::RunStatus::USER_CONSENT_TIMED_OUT) { @@ -3301,28 +3376,67 @@ void Dumpstate::MaybeSnapshotSystemTrace() { // file in the later stages. } -void Dumpstate::MaybeSnapshotWinTrace() { - // Include the proto logging from WMShell. - RunCommand( - // Empty name because it's not intended to be classified as a bugreport section. - // Actual logging files can be found as "/data/misc/wmtrace/shell_log.winscope" - // in the bugreport. - "", {"dumpsys", "activity", "service", "SystemUIService", - "WMShell", "protolog", "save-for-bugreport"}, - CommandOptions::WithTimeout(10).Always().DropRoot().RedirectStderr().Build()); - - // Currently WindowManagerService and InputMethodManagerSerivice support WinScope protocol. - for (const auto& service : {"window", "input_method"}) { +void Dumpstate::MaybeSnapshotUiTraces() { + if (PropertiesHelper::IsUserBuild() || options_->use_predumped_ui_data) { + return; + } + + const std::vector> dumpTracesForBugReportCommands = { + {"dumpsys", "activity", "service", "SystemUIService", "WMShell", "protolog", + "save-for-bugreport"}, + {"dumpsys", "activity", "service", "SystemUIService", "WMShell", "transitions", "tracing", + "save-for-bugreport"}, + {"cmd", "input_method", "tracing", "save-for-bugreport"}, + {"cmd", "window", "tracing", "save-for-bugreport"}, + {"cmd", "window", "shell", "tracing", "save-for-bugreport"}, + }; + + for (const auto& command : dumpTracesForBugReportCommands) { RunCommand( // Empty name because it's not intended to be classified as a bugreport section. // Actual tracing files can be found in "/data/misc/wmtrace/" in the bugreport. - "", {"cmd", service, "tracing", "save-for-bugreport"}, + "", command, CommandOptions::WithTimeout(10).Always().DropRoot().RedirectStderr().Build()); } + + // This command needs to be run as root + static const auto SURFACEFLINGER_COMMAND_SAVE_ALL_TRACES = std::vector { + "service", "call", "SurfaceFlinger", "1042" + }; + // Empty name because it's not intended to be classified as a bugreport section. + // Actual tracing files can be found in "/data/misc/wmtrace/" in the bugreport. + RunCommand( + "", SURFACEFLINGER_COMMAND_SAVE_ALL_TRACES, + CommandOptions::WithTimeout(10).Always().AsRoot().RedirectStderr().Build()); +} + +void Dumpstate::MaybePostProcessUiTraces() { + if (PropertiesHelper::IsUserBuild()) { + return; + } + + RunCommand( + // Empty name because it's not intended to be classified as a bugreport section. + // Actual tracing files can be found in "/data/misc/wmtrace/" in the bugreport. + "", { + "/system/xbin/su", "system", + "/system/bin/layertracegenerator", + "/data/misc/wmtrace/transactions_trace.winscope", + "/data/misc/wmtrace/layers_trace_from_transactions.winscope" + }, + CommandOptions::WithTimeout(120).Always().RedirectStderr().Build()); +} + +void Dumpstate::MaybeAddUiTracesToZip() { + if (PropertiesHelper::IsUserBuild()) { + return; + } + + ds.AddDir(WMTRACE_DATA_DIR, false); } void Dumpstate::onUiIntensiveBugreportDumpsFinished(int32_t calling_uid) { - if (calling_uid == AID_SHELL || !CalledByApi()) { + if (multiuser_get_app_id(calling_uid) == AID_SHELL || !CalledByApi()) { return; } if (listener_ != nullptr) { @@ -3333,9 +3447,11 @@ void Dumpstate::onUiIntensiveBugreportDumpsFinished(int32_t calling_uid) { } void Dumpstate::MaybeCheckUserConsent(int32_t calling_uid, const std::string& calling_package) { - if (calling_uid == AID_SHELL || !CalledByApi()) { - // No need to get consent for shell triggered dumpstates, or not through - // bugreporting API (i.e. no fd to copy back). + if (multiuser_get_app_id(calling_uid) == AID_SHELL || + !CalledByApi() || options_->is_consent_deferred) { + // 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. return; } consent_callback_ = new ConsentCallback(); @@ -3344,9 +3460,12 @@ void Dumpstate::MaybeCheckUserConsent(int32_t calling_uid, const std::string& ca android::String16 package(calling_package.c_str()); if (ics != nullptr) { MYLOGD("Checking user consent via incidentcompanion service\n"); + int flags = 0x1; // IncidentManager.FLAG_CONFIRMATION_DIALOG + if (IsConsentlessBugreportAllowed(*options_)) { + flags |= 0x2; // IncidentManager.FLAG_ALLOW_CONSENTLESS_BUGREPORT + } android::interface_cast(ics)->authorizeReport( - calling_uid, package, String16(), String16(), - 0x1 /* FLAG_CONFIRMATION_DIALOG */, consent_callback_.get()); + calling_uid, package, String16(), String16(), flags, consent_callback_.get()); } else { MYLOGD("Unable to check user consent; incidentcompanion service unavailable\n"); } @@ -3415,7 +3534,7 @@ 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 (calling_uid == AID_SHELL) { + if (multiuser_get_app_id(calling_uid) == AID_SHELL) { consent_result = UserConsentResult::APPROVED; } else { consent_result = consent_callback_->getResult(); @@ -3486,7 +3605,7 @@ Dumpstate::RunStatus Dumpstate::ParseCommandlineAndRun(int argc, char* argv[]) { // an app; they are irrelevant here because bugreport is triggered via command line. // Update Last ID before calling Run(). Initialize(); - status = Run(-1 /* calling_uid */, "" /* calling_package */); + status = Run(0 /* calling_uid */, "" /* calling_package */); } return status; } diff --git a/cmds/dumpstate/dumpstate.h b/cmds/dumpstate/dumpstate.h index 7ffe80eba3029688242fdcfdf09d1789dcfe2f6d..8a31c314d9dff8683a88525d60413276849f2699 100644 --- a/cmds/dumpstate/dumpstate.h +++ b/cmds/dumpstate/dumpstate.h @@ -204,6 +204,14 @@ class Dumpstate { BUGREPORT_DEFAULT = android::os::IDumpstate::BUGREPORT_MODE_DEFAULT }; + // The flags used to customize bugreport requests. + enum BugreportFlag { + BUGREPORT_USE_PREDUMPED_UI_DATA = + android::os::IDumpstate::BUGREPORT_FLAG_USE_PREDUMPED_UI_DATA, + BUGREPORT_FLAG_DEFER_CONSENT = + android::os::IDumpstate::BUGREPORT_FLAG_DEFER_CONSENT + }; + static android::os::dumpstate::CommandOptions DEFAULT_DUMPSYS; static Dumpstate& GetInstance(); @@ -333,6 +341,12 @@ class Dumpstate { struct DumpOptions; + /** + * Pre-dump critical UI data, e.g. data stored in short ring buffers that might get lost + * by the time the actual bugreport is requested. + */ + void PreDumpUiData(); + /* * Main entry point for running a complete bugreport. * @@ -341,6 +355,15 @@ class Dumpstate { */ RunStatus Run(int32_t calling_uid, const std::string& calling_package); + /* + * Entry point for retrieving a previous-generated bugreport. + * + * Initialize() dumpstate before calling this method. + */ + RunStatus Retrieve(int32_t calling_uid, const std::string& calling_package); + + + RunStatus ParseCommandlineAndRun(int argc, char* argv[]); /* Deletes in-progress files */ @@ -384,6 +407,7 @@ class Dumpstate { bool progress_updates_to_socket = false; bool do_screenshot = false; bool is_screenshot_copied = false; + bool is_consent_deferred = false; bool is_remote_mode = false; bool show_header_only = false; bool telephony_only = false; @@ -396,6 +420,8 @@ class Dumpstate { // TODO(b/148168577) get rid of the AIDL values, replace them with the HAL values instead. // The HAL is actually an API surface that can be validated, while the AIDL is not (@hide). BugreportMode bugreport_mode = Dumpstate::BugreportMode::BUGREPORT_DEFAULT; + // Will use data collected through a previous call to PreDumpUiData(). + bool use_predumped_ui_data; // File descriptor to output zip file. Takes precedence over out_dir. android::base::unique_fd bugreport_fd; // File descriptor to screenshot file. @@ -414,7 +440,8 @@ class Dumpstate { RunStatus Initialize(int argc, char* argv[]); /* Initializes options from the requested mode. */ - void Initialize(BugreportMode bugreport_mode, const android::base::unique_fd& bugreport_fd, + 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); @@ -533,12 +560,16 @@ 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); RunStatus DumpstateDefaultAfterCritical(); + RunStatus dumpstate(); void MaybeTakeEarlyScreenshot(); void MaybeSnapshotSystemTrace(); - void MaybeSnapshotWinTrace(); + void MaybeSnapshotUiTraces(); + void MaybePostProcessUiTraces(); + void MaybeAddUiTracesToZip(); void onUiIntensiveBugreportDumpsFinished(int32_t calling_uid); @@ -554,6 +585,8 @@ class Dumpstate { RunStatus HandleUserConsentDenied(); + void HandleRunStatus(RunStatus status); + // Copies bugreport artifacts over to the caller's directories provided there is user consent or // called by Shell. RunStatus CopyBugreportIfUserConsented(int32_t calling_uid); diff --git a/cmds/dumpstate/main.cpp b/cmds/dumpstate/main.cpp index ec89c0dd6e07918280a718826b7b45563ab3bd1a..a634f9371fc8cd90e79e11d8cbab112add9cac3f 100644 --- a/cmds/dumpstate/main.cpp +++ b/cmds/dumpstate/main.cpp @@ -56,7 +56,7 @@ int main(int argc, char* argv[]) { MYLOGE("Unable to start 'dumpstate' service: %d", ret); exit(1); } - MYLOGI("'dumpstate' service started and will wait for a call to startBugreport()"); + MYLOGI("'dumpstate' service started and will wait for a call"); // Waits forever for an incoming connection. // TODO(b/111441001): should this time out? diff --git a/cmds/dumpstate/tests/dumpstate_smoke_test.cpp b/cmds/dumpstate/tests/dumpstate_smoke_test.cpp index 28e5ee2ca915de00d3692ab451c46906b1202efa..ccf64fe54e8664c90474a639cb538c9a7342e5c6 100644 --- a/cmds/dumpstate/tests/dumpstate_smoke_test.cpp +++ b/cmds/dumpstate/tests/dumpstate_smoke_test.cpp @@ -160,7 +160,7 @@ class DumpstateListener : public BnDumpstateListener { return binder::Status::ok(); } - binder::Status onFinished() override { + binder::Status onFinished([[maybe_unused]] const std::string& bugreport_file) override { std::lock_guard lock(lock_); is_finished_ = true; dprintf(out_fd_, "\rFinished"); @@ -497,14 +497,17 @@ TEST_F(DumpstateBinderTest, Baseline) { // Prepare arguments unique_fd bugreport_fd(OpenForWrite("/bugreports/tmp.zip")); unique_fd screenshot_fd(OpenForWrite("/bugreports/tmp.png")); + int flags = 0; EXPECT_NE(bugreport_fd.get(), -1); EXPECT_NE(screenshot_fd.get(), -1); sp listener(new DumpstateListener(dup(fileno(stdout)))); android::binder::Status status = - ds_binder->startBugreport(123, "com.dummy.package", std::move(bugreport_fd), std::move(screenshot_fd), - Dumpstate::BugreportMode::BUGREPORT_INTERACTIVE, listener, true); + ds_binder->startBugreport(123, "com.example.package", std::move(bugreport_fd), + std::move(screenshot_fd), + Dumpstate::BugreportMode::BUGREPORT_INTERACTIVE, flags, listener, + true); // startBugreport is an async call. Verify binder call succeeded first, then wait till listener // gets expected callbacks. EXPECT_TRUE(status.isOk()); @@ -532,6 +535,7 @@ TEST_F(DumpstateBinderTest, ServiceDies_OnInvalidInput) { // Prepare arguments unique_fd bugreport_fd(OpenForWrite("/data/local/tmp/tmp.zip")); unique_fd screenshot_fd(OpenForWrite("/data/local/tmp/tmp.png")); + int flags = 0; EXPECT_NE(bugreport_fd.get(), -1); EXPECT_NE(screenshot_fd.get(), -1); @@ -539,9 +543,9 @@ TEST_F(DumpstateBinderTest, ServiceDies_OnInvalidInput) { // Call startBugreport with bad arguments. sp listener(new DumpstateListener(dup(fileno(stdout)))); android::binder::Status status = - ds_binder->startBugreport(123, "com.dummy.package", std::move(bugreport_fd), std::move(screenshot_fd), - 2000, // invalid bugreport mode - listener, false); + ds_binder->startBugreport(123, "com.example.package", std::move(bugreport_fd), + std::move(screenshot_fd), 2000, // invalid bugreport mode + flags, listener, false); EXPECT_EQ(listener->getErrorCode(), IDumpstateListener::BUGREPORT_ERROR_INVALID_INPUT); // The service should have died, freeing itself up for a new invocation. @@ -563,6 +567,7 @@ TEST_F(DumpstateBinderTest, SimultaneousBugreportsNotAllowed) { unique_fd bugreport_fd2(dup(bugreport_fd.get())); unique_fd screenshot_fd(OpenForWrite("/data/local/tmp/tmp.png")); unique_fd screenshot_fd2(dup(screenshot_fd.get())); + int flags = 0; EXPECT_NE(bugreport_fd.get(), -1); EXPECT_NE(bugreport_fd2.get(), -1); @@ -571,14 +576,18 @@ TEST_F(DumpstateBinderTest, SimultaneousBugreportsNotAllowed) { sp listener1(new DumpstateListener(dup(fileno(stdout)))); android::binder::Status status = - ds_binder->startBugreport(123, "com.dummy.package", std::move(bugreport_fd), std::move(screenshot_fd), - Dumpstate::BugreportMode::BUGREPORT_INTERACTIVE, listener1, true); + ds_binder->startBugreport(123, "com.example.package", std::move(bugreport_fd), + std::move(screenshot_fd), + Dumpstate::BugreportMode::BUGREPORT_INTERACTIVE, flags, listener1, + true); EXPECT_TRUE(status.isOk()); // try to make another call to startBugreport. This should fail. sp listener2(new DumpstateListener(dup(fileno(stdout)))); - status = ds_binder->startBugreport(123, "com.dummy.package", std::move(bugreport_fd2), std::move(screenshot_fd2), - Dumpstate::BugreportMode::BUGREPORT_INTERACTIVE, listener2, true); + status = ds_binder->startBugreport(123, "com.example.package", std::move(bugreport_fd2), + std::move(screenshot_fd2), + Dumpstate::BugreportMode::BUGREPORT_INTERACTIVE, flags, + listener2, true); 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 0012177a241f9f718fd8525846f61debdb3cecc8..a417837ef9c38eb28a6e97836a12181f8404db9d 100644 --- a/cmds/dumpstate/tests/dumpstate_test.cpp +++ b/cmds/dumpstate/tests/dumpstate_test.cpp @@ -31,6 +31,7 @@ #include #include #include +#include #include #include @@ -70,7 +71,7 @@ class DumpstateListenerMock : public IDumpstateListener { public: MOCK_METHOD1(onProgress, binder::Status(int32_t progress)); MOCK_METHOD1(onError, binder::Status(int32_t error_code)); - MOCK_METHOD0(onFinished, binder::Status()); + MOCK_METHOD1(onFinished, binder::Status(const std::string& bugreport_file)); MOCK_METHOD1(onScreenshotTaken, binder::Status(bool success)); MOCK_METHOD0(onUiIntensiveBugreportDumpsFinished, binder::Status()); @@ -237,7 +238,7 @@ TEST_F(DumpOptionsTest, InitializeAdbShellBugreport) { } TEST_F(DumpOptionsTest, InitializeFullBugReport) { - options_.Initialize(Dumpstate::BugreportMode::BUGREPORT_FULL, fd, fd, true); + options_.Initialize(Dumpstate::BugreportMode::BUGREPORT_FULL, 0, fd, fd, true); EXPECT_TRUE(options_.do_screenshot); // Other options retain default values @@ -251,7 +252,7 @@ TEST_F(DumpOptionsTest, InitializeFullBugReport) { } TEST_F(DumpOptionsTest, InitializeInteractiveBugReport) { - options_.Initialize(Dumpstate::BugreportMode::BUGREPORT_INTERACTIVE, fd, fd, true); + options_.Initialize(Dumpstate::BugreportMode::BUGREPORT_INTERACTIVE, 0, fd, fd, true); EXPECT_TRUE(options_.do_progress_updates); EXPECT_TRUE(options_.do_screenshot); @@ -265,7 +266,7 @@ TEST_F(DumpOptionsTest, InitializeInteractiveBugReport) { } TEST_F(DumpOptionsTest, InitializeRemoteBugReport) { - options_.Initialize(Dumpstate::BugreportMode::BUGREPORT_REMOTE, fd, fd, false); + options_.Initialize(Dumpstate::BugreportMode::BUGREPORT_REMOTE, 0, fd, fd, false); EXPECT_TRUE(options_.is_remote_mode); EXPECT_FALSE(options_.do_vibrate); EXPECT_FALSE(options_.do_screenshot); @@ -279,7 +280,7 @@ TEST_F(DumpOptionsTest, InitializeRemoteBugReport) { } TEST_F(DumpOptionsTest, InitializeWearBugReport) { - options_.Initialize(Dumpstate::BugreportMode::BUGREPORT_WEAR, fd, fd, true); + options_.Initialize(Dumpstate::BugreportMode::BUGREPORT_WEAR, 0, fd, fd, true); EXPECT_TRUE(options_.do_screenshot); EXPECT_TRUE(options_.do_progress_updates); @@ -294,7 +295,7 @@ TEST_F(DumpOptionsTest, InitializeWearBugReport) { } TEST_F(DumpOptionsTest, InitializeTelephonyBugReport) { - options_.Initialize(Dumpstate::BugreportMode::BUGREPORT_TELEPHONY, fd, fd, false); + options_.Initialize(Dumpstate::BugreportMode::BUGREPORT_TELEPHONY, 0, fd, fd, false); EXPECT_FALSE(options_.do_screenshot); EXPECT_TRUE(options_.telephony_only); EXPECT_TRUE(options_.do_progress_updates); @@ -309,7 +310,7 @@ TEST_F(DumpOptionsTest, InitializeTelephonyBugReport) { } TEST_F(DumpOptionsTest, InitializeWifiBugReport) { - options_.Initialize(Dumpstate::BugreportMode::BUGREPORT_WIFI, fd, fd, false); + options_.Initialize(Dumpstate::BugreportMode::BUGREPORT_WIFI, 0, fd, fd, false); EXPECT_FALSE(options_.do_screenshot); EXPECT_TRUE(options_.wifi_only); @@ -485,6 +486,20 @@ TEST_F(DumpOptionsTest, ValidateOptionsRemoteMode) { EXPECT_TRUE(options_.ValidateOptions()); } +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); + EXPECT_TRUE(options_.is_consent_deferred); + EXPECT_TRUE(options_.use_predumped_ui_data); + + options_.Initialize( + Dumpstate::BugreportMode::BUGREPORT_FULL, 0, fd, fd, true); + EXPECT_FALSE(options_.is_consent_deferred); + EXPECT_FALSE(options_.use_predumped_ui_data); +} + class DumpstateTest : public DumpstateBaseTest { public: void SetUp() { @@ -982,6 +997,24 @@ TEST_F(DumpstateTest, DumpPool_withParallelRunDisabled_isNull) { EXPECT_FALSE(ds.dump_pool_); } +TEST_F(DumpstateTest, PreDumpUiData) { + // These traces are always enabled, i.e. they are always pre-dumped + const std::vector uiTraces = { + std::filesystem::path{"/data/misc/wmtrace/transactions_trace.winscope"}, + std::filesystem::path{"/data/misc/wmtrace/wm_transition_trace.winscope"}, + std::filesystem::path{"/data/misc/wmtrace/shell_transition_trace.winscope"}, + }; + + for (const auto traceFile : uiTraces) { + std::system(("rm -f " + traceFile.string()).c_str()); + EXPECT_FALSE(std::filesystem::exists(traceFile)) << traceFile << " was not deleted."; + + Dumpstate& ds_ = Dumpstate::GetInstance(); + ds_.PreDumpUiData(); + EXPECT_TRUE(std::filesystem::exists(traceFile)) << traceFile << " was not created."; + } +} + class ZippedBugReportStreamTest : public DumpstateBaseTest { public: void SetUp() { @@ -1046,11 +1079,6 @@ TEST_F(ZippedBugReportStreamTest, DISABLED_StreamLimitedOnlyReport) { VerifyEntry(handle_, bugreport_txt_name, &entry); } -class DumpstateServiceTest : public DumpstateBaseTest { - public: - DumpstateService dss; -}; - class ProgressTest : public DumpstateBaseTest { public: Progress GetInstance(int32_t max, double growth_factor, const std::string& path = "") { diff --git a/cmds/flatland/GLHelper.cpp b/cmds/flatland/GLHelper.cpp index 01f7d30a42b14cd0d0f6e7a6d230c1fb2c33b3f4..c163095c50a72791d34513718ca72cc1adfdf553 100644 --- a/cmds/flatland/GLHelper.cpp +++ b/cmds/flatland/GLHelper.cpp @@ -35,9 +35,12 @@ GLHelper::GLHelper() : GLHelper::~GLHelper() { } -bool GLHelper::setUp(const ShaderDesc* shaderDescs, size_t numShaders) { +bool GLHelper::setUp(const sp& displayToken, const ShaderDesc* shaderDescs, + size_t numShaders) { bool result; + mDisplayToken = displayToken; + mDisplay = eglGetDisplay(EGL_DEFAULT_DISPLAY); if (mDisplay == EGL_NO_DISPLAY) { fprintf(stderr, "eglGetDisplay error: %#x\n", eglGetError()); @@ -221,14 +224,8 @@ bool GLHelper::createNamedSurfaceTexture(GLuint name, uint32_t w, uint32_t h, } bool GLHelper::computeWindowScale(uint32_t w, uint32_t h, float* scale) { - const sp dpy = mSurfaceComposerClient->getInternalDisplayToken(); - if (dpy == nullptr) { - fprintf(stderr, "SurfaceComposer::getInternalDisplayToken failed.\n"); - return false; - } - ui::DisplayMode mode; - status_t err = mSurfaceComposerClient->getActiveDisplayMode(dpy, &mode); + status_t err = mSurfaceComposerClient->getActiveDisplayMode(mDisplayToken, &mode); if (err != NO_ERROR) { fprintf(stderr, "SurfaceComposer::getActiveDisplayMode failed: %#x\n", err); return false; diff --git a/cmds/flatland/GLHelper.h b/cmds/flatland/GLHelper.h index d09463a9b80ede5efee630ba7d6937b63d47dc01..5194f5084fd40a66a0add0fad444bc76150699da 100644 --- a/cmds/flatland/GLHelper.h +++ b/cmds/flatland/GLHelper.h @@ -44,7 +44,7 @@ public: ~GLHelper(); - bool setUp(const ShaderDesc* shaderDescs, size_t numShaders); + bool setUp(const sp& displayToken, const ShaderDesc* shaderDescs, size_t numShaders); void tearDown(); @@ -87,6 +87,8 @@ private: size_t mNumShaders; GLuint mDitherTexture; + + sp mDisplayToken; }; } // namespace android diff --git a/cmds/flatland/Main.cpp b/cmds/flatland/Main.cpp index 7ceb39703299006f1b9784b8df0188b2e632f489..6d14d568a49bf65b84bfe1ae6601d1c8fc35b5c1 100644 --- a/cmds/flatland/Main.cpp +++ b/cmds/flatland/Main.cpp @@ -20,6 +20,7 @@ #include #include #include +#include #include #include @@ -34,9 +35,10 @@ using namespace ::android; -static uint32_t g_SleepBetweenSamplesMs = 0; -static bool g_PresentToWindow = false; -static size_t g_BenchmarkNameLen = 0; +static uint32_t g_SleepBetweenSamplesMs = 0; +static bool g_PresentToWindow = false; +static size_t g_BenchmarkNameLen = 0; +static sp g_DisplayToken = nullptr; struct BenchmarkDesc { // The name of the test. @@ -393,7 +395,7 @@ public: uint32_t h = mDesc.runHeights[mInstance]; mGLHelper = new GLHelper(); - result = mGLHelper->setUp(shaders, NELEMS(shaders)); + result = mGLHelper->setUp(g_DisplayToken, shaders, NELEMS(shaders)); if (!result) { return false; } @@ -718,13 +720,17 @@ static size_t maxBenchmarkNameLen() { } // Print the command usage help to stderr. -static void showHelp(const char *cmd) { - fprintf(stderr, "usage: %s [options]\n", cmd); - fprintf(stderr, "options include:\n" - " -s N sleep for N ms between samples\n" - " -d display the test frame to a window\n" - " --help print this helpful message and exit\n" - ); +static void showHelp(const char* cmd) { + fprintf(stderr, "usage: %s [options]\n", cmd); + fprintf( + stderr, + "options include:\n" + " -s N sleep for N ms between samples\n" + " -d display the test frame to a window\n" + " -i display-id specify a display ID to use for multi-display device\n" + " see \"dumpsys SurfaceFlinger --display-id\" for valid " + "display IDs\n" + " --help print this helpful message and exit\n"); } int main(int argc, char** argv) { @@ -733,6 +739,14 @@ int main(int argc, char** argv) { exit(0); } + const auto ids = SurfaceComposerClient::getPhysicalDisplayIds(); + if (ids.empty()) { + fprintf(stderr, "Failed to get ID for any displays.\n"); + exit(3); + } + + std::optional displayId; + for (;;) { int ret; int option_index = 0; @@ -741,7 +755,7 @@ int main(int argc, char** argv) { { 0, 0, 0, 0 } }; - ret = getopt_long(argc, argv, "ds:", + ret = getopt_long(argc, argv, "ds:i:", long_options, &option_index); if (ret < 0) { @@ -757,6 +771,14 @@ int main(int argc, char** argv) { g_SleepBetweenSamplesMs = atoi(optarg); break; + case 'i': + displayId = DisplayId::fromValue(atoll(optarg)); + if (!displayId) { + fprintf(stderr, "Invalid display ID: %s.\n", optarg); + exit(4); + } + break; + case 0: if (strcmp(long_options[option_index].name, "help")) { showHelp(argv[0]); @@ -770,6 +792,22 @@ int main(int argc, char** argv) { } } + if (!displayId) { // no display id is specified + if (ids.size() == 1) { + displayId = ids.front(); + } else { + fprintf(stderr, "Please specify a display ID for multi-display device.\n"); + showHelp(argv[0]); + exit(5); + } + } + + g_DisplayToken = SurfaceComposerClient::getPhysicalDisplayToken(*displayId); + if (g_DisplayToken == nullptr) { + fprintf(stderr, "SurfaceComposer::getPhysicalDisplayToken failed.\n"); + exit(6); + } + g_BenchmarkNameLen = maxBenchmarkNameLen(); printf(" cmdline:"); @@ -782,4 +820,6 @@ int main(int argc, char** argv) { fprintf(stderr, "exiting due to error.\n"); return 1; } + + return 0; } diff --git a/cmds/installd/utils_default.cpp b/cmds/installd/utils_default.cpp index a6025e67c84a088fa73afc897e15d311e3c1e4e6..85ce4509e1e5a49386632894bc944f32d420f2c8 100644 --- a/cmds/installd/utils_default.cpp +++ b/cmds/installd/utils_default.cpp @@ -23,7 +23,7 @@ namespace installd { // platform dependent logic. int rm_package_dir(const std::string& package_dir) { - return delete_dir_contents_and_dir(package_dir); + return rename_delete_dir_contents_and_dir(package_dir); } } // namespace installd diff --git a/cmds/servicemanager/ServiceManager.cpp b/cmds/servicemanager/ServiceManager.cpp index facb8b133bc80f7e85412269e6e7119162609558..77989d148bb19f7e9b9164983acf6c5ad89a4ea9 100644 --- a/cmds/servicemanager/ServiceManager.cpp +++ b/cmds/servicemanager/ServiceManager.cpp @@ -40,6 +40,11 @@ using ::android::internal::Stability; namespace android { +bool is_multiuser_uid_isolated(uid_t uid) { + uid_t appid = multiuser_get_app_id(uid); + return appid >= AID_ISOLATED_START && appid <= AID_ISOLATED_END; +} + #ifndef VENDORSERVICEMANAGER struct ManifestWithDescription { @@ -302,13 +307,8 @@ sp ServiceManager::tryGetService(const std::string& name, bool startIfN if (auto it = mNameToService.find(name); it != mNameToService.end()) { service = &(it->second); - if (!service->allowIsolated) { - uid_t appid = multiuser_get_app_id(ctx.uid); - bool isIsolated = appid >= AID_ISOLATED_START && appid <= AID_ISOLATED_END; - - if (isIsolated) { - return nullptr; - } + if (!service->allowIsolated && is_multiuser_uid_isolated(ctx.uid)) { + return nullptr; } out = service->binder; } @@ -472,7 +472,17 @@ Status ServiceManager::registerForNotifications( auto ctx = mAccess->getCallingContext(); if (!mAccess->canFind(ctx, name)) { - return Status::fromExceptionCode(Status::EX_SECURITY); + return Status::fromExceptionCode(Status::EX_SECURITY, "SELinux"); + } + + // note - we could allow isolated apps to get notifications if we + // keep track of isolated callbacks and non-isolated callbacks, but + // this is done since isolated apps shouldn't access lazy services + // so we should be able to use different APIs to keep things simple. + // Here, we disallow everything, because the service might not be + // registered yet. + if (is_multiuser_uid_isolated(ctx.uid)) { + return Status::fromExceptionCode(Status::EX_SECURITY, "isolated app"); } if (!isValidServiceName(name)) { diff --git a/cmds/servicemanager/test_sm.cpp b/cmds/servicemanager/test_sm.cpp index 9e5b8a445a0161a06586b264f39c7fc353fe3d20..97e500d0a79255aa5bcda56538d648e8d98ef34f 100644 --- a/cmds/servicemanager/test_sm.cpp +++ b/cmds/servicemanager/test_sm.cpp @@ -388,6 +388,22 @@ TEST(ServiceNotifications, NoPermissionsRegister) { sp cb = sp::make(); + EXPECT_EQ(sm->registerForNotifications("foofoo", cb).exceptionCode(), Status::EX_SECURITY); +} + +TEST(GetService, IsolatedCantRegister) { + std::unique_ptr access = std::make_unique>(); + + EXPECT_CALL(*access, getCallingContext()) + .WillOnce(Return(Access::CallingContext{ + .uid = AID_ISOLATED_START, + })); + EXPECT_CALL(*access, canFind(_, _)).WillOnce(Return(true)); + + sp sm = sp::make(std::move(access)); + + sp cb = sp::make(); + EXPECT_EQ(sm->registerForNotifications("foofoo", cb).exceptionCode(), Status::EX_SECURITY); } diff --git a/cmds/surfacereplayer/Android.bp b/cmds/surfacereplayer/Android.bp deleted file mode 100644 index 34fc8b10ea8e080342d79b33faf2fd798c3261d6..0000000000000000000000000000000000000000 --- a/cmds/surfacereplayer/Android.bp +++ /dev/null @@ -1,13 +0,0 @@ -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"], -} - -subdirs = [ - "proto", - "replayer", -] diff --git a/cmds/surfacereplayer/OWNERS b/cmds/surfacereplayer/OWNERS deleted file mode 100644 index 32bcc834683ecbc7a6da08061dcc052fbc5ccd39..0000000000000000000000000000000000000000 --- a/cmds/surfacereplayer/OWNERS +++ /dev/null @@ -1 +0,0 @@ -include platform/frameworks/native:/services/surfaceflinger/OWNERS diff --git a/cmds/surfacereplayer/proto/Android.bp b/cmds/surfacereplayer/proto/Android.bp deleted file mode 100644 index 23b54ee5b044b3453d49bf768f855122c45e3c14..0000000000000000000000000000000000000000 --- a/cmds/surfacereplayer/proto/Android.bp +++ /dev/null @@ -1,23 +0,0 @@ -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_static { - name: "libtrace_proto", - srcs: [ - "src/trace.proto", - ], - cflags: [ - "-Wall", - "-Werror", - ], - proto: { - type: "lite", - export_proto_headers: true, - }, -} diff --git a/cmds/surfacereplayer/proto/src/trace.proto b/cmds/surfacereplayer/proto/src/trace.proto deleted file mode 100644 index a177027e5c0ca1800906886f5df41e44ec9220e0..0000000000000000000000000000000000000000 --- a/cmds/surfacereplayer/proto/src/trace.proto +++ /dev/null @@ -1,225 +0,0 @@ -syntax = "proto2"; -option optimize_for = LITE_RUNTIME; -package android.surfaceflinger; - -message Trace { - repeated Increment increment = 1; -} - -message Increment { - required int64 time_stamp = 1; - - oneof increment { - Transaction transaction = 2; - SurfaceCreation surface_creation = 3; - SurfaceDeletion surface_deletion = 4; - BufferUpdate buffer_update = 5; - VSyncEvent vsync_event = 6; - DisplayCreation display_creation = 7; - DisplayDeletion display_deletion = 8; - PowerModeUpdate power_mode_update = 9; - } -} - -message Transaction { - repeated SurfaceChange surface_change = 1; - repeated DisplayChange display_change = 2; - - required bool synchronous = 3; - required bool animation = 4; - optional Origin origin = 5; - optional uint64 id = 6; -} - -message SurfaceChange { - required int32 id = 1; - reserved 7; - oneof SurfaceChange { - PositionChange position = 2; - SizeChange size = 3; - AlphaChange alpha = 4; - LayerChange layer = 5; - CropChange crop = 6; - MatrixChange matrix = 8; - TransparentRegionHintChange transparent_region_hint = 10; - LayerStackChange layer_stack = 11; - HiddenFlagChange hidden_flag = 12; - OpaqueFlagChange opaque_flag = 13; - SecureFlagChange secure_flag = 14; - CornerRadiusChange corner_radius = 16; - ReparentChange reparent = 17; - RelativeParentChange relative_parent = 18; - BackgroundBlurRadiusChange background_blur_radius = 20; - ShadowRadiusChange shadow_radius = 21; - BlurRegionsChange blur_regions = 22; - TrustedOverlayChange trusted_overlay = 23; - } -} - -message PositionChange { - required float x = 1; - required float y = 2; -} - -message SizeChange { - required uint32 w = 1; - required uint32 h = 2; -} - -message AlphaChange { - required float alpha = 1; -} - -message CornerRadiusChange { - required float corner_radius = 1; -} - -message BackgroundBlurRadiusChange { - required float background_blur_radius = 1; -} - -message LayerChange { - required uint32 layer = 1; -} - -message CropChange { - required Rectangle rectangle = 1; -} - -message MatrixChange { - required float dsdx = 1; - required float dtdx = 2; - required float dsdy = 3; - required float dtdy = 4; -} - -message TransparentRegionHintChange { - repeated Rectangle region = 1; -} - -message LayerStackChange { - required uint32 layer_stack = 1; -} - -message DisplayFlagsChange { - required uint32 flags = 1; -} - -message HiddenFlagChange { - required bool hidden_flag = 1; -} - -message OpaqueFlagChange { - required bool opaque_flag = 1; -} - -message SecureFlagChange { - required bool secure_flag = 1; -} - -message DisplayChange { - required int32 id = 1; - - oneof DisplayChange { - DispSurfaceChange surface = 2; - LayerStackChange layer_stack = 3; - SizeChange size = 4; - ProjectionChange projection = 5; - DisplayFlagsChange flags = 6; - } -} - -message DispSurfaceChange { - required uint64 buffer_queue_id = 1; - required string buffer_queue_name = 2; -} - -message ProjectionChange { - required int32 orientation = 1; - required Rectangle viewport = 2; - required Rectangle frame = 3; -} - -message Rectangle { - required int32 left = 1; - required int32 top = 2; - required int32 right = 3; - required int32 bottom = 4; -} - -message SurfaceCreation { - required int32 id = 1; - required string name = 2; - required uint32 w = 3; - required uint32 h = 4; -} - -message SurfaceDeletion { - required int32 id = 1; -} - -message BufferUpdate { - required int32 id = 1; - required uint32 w = 2; - required uint32 h = 3; - required uint64 frame_number = 4; -} - -message VSyncEvent { - required int64 when = 1; -} - -message DisplayCreation { - required int32 id = 1; - required string name = 2; - optional uint64 display_id = 3; - required bool is_secure = 4; -} - -message DisplayDeletion { - required int32 id = 1; -} - -message PowerModeUpdate { - required int32 id = 1; - required int32 mode = 2; -} - -message ReparentChange { - required int32 parent_id = 1; -} - -message RelativeParentChange { - required int32 relative_parent_id = 1; - required int32 z = 2; -} - -message ShadowRadiusChange { - required float radius = 1; -} - -message TrustedOverlayChange { - required float is_trusted_overlay = 1; -} - -message BlurRegionsChange { - repeated BlurRegionChange blur_regions = 1; -} - -message BlurRegionChange { - required uint32 blur_radius = 1; - required float corner_radius_tl = 2; - required float corner_radius_tr = 3; - required float corner_radius_bl = 4; - required float corner_radius_br = 5; - required float alpha = 6; - required int32 left = 7; - required int32 top = 8; - required int32 right = 9; - required int32 bottom = 10; -} - -message Origin { - required int32 pid = 1; - required int32 uid = 2; -} diff --git a/cmds/surfacereplayer/replayer/Android.bp b/cmds/surfacereplayer/replayer/Android.bp deleted file mode 100644 index 3985230f089c75778e541b039240707e3449df8c..0000000000000000000000000000000000000000 --- a/cmds/surfacereplayer/replayer/Android.bp +++ /dev/null @@ -1,71 +0,0 @@ -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: "libsurfacereplayer", - srcs: [ - "BufferQueueScheduler.cpp", - "Event.cpp", - "Replayer.cpp", - ], - cppflags: [ - "-Werror", - "-Wno-unused-parameter", - "-Wno-format", - "-Wno-c++98-compat-pedantic", - "-Wno-float-conversion", - "-Wno-disabled-macro-expansion", - "-Wno-float-equal", - "-Wno-sign-conversion", - "-Wno-padded", - ], - static_libs: [ - "libtrace_proto", - ], - shared_libs: [ - "libEGL", - "libGLESv2", - "libbinder", - "liblog", - "libcutils", - "libgui", - "libui", - "libutils", - "libprotobuf-cpp-lite", - "libbase", - "libnativewindow", - ], - export_include_dirs: [ - ".", - ], -} - -cc_binary { - name: "surfacereplayer", - srcs: [ - "Main.cpp", - ], - shared_libs: [ - "libprotobuf-cpp-lite", - "libsurfacereplayer", - "libutils", - "libgui", - ], - static_libs: [ - "libtrace_proto", - ], - cppflags: [ - "-Werror", - "-Wno-unused-parameter", - "-Wno-c++98-compat-pedantic", - "-Wno-float-conversion", - "-Wno-disabled-macro-expansion", - "-Wno-float-equal", - ], -} diff --git a/cmds/surfacereplayer/replayer/BufferQueueScheduler.cpp b/cmds/surfacereplayer/replayer/BufferQueueScheduler.cpp deleted file mode 100644 index 77de8dc44c092196a4795e86dcf9a5912e6d0a44..0000000000000000000000000000000000000000 --- a/cmds/surfacereplayer/replayer/BufferQueueScheduler.cpp +++ /dev/null @@ -1,109 +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_TAG "BufferQueueScheduler" - -#include "BufferQueueScheduler.h" - -#include -#include - -using namespace android; - -BufferQueueScheduler::BufferQueueScheduler( - const sp& surfaceControl, const HSV& color, int id) - : mSurfaceControl(surfaceControl), mColor(color), mSurfaceId(id), mContinueScheduling(true) {} - -void BufferQueueScheduler::startScheduling() { - ALOGV("Starting Scheduler for %d Layer", mSurfaceId); - std::unique_lock lock(mMutex); - if (mSurfaceControl == nullptr) { - mCondition.wait(lock, [&] { return (mSurfaceControl != nullptr); }); - } - - while (mContinueScheduling) { - while (true) { - if (mBufferEvents.empty()) { - break; - } - - BufferEvent event = mBufferEvents.front(); - lock.unlock(); - - bufferUpdate(event.dimensions); - fillSurface(event.event); - mColor.modulate(); - lock.lock(); - mBufferEvents.pop(); - } - mCondition.wait(lock); - } -} - -void BufferQueueScheduler::addEvent(const BufferEvent& event) { - std::lock_guard lock(mMutex); - mBufferEvents.push(event); - mCondition.notify_one(); -} - -void BufferQueueScheduler::stopScheduling() { - std::lock_guard lock(mMutex); - mContinueScheduling = false; - mCondition.notify_one(); -} - -void BufferQueueScheduler::setSurfaceControl( - const sp& surfaceControl, const HSV& color) { - std::lock_guard lock(mMutex); - mSurfaceControl = surfaceControl; - mColor = color; - mCondition.notify_one(); -} - -void BufferQueueScheduler::bufferUpdate(const Dimensions& dimensions) { - sp s = mSurfaceControl->getSurface(); - s->setBuffersDimensions(dimensions.width, dimensions.height); -} - -void BufferQueueScheduler::fillSurface(const std::shared_ptr& event) { - ANativeWindow_Buffer outBuffer; - sp s = mSurfaceControl->getSurface(); - - status_t status = s->lock(&outBuffer, nullptr); - - if (status != NO_ERROR) { - ALOGE("fillSurface: failed to lock buffer, (%d)", status); - return; - } - - auto color = mColor.getRGB(); - - auto img = reinterpret_cast(outBuffer.bits); - for (int y = 0; y < outBuffer.height; y++) { - for (int x = 0; x < outBuffer.width; x++) { - uint8_t* pixel = img + (4 * (y * outBuffer.stride + x)); - pixel[0] = color.r; - pixel[1] = color.g; - pixel[2] = color.b; - pixel[3] = LAYER_ALPHA; - } - } - - event->readyToExecute(); - - status = s->unlockAndPost(); - - ALOGE_IF(status != NO_ERROR, "fillSurface: failed to unlock and post buffer, (%d)", status); -} diff --git a/cmds/surfacereplayer/replayer/BufferQueueScheduler.h b/cmds/surfacereplayer/replayer/BufferQueueScheduler.h deleted file mode 100644 index cb20fcc798054f919daaac3c7ca5a99e7808dd76..0000000000000000000000000000000000000000 --- a/cmds/surfacereplayer/replayer/BufferQueueScheduler.h +++ /dev/null @@ -1,82 +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. - */ - -#ifndef ANDROID_SURFACEREPLAYER_BUFFERQUEUESCHEDULER_H -#define ANDROID_SURFACEREPLAYER_BUFFERQUEUESCHEDULER_H - -#include "Color.h" -#include "Event.h" - -#include - -#include - -#include -#include -#include -#include -#include - -namespace android { - -auto constexpr LAYER_ALPHA = 190; - -struct Dimensions { - Dimensions() = default; - Dimensions(int w, int h) : width(w), height(h) {} - - int width = 0; - int height = 0; -}; - -struct BufferEvent { - BufferEvent() = default; - BufferEvent(std::shared_ptr e, Dimensions d) : event(e), dimensions(d) {} - - std::shared_ptr event; - Dimensions dimensions; -}; - -class BufferQueueScheduler { - public: - BufferQueueScheduler(const sp& surfaceControl, const HSV& color, int id); - - void startScheduling(); - void addEvent(const BufferEvent&); - void stopScheduling(); - - void setSurfaceControl(const sp& surfaceControl, const HSV& color); - - private: - void bufferUpdate(const Dimensions& dimensions); - - // Lock and fill the surface, block until the event is signaled by the main loop, - // then unlock and post the buffer. - void fillSurface(const std::shared_ptr& event); - - sp mSurfaceControl; - HSV mColor; - const int mSurfaceId; - - bool mContinueScheduling; - - std::queue mBufferEvents; - std::mutex mMutex; - std::condition_variable mCondition; -}; - -} // namespace android -#endif diff --git a/cmds/surfacereplayer/replayer/Color.h b/cmds/surfacereplayer/replayer/Color.h deleted file mode 100644 index ce644be7becf4f3169aed59b03f0df07b3d56407..0000000000000000000000000000000000000000 --- a/cmds/surfacereplayer/replayer/Color.h +++ /dev/null @@ -1,124 +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. - */ - -#ifndef ANDROID_SURFACEREPLAYER_COLOR_H -#define ANDROID_SURFACEREPLAYER_COLOR_H - -#include -#include - -namespace android { - -constexpr double modulateFactor = .0001; -constexpr double modulateLimit = .80; - -struct RGB { - RGB(uint8_t rIn, uint8_t gIn, uint8_t bIn) : r(rIn), g(gIn), b(bIn) {} - - uint8_t r = 0; - uint8_t g = 0; - uint8_t b = 0; -}; - -struct HSV { - HSV() = default; - HSV(double hIn, double sIn, double vIn) : h(hIn), s(sIn), v(vIn) {} - - double h = 0; - double s = 0; - double v = 0; - - RGB getRGB() const; - - bool modulateUp = false; - - void modulate(); -}; - -void inline HSV::modulate() { - if(modulateUp) { - v += modulateFactor; - } else { - v -= modulateFactor; - } - - if(v <= modulateLimit || v >= 1) { - modulateUp = !modulateUp; - } -} - -inline RGB HSV::getRGB() const { - using namespace std; - double r = 0, g = 0, b = 0; - - if (s == 0) { - r = v; - g = v; - b = v; - } else { - auto tempHue = static_cast(h) % 360; - tempHue = tempHue / 60; - - int i = static_cast(trunc(tempHue)); - double f = h - i; - - double x = v * (1.0 - s); - double y = v * (1.0 - (s * f)); - double z = v * (1.0 - (s * (1.0 - f))); - - switch (i) { - case 0: - r = v; - g = z; - b = x; - break; - - case 1: - r = y; - g = v; - b = x; - break; - - case 2: - r = x; - g = v; - b = z; - break; - - case 3: - r = x; - g = y; - b = v; - break; - - case 4: - r = z; - g = x; - b = v; - break; - - default: - r = v; - g = x; - b = y; - break; - } - } - - return RGB(round(r * 255), round(g * 255), round(b * 255)); -} -} -#endif diff --git a/cmds/surfacereplayer/replayer/Event.cpp b/cmds/surfacereplayer/replayer/Event.cpp deleted file mode 100644 index 64db5f07b106cc0b32bf4b0047df99e9e019caa6..0000000000000000000000000000000000000000 --- a/cmds/surfacereplayer/replayer/Event.cpp +++ /dev/null @@ -1,51 +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. - */ - -#include "Event.h" - -using namespace android; -using Increment = surfaceflinger::Increment; - -Event::Event(Increment::IncrementCase type) : mIncrementType(type) {} - -void Event::readyToExecute() { - changeState(Event::EventState::Waiting); - waitUntil(Event::EventState::Signaled); - changeState(Event::EventState::Running); -} - -void Event::complete() { - waitUntil(Event::EventState::Waiting); - changeState(Event::EventState::Signaled); - waitUntil(Event::EventState::Running); -} - -void Event::waitUntil(Event::EventState state) { - std::unique_lock lock(mLock); - mCond.wait(lock, [this, state] { return (mState == state); }); -} - -void Event::changeState(Event::EventState state) { - std::unique_lock lock(mLock); - mState = state; - lock.unlock(); - - mCond.notify_one(); -} - -Increment::IncrementCase Event::getIncrementType() { - return mIncrementType; -} diff --git a/cmds/surfacereplayer/replayer/Event.h b/cmds/surfacereplayer/replayer/Event.h deleted file mode 100644 index 09a7c248d57d69ba77ae924d7b10c81b9ab44e44..0000000000000000000000000000000000000000 --- a/cmds/surfacereplayer/replayer/Event.h +++ /dev/null @@ -1,57 +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. - */ - -#ifndef ANDROID_SURFACEREPLAYER_EVENT_H -#define ANDROID_SURFACEREPLAYER_EVENT_H - -#include - -#include -#include - -namespace android { - -using Increment = surfaceflinger::Increment; - -class Event { - public: - Event(Increment::IncrementCase); - - enum class EventState { - SettingUp, // Completing as much time-independent work as possible - Waiting, // Waiting for signal from main thread to finish execution - Signaled, // Signaled by main thread, about to immediately switch to Running - Running // Finishing execution of rest of work - }; - - void readyToExecute(); - void complete(); - - Increment::IncrementCase getIncrementType(); - - private: - void waitUntil(EventState state); - void changeState(EventState state); - - std::mutex mLock; - std::condition_variable mCond; - - EventState mState = EventState::SettingUp; - - Increment::IncrementCase mIncrementType; -}; -} -#endif diff --git a/cmds/surfacereplayer/replayer/Main.cpp b/cmds/surfacereplayer/replayer/Main.cpp deleted file mode 100644 index fbfcacf1aa72e8a6d39c7632bcb26f5ac04ec515..0000000000000000000000000000000000000000 --- a/cmds/surfacereplayer/replayer/Main.cpp +++ /dev/null @@ -1,116 +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. - */ - -/* - * Replayer - Main.cpp - * - * 1. Get flags from command line - * 2. Commit actions or settings based on the flags - * 3. Initalize a replayer object with the filename passed in - * 4. Replay - * 5. Exit successfully or print error statement - */ - -#include - -#include -#include -#include -#include - -using namespace android; - -void printHelpMenu() { - std::cout << "SurfaceReplayer options:\n"; - std::cout << "Usage: surfacereplayer [OPTIONS...] \n"; - std::cout << " File path must be absolute" << std::endl << std::endl; - - std::cout << " -m Stops the replayer at the start of the trace and switches "; - "to manual replay\n"; - - std::cout << "\n -t [Number of Threads] Specifies the number of threads to be used while " - "replaying (default is " << android::DEFAULT_THREADS << ")\n"; - - std::cout << "\n -s [Timestamp] Specify at what timestamp should the replayer switch " - "to manual replay\n"; - - std::cout << " -n Ignore timestamps and run through trace as fast as possible\n"; - - std::cout << " -l Indefinitely loop the replayer\n"; - - std::cout << " -h Display help menu\n"; - - std::cout << std::endl; -} - -int main(int argc, char** argv) { - std::string filename; - bool loop = false; - bool wait = true; - bool pauseBeginning = false; - int numThreads = DEFAULT_THREADS; - long stopHere = -1; - - int opt = 0; - while ((opt = getopt(argc, argv, "mt:s:nlh?")) != -1) { - switch (opt) { - case 'm': - pauseBeginning = true; - break; - case 't': - numThreads = atoi(optarg); - break; - case 's': - stopHere = atol(optarg); - break; - case 'n': - wait = false; - break; - case 'l': - loop = true; - break; - case 'h': - case '?': - printHelpMenu(); - exit(0); - default: - std::cerr << "Invalid argument...exiting" << std::endl; - printHelpMenu(); - exit(0); - } - } - - char** input = argv + optind; - if (input[0] == nullptr) { - std::cerr << "No trace file provided...exiting" << std::endl; - abort(); - } - filename.assign(input[0]); - - status_t status = NO_ERROR; - do { - android::Replayer r(filename, pauseBeginning, numThreads, wait, stopHere); - status = r.replay(); - } while(loop); - - if (status == NO_ERROR) { - std::cout << "Successfully finished replaying trace" << std::endl; - } else { - std::cerr << "Trace replayer returned error: " << status << std::endl; - } - - return 0; -} diff --git a/cmds/surfacereplayer/replayer/README.md b/cmds/surfacereplayer/replayer/README.md deleted file mode 100644 index 893f0dc0f65c8fa5fd5b2195c41e9816098cf042..0000000000000000000000000000000000000000 --- a/cmds/surfacereplayer/replayer/README.md +++ /dev/null @@ -1,262 +0,0 @@ -SurfaceReplayer Documentation -=================== - -[go/SurfaceReplayer](go/SurfaceReplayer) - -SurfaceReplayer is a playback mechanism that allows the replaying of traces recorded by -[SurfaceInterceptor](go/SurfaceInterceptor) from SurfaceFlinger. It specifically replays - -* Creation and deletion of surfaces/displays -* Alterations to the surfaces/displays called Transactions -* Buffer Updates to surfaces -* VSync events - -At their specified times to be as close to the original trace. - -Usage --------- - -###Creating a trace - -SurfaceInterceptor is the mechanism used to create traces. The device needs to be rooted in order to -utilize it. To allow it to write to the device, run - -`setenforce 0` - -To start recording a trace, run - -`service call SurfaceFlinger 1020 i32 1` - -To stop recording, run - -`service call SurfaceFlinger 1020 i32 0` - -The default location for the trace is `/data/SurfaceTrace.dat` - -###Executable - -To replay a specific trace, execute - -`/data/local/tmp/surfacereplayer /absolute/path/to/trace` - -inside the android shell. This will replay the full trace and then exit. Running this command -outside of the shell by prepending `adb shell` will not allow for manual control and will not turn -off VSync injections if it interrupted in any way other than fully replaying the trace - -The replay will not fill surfaces with their contents during the capture. Rather they are given a -random color which will be the same every time the trace is replayed. Surfaces modulate their color -at buffer updates. - -**Options:** - -- -m pause the replayer at the start of the trace for manual replay -- -t [Number of Threads] uses specified number of threads to queue up actions (default is 3) -- -s [Timestamp] switches to manual replay at specified timestamp -- -n Ignore timestamps and run through trace as fast as possible -- -l Indefinitely loop the replayer -- -h displays help menu - -**Manual Replay:** -When replaying, if the user presses CTRL-C, the replay will stop and can be manually controlled -by the user. Pressing CTRL-C again will exit the replayer. - -Manual replaying is similar to debugging in gdb. A prompt is presented and the user is able to -input commands to choose how to proceed by hitting enter after inputting a command. Pressing enter -without inputting a command repeats the previous command. - -- n - steps the replayer to the next VSync event -- ni - steps the replayer to the next increment -- c - continues normal replaying -- c [milliseconds] - continue until specified number of milliseconds have passed -- s [timestamp] - continue and stop at specified timestamp -- l - list out timestamp of current increment -- h - displays help menu - -###Shared Library - -To use the shared library include these shared libraries - -`libsurfacereplayer` -`libprotobuf-cpp-full` -`libutils` - -And the static library - -`libtrace_proto` - -Include the replayer header at the top of your file - -`#include ` - -There are two constructors for the replayer - -`Replayer(std::string& filename, bool replayManually, int numThreads, bool wait, nsecs_t stopHere)` -`Replayer(Trace& trace, ... ditto ...)` - -The first constructor takes in the filepath where the trace is located and loads in the trace -object internally. -- replayManually - **True**: if the replayer will immediately switch to manual replay at the start -- numThreads - Number of worker threads the replayer will use. -- wait - **False**: Replayer ignores waits in between increments -- stopHere - Time stamp of where the replayer should run to then switch to manual replay - -The second constructor includes all of the same parameters but takes in a preloaded trace object. -To use add - -`#include ` - -To your file - -After initializing the Replayer call - - replayer.replay(); - -And the trace will start replaying. Once the trace is finished replaying, the function will return. -The layers that are visible at the end of the trace will remain on screen until the program -terminates. - - -**If VSyncs are broken after running the replayer** that means `enableVSyncInjections(false)` was -never executed. This can be fixed by executing - -`service call SurfaceFlinger 23 i32 0` - -in the android shell - -Code Breakdown -------------- - -The Replayer is composed of 5 components. - -- The data format of the trace (Trace.proto) -- The Replayer object (Replayer.cpp) -- The synchronization mechanism to signal threads within the Replayer (Event.cpp) -- The scheduler for buffer updates per surface (BufferQueueScheduler.cpp) -- The Main executable (Main.cpp) - -### Traces - -Traces are represented as a protobuf message located in surfacereplayer/proto/src. - -**Traces** contain *repeated* **Increments** (events that have occurred in SurfaceFlinger). -**Increments** contain the time stamp of when it occurred and a *oneof* which can be a - - - Transaction - - SurfaceCreation - - SurfaceDeletion - - DisplayCreation - - DisplayDeleteion - - BufferUpdate - - VSyncEvent - - PowerModeUpdate - -**Transactions** contain whether the transaction was synchronous or animated and *repeated* -**SurfaceChanges** and **DisplayChanges** - -- **SurfaceChanges** contain an id of the surface being manipulated and can be changes such as -position, alpha, hidden, size, etc. -- **DisplayChanges** contain the id of the display being manipulated and can be changes such as -size, layer stack, projection, etc. - -**Surface/Display Creation** contain the id of the surface/display and the name of the -surface/display - -**Surface/Display Deletion** contain the id of the surface/display to be deleted - -**Buffer Updates** contain the id of the surface who's buffer is being updated, the size of the -buffer, and the frame number. - -**VSyncEvents** contain when the VSync event has occurred. - -**PowerModeUpdates** contain the id of the display being updated and what mode it is being -changed to. - -To output the contents of a trace in a readable format, execute - -`**aprotoc** --decode=Trace \ --I=$ANDROID_BUILD_TOP/frameworks/native/cmds/surfacereplayer/proto/src \ -$ANDROID_BUILD_TOP/frameworks/native/cmds/surfacereplayer/proto/src/trace.proto \ - < **YourTraceFile.dat** > **YourOutputName.txt**` - - -###Replayer - -Fundamentally the replayer loads a trace and iterates through each increment, waiting the required -amount of time until the increment should be executed, then executing the increment. The first -increment in a trace does not start at 0, rather the replayer treats its time stamp as time 0 and -goes from there. - -Increments from the trace are played asynchronously rather than one by one, being dispatched by -the main thread, queued up in a thread pool and completed when the main thread deems they are -ready to finish execution. - -When an increment is dispatched, it completes as much work as it can before it has to be -synchronized (e.g. prebaking a buffer for a BufferUpdate). When it gets to a critical action -(e.g. locking and pushing a buffer), it waits for the main thread to complete it using an Event -object. The main thread holds a queue of these Event objects and completes the -corresponding Event base on its time stamp. After completing an increment, the main thread will -dispatch another increment and continue. - -The main thread's execution flow is outlined below - - initReplay() //queue up the initial increments - while(!pendingIncrements.empty()) { //while increments remaining - event = pendingIncrement.pop(); - wait(event.time_stamp(); //waitUntil it is time to complete this increment - - event.complete() //signal to let event finish - if(increments remaing()) { - dispatchEvent() //queue up another increment - } - } - -A worker thread's flow looks like so - - //dispatched! - Execute non-time sensitive work here - ... - event.readyToExecute() //time sensitive point...waiting for Main Thread - ... - Finish execution - - -### Event - -An Event is a simple synchronization mechanism used to facilitate communication between the main -and worker threads. Every time an increment is dispatched, an Event object is also created. - -An Event can be in 4 different states: - -- **SettingUp** - The worker is in the process of completing all non-time sensitive work -- **Waiting** - The worker is waiting on the main thread to signal it. -- **Signaled** - The worker has just been signaled by the main thread -- **Running** - The worker is running again and finishing the rest of its work. - -When the main thread wants to finish the execution of a worker, the worker can either still be -**SettingUp**, in which the main thread will wait, or the worker will be **Waiting**, in which the -main thread will **Signal** it to complete. The worker thread changes itself to the **Running** -state once **Signaled**. This last step exists in order to communicate back to the main thread that -the worker thread has actually started completing its execution, rather than being preempted right -after signalling. Once this happens, the main thread schedules the next worker. This makes sure -there is a constant amount of workers running at one time. - -This activity is encapsulated in the `readyToExecute()` and `complete()` functions called by the -worker and main thread respectively. - -### BufferQueueScheduler - -During a **BuferUpdate**, the worker thread will wait until **Signaled** to unlock and post a -buffer that has been prefilled during the **SettingUp** phase. However if there are two sequential -**BufferUpdates** that act on the same surface, both threads will try to lock a buffer and fill it, -which isn't possible and will cause a deadlock. The BufferQueueScheduler solves this problem by -handling when **BufferUpdates** should be scheduled, making sure that they don't overlap. - -When a surface is created, a BufferQueueScheduler is also created along side it. Whenever a -**BufferUpdate** is read, it schedules the event onto its own internal queue and then schedules one -every time an Event is completed. - -### Main - -The main exectuable reads in the command line arguments. Creates the Replayer using those -arguments. Executes `replay()` on the Replayer. If there are no errors while replaying it will exit -gracefully, if there are then it will report the error and then exit. diff --git a/cmds/surfacereplayer/replayer/Replayer.cpp b/cmds/surfacereplayer/replayer/Replayer.cpp index 3f7c7d6a7be0b0313ef685d8f72799a599316519..44235ccdef48c20abdc0adda0031e08b5f6f9ee9 100644 --- a/cmds/surfacereplayer/replayer/Replayer.cpp +++ b/cmds/surfacereplayer/replayer/Replayer.cpp @@ -464,7 +464,6 @@ void Replayer::setPosition(SurfaceComposerClient::Transaction& t, 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()); - t.setSize(mLayers[id], sc.w(), sc.h()); } void Replayer::setLayer(SurfaceComposerClient::Transaction& t, diff --git a/cmds/surfacereplayer/replayer/Replayer.h b/cmds/surfacereplayer/replayer/Replayer.h deleted file mode 100644 index d62522a497b770dbdf3f6e696467e485d5dcd8c9..0000000000000000000000000000000000000000 --- a/cmds/surfacereplayer/replayer/Replayer.h +++ /dev/null @@ -1,170 +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. - */ - -#ifndef ANDROID_SURFACEREPLAYER_H -#define ANDROID_SURFACEREPLAYER_H - -#include "BufferQueueScheduler.h" -#include "Color.h" -#include "Event.h" - -#include - -#include -#include - -#include -#include - -#include -#include -#include -#include -#include -#include -#include -#include - -using namespace android::surfaceflinger; - -namespace android { - -const auto DEFAULT_PATH = "/data/local/tmp/SurfaceTrace.dat"; -const auto RAND_COLOR_SEED = 700; -const auto DEFAULT_THREADS = 3; - -typedef int32_t layer_id; -typedef int32_t display_id; - -typedef google::protobuf::RepeatedPtrField SurfaceChanges; -typedef google::protobuf::RepeatedPtrField DisplayChanges; - -class Replayer { - public: - Replayer(const std::string& filename, bool replayManually = false, - int numThreads = DEFAULT_THREADS, bool wait = true, nsecs_t stopHere = -1); - Replayer(const Trace& trace, bool replayManually = false, int numThreads = DEFAULT_THREADS, - bool wait = true, nsecs_t stopHere = -1); - - status_t replay(); - - private: - status_t initReplay(); - - void waitForConsoleCommmand(); - static void stopAutoReplayHandler(int signal); - - status_t dispatchEvent(int index); - - status_t doTransaction(const Transaction& transaction, const std::shared_ptr& event); - status_t createSurfaceControl(const SurfaceCreation& create, - const std::shared_ptr& event); - status_t injectVSyncEvent(const VSyncEvent& vsyncEvent, const std::shared_ptr& event); - void createDisplay(const DisplayCreation& create, const std::shared_ptr& event); - void deleteDisplay(const DisplayDeletion& delete_, const std::shared_ptr& event); - void updatePowerMode(const PowerModeUpdate& update, const std::shared_ptr& event); - - status_t doSurfaceTransaction(SurfaceComposerClient::Transaction& transaction, - const SurfaceChanges& surfaceChange); - void doDisplayTransaction(SurfaceComposerClient::Transaction& transaction, - const DisplayChanges& displayChange); - - void setPosition(SurfaceComposerClient::Transaction& t, - layer_id id, const PositionChange& pc); - void setSize(SurfaceComposerClient::Transaction& t, - layer_id id, const SizeChange& sc); - void setAlpha(SurfaceComposerClient::Transaction& t, - layer_id id, const AlphaChange& ac); - void setLayer(SurfaceComposerClient::Transaction& t, - layer_id id, const LayerChange& lc); - void setCrop(SurfaceComposerClient::Transaction& t, - layer_id id, const CropChange& cc); - void setCornerRadius(SurfaceComposerClient::Transaction& t, - layer_id id, const CornerRadiusChange& cc); - void setBackgroundBlurRadius(SurfaceComposerClient::Transaction& t, - layer_id id, const BackgroundBlurRadiusChange& cc); - void setBlurRegions(SurfaceComposerClient::Transaction& t, - layer_id id, const BlurRegionsChange& cc); - void setMatrix(SurfaceComposerClient::Transaction& t, - layer_id id, const MatrixChange& mc); - void setTransparentRegionHint(SurfaceComposerClient::Transaction& t, - layer_id id, const TransparentRegionHintChange& trgc); - void setLayerStack(SurfaceComposerClient::Transaction& t, - layer_id id, const LayerStackChange& lsc); - void setHiddenFlag(SurfaceComposerClient::Transaction& t, - layer_id id, const HiddenFlagChange& hfc); - void setOpaqueFlag(SurfaceComposerClient::Transaction& t, - layer_id id, const OpaqueFlagChange& ofc); - void setSecureFlag(SurfaceComposerClient::Transaction& t, - layer_id id, const SecureFlagChange& sfc); - void setReparentChange(SurfaceComposerClient::Transaction& t, - layer_id id, const ReparentChange& c); - void setRelativeParentChange(SurfaceComposerClient::Transaction& t, - layer_id id, const RelativeParentChange& c); - void setShadowRadiusChange(SurfaceComposerClient::Transaction& t, - layer_id id, const ShadowRadiusChange& c); - void setBlurRegionsChange(SurfaceComposerClient::Transaction& t, - layer_id id, const BlurRegionsChange& c); - - void setDisplaySurface(SurfaceComposerClient::Transaction& t, - display_id id, const DispSurfaceChange& dsc); - void setDisplayLayerStack(SurfaceComposerClient::Transaction& t, - display_id id, const LayerStackChange& lsc); - void setDisplaySize(SurfaceComposerClient::Transaction& t, - display_id id, const SizeChange& sc); - void setDisplayProjection(SurfaceComposerClient::Transaction& t, - display_id id, const ProjectionChange& pc); - - void waitUntilTimestamp(int64_t timestamp); - status_t loadSurfaceComposerClient(); - - Trace mTrace; - bool mLoaded = false; - int32_t mIncrementIndex = 0; - int64_t mCurrentTime = 0; - int32_t mNumThreads = DEFAULT_THREADS; - - Increment mCurrentIncrement; - - std::string mLastInput; - - static atomic_bool sReplayingManually; - bool mWaitingForNextVSync; - bool mWaitForTimeStamps; - nsecs_t mStopTimeStamp; - bool mHasStopped; - - std::mutex mLayerLock; - std::condition_variable mLayerCond; - std::unordered_map> mLayers; - std::unordered_map mColors; - - std::mutex mPendingLayersLock; - std::vector mLayersPendingRemoval; - - std::mutex mBufferQueueSchedulerLock; - std::unordered_map> mBufferQueueSchedulers; - - std::mutex mDisplayLock; - std::condition_variable mDisplayCond; - std::unordered_map> mDisplays; - - sp mComposerClient; - std::queue> mPendingIncrements; -}; - -} // namespace android -#endif diff --git a/cmds/surfacereplayer/replayer/trace_creator/trace_creator.py b/cmds/surfacereplayer/replayer/trace_creator/trace_creator.py deleted file mode 100644 index 58bfbf3c43b746a0b1f2e5882e85f0f8e81c4b41..0000000000000000000000000000000000000000 --- a/cmds/surfacereplayer/replayer/trace_creator/trace_creator.py +++ /dev/null @@ -1,285 +0,0 @@ -#!/usr/bin/python -from subprocess import call -import os -proto_path = os.environ['ANDROID_BUILD_TOP'] + "/frameworks/native/cmds/surfacereplayer/proto/src/" -call(["aprotoc", "-I=" + proto_path, "--python_out=.", proto_path + "trace.proto"]) - -from trace_pb2 import * - -trace = Trace() - -def main(): - global trace - while(1): - option = main_menu() - - if option == 0: - break - - increment = trace.increment.add() - increment.time_stamp = int(input("Time stamp of action: ")) - - if option == 1: - transaction(increment) - elif option == 2: - surface_create(increment) - elif option == 3: - surface_delete(increment) - elif option == 4: - display_create(increment) - elif option == 5: - display_delete(increment) - elif option == 6: - buffer_update(increment) - elif option == 7: - vsync_event(increment) - elif option == 8: - power_mode_update(increment) - - seralizeTrace() - -def seralizeTrace(): - with open("trace.dat", 'wb') as f: - f.write(trace.SerializeToString()) - - -def main_menu(): - print ("") - print ("What would you like to do?") - print ("1. Add transaction") - print ("2. Add surface creation") - print ("3. Add surface deletion") - print ("4. Add display creation") - print ("5. Add display deletion") - print ("6. Add buffer update") - print ("7. Add VSync event") - print ("8. Add power mode update") - print ("0. Finish and serialize") - print ("") - - return int(input("> ")) - -def transaction_menu(): - print ("") - print ("What kind of transaction?") - print ("1. Position Change") - print ("2. Size Change") - print ("3. Alpha Change") - print ("4. Layer Change") - print ("5. Crop Change") - print ("6. Final Crop Change") - print ("7. Matrix Change") - print ("9. Transparent Region Hint Change") - print ("10. Layer Stack Change") - print ("11. Hidden Flag Change") - print ("12. Opaque Flag Change") - print ("13. Secure Flag Change") - print ("14. Deferred Transaction Change") - print ("15. Display - Surface Change") - print ("16. Display - Layer Stack Change") - print ("17. Display - Size Change") - print ("18. Display - Projection Change") - print ("0. Finished adding Changes to this transaction") - print ("") - - return int(input("> ")) - -def transaction(increment): - global trace - - increment.transaction.synchronous \ - = bool(input("Is transaction synchronous (True/False): ")) - increment.transaction.animation \ - = bool(input("Is transaction animated (True/False): ")) - - while(1): - option = transaction_menu() - - if option == 0: - break - - change = None - if option <= 14: - change = increment.transaction.surface_change.add() - elif option >= 15 and option <= 18: - change = increment.transaction.display_change.add() - - change.id = int(input("ID of layer/display to undergo a change: ")) - - if option == 1: - change.position.x, change.position.y = position() - elif option == 2: - change.size.w, change.size.h = size() - elif option == 3: - change.alpha.alpha = alpha() - elif option == 4: - change.layer.layer = layer() - elif option == 5: - change.crop.rectangle.left, change.crop.rectangle.top, \ - change.crop.rectangle.right, change.crop.rectangle.bottom = crop() - elif option == 6: - change.final_crop.rectangle.left, \ - change.final_crop.rectangle.top, \ - change.final_crop.rectangle.right,\ - change.final_crop.rectangle.bottom = final_crop() - elif option == 7: - change.matrix.dsdx,\ - change.matrix.dtdx,\ - change.matrix.dsdy,\ - change.matrix.dtdy = layer() - elif option == 9: - for rect in transparent_region_hint(): - new = increment.transparent_region_hint.region.add() - new.left = rect[0] - new.top = rect[1] - new.right = rect[2] - new.bottom = rect[3] - elif option == 10: - change.layer_stack.layer_stack = layer_stack() - elif option == 11: - change.hidden_flag.hidden_flag = hidden_flag() - elif option == 12: - change.opaque_flag.opaque_flag = opaque_flag() - elif option == 13: - change.secure_flag.secure_flag = secure_flag() - elif option == 14: - change.deferred_transaction.layer_id, \ - change.deferred_transaction.frame_number = deferred_transaction() - elif option == 15: - change.surface.buffer_queue_id, \ - change.surface.buffer_queue_name = surface() - elif option == 16: - change.layer_stack.layer_stack = layer_stack() - elif option == 17: - change.size.w, change.size.h = size() - elif option == 18: - projection(change) - -def surface_create(increment): - increment.surface_creation.id = int(input("Enter id: ")) - n = str(raw_input("Enter name: ")) - increment.surface_creation.name = n - increment.surface_creation.w = input("Enter w: ") - increment.surface_creation.h = input("Enter h: ") - -def surface_delete(increment): - increment.surface_deletion.id = int(input("Enter id: ")) - -def display_create(increment): - increment.display_creation.id = int(input("Enter id: ")) - increment.display_creation.name = str(raw_input("Enter name: ")) - increment.display_creation.display_id = int(input("Enter display ID: ")) - increment.display_creation.is_secure = bool(input("Enter if secure: ")) - -def display_delete(increment): - increment.surface_deletion.id = int(input("Enter id: ")) - -def buffer_update(increment): - increment.buffer_update.id = int(input("Enter id: ")) - increment.buffer_update.w = int(input("Enter w: ")) - increment.buffer_update.h = int(input("Enter h: ")) - increment.buffer_update.frame_number = int(input("Enter frame_number: ")) - -def vsync_event(increment): - increment.vsync_event.when = int(input("Enter when: ")) - -def power_mode_update(increment): - increment.power_mode_update.id = int(input("Enter id: ")) - increment.power_mode_update.mode = int(input("Enter mode: ")) - -def position(): - x = input("Enter x: ") - y = input("Enter y: ") - - return float(x), float(y) - -def size(): - w = input("Enter w: ") - h = input("Enter h: ") - - return int(w), int(h) - -def alpha(): - alpha = input("Enter alpha: ") - - return float(alpha) - -def layer(): - layer = input("Enter layer: ") - - return int(layer) - -def crop(): - return rectangle() - -def final_crop(): - return rectangle() - -def matrix(): - dsdx = input("Enter dsdx: ") - dtdx = input("Enter dtdx: ") - dsdy = input("Enter dsdy: ") - dtdy = input("Enter dtdy: ") - - return float(dsdx) - -def transparent_region_hint(): - num = input("Enter number of rectangles in region: ") - - return [rectangle() in range(x)] - -def layer_stack(): - layer_stack = input("Enter layer stack: ") - - return int(layer_stack) - -def hidden_flag(): - flag = input("Enter hidden flag state (True/False): ") - - return bool(flag) - -def opaque_flag(): - flag = input("Enter opaque flag state (True/False): ") - - return bool(flag) - -def secure_flag(): - flag = input("Enter secure flag state (True/False): ") - - return bool(flag) - -def deferred_transaction(): - layer_id = input("Enter layer_id: ") - frame_number = input("Enter frame_number: ") - - return int(layer_id), int(frame_number) - -def surface(): - id = input("Enter id: ") - name = raw_input("Enter name: ") - - return int(id), str(name) - -def projection(change): - change.projection.orientation = input("Enter orientation: ") - print("Enter rectangle for viewport") - change.projection.viewport.left, \ - change.projection.viewport.top, \ - change.projection.viewport.right,\ - change.projection.viewport.bottom = rectangle() - print("Enter rectangle for frame") - change.projection.frame.left, \ - change.projection.frame.top, \ - change.projection.frame.right,\ - change.projection.frame.bottom = rectangle() - -def rectangle(): - left = input("Enter left: ") - top = input("Enter top: ") - right = input("Enter right: ") - bottom = input("Enter bottom: ") - - return int(left), int(top), int(right), int(bottom) - -if __name__ == "__main__": - main() diff --git a/data/etc/Android.bp b/data/etc/Android.bp index 249fe801bd623a011efd28d09c0a8fc71b6eaa85..e95432c98f894898dc073a5cf0a4de9eb34b9469 100644 --- a/data/etc/Android.bp +++ b/data/etc/Android.bp @@ -130,30 +130,102 @@ prebuilt_etc { defaults: ["frameworks_native_data_etc_defaults"], } +prebuilt_etc { + name: "android.hardware.sensor.accelerometer_limited_axes_uncalibrated.prebuilt.xml", + src: "android.hardware.sensor.accelerometer_limited_axes_uncalibrated.xml", + defaults: ["frameworks_native_data_etc_defaults"], +} + +prebuilt_etc { + name: "android.hardware.sensor.accelerometer_limited_axes.prebuilt.xml", + src: "android.hardware.sensor.accelerometer_limited_axes.xml", + defaults: ["frameworks_native_data_etc_defaults"], +} + +prebuilt_etc { + name: "android.hardware.sensor.accelerometer.prebuilt.xml", + src: "android.hardware.sensor.accelerometer.xml", + defaults: ["frameworks_native_data_etc_defaults"], +} + prebuilt_etc { name: "android.hardware.sensor.ambient_temperature.prebuilt.xml", src: "android.hardware.sensor.ambient_temperature.xml", defaults: ["frameworks_native_data_etc_defaults"], } +prebuilt_etc { + name: "android.hardware.sensor.assist.prebuilt.xml", + src: "android.hardware.sensor.assist.xml", + defaults: ["frameworks_native_data_etc_defaults"], +} + prebuilt_etc { name: "android.hardware.sensor.barometer.prebuilt.xml", src: "android.hardware.sensor.barometer.xml", defaults: ["frameworks_native_data_etc_defaults"], } +prebuilt_etc { + name: "android.hardware.sensor.compass.prebuilt.xml", + src: "android.hardware.sensor.compass.xml", + defaults: ["frameworks_native_data_etc_defaults"], +} + prebuilt_etc { name: "android.hardware.sensor.dynamic.head_tracker.prebuilt.xml", src: "android.hardware.sensor.dynamic.head_tracker.xml", defaults: ["frameworks_native_data_etc_defaults"], } +prebuilt_etc { + name: "android.hardware.sensor.gyroscope_limited_axes_uncalibrated.prebuilt.xml", + src: "android.hardware.sensor.gyroscope_limited_axes_uncalibrated.xml", + defaults: ["frameworks_native_data_etc_defaults"], +} + +prebuilt_etc { + name: "android.hardware.sensor.gyroscope_limited_axes.prebuilt.xml", + src: "android.hardware.sensor.gyroscope_limited_axes.xml", + defaults: ["frameworks_native_data_etc_defaults"], +} + prebuilt_etc { name: "android.hardware.sensor.gyroscope.prebuilt.xml", src: "android.hardware.sensor.gyroscope.xml", defaults: ["frameworks_native_data_etc_defaults"], } +prebuilt_etc { + name: "android.hardware.sensor.heading.prebuilt.xml", + src: "android.hardware.sensor.heading.xml", + defaults: ["frameworks_native_data_etc_defaults"], +} + +prebuilt_etc { + name: "android.hardware.sensor.heartrate.ecg.prebuilt.xml", + src: "android.hardware.sensor.heartrate.ecg.xml", + defaults: ["frameworks_native_data_etc_defaults"], +} + +prebuilt_etc { + name: "android.hardware.sensor.heartrate.fitness.prebuilt.xml", + src: "android.hardware.sensor.heartrate.fitness.xml", + defaults: ["frameworks_native_data_etc_defaults"], +} + +prebuilt_etc { + name: "android.hardware.sensor.heartrate.prebuilt.xml", + src: "android.hardware.sensor.heartrate.xml", + defaults: ["frameworks_native_data_etc_defaults"], +} + +prebuilt_etc { + name: "android.hardware.sensor.hifi_sensors.prebuilt.xml", + src: "android.hardware.sensor.hifi_sensors.xml", + defaults: ["frameworks_native_data_etc_defaults"], +} + prebuilt_etc { name: "android.hardware.sensor.hinge_angle.prebuilt.xml", src: "android.hardware.sensor.hinge_angle.xml", @@ -178,6 +250,18 @@ prebuilt_etc { defaults: ["frameworks_native_data_etc_defaults"], } +prebuilt_etc { + name: "android.hardware.sensor.stepcounter.prebuilt.xml", + src: "android.hardware.sensor.stepcounter.xml", + defaults: ["frameworks_native_data_etc_defaults"], +} + +prebuilt_etc { + name: "android.hardware.sensor.stepdetector.prebuilt.xml", + src: "android.hardware.sensor.stepdetector.xml", + defaults: ["frameworks_native_data_etc_defaults"], +} + prebuilt_etc { name: "android.hardware.telephony.gsm.prebuilt.xml", src: "android.hardware.telephony.gsm.xml", @@ -268,6 +352,12 @@ prebuilt_etc { defaults: ["frameworks_native_data_etc_defaults"], } +prebuilt_etc { + name: "android.software.opengles.deqp.level-2023-03-01.prebuilt.xml", + src: "android.software.opengles.deqp.level-2023-03-01.xml", + defaults: ["frameworks_native_data_etc_defaults"], +} + prebuilt_etc { name: "android.software.sip.voip.prebuilt.xml", src: "android.software.sip.voip.xml", @@ -292,6 +382,12 @@ prebuilt_etc { defaults: ["frameworks_native_data_etc_defaults"], } +prebuilt_etc { + name: "android.software.vulkan.deqp.level-2023-03-01.prebuilt.xml", + src: "android.software.vulkan.deqp.level-2023-03-01.xml", + defaults: ["frameworks_native_data_etc_defaults"], +} + prebuilt_etc { name: "aosp_excluded_hardware.prebuilt.xml", src: "aosp_excluded_hardware.xml", diff --git a/data/etc/android.hardware.telephony.satellite.xml b/data/etc/android.hardware.telephony.satellite.xml index 5966cba27789d2949226173659f24d549f4ffb9a..d36c9587635b99a9f47e82644e3370e8a048509d 100644 --- a/data/etc/android.hardware.telephony.satellite.xml +++ b/data/etc/android.hardware.telephony.satellite.xml @@ -14,7 +14,7 @@ limitations under the License. --> - + diff --git a/data/etc/android.hardware.type.automotive.xml b/data/etc/android.hardware.type.automotive.xml index 113945b0c56815ee355cc778f876d1308fea8e3a..a9b4b0526a2a3a0b1f16322e5266b57bcad78ba4 100644 --- a/data/etc/android.hardware.type.automotive.xml +++ b/data/etc/android.hardware.type.automotive.xml @@ -17,6 +17,4 @@ - - diff --git a/data/etc/android.software.credentials.xml b/data/etc/android.software.credentials.xml new file mode 100644 index 0000000000000000000000000000000000000000..234d14411cbb36b4c8f9cc049d4bd0574fa9acf4 --- /dev/null +++ b/data/etc/android.software.credentials.xml @@ -0,0 +1,19 @@ + + + + + + diff --git a/data/etc/android.software.opengles.deqp.level-2023-03-01.xml b/data/etc/android.software.opengles.deqp.level-2023-03-01.xml new file mode 100644 index 0000000000000000000000000000000000000000..d0b594c73d6aa3578cc7b875aa5c175e7741e0b7 --- /dev/null +++ b/data/etc/android.software.opengles.deqp.level-2023-03-01.xml @@ -0,0 +1,21 @@ + + + + + + + diff --git a/data/etc/android.software.vulkan.deqp.level-2023-03-01.xml b/data/etc/android.software.vulkan.deqp.level-2023-03-01.xml new file mode 100644 index 0000000000000000000000000000000000000000..6ae248ac3ce49f7bd7635662b00d03cf15330063 --- /dev/null +++ b/data/etc/android.software.vulkan.deqp.level-2023-03-01.xml @@ -0,0 +1,21 @@ + + + + + + + diff --git a/data/etc/handheld_core_hardware.xml b/data/etc/handheld_core_hardware.xml index 8fdd8d06d5c7a8762a0be8a248008fb94b6a5908..08330126c748ec8fc55168e42dabb7bed5d0218f 100644 --- a/data/etc/handheld_core_hardware.xml +++ b/data/etc/handheld_core_hardware.xml @@ -52,6 +52,7 @@ + diff --git a/data/etc/input/Android.bp b/data/etc/input/Android.bp new file mode 100644 index 0000000000000000000000000000000000000000..90f3c6b49aacb03381517621a6a670b4996da120 --- /dev/null +++ b/data/etc/input/Android.bp @@ -0,0 +1,14 @@ +package { + default_applicable_licenses: ["frameworks_native_license"], +} + +filegroup { + name: "motion_predictor_model.fb", + srcs: ["motion_predictor_model.fb"], +} + +prebuilt_etc { + name: "motion_predictor_model_prebuilt", + filename_from_src: true, + src: "motion_predictor_model.fb", +} diff --git a/data/etc/input/motion_predictor_model.fb b/data/etc/input/motion_predictor_model.fb new file mode 100644 index 0000000000000000000000000000000000000000..10b3c8b1143cf51be9a11b3f12fd54c1458ad398 Binary files /dev/null and b/data/etc/input/motion_predictor_model.fb differ diff --git a/data/etc/tablet_core_hardware.xml b/data/etc/tablet_core_hardware.xml index 59d5b10947deb473512e33ff41e43710d0307d60..6af4d914189409e217cb102912c9d812001e646b 100644 --- a/data/etc/tablet_core_hardware.xml +++ b/data/etc/tablet_core_hardware.xml @@ -52,6 +52,7 @@ + diff --git a/headers/media_plugin/media/hardware/VideoAPI.h b/headers/media_plugin/media/hardware/VideoAPI.h index a09087698c06dddfd0febabb6b2bc0d46e6d89da..54666804c06e3373f1274659608d4412bed89411 100644 --- a/headers/media_plugin/media/hardware/VideoAPI.h +++ b/headers/media_plugin/media/hardware/VideoAPI.h @@ -127,6 +127,8 @@ struct __attribute__ ((__packed__, aligned(alignof(uint32_t)))) ColorAspects { PrimariesBT601_6_525, // Rec.ITU-R BT.601-6 525 or equivalent PrimariesGenericFilm, // Generic Film PrimariesBT2020, // Rec.ITU-R BT.2020 or equivalent + PrimariesRP431, // SMPTE RP 431-2 (DCI P3) + PrimariesEG432, // SMPTE EG 432-1 (Display P3) PrimariesOther = 0xff, }; @@ -173,6 +175,8 @@ struct __attribute__ ((__packed__, aligned(alignof(uint32_t)))) ColorAspects { StandardBT2020Constant, // PrimariesBT2020 and MatrixBT2020Constant StandardBT470M, // PrimariesBT470_6M and MatrixBT470_6M StandardFilm, // PrimariesGenericFilm and KR=0.253, KB=0.068 + StandardDisplayP3, // PrimariesEG432 and MatrixBT601_6 + // StandardAdobeRGB, // for placeholder only (not used by media) StandardOther = 0xff, }; @@ -282,6 +286,8 @@ inline static const char *asString(ColorAspects::Primaries i, const char *def = case ColorAspects::PrimariesBT601_6_525: return "BT601_6_525"; case ColorAspects::PrimariesGenericFilm: return "GenericFilm"; case ColorAspects::PrimariesBT2020: return "BT2020"; + case ColorAspects::PrimariesRP431: return "RP431"; + case ColorAspects::PrimariesEG432: return "EG432"; case ColorAspects::PrimariesOther: return "Other"; default: return def; } @@ -332,6 +338,8 @@ inline static const char *asString(ColorAspects::Standard i, const char *def = " case ColorAspects::StandardBT2020Constant: return "BT2020Constant"; case ColorAspects::StandardBT470M: return "BT470M"; case ColorAspects::StandardFilm: return "Film"; + case ColorAspects::StandardDisplayP3: return "DisplayP3"; + // case ColorAspects::StandardAdobeRGB: return "AdobeRGB"; case ColorAspects::StandardOther: return "Other"; default: return def; } diff --git a/include/android/choreographer.h b/include/android/choreographer.h index cd8e63dec2eb1c351cce8efb9393ec534ae163a7..f999708f0473512725320b64d69646421b2d673b 100644 --- a/include/android/choreographer.h +++ b/include/android/choreographer.h @@ -219,12 +219,16 @@ void AChoreographer_unregisterRefreshRateCallback(AChoreographer* choreographer, * * Note that this time should \b not be used to advance animation clocks. * Instead, see AChoreographerFrameCallbackData_getFrameTimelineExpectedPresentationTimeNanos(). + * + * Available since API level 33. */ int64_t AChoreographerFrameCallbackData_getFrameTimeNanos( const AChoreographerFrameCallbackData* data) __INTRODUCED_IN(33); /** * The number of possible frame timelines. + * + * Available since API level 33. */ size_t AChoreographerFrameCallbackData_getFrameTimelinesLength( const AChoreographerFrameCallbackData* data) __INTRODUCED_IN(33); @@ -233,15 +237,20 @@ size_t AChoreographerFrameCallbackData_getFrameTimelinesLength( * Gets the index of the platform-preferred frame timeline. * The preferred frame timeline is the default * by which the platform scheduled the app, based on the device configuration. + * + * Available since API level 33. */ size_t AChoreographerFrameCallbackData_getPreferredFrameTimelineIndex( const AChoreographerFrameCallbackData* data) __INTRODUCED_IN(33); /** * Gets the token used by the platform to identify the frame timeline at the given \c index. + * q + * Available since API level 33. * * \param index index of a frame timeline, in \f( [0, FrameTimelinesLength) \f). See * AChoreographerFrameCallbackData_getFrameTimelinesLength() + * */ AVsyncId AChoreographerFrameCallbackData_getFrameTimelineVsyncId( const AChoreographerFrameCallbackData* data, size_t index) __INTRODUCED_IN(33); @@ -250,6 +259,8 @@ AVsyncId AChoreographerFrameCallbackData_getFrameTimelineVsyncId( * Gets the time in nanoseconds at which the frame described at the given \c index is expected to * be presented. This time should be used to advance any animation clocks. * + * Available since API level 33. + * * \param index index of a frame timeline, in \f( [0, FrameTimelinesLength) \f). See * AChoreographerFrameCallbackData_getFrameTimelinesLength() */ @@ -260,6 +271,8 @@ int64_t AChoreographerFrameCallbackData_getFrameTimelineExpectedPresentationTime * Gets the time in nanoseconds at which the frame described at the given \c index needs to be * ready by in order to be presented on time. * + * Available since API level 33. + * * \param index index of a frame timeline, in \f( [0, FrameTimelinesLength) \f). See * AChoreographerFrameCallbackData_getFrameTimelinesLength() */ diff --git a/include/android/configuration.h b/include/android/configuration.h index 88019ae0547c2d208efc54601cbbe14e300a604a..46c7dfeceb58172554b8d0bca198ce3c69888806 100644 --- a/include/android/configuration.h +++ b/include/android/configuration.h @@ -470,11 +470,37 @@ enum { * and HDR configurations. */ ACONFIGURATION_COLOR_MODE = 0x10000, + /** + * Bit mask for + * grammatical gender + * configuration. + */ + ACONFIGURATION_GRAMMATICAL_GENDER = 0x20000, /** * Constant used to to represent MNC (Mobile Network Code) zero. * 0 cannot be used, since it is used to represent an undefined MNC. */ ACONFIGURATION_MNC_ZERO = 0xffff, + + /** + * Grammatical gender: not specified. + */ + ACONFIGURATION_GRAMMATICAL_GENDER_ANY = 0, + + /** + * Grammatical gender: neuter. + */ + ACONFIGURATION_GRAMMATICAL_GENDER_NEUTER = 1, + + /** + * Grammatical gender: feminine. + */ + ACONFIGURATION_GRAMMATICAL_GENDER_FEMININE = 2, + + /** + * Grammatical gender: masculine. + */ + ACONFIGURATION_GRAMMATICAL_GENDER_MASCULINE = 3, }; /** @@ -725,6 +751,24 @@ int32_t AConfiguration_getLayoutDirection(AConfiguration* config) __INTRODUCED_I */ void AConfiguration_setLayoutDirection(AConfiguration* config, int32_t value) __INTRODUCED_IN(17); +/** + * Return the configuration's grammatical gender, or ACONFIGURATION_GRAMMATICAL_GENDER_ANY if + * not set. + * + * Available since API level 34. + */ +int32_t AConfiguration_getGrammaticalGender(AConfiguration* config) + __INTRODUCED_IN(__ANDROID_API_U__); + +/** + * Set the configuration's grammatical gender to one of the + * ACONFIGURATION_GRAMMATICAL_GENDER_* constants. + * + * Available since API level 34. + */ +void AConfiguration_setGrammaticalGender(AConfiguration* config, int32_t value) + __INTRODUCED_IN(__ANDROID_API_U__); + /** * Perform a diff between two configurations. Returns a bit mask of * ACONFIGURATION_* constants, each bit set meaning that configuration element diff --git a/include/android/font.h b/include/android/font.h index 8a3a474f2521183894f89da7720491884aa2c1b4..022572535b47baf46f1070ed4645fe49bf704c88 100644 --- a/include/android/font.h +++ b/include/android/font.h @@ -31,6 +31,7 @@ #include #include +#include #include /****************************************************************** @@ -86,10 +87,11 @@ enum { AFONT_WEIGHT_MAX = 1000 }; +struct AFont; /** * AFont provides information of the single font configuration. */ -struct AFont; +typedef struct AFont AFont; /** * Close an AFont. diff --git a/include/android/font_matcher.h b/include/android/font_matcher.h index 4417422687aeac245e02d5383c889576511b47a5..60ff95e1235219308b11aa48542cb087eb9b0754 100644 --- a/include/android/font_matcher.h +++ b/include/android/font_matcher.h @@ -75,6 +75,7 @@ #include #include +#include #include #include @@ -116,11 +117,12 @@ enum { AFAMILY_VARIANT_ELEGANT = 2, }; +struct AFontMatcher; /** * AFontMatcher performs match operation on given parameters and available font files. * This matcher is not a thread-safe object. Do not pass this matcher to other threads. */ -struct AFontMatcher; +typedef struct AFontMatcher AFontMatcher; /** * Select the best font from given parameters. diff --git a/include/android/input.h b/include/android/input.h index 2a7cea69f8de8b18fa04d1fc28cdcb982c830035..a45f065dd037ba5f0f35742d3342ac8d7ba81f98 100644 --- a/include/android/input.h +++ b/include/android/input.h @@ -54,7 +54,14 @@ #include #include #include + +// This file is included by modules that have host support but android/looper.h is not supported +// on host. __REMOVED_IN needs to be defined in order for android/looper.h to be compiled. +#ifndef __BIONIC__ +#define __REMOVED_IN(x) __attribute__((deprecated)) +#endif #include + #include #if !defined(__INTRODUCED_IN) @@ -764,9 +771,64 @@ enum { * The interpretation of a generic axis is device-specific. */ AMOTION_EVENT_AXIS_GENERIC_16 = 47, + /** + * Axis constant: X gesture offset axis of a motion event. + * + * - For a touch pad, reports the distance that a swipe gesture has moved in the X axis, as a + * proportion of the touch pad's size. For example, if a touch pad is 1000 units wide, and a + * swipe gesture starts at X = 500 then moves to X = 400, this axis would have a value of + * -0.1. + * + * These values are relative to the state from the last event, not accumulated, so developers + * should make sure to process this axis value for all batched historical events. + */ + AMOTION_EVENT_AXIS_GESTURE_X_OFFSET = 48, + /** + * Axis constant: Y gesture offset axis of a motion event. + * + * The same as {@link AMOTION_EVENT_AXIS_GESTURE_X_OFFSET}, but for the Y axis. + */ + AMOTION_EVENT_AXIS_GESTURE_Y_OFFSET = 49, + /** + * Axis constant: X scroll distance axis of a motion event. + * + * - For a touch pad, reports the distance that should be scrolled in the X axis as a result of + * the user's two-finger scroll gesture, in display pixels. + * + * These values are relative to the state from the last event, not accumulated, so developers + * should make sure to process this axis value for all batched historical events. + */ + AMOTION_EVENT_AXIS_GESTURE_SCROLL_X_DISTANCE = 50, + /** + * Axis constant: Y scroll distance axis of a motion event. + * + * The same as {@link AMOTION_EVENT_AXIS_GESTURE_SCROLL_X_DISTANCE}, but for the Y axis. + */ + AMOTION_EVENT_AXIS_GESTURE_SCROLL_Y_DISTANCE = 51, + /** + * Axis constant: pinch scale factor of a motion event. + * + * - For a touch pad, reports the change in distance between the fingers when the user is making + * a pinch gesture, as a proportion of that distance when the gesture was last reported. For + * example, if the fingers were 50 units apart and are now 52 units apart, the scale factor + * would be 1.04. + * + * These values are relative to the state from the last event, not accumulated, so developers + * should make sure to process this axis value for all batched historical events. + */ + AMOTION_EVENT_AXIS_GESTURE_PINCH_SCALE_FACTOR = 52, + + /** + * Note: This is not an "Axis constant". It does not represent any axis, nor should it be used + * to represent any axis. It is a constant holding the value of the largest defined axis value, + * to make some computations (like iterating through all possible axes) cleaner. + * Please update the value accordingly if you add a new axis. + */ + AMOTION_EVENT_MAXIMUM_VALID_AXIS_VALUE = AMOTION_EVENT_AXIS_GESTURE_PINCH_SCALE_FACTOR, // NOTE: If you add a new axis here you must also add it to several other files. // Refer to frameworks/base/core/java/android/view/MotionEvent.java for the full list. + // Update AMOTION_EVENT_MAXIMUM_VALID_AXIS_VALUE accordingly as well. }; /** @@ -834,6 +896,27 @@ enum AMotionClassification : uint32_t { * This classification type should be used to accelerate the long press behaviour. */ AMOTION_EVENT_CLASSIFICATION_DEEP_PRESS = 2, + /** + * Classification constant: touchpad two-finger swipe. + * + * The current event stream represents the user swiping with two fingers on a touchpad. + */ + AMOTION_EVENT_CLASSIFICATION_TWO_FINGER_SWIPE = 3, + /** + * Classification constant: multi-finger swipe. + * + * The current event stream represents the user swiping with three or more fingers on a + * touchpad. Unlike two-finger swipes, these are only to be handled by the system UI, which is + * why they have a separate constant from two-finger swipes. + */ + AMOTION_EVENT_CLASSIFICATION_MULTI_FINGER_SWIPE = 4, + /** + * Classification constant: pinch. + * + * The current event stream represents the user pinching with two fingers on a touchpad. The + * gesture is centered around the current cursor position. + */ + AMOTION_EVENT_CLASSIFICATION_PINCH = 5, }; /** diff --git a/include/android/keycodes.h b/include/android/keycodes.h index 3357660f5cba5bb2ee8de796ea6eecf08840b0b2..f8fb256fae1954735a2a23e5a494947e2eecefb2 100644 --- a/include/android/keycodes.h +++ b/include/android/keycodes.h @@ -809,6 +809,36 @@ enum { AKEYCODE_DEMO_APP_3 = 303, /** Demo Application key #4. */ AKEYCODE_DEMO_APP_4 = 304, + /** Keyboard backlight Down key. + * Adjusts the keyboard backlight brightness down. */ + AKEYCODE_KEYBOARD_BACKLIGHT_DOWN = 305, + /** Keyboard backlight Up key. + * Adjusts the keyboard backlight brightness up. */ + AKEYCODE_KEYBOARD_BACKLIGHT_UP = 306, + /** Keyboard backlight Toggle key. + * Toggles the keyboard backlight on/off. */ + AKEYCODE_KEYBOARD_BACKLIGHT_TOGGLE = 307, + /** The primary button on the barrel of a stylus. + * This is usually the button closest to the tip of the stylus. */ + AKEYCODE_STYLUS_BUTTON_PRIMARY = 308, + /** The secondary button on the barrel of a stylus. + * This is usually the second button from the tip of the stylus. */ + AKEYCODE_STYLUS_BUTTON_SECONDARY = 309, + /** The tertiary button on the barrel of a stylus. + * This is usually the third button from the tip of the stylus. */ + AKEYCODE_STYLUS_BUTTON_TERTIARY = 310, + /** A button on the tail end of a stylus. */ + AKEYCODE_STYLUS_BUTTON_TAIL = 311, + /** Key to open recent apps (a.k.a. Overview) */ + AKEYCODE_RECENT_APPS = 312, + /** User customizable key #1. */ + AKEYCODE_MACRO_1 = 313, + /** User customizable key #2. */ + AKEYCODE_MACRO_2 = 314, + /** User customizable key #3. */ + AKEYCODE_MACRO_3 = 315, + /** User customizable key #4. */ + AKEYCODE_MACRO_4 = 316, // NOTE: If you add a new keycode here you must also add it to several other files. // Refer to frameworks/base/core/java/android/view/KeyEvent.java for the full list. diff --git a/include/android/looper.h b/include/android/looper.h index 718f703048dcbd5dae976729f1df73327b37dc11..4fe142a8e2644ca14de93491947a7c287c7bab03 100644 --- a/include/android/looper.h +++ b/include/android/looper.h @@ -201,8 +201,11 @@ int ALooper_pollOnce(int timeoutMillis, int* outFd, int* outEvents, void** outDa * Like ALooper_pollOnce(), but performs all pending callbacks until all * data has been consumed or a file descriptor is available with no callback. * This function will never return ALOOPER_POLL_CALLBACK. + * + * Removed in API 34 as ALooper_pollAll can swallow ALooper_wake calls. + * Use ALooper_pollOnce instead. */ -int ALooper_pollAll(int timeoutMillis, int* outFd, int* outEvents, void** outData); +int ALooper_pollAll(int timeoutMillis, int* outFd, int* outEvents, void** outData) __REMOVED_IN(1); /** * Wakes the poll asynchronously. diff --git a/include/android/performance_hint.h b/include/android/performance_hint.h index 5fa47f64be946d6475fc22ab504e3bcacf94a3c3..b494f897d5df2680a67fe97dcbac5d92fcb32c55 100644 --- a/include/android/performance_hint.h +++ b/include/android/performance_hint.h @@ -37,6 +37,7 @@ #include #include +#include __BEGIN_DECLS @@ -159,6 +160,23 @@ int APerformanceHint_reportActualWorkDuration( void APerformanceHint_closeSession( APerformanceHintSession* session) __INTRODUCED_IN(__ANDROID_API_T__); +/** + * Set a list of threads to the performance hint session. This operation will replace + * the current list of threads with the given list of threads. + * + * @param session The performance hint session instance for the threads. + * @param threadIds The list of threads to be associated with this session. They must be part of + * this app's thread group. + * @param size the size of the list of threadIds. + * @return 0 on success. + * EINVAL if the list of thread ids is empty or if any of the thread ids is not part of the thread group. + * EPIPE if communication with the system service has failed. + */ +int APerformanceHint_setThreads( + APerformanceHintSession* session, + const pid_t* threadIds, + size_t size) __INTRODUCED_IN(__ANDROID_API_U__); + __END_DECLS #endif // ANDROID_NATIVE_PERFORMANCE_HINT_H diff --git a/include/android/sensor.h b/include/android/sensor.h index ba81bc8d8e1cb9666cea9053f2a1fd41b6f17262..16c5dde60fab56709e95d84dc548783ea892ba45 100644 --- a/include/android/sensor.h +++ b/include/android/sensor.h @@ -45,6 +45,11 @@ * - DO NOT CHANGE THE LAYOUT OR SIZE OF STRUCTURES */ +// This file is included by modules that have host support but android/looper.h is not supported +// on host. __REMOVED_IN needs to be defined in order for android/looper.h to be compiled. +#ifndef __BIONIC__ +#define __REMOVED_IN(x) __attribute__((deprecated)) +#endif #include #include @@ -606,10 +611,14 @@ typedef struct AHeadingEvent { * sensors_event_t */ typedef struct ASensorEvent { - int32_t version; /* sizeof(struct ASensorEvent) */ - int32_t sensor; /** The sensor that generates this event */ - int32_t type; /** Sensor type for the event, such as {@link ASENSOR_TYPE_ACCELEROMETER} */ - int32_t reserved0; /** do not use */ + /* sizeof(struct ASensorEvent) */ + int32_t version; + /** The sensor that generates this event */ + int32_t sensor; + /** Sensor type for the event, such as {@link ASENSOR_TYPE_ACCELEROMETER} */ + int32_t type; + /** do not use */ + int32_t reserved0; /** * The time in nanoseconds at which the event happened, and its behavior * is identical to diff --git a/include/android/surface_control.h b/include/android/surface_control.h index 6223ef7f82f6b23bf3d86e75f52431d0186fb33a..cce2e46471d5cb1e6c5a389de6d73a320f90d8cd 100644 --- a/include/android/surface_control.h +++ b/include/android/surface_control.h @@ -54,6 +54,12 @@ typedef struct ASurfaceControl ASurfaceControl; * The caller takes ownership of the ASurfaceControl returned and must release it * using ASurfaceControl_release below. * + * By default the \a ASurfaceControl will be visible and display any buffer submitted. In + * addition, the default buffer submission control may release and not display all buffers + * that are submitted before receiving a callback for the previous buffer. See + * \a ASurfaceTransaction_setVisibility and \a ASurfaceTransaction_setEnableBackPressure to + * change the default behaviors after creation. + * * Available since API level 29. */ ASurfaceControl* ASurfaceControl_createFromWindow(ANativeWindow* parent, const char* debug_name) @@ -520,6 +526,53 @@ void ASurfaceTransaction_setHdrMetadata_cta861_3(ASurfaceTransaction* transactio struct AHdrMetadata_cta861_3* metadata) __INTRODUCED_IN(29); +/** + * Sets the desired extended range brightness for the layer. This only applies for layers whose + * dataspace has RANGE_EXTENDED set on it. + * + * Available since API level 34. + * + * @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 + * 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. + * + * Default value is 1.0f. + * + * Transfer functions that encode their own brightness ranges, such as + * HLG or PQ, should also set this to 1.0f and instead communicate + * extended content brightness information via metadata such as CTA861_3 + * or SMPTE2086. + * + * Must be finite && >= 1.0f + * + * @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 + * to the max display brightness. The system may not be able to, or may choose + * not to, deliver the requested range. + * + * While requesting a large desired ratio will result in the most + * dynamic range, voluntarily reducing the requested range can help + * improve battery life as well as can improve quality by ensuring + * greater bit depth is allocated to the luminance range in use. + * + * Default value is 1.0f and indicates that extended range brightness + * is not being used, so the resulting SDR or HDR behavior will be + * determined entirely by the dataspace being used (ie, typically SDR + * however PQ or HLG transfer functions will still result in HDR) + * + * Must be finite && >= 1.0f + * + * Available since API level 34. + */ +void ASurfaceTransaction_setExtendedRangeBrightness(ASurfaceTransaction* transaction, + ASurfaceControl* surface_control, + float currentBufferRatio, + float desiredRatio) __INTRODUCED_IN(__ANDROID_API_U__); + /** * Same as ASurfaceTransaction_setFrameRateWithChangeStrategy(transaction, surface_control, * frameRate, compatibility, ANATIVEWINDOW_CHANGE_FRAME_RATE_ONLY_IF_SEAMLESS). @@ -545,6 +598,8 @@ void ASurfaceTransaction_setFrameRate(ASurfaceTransaction* transaction, * You can register for changes in the refresh rate using * \a AChoreographer_registerRefreshRateCallback. * + * See ASurfaceTransaction_clearFrameRate(). + * * \param frameRate is the intended frame rate of this surface, in frames per second. 0 is a special * value that indicates the app will accept the system's choice for the display frame rate, which is * the default behavior if this function isn't called. The frameRate param does not need to @@ -567,6 +622,31 @@ void ASurfaceTransaction_setFrameRateWithChangeStrategy(ASurfaceTransaction* tra int8_t compatibility, int8_t changeFrameRateStrategy) __INTRODUCED_IN(31); +/** + * Clears the frame rate which is set for \a surface_control. + * + * This is equivalent to calling + * ASurfaceTransaction_setFrameRateWithChangeStrategy( + * transaction, 0, compatibility, changeFrameRateStrategy). + * + * Usage of this API won't directly affect the application's frame production pipeline. However, + * because the system may change the display refresh rate, calls to this function may result in + * changes to Choreographer callback timings, and changes to the time interval at which the system + * releases buffers back to the application. + * + * See ASurfaceTransaction_setFrameRateWithChangeStrategy() + * + * You can register for changes in the refresh rate using + * \a AChoreographer_registerRefreshRateCallback. + * + * See ASurfaceTransaction_setFrameRateWithChangeStrategy(). + * + * Available since API level 34. + */ +void ASurfaceTransaction_clearFrameRate(ASurfaceTransaction* transaction, + ASurfaceControl* surface_control) + __INTRODUCED_IN(__ANDROID_API_U__); + /** * Indicate whether to enable backpressure for buffer submission to a given SurfaceControl. * @@ -587,6 +667,8 @@ void ASurfaceTransaction_setFrameRateWithChangeStrategy(ASurfaceTransaction* tra * and pushing buffers earlier for server side queuing will be advantageous * in such cases. * + * Available since API level 31. + * * \param transaction The transaction in which to make the change. * \param surface_control The ASurfaceControl on which to control buffer backpressure behavior. * \param enableBackPressure Whether to enable back pressure. @@ -608,6 +690,8 @@ void ASurfaceTransaction_setEnableBackPressure(ASurfaceTransaction* transaction, * AChoreographer_postVsyncCallback(). The \c vsyncId can then be extracted from the * callback payload using AChoreographerFrameCallbackData_getFrameTimelineVsyncId(). * + * Available since API level 33. + * * \param vsyncId The vsync ID received from AChoreographer, setting the frame's presentation target * to the corresponding expected presentation time and deadline from the frame to be rendered. A * stale or invalid value will be ignored. diff --git a/include/android/surface_control_jni.h b/include/android/surface_control_jni.h new file mode 100644 index 0000000000000000000000000000000000000000..840f6e724b034dea8adab1291dec913e356971bd --- /dev/null +++ b/include/android/surface_control_jni.h @@ -0,0 +1,68 @@ +/* + * 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. + */ + +/** + * @addtogroup NativeActivity Native Activity + * @{ + */ + +/** + * @file surface_control_jni.h + */ + +#ifndef ANDROID_SURFACE_CONTROL_JNI_H +#define ANDROID_SURFACE_CONTROL_JNI_H + +#include +#include + +#include + +__BEGIN_DECLS + +/** + * Return the ASurfaceControl wrapped by a Java SurfaceControl object. + * + * The caller takes ownership of the returned ASurfaceControl returned and must + * release it * using ASurfaceControl_release. + * + * surfaceControlObj must be a non-null instance of android.view.SurfaceControl + * and isValid() must be true. + * + * Available since API level 34. + */ +ASurfaceControl* _Nonnull ASurfaceControl_fromJava(JNIEnv* _Nonnull env, + jobject _Nonnull surfaceControlObj) __INTRODUCED_IN(__ANDROID_API_U__); + +/** + * Return the ASurfaceTransaction wrapped by a Java Transaction object. + * + * The returned ASurfaceTransaction is still owned by the Java Transaction object is only + * valid while the Java Transaction object is alive. In particular, the returned transaction + * must NOT be deleted with ASurfaceTransaction_delete. + * + * transactionObj must be a non-null instance of + * android.view.SurfaceControl.Transaction and close() must not already be called. + * + * Available since API level 34. + */ +ASurfaceTransaction* _Nonnull ASurfaceTransaction_fromJava(JNIEnv* _Nonnull env, + jobject _Nonnull transactionObj) __INTRODUCED_IN(__ANDROID_API_U__); + +__END_DECLS + +#endif // ANDROID_SURFACE_CONTROL_JNI_H +/** @} */ diff --git a/include/android/system_fonts.h b/include/android/system_fonts.h index b0bbb954a937881bff44b2bd718a8234c123ae44..94484eaf54cef1c06702137919aec78a5592cdd0 100644 --- a/include/android/system_fonts.h +++ b/include/android/system_fonts.h @@ -87,13 +87,14 @@ __BEGIN_DECLS +struct ASystemFontIterator; /** * ASystemFontIterator provides access to the system font configuration. * * ASystemFontIterator is an iterator for all available system font settings. * This iterator is not a thread-safe object. Do not pass this iterator to other threads. */ -struct ASystemFontIterator; +typedef struct ASystemFontIterator ASystemFontIterator; /** * Create a system font iterator. diff --git a/include/audiomanager/AudioManager.h b/include/audiomanager/AudioManager.h index 4aa2f60d45c73255a835edcf2785c3473b6ad3d3..43048dbff05d8a74cd9bebde020b02b62f155af6 100644 --- a/include/audiomanager/AudioManager.h +++ b/include/audiomanager/AudioManager.h @@ -38,8 +38,60 @@ typedef enum { PLAYER_STATE_PAUSED = 3, PLAYER_STATE_STOPPED = 4, PLAYER_UPDATE_DEVICE_ID = 5, + PLAYER_UPDATE_PORT_ID = 6, + PLAYER_UPDATE_MUTED = 7, + PLAYER_UPDATE_FORMAT = 8, } player_state_t; +static constexpr char + kExtraPlayerEventSpatializedKey[] = "android.media.extra.PLAYER_EVENT_SPATIALIZED"; +static constexpr char + kExtraPlayerEventSampleRateKey[] = "android.media.extra.PLAYER_EVENT_SAMPLE_RATE"; +static constexpr char + kExtraPlayerEventChannelMaskKey[] = "android.media.extra.PLAYER_EVENT_CHANNEL_MASK"; + +static constexpr char + kExtraPlayerEventMuteKey[] = "android.media.extra.PLAYER_EVENT_MUTE"; +enum { + PLAYER_MUTE_MASTER = (1 << 0), + PLAYER_MUTE_STREAM_VOLUME = (1 << 1), + PLAYER_MUTE_STREAM_MUTED = (1 << 2), + PLAYER_MUTE_PLAYBACK_RESTRICTED = (1 << 3), + PLAYER_MUTE_CLIENT_VOLUME = (1 << 4), + PLAYER_MUTE_VOLUME_SHAPER = (1 << 5), +}; + +struct mute_state_t { + /** Flag used when the master volume is causing the mute state. */ + bool muteFromMasterMute = false; + /** Flag used when the stream volume is causing the mute state. */ + bool muteFromStreamVolume = false; + /** Flag used when the stream muted is causing the mute state. */ + bool muteFromStreamMuted = false; + /** Flag used when playback is restricted by AppOps manager with OP_PLAY_AUDIO. */ + bool muteFromPlaybackRestricted = false; + /** Flag used when audio track was muted by client volume. */ + bool muteFromClientVolume = false; + /** Flag used when volume is muted by volume shaper. */ + bool muteFromVolumeShaper = false; + + explicit operator int() const + { + int result = muteFromMasterMute * PLAYER_MUTE_MASTER; + result |= muteFromStreamVolume * PLAYER_MUTE_STREAM_VOLUME; + result |= muteFromStreamMuted * PLAYER_MUTE_STREAM_MUTED; + result |= muteFromPlaybackRestricted * PLAYER_MUTE_PLAYBACK_RESTRICTED; + result |= muteFromClientVolume * PLAYER_MUTE_CLIENT_VOLUME; + result |= muteFromVolumeShaper * PLAYER_MUTE_VOLUME_SHAPER; + return result; + } + + bool operator==(const mute_state_t& other) const + { + return static_cast(*this) == static_cast(other); + } +}; + // must be kept in sync with definitions in AudioManager.java #define RECORD_RIID_INVALID -1 diff --git a/include/audiomanager/IAudioManager.h b/include/audiomanager/IAudioManager.h index 426e10c9bcf558767c6e95a276c567d1e2235899..769670ea99cc21bee55a18825622ef53f4c4b54e 100644 --- a/include/audiomanager/IAudioManager.h +++ b/include/audiomanager/IAudioManager.h @@ -17,8 +17,10 @@ #ifndef ANDROID_IAUDIOMANAGER_H #define ANDROID_IAUDIOMANAGER_H +#include #include #include +#include #include #include @@ -40,6 +42,7 @@ public: RECORDER_EVENT = IBinder::FIRST_CALL_TRANSACTION + 5, RELEASE_RECORDER = IBinder::FIRST_CALL_TRANSACTION + 6, PLAYER_SESSION_ID = IBinder::FIRST_CALL_TRANSACTION + 7, + PORT_EVENT = IBinder::FIRST_CALL_TRANSACTION + 8, }; DECLARE_META_INTERFACE(AudioManager) @@ -52,12 +55,14 @@ public: /*oneway*/ virtual status_t playerAttributes(audio_unique_id_t piid, audio_usage_t usage, audio_content_type_t content)= 0; /*oneway*/ virtual status_t playerEvent(audio_unique_id_t piid, player_state_t event, - audio_port_handle_t deviceId) = 0; + audio_port_handle_t eventId) = 0; /*oneway*/ virtual status_t releasePlayer(audio_unique_id_t piid) = 0; virtual audio_unique_id_t trackRecorder(const sp& recorder) = 0; /*oneway*/ virtual status_t recorderEvent(audio_unique_id_t riid, recorder_state_t event) = 0; /*oneway*/ virtual status_t releaseRecorder(audio_unique_id_t riid) = 0; /*oneway*/ virtual status_t playerSessionId(audio_unique_id_t piid, audio_session_t sessionId) = 0; + /*oneway*/ virtual status_t portEvent(audio_port_handle_t portId, player_state_t event, + const std::unique_ptr& extras) = 0; }; // ---------------------------------------------------------------------------- diff --git a/include/ftl/algorithm.h b/include/ftl/algorithm.h new file mode 100644 index 0000000000000000000000000000000000000000..c0f67683ab6f820476d572bca1c027fbe37863d8 --- /dev/null +++ b/include/ftl/algorithm.h @@ -0,0 +1,96 @@ +/* + * 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 +#include + +#include + +namespace android::ftl { + +// Adapter for std::find_if that converts the return value from iterator to optional. +// +// const ftl::StaticVector vector = {"upside"sv, "down"sv, "cake"sv}; +// assert(ftl::find_if(vector, [](const auto& str) { return str.front() == 'c'; }) == "cake"sv); +// +template +constexpr auto find_if(const Container& container, Predicate&& predicate) + -> Optional> { + const auto it = std::find_if(std::cbegin(container), std::cend(container), + std::forward(predicate)); + if (it == std::cend(container)) return {}; + return std::cref(*it); +} + +// Transformers for ftl::find_if on a map-like `Container` that contains key-value pairs. +// +// const ftl::SmallMap map = ftl::init::map>( +// 12, "snow"sv, "cone"sv)(13, "tiramisu"sv)(14, "upside"sv, "down"sv, "cake"sv); +// +// using Map = decltype(map); +// +// assert(14 == ftl::find_if(map, [](const auto& pair) { +// return pair.second.size() == 3; +// }).transform(ftl::to_key)); +// +// const auto opt = ftl::find_if(map, [](const auto& pair) { +// return pair.second.size() == 1; +// }).transform(ftl::to_mapped_ref); +// +// assert(opt); +// assert(opt->get() == ftl::StaticVector("tiramisu"sv)); +// +template +constexpr auto to_key(const Pair& pair) -> Key { + return pair.first; +} + +template +constexpr auto to_mapped_ref(const Pair& pair) -> std::reference_wrapper { + return std::cref(pair.second); +} + +// Combinator for ftl::Optional::or_else when T is std::reference_wrapper. Given a +// lambda argument that returns a `constexpr` value, ftl::static_ref binds a reference to a +// static T initialized to that constant. +// +// const ftl::SmallMap map = ftl::init::map(13, "tiramisu"sv)(14, "upside-down cake"sv); +// assert("???"sv == +// map.get(20).or_else(ftl::static_ref([] { return "???"sv; }))->get()); +// +// using Map = decltype(map); +// +// assert("snow cone"sv == +// ftl::find_if(map, [](const auto& pair) { return pair.second.front() == 's'; }) +// .transform(ftl::to_mapped_ref) +// .or_else(ftl::static_ref([] { return "snow cone"sv; })) +// ->get()); +// +template +constexpr auto static_ref(F&& f) { + return [f = std::forward(f)] { + constexpr auto kInitializer = f(); + static const T kValue = kInitializer; + return Optional(std::cref(kValue)); + }; +} + +} // namespace android::ftl diff --git a/include/ftl/concat.h b/include/ftl/concat.h index ded48f7c8c7bf762ddd0b85cf496d4c88d2ccd9b..e0774d39f3f47e9ff2b8087df262ea04dfa866ee 100644 --- a/include/ftl/concat.h +++ b/include/ftl/concat.h @@ -20,7 +20,9 @@ namespace android::ftl { -// Lightweight (not allocating nor sprintf-based) concatenation. +// Lightweight (not allocating nor sprintf-based) concatenation. The variadic arguments can be +// values of integral type (including bool and char), string literals, or strings whose length +// is constrained: // // std::string_view name = "Volume"; // ftl::Concat string(ftl::truncated<3>(name), ": ", -3, " dB"); diff --git a/include/ftl/details/concat.h b/include/ftl/details/concat.h index 8ce949ef0531045aa302ad5a23eb407c5fb1b1ae..726ba0297e96347f04a00258b70f581fbfa288a6 100644 --- a/include/ftl/details/concat.h +++ b/include/ftl/details/concat.h @@ -19,6 +19,7 @@ #include #include +#include #include namespace android::ftl::details { @@ -26,16 +27,42 @@ namespace android::ftl::details { template struct StaticString; +// Booleans. template -struct StaticString>> { - static constexpr std::size_t N = to_chars_length_v; +struct StaticString>> { + static constexpr std::size_t N = 5; // Length of "false". - explicit StaticString(T v) : view(to_chars(buffer, v)) {} + explicit constexpr StaticString(bool b) : view(b ? "true" : "false") {} - to_chars_buffer_t buffer; const std::string_view view; }; +// Characters. +template +struct StaticString>> { + static constexpr std::size_t N = 1; + + explicit constexpr StaticString(char c) : character(c) {} + + const char character; + const std::string_view view{&character, 1u}; +}; + +// Integers, including the integer value of other character types like char32_t. +template +struct StaticString< + T, std::enable_if_t> && !is_bool_v && !is_char_v>> { + using U = remove_cvref_t; + static constexpr std::size_t N = to_chars_length_v; + + // TODO: Mark this and to_chars as `constexpr` in C++23. + explicit StaticString(U v) : view(to_chars(buffer, v)) {} + + to_chars_buffer_t buffer; + const std::string_view view; +}; + +// Character arrays. template struct StaticString { static constexpr std::size_t N = M - 1; @@ -50,6 +77,7 @@ struct Truncated { std::string_view view; }; +// Strings with constrained length. template struct StaticString, void> { static constexpr std::size_t N = M; diff --git a/include/ftl/details/match.h b/include/ftl/details/match.h new file mode 100644 index 0000000000000000000000000000000000000000..51b99d2f13f55fcaab7e785603bb6d897ef23041 --- /dev/null +++ b/include/ftl/details/match.h @@ -0,0 +1,59 @@ +/* + * 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::ftl::details { + +template +struct Matcher : Ms... { + using Ms::operator()...; +}; + +// Deduction guide. +template +Matcher(Ms...) -> Matcher; + +template +constexpr bool is_exhaustive_match_v = (std::is_invocable_v && ...); + +template +struct Match; + +template +struct Match { + template + static decltype(auto) match(Variant& variant, const Matcher& matcher) { + if (auto* const ptr = std::get_if(&variant)) { + return matcher(*ptr); + } else { + return Match::match(variant, matcher); + } + } +}; + +template +struct Match { + template + static decltype(auto) match(Variant& variant, const Matcher& matcher) { + return matcher(std::get(variant)); + } +}; + +} // namespace android::ftl::details diff --git a/include/ftl/details/mixins.h b/include/ftl/details/mixins.h new file mode 100644 index 0000000000000000000000000000000000000000..9ab9e083aed75e628197beb0183c91d4bdc159c9 --- /dev/null +++ b/include/ftl/details/mixins.h @@ -0,0 +1,30 @@ +/* + * 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 + +namespace android::ftl::details { + +template class> +class Mixin { + protected: + constexpr Self& self() { return *static_cast(this); } + constexpr const Self& self() const { return *static_cast(this); } + + constexpr auto& mut() { return self().value_; } +}; + +} // namespace android::ftl::details diff --git a/include/ftl/details/optional.h b/include/ftl/details/optional.h new file mode 100644 index 0000000000000000000000000000000000000000..e45c1f5e23ba010acaee438c33c57e4ccaa817e0 --- /dev/null +++ b/include/ftl/details/optional.h @@ -0,0 +1,68 @@ +/* + * 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 + +#include + +namespace android::ftl { + +template +struct Optional; + +namespace details { + +template +struct is_optional : std::false_type {}; + +template +struct is_optional> : std::true_type {}; + +template +struct is_optional> : std::true_type {}; + +template +struct transform_result { + using type = Optional>>; +}; + +template +using transform_result_t = typename transform_result::type; + +template +struct and_then_result { + using type = remove_cvref_t>; + static_assert(is_optional{}, "and_then function must return an optional"); +}; + +template +using and_then_result_t = typename and_then_result::type; + +template +struct or_else_result { + using type = remove_cvref_t>; + static_assert(std::is_same_v> || std::is_same_v>, + "or_else function must return an optional T"); +}; + +template +using or_else_result_t = typename or_else_result::type; + +} // namespace details +} // namespace android::ftl diff --git a/include/ftl/details/type_traits.h b/include/ftl/details/type_traits.h new file mode 100644 index 0000000000000000000000000000000000000000..47bebc51149365c9ecb49f038192ee32af5b7058 --- /dev/null +++ b/include/ftl/details/type_traits.h @@ -0,0 +1,33 @@ +/* + * 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::ftl::details { + +// TODO: Replace with std::remove_cvref_t in C++20. +template +using remove_cvref_t = std::remove_cv_t>; + +template +constexpr bool is_bool_v = std::is_same_v, bool>; + +template +constexpr bool is_char_v = std::is_same_v, char>; + +} // namespace android::ftl::details diff --git a/include/ftl/enum.h b/include/ftl/enum.h index 82af1d6cf86e8b4822e810910ef14ad0906bab67..075d12bd1757011851fed72354fc364f06d141ca 100644 --- a/include/ftl/enum.h +++ b/include/ftl/enum.h @@ -92,7 +92,7 @@ inline constexpr bool is_scoped_enum_v = is_scoped_enum::value; // enum class E { A, B, C }; // static_assert(ftl::to_underlying(E::B) == 1); // -template +template >> constexpr auto to_underlying(E v) { return static_cast>(v); } diff --git a/include/ftl/flags.h b/include/ftl/flags.h index 70aaa0e6ddbfcb8b88bf5758771d52df621fe860..dbe3148fc57c1cfb753187b7f15089dc6a87c299 100644 --- a/include/ftl/flags.h +++ b/include/ftl/flags.h @@ -120,12 +120,12 @@ public: } /* Tests whether any of the given flags are set */ - bool any(Flags f) const { return (mFlags & f.mFlags) != 0; } + bool any(Flags f = ~Flags()) const { return (mFlags & f.mFlags) != 0; } /* Tests whether all of the given flags are set */ bool all(Flags f) const { return (mFlags & f.mFlags) == f.mFlags; } - Flags operator|(Flags rhs) const { return static_cast(mFlags | rhs.mFlags); } + constexpr Flags operator|(Flags rhs) const { return static_cast(mFlags | rhs.mFlags); } Flags& operator|=(Flags rhs) { mFlags = mFlags | rhs.mFlags; return *this; @@ -217,7 +217,7 @@ inline Flags operator~(F f) { } template >> -Flags operator|(F lhs, F rhs) { +constexpr Flags operator|(F lhs, F rhs) { return static_cast(to_underlying(lhs) | to_underlying(rhs)); } diff --git a/include/ftl/match.h b/include/ftl/match.h new file mode 100644 index 0000000000000000000000000000000000000000..7318c45b7b0c07220a0e188e9ca00564024799bd --- /dev/null +++ b/include/ftl/match.h @@ -0,0 +1,62 @@ +/* + * 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 + +#include + +namespace android::ftl { + +// Concise alternative to std::visit that compiles to branches rather than a dispatch table. For +// std::variant where N is small, this is slightly faster since the branches can be +// inlined unlike the function pointers. +// +// using namespace std::chrono; +// std::variant duration = 119min; +// +// // Mutable match. +// ftl::match(duration, [](auto& d) { ++d; }); +// +// // Immutable match. Exhaustive due to minutes being convertible to seconds. +// assert("2 hours"s == +// ftl::match(duration, +// [](const seconds& s) { +// const auto h = duration_cast(s); +// return std::to_string(h.count()) + " hours"s; +// }, +// [](const hours& h) { return std::to_string(h.count() / 24) + " days"s; })); +// +template +decltype(auto) match(std::variant& variant, Ms&&... matchers) { + const auto matcher = details::Matcher{std::forward(matchers)...}; + static_assert(details::is_exhaustive_match_v, "Non-exhaustive match"); + + return details::Match::match(variant, matcher); +} + +template +decltype(auto) match(const std::variant& variant, Ms&&... matchers) { + const auto matcher = details::Matcher{std::forward(matchers)...}; + static_assert(details::is_exhaustive_match_v, + "Non-exhaustive match"); + + return details::Match::match(variant, matcher); +} + +} // namespace android::ftl diff --git a/include/ftl/mixins.h b/include/ftl/mixins.h new file mode 100644 index 0000000000000000000000000000000000000000..0e1d2004a3ab788d3208f48fbd679625397199a8 --- /dev/null +++ b/include/ftl/mixins.h @@ -0,0 +1,148 @@ +/* + * 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::ftl { + +// CRTP mixins for defining type-safe wrappers that are distinct from their underlying type. Common +// uses are IDs, opaque handles, and physical quantities. The constructor is provided by (and must +// be inherited from) the `Constructible` mixin, whereas operators (equality, ordering, arithmetic, +// etc.) are enabled through inheritance: +// +// struct Id : ftl::Constructible, ftl::Equatable { +// using Constructible::Constructible; +// }; +// +// static_assert(!std::is_default_constructible_v); +// +// Unlike `Constructible`, `DefaultConstructible` allows default construction. The default value is +// zero-initialized unless specified: +// +// struct Color : ftl::DefaultConstructible, +// ftl::Equatable, +// ftl::Orderable { +// using DefaultConstructible::DefaultConstructible; +// }; +// +// static_assert(Color() == Color(0u)); +// static_assert(ftl::to_underlying(Color(-1)) == 255u); +// static_assert(Color(1u) < Color(2u)); +// +// struct Sequence : ftl::DefaultConstructible, +// ftl::Equatable, +// ftl::Orderable, +// ftl::Incrementable { +// using DefaultConstructible::DefaultConstructible; +// }; +// +// static_assert(Sequence() == Sequence(-1)); +// +// The underlying type need not be a fundamental type: +// +// struct Timeout : ftl::DefaultConstructible, +// ftl::Equatable, +// ftl::Addable { +// using DefaultConstructible::DefaultConstructible; +// }; +// +// using namespace std::chrono_literals; +// static_assert(Timeout() + Timeout(5s) == Timeout(15s)); +// +template +struct Constructible { + explicit constexpr Constructible(T value) : value_(value) {} + + explicit constexpr operator const T&() const { return value_; } + + private: + template class> + friend class details::Mixin; + + T value_; +}; + +template +struct DefaultConstructible : Constructible { + using Constructible::Constructible; + constexpr DefaultConstructible() : DefaultConstructible(T{kDefault}) {} +}; + +// Shorthand for casting a type-safe wrapper to its underlying value. +template +constexpr const T& to_underlying(const Constructible& c) { + return static_cast(c); +} + +// Comparison operators for equality. +template +struct Equatable : details::Mixin { + constexpr bool operator==(const Self& other) const { + return to_underlying(this->self()) == to_underlying(other); + } + + constexpr bool operator!=(const Self& other) const { return !(*this == other); } +}; + +// Comparison operators for ordering. +template +struct Orderable : details::Mixin { + constexpr bool operator<(const Self& other) const { + return to_underlying(this->self()) < to_underlying(other); + } + + constexpr bool operator>(const Self& other) const { return other < this->self(); } + constexpr bool operator>=(const Self& other) const { return !(*this < other); } + constexpr bool operator<=(const Self& other) const { return !(*this > other); } +}; + +// Pre-increment and post-increment operators. +template +struct Incrementable : details::Mixin { + constexpr Self& operator++() { + ++this->mut(); + return this->self(); + } + + constexpr Self operator++(int) { + const Self tmp = this->self(); + operator++(); + return tmp; + } +}; + +// Additive operators, including incrementing. +template +struct Addable : details::Mixin, Incrementable { + constexpr Self& operator+=(const Self& other) { + this->mut() += to_underlying(other); + return this->self(); + } + + constexpr Self operator+(const Self& other) const { + Self tmp = this->self(); + return tmp += other; + } + + private: + using Base = details::Mixin; + using Base::mut; + using Base::self; +}; + +} // namespace android::ftl diff --git a/include/ftl/non_null.h b/include/ftl/non_null.h new file mode 100644 index 0000000000000000000000000000000000000000..35d09d71de82a4b0568555ee50e83fc562485b4d --- /dev/null +++ b/include/ftl/non_null.h @@ -0,0 +1,116 @@ +/* + * 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 +#include + +namespace android::ftl { + +// Enforces and documents non-null pre/post-condition for (raw or smart) pointers. +// +// void get_length(const ftl::NonNull>& string_ptr, +// ftl::NonNull length_ptr) { +// // No need for `nullptr` checks. +// *length_ptr = string_ptr->length(); +// } +// +// const auto string_ptr = ftl::as_non_null(std::make_shared("android")); +// std::size_t size; +// get_length(string_ptr, ftl::as_non_null(&size)); +// assert(size == 7u); +// +// For compatibility with std::unique_ptr and performance with std::shared_ptr, move +// operations are allowed despite breaking the invariant: +// +// using Pair = std::pair>, std::shared_ptr>; +// +// Pair dupe_if(ftl::NonNull> non_null_ptr, bool condition) { +// // Move the underlying pointer out, so `non_null_ptr` must not be accessed after this point. +// auto unique_ptr = std::move(non_null_ptr).take(); +// +// auto non_null_shared_ptr = ftl::as_non_null(std::shared_ptr(std::move(unique_ptr))); +// auto nullable_shared_ptr = condition ? non_null_shared_ptr.get() : nullptr; +// +// return {std::move(non_null_shared_ptr), std::move(nullable_shared_ptr)}; +// } +// +// auto ptr = ftl::as_non_null(std::make_unique(42)); +// const auto [ptr1, ptr2] = dupe_if(std::move(ptr), true); +// assert(ptr1.get() == ptr2); +// +template +class NonNull final { + struct Passkey {}; + + public: + // Disallow `nullptr` explicitly for clear compilation errors. + NonNull() = delete; + NonNull(std::nullptr_t) = delete; + + // Copy operations. + + 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(); } + + // 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(); } + + // Dereferencing. + constexpr decltype(auto) operator*() const { return *get(); } + constexpr decltype(auto) operator->() const { return get(); } + + // 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(); + } + + private: + template + friend constexpr auto as_non_null(P&&) -> NonNull>; + + Pointer pointer_; +}; + +template +constexpr auto as_non_null(P&& pointer) -> NonNull> { + using Passkey = typename NonNull>::Passkey; + return {Passkey{}, std::forward

(pointer)}; +} + +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 !operator==(lhs, rhs); +} + +} // namespace android::ftl diff --git a/include/ftl/optional.h b/include/ftl/optional.h new file mode 100644 index 0000000000000000000000000000000000000000..94d8e3d7cb04f4f5c52d4199b78505b3b20ed5ad --- /dev/null +++ b/include/ftl/optional.h @@ -0,0 +1,137 @@ +/* + * 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 +#include + +#include + +namespace android::ftl { + +// Superset of std::optional with monadic operations, as proposed in https://wg21.link/P0798R8. +// +// TODO: Remove in C++23. +// +template +struct Optional final : std::optional { + using std::optional::optional; + + // Implicit downcast. + Optional(std::optional other) : std::optional(std::move(other)) {} + + using std::optional::has_value; + using std::optional::value; + + // Returns Optional where F is a function that maps T to U. + template + constexpr auto transform(F&& f) const& { + using R = details::transform_result_t; + if (has_value()) return R(std::invoke(std::forward(f), value())); + return R(); + } + + template + constexpr auto transform(F&& f) & { + using R = details::transform_result_t; + if (has_value()) return R(std::invoke(std::forward(f), value())); + return R(); + } + + template + constexpr auto transform(F&& f) const&& { + using R = details::transform_result_t; + if (has_value()) return R(std::invoke(std::forward(f), std::move(value()))); + return R(); + } + + template + constexpr auto transform(F&& f) && { + using R = details::transform_result_t; + if (has_value()) return R(std::invoke(std::forward(f), std::move(value()))); + return R(); + } + + // Returns Optional where F is a function that maps T to Optional. + template + constexpr auto and_then(F&& f) const& { + using R = details::and_then_result_t; + if (has_value()) return std::invoke(std::forward(f), value()); + return R(); + } + + template + constexpr auto and_then(F&& f) & { + using R = details::and_then_result_t; + if (has_value()) return std::invoke(std::forward(f), value()); + return R(); + } + + template + constexpr auto and_then(F&& f) const&& { + using R = details::and_then_result_t; + if (has_value()) return std::invoke(std::forward(f), std::move(value())); + return R(); + } + + template + constexpr auto and_then(F&& f) && { + using R = details::and_then_result_t; + if (has_value()) return std::invoke(std::forward(f), std::move(value())); + return R(); + } + + // Returns this Optional if not nullopt, or else the Optional returned by the function F. + template + constexpr auto or_else(F&& f) const& -> details::or_else_result_t { + if (has_value()) return *this; + return std::forward(f)(); + } + + template + constexpr auto or_else(F&& f) && -> details::or_else_result_t { + if (has_value()) return std::move(*this); + return std::forward(f)(); + } + + // 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 + // anyway. + static void* operator new(size_t) = delete; + static void* operator new[](size_t) = delete; +}; + +template +constexpr bool operator==(const Optional& lhs, const Optional& rhs) { + return static_cast>(lhs) == static_cast>(rhs); +} + +template +constexpr bool operator!=(const Optional& lhs, const Optional& rhs) { + return !(lhs == rhs); +} + +// Deduction guides. +template +Optional(T) -> Optional; + +template +Optional(std::optional) -> Optional; + +} // namespace android::ftl diff --git a/include/ftl/shared_mutex.h b/include/ftl/shared_mutex.h new file mode 100644 index 0000000000000000000000000000000000000000..146f5ba4a9f7295dddff88ace81fb497dc68c862 --- /dev/null +++ b/include/ftl/shared_mutex.h @@ -0,0 +1,47 @@ +/* + * 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::ftl { + +// Wrapper around std::shared_mutex to provide capabilities for thread-safety +// annotations. +// TODO(b/257958323): This class is no longer needed once b/135688034 is fixed (currently blocked on +// b/175635923). +class [[clang::capability("shared_mutex")]] SharedMutex final { + public: + [[clang::acquire_capability()]] void lock() { + mutex_.lock(); + } + [[clang::release_capability()]] void unlock() { + mutex_.unlock(); + } + + [[clang::acquire_shared_capability()]] void lock_shared() { + mutex_.lock_shared(); + } + [[clang::release_shared_capability()]] void unlock_shared() { + mutex_.unlock_shared(); + } + + private: + std::shared_mutex mutex_; +}; + +} // namespace android::ftl diff --git a/include/ftl/small_map.h b/include/ftl/small_map.h index 5217e760644192055ef4ff1b54d25645d54ba882..49cde7fedc215ecc8bc7cf0b8939897813860c65 100644 --- a/include/ftl/small_map.h +++ b/include/ftl/small_map.h @@ -17,11 +17,11 @@ #pragma once #include +#include #include #include #include -#include #include #include @@ -47,7 +47,7 @@ namespace android::ftl { // assert(!map.dynamic()); // // assert(map.contains(123)); -// assert(map.get(42, [](const std::string& s) { return s.size(); }) == 3u); +// assert(map.get(42).transform([](const std::string& s) { return s.size(); }) == 3u); // // const auto opt = map.get(-1); // assert(opt); @@ -59,7 +59,7 @@ namespace android::ftl { // map.emplace_or_replace(0, "vanilla", 2u, 3u); // assert(map.dynamic()); // -// assert(map == SmallMap(ftl::init::map(-1, "xyz")(0, "nil")(42, "???")(123, "abc"))); +// assert(map == SmallMap(ftl::init::map(-1, "xyz"sv)(0, "nil"sv)(42, "???"sv)(123, "abc"sv))); // template > class SmallMap final { @@ -123,9 +123,7 @@ class SmallMap final { const_iterator cend() const { return map_.cend(); } // Returns whether a mapping exists for the given key. - bool contains(const key_type& key) const { - return get(key, [](const mapped_type&) {}); - } + bool contains(const key_type& key) const { return get(key).has_value(); } // Returns a reference to the value for the given key, or std::nullopt if the key was not found. // @@ -139,46 +137,24 @@ class SmallMap final { // ref.get() = 'D'; // assert(d == 'D'); // - auto get(const key_type& key) const -> std::optional> { - return get(key, [](const mapped_type& v) { return std::cref(v); }); - } - - auto get(const key_type& key) -> std::optional> { - return get(key, [](mapped_type& v) { return std::ref(v); }); + auto get(const key_type& key) const -> Optional> { + for (const auto& [k, v] : *this) { + if (KeyEqual{}(k, key)) { + return std::cref(v); + } + } + return {}; } - // Returns the result R of a unary operation F on (a constant or mutable reference to) the value - // for the given key, or std::nullopt if the key was not found. If F has a return type of void, - // then the Boolean result indicates whether the key was found. - // - // ftl::SmallMap map = ftl::init::map('a', 'x')('b', 'y')('c', 'z'); - // - // assert(map.get('c', [](char c) { return std::toupper(c); }) == 'Z'); - // assert(map.get('c', [](char& c) { c = std::toupper(c); })); - // - template > - auto get(const key_type& key, F f) const - -> std::conditional_t, bool, std::optional> { + auto get(const key_type& key) -> Optional> { for (auto& [k, v] : *this) { if (KeyEqual{}(k, key)) { - if constexpr (std::is_void_v) { - f(v); - return true; - } else { - return f(v); - } + return std::ref(v); } } - return {}; } - template - auto get(const key_type& key, F f) { - return std::as_const(*this).get( - key, [&f](const mapped_type& v) { return f(const_cast(v)); }); - } - // Returns an iterator to an existing mapping for the given key, or the end() iterator otherwise. const_iterator find(const key_type& key) const { return const_cast(*this).find(key); } iterator find(const key_type& key) { return find(key, begin()); } @@ -286,7 +262,7 @@ bool operator==(const SmallMap& lhs, const SmallMap& rhs for (const auto& [k, v] : lhs) { const auto& lv = v; - if (!rhs.get(k, [&lv](const auto& rv) { return lv == rv; }).value_or(false)) { + if (!rhs.get(k).transform([&lv](const W& rv) { return lv == rv; }).value_or(false)) { return false; } } diff --git a/include/ftl/small_vector.h b/include/ftl/small_vector.h index 339726e4ea869026d47c6146ef862269a61487d7..11294c3ac8d766ec36d41ed220f23c92b12f23a7 100644 --- a/include/ftl/small_vector.h +++ b/include/ftl/small_vector.h @@ -21,11 +21,12 @@ #include #include -#include #include #include #include +#include + namespace android::ftl { template @@ -80,10 +81,6 @@ class SmallVector final : details::ArrayTraits, details::ArrayComparators; using Dynamic = SmallVector; - // TODO: Replace with std::remove_cvref_t in C++20. - template - using remove_cvref_t = std::remove_cv_t>; - public: FTL_ARRAY_TRAIT(T, value_type); FTL_ARRAY_TRAIT(T, size_type); @@ -104,7 +101,7 @@ class SmallVector final : details::ArrayTraits, details::ArrayComparators>{}>> + typename = std::enable_if_t>{}>> SmallVector(Arg&& arg, Args&&... args) : vector_(std::in_place_type, std::forward(arg), std::forward(args)...) {} diff --git a/include/ftl/unit.h b/include/ftl/unit.h new file mode 100644 index 0000000000000000000000000000000000000000..e38230b976ea27558ef35d21b88b8ecd851b01d1 --- /dev/null +++ b/include/ftl/unit.h @@ -0,0 +1,61 @@ +/* + * 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::ftl { + +// The unit type, and its only value. +constexpr struct Unit { +} unit; + +constexpr bool operator==(Unit, Unit) { + return true; +} + +constexpr bool operator!=(Unit, Unit) { + return false; +} + +// Adapts a function object F to return Unit. The return value of F is ignored. +// +// As a practical use, the function passed to ftl::Optional::transform is not allowed to return +// void (cf. https://wg21.link/P0798R8#mapping-functions-returning-void), but may return Unit if +// only its side effects are meaningful: +// +// ftl::Optional opt = "food"s; +// opt.transform(ftl::unit_fn([](std::string& str) { str.pop_back(); })); +// assert(opt == "foo"s); +// +template +struct UnitFn { + F f; + + template + Unit operator()(Args&&... args) { + return f(std::forward(args)...), unit; + } +}; + +template +constexpr auto unit_fn(F&& f) -> UnitFn> { + return {std::forward(f)}; +} + +} // namespace android::ftl diff --git a/include/input/DisplayViewport.h b/include/input/DisplayViewport.h index 9148fee53233e3569a0af7c44857d94e2a125d1b..7457496784110bb00a35c12a1e8404f3ca5e2ed8 100644 --- a/include/input/DisplayViewport.h +++ b/include/input/DisplayViewport.h @@ -14,14 +14,14 @@ * limitations under the License. */ -#ifndef _LIBINPUT_DISPLAY_VIEWPORT_H -#define _LIBINPUT_DISPLAY_VIEWPORT_H +#pragma once #include #include #include #include #include +#include #include #include @@ -30,13 +30,6 @@ using android::base::StringPrintf; namespace android { -enum { - DISPLAY_ORIENTATION_0 = 0, - DISPLAY_ORIENTATION_90 = 1, - DISPLAY_ORIENTATION_180 = 2, - DISPLAY_ORIENTATION_270 = 3 -}; - /** * Describes the different type of viewports supported by input flinger. * Keep in sync with values in InputManagerService.java. @@ -55,7 +48,7 @@ enum class ViewportType : int32_t { */ struct DisplayViewport { int32_t displayId; // -1 if invalid - int32_t orientation; + ui::Rotation orientation; int32_t logicalLeft; int32_t logicalTop; int32_t logicalRight; @@ -75,7 +68,7 @@ struct DisplayViewport { DisplayViewport() : displayId(ADISPLAY_ID_NONE), - orientation(DISPLAY_ORIENTATION_0), + orientation(ui::ROTATION_0), logicalLeft(0), logicalTop(0), logicalRight(0), @@ -112,7 +105,7 @@ struct DisplayViewport { void setNonDisplayViewport(int32_t width, int32_t height) { displayId = ADISPLAY_ID_NONE; - orientation = DISPLAY_ORIENTATION_0; + orientation = ui::ROTATION_0; logicalLeft = 0; logicalTop = 0; logicalRight = width; @@ -144,5 +137,3 @@ struct DisplayViewport { }; } // namespace android - -#endif // _LIBINPUT_DISPLAY_VIEWPORT_H diff --git a/include/input/Input.h b/include/input/Input.h index 1eda981ee9fc1a4675139467108858e8e6c84123..527a47741c4ba71ab23df5e514cd906e5c56e51e 100644 --- a/include/input/Input.h +++ b/include/input/Input.h @@ -14,8 +14,7 @@ * limitations under the License. */ -#ifndef _LIBINPUT_INPUT_H -#define _LIBINPUT_INPUT_H +#pragma once #pragma GCC system_header @@ -31,7 +30,6 @@ #include #include #include -#include #include #include #include @@ -204,12 +202,61 @@ class Parcel; */ vec2 transformWithoutTranslation(const ui::Transform& transform, const vec2& xy); -const char* inputEventTypeToString(int32_t type); +/* + * 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. + */ +float transformAngle(const ui::Transform& transform, float angleRadians); + +/** + * The type of the InputEvent. + * This should have 1:1 correspondence with the values of anonymous enum defined in input.h. + */ +enum class InputEventType { + KEY = AINPUT_EVENT_TYPE_KEY, + MOTION = AINPUT_EVENT_TYPE_MOTION, + FOCUS = AINPUT_EVENT_TYPE_FOCUS, + CAPTURE = AINPUT_EVENT_TYPE_CAPTURE, + DRAG = AINPUT_EVENT_TYPE_DRAG, + TOUCH_MODE = AINPUT_EVENT_TYPE_TOUCH_MODE, + ftl_first = KEY, + ftl_last = TOUCH_MODE, +}; std::string inputEventSourceToString(int32_t source); bool isFromSource(uint32_t source, uint32_t test); +/** + * The pointer tool type. + */ +enum class ToolType { + UNKNOWN = AMOTION_EVENT_TOOL_TYPE_UNKNOWN, + FINGER = AMOTION_EVENT_TOOL_TYPE_FINGER, + STYLUS = AMOTION_EVENT_TOOL_TYPE_STYLUS, + MOUSE = AMOTION_EVENT_TOOL_TYPE_MOUSE, + ERASER = AMOTION_EVENT_TOOL_TYPE_ERASER, + PALM = AMOTION_EVENT_TOOL_TYPE_PALM, + ftl_first = UNKNOWN, + ftl_last = PALM, +}; + +/** + * The state of the key. This should have 1:1 correspondence with the values of anonymous enum + * defined in input.h + */ +enum class KeyState { + UNKNOWN = AKEY_STATE_UNKNOWN, + UP = AKEY_STATE_UP, + DOWN = AKEY_STATE_DOWN, + VIRTUAL = AKEY_STATE_VIRTUAL, + ftl_first = UNKNOWN, + ftl_last = VIRTUAL, +}; + +bool isStylusToolType(ToolType toolType); + /* * Flags that flow alongside events in the input dispatch system to help with certain * policy decisions such as waking from device sleep. @@ -290,6 +337,21 @@ enum class MotionClassification : uint8_t { * The current gesture likely represents a user intentionally exerting force on the touchscreen. */ DEEP_PRESS = AMOTION_EVENT_CLASSIFICATION_DEEP_PRESS, + /** + * The current gesture represents the user swiping with two fingers on a touchpad. + */ + TWO_FINGER_SWIPE = AMOTION_EVENT_CLASSIFICATION_TWO_FINGER_SWIPE, + /** + * The current gesture represents the user swiping with three or more fingers on a touchpad. + * Unlike two-finger swipes, these are only to be handled by the system UI, which is why they + * have a separate constant from two-finger swipes. + */ + MULTI_FINGER_SWIPE = AMOTION_EVENT_CLASSIFICATION_MULTI_FINGER_SWIPE, + /** + * The current gesture represents the user pinching with two fingers on a touchpad. The gesture + * is centered around the current cursor position. + */ + PINCH = AMOTION_EVENT_CLASSIFICATION_PINCH, }; /** @@ -297,8 +359,6 @@ enum class MotionClassification : uint8_t { */ const char* motionClassificationToString(MotionClassification classification); -const char* motionToolTypeToString(int32_t toolType); - /** * Portion of FrameMetrics timeline of interest to input code. */ @@ -356,17 +416,24 @@ constexpr float AMOTION_EVENT_INVALID_CURSOR_POSITION = std::numeric_limits values; + + // Whether these coordinate data were generated by resampling. + bool isResampled; + + static_assert(sizeof(bool) == 1); // Ensure padding is correctly sized. + uint8_t empty[7]; inline void clear() { BitSet64::clear(bits); + isResampled = false; } bool isEmpty() const { @@ -403,7 +470,8 @@ struct PointerCoords { return !(*this == other); } - void copyFrom(const PointerCoords& other); + inline void copyFrom(const PointerCoords& other) { *this = other; } + PointerCoords& operator=(const PointerCoords&) = default; private: void tooManyAxes(int axis); @@ -417,11 +485,11 @@ struct PointerProperties { int32_t id; // The pointer tool type. - int32_t toolType; + ToolType toolType; inline void clear() { id = -1; - toolType = 0; + toolType = ToolType::UNKNOWN; } bool operator==(const PointerProperties& other) const; @@ -439,7 +507,7 @@ class InputEvent : public AInputEvent { public: virtual ~InputEvent() { } - virtual int32_t getType() const = 0; + virtual InputEventType getType() const = 0; inline int32_t getId() const { return mId; } @@ -470,6 +538,8 @@ protected: std::array mHmac; }; +std::ostream& operator<<(std::ostream& out, const InputEvent& event); + /* * Key events. */ @@ -477,7 +547,7 @@ class KeyEvent : public InputEvent { public: virtual ~KeyEvent() { } - virtual int32_t getType() const { return AINPUT_EVENT_TYPE_KEY; } + virtual InputEventType getType() const { return InputEventType::KEY; } inline int32_t getAction() const { return mAction; } @@ -498,7 +568,7 @@ public: inline nsecs_t getEventTime() const { return mEventTime; } static const char* getLabel(int32_t keyCode); - static int32_t getKeyCodeFromLabel(const char* label); + static std::optional getKeyCodeFromLabel(const char* label); void 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, @@ -519,6 +589,8 @@ protected: nsecs_t mEventTime; }; +std::ostream& operator<<(std::ostream& out, const KeyEvent& event); + /* * Motion events. */ @@ -526,7 +598,7 @@ class MotionEvent : public InputEvent { public: virtual ~MotionEvent() { } - virtual int32_t getType() const { return AINPUT_EVENT_TYPE_MOTION; } + virtual InputEventType getType() const { return InputEventType::MOTION; } inline int32_t getAction() const { return mAction; } @@ -571,7 +643,7 @@ public: inline const ui::Transform& getTransform() const { return mTransform; } - int getSurfaceRotation() const; + std::optional getSurfaceRotation() const; inline float getXPrecision() const { return mXPrecision; } @@ -605,7 +677,7 @@ public: return mPointerProperties[pointerIndex].id; } - inline int32_t getToolType(size_t pointerIndex) const { + inline ToolType getToolType(size_t pointerIndex) const { return mPointerProperties[pointerIndex].toolType; } @@ -756,6 +828,10 @@ public: AMOTION_EVENT_AXIS_ORIENTATION, pointerIndex, historicalIndex); } + inline bool isResampled(size_t pointerIndex, size_t historicalIndex) const { + return getHistoricalRawPointerCoords(pointerIndex, historicalIndex)->isResampled; + } + ssize_t findPointerIndex(int32_t pointerId) const; void initialize(int32_t id, int32_t deviceId, uint32_t source, int32_t displayId, @@ -805,7 +881,7 @@ public: } static const char* getLabel(int32_t axis); - static int32_t getAxisFromLabel(const char* label); + static std::optional getAxisFromLabel(const char* label); static std::string actionToString(int32_t action); @@ -850,7 +926,7 @@ class FocusEvent : public InputEvent { public: virtual ~FocusEvent() {} - virtual int32_t getType() const override { return AINPUT_EVENT_TYPE_FOCUS; } + virtual InputEventType getType() const override { return InputEventType::FOCUS; } inline bool getHasFocus() const { return mHasFocus; } @@ -869,7 +945,7 @@ class CaptureEvent : public InputEvent { public: virtual ~CaptureEvent() {} - virtual int32_t getType() const override { return AINPUT_EVENT_TYPE_CAPTURE; } + virtual InputEventType getType() const override { return InputEventType::CAPTURE; } inline bool getPointerCaptureEnabled() const { return mPointerCaptureEnabled; } @@ -888,7 +964,7 @@ class DragEvent : public InputEvent { public: virtual ~DragEvent() {} - virtual int32_t getType() const override { return AINPUT_EVENT_TYPE_DRAG; } + virtual InputEventType getType() const override { return InputEventType::DRAG; } inline bool isExiting() const { return mIsExiting; } @@ -912,7 +988,7 @@ class TouchModeEvent : public InputEvent { public: virtual ~TouchModeEvent() {} - virtual int32_t getType() const override { return AINPUT_EVENT_TYPE_TOUCH_MODE; } + virtual InputEventType getType() const override { return InputEventType::TOUCH_MODE; } inline bool isInTouchMode() const { return mIsInTouchMode; } @@ -1063,6 +1139,48 @@ public: uint32_t seq; }; -} // namespace android +/* Pointer icon styles. + * Must match the definition in android.view.PointerIcon. + * + * Due to backwards compatibility and public api constraints, this is a duplicate (but type safe) + * definition of PointerIcon.java. + * + * TODO(b/235023317) move this definition to an aidl and statically assign to the below java public + * api values. + * + * WARNING: Keep these definitions in sync with + * frameworks/base/core/java/android/view/PointerIcon.java + */ +enum class PointerIconStyle : int32_t { + TYPE_CUSTOM = -1, + TYPE_NULL = 0, + TYPE_NOT_SPECIFIED = 1, + TYPE_ARROW = 1000, + TYPE_CONTEXT_MENU = 1001, + TYPE_HAND = 1002, + TYPE_HELP = 1003, + TYPE_WAIT = 1004, + TYPE_CELL = 1006, + TYPE_CROSSHAIR = 1007, + TYPE_TEXT = 1008, + TYPE_VERTICAL_TEXT = 1009, + TYPE_ALIAS = 1010, + TYPE_COPY = 1011, + TYPE_NO_DROP = 1012, + TYPE_ALL_SCROLL = 1013, + TYPE_HORIZONTAL_DOUBLE_ARROW = 1014, + TYPE_VERTICAL_DOUBLE_ARROW = 1015, + TYPE_TOP_RIGHT_DOUBLE_ARROW = 1016, + TYPE_TOP_LEFT_DOUBLE_ARROW = 1017, + TYPE_ZOOM_IN = 1018, + TYPE_ZOOM_OUT = 1019, + TYPE_GRAB = 1020, + TYPE_GRABBING = 1021, + TYPE_HANDWRITING = 1022, + + TYPE_SPOT_HOVER = 2000, + TYPE_SPOT_TOUCH = 2001, + TYPE_SPOT_ANCHOR = 2002, +}; -#endif // _LIBINPUT_INPUT_H +} // namespace android diff --git a/include/input/InputDevice.h b/include/input/InputDevice.h index 3585392c2b7edd01d6d3cf7a6de0fc5e0693c33d..1a40fdb90c7cfc475c395c145eca83dfa438c0cf 100644 --- a/include/input/InputDevice.h +++ b/include/input/InputDevice.h @@ -14,15 +14,17 @@ * limitations under the License. */ -#ifndef _LIBINPUT_INPUT_DEVICE_H -#define _LIBINPUT_INPUT_DEVICE_H +#pragma once #include +#include #include #include #include #include +#include + namespace android { /* @@ -55,6 +57,9 @@ struct InputDeviceIdentifier { // reuse values that are not associated with an input anymore. uint16_t nonce; + // The bluetooth address of the device, if known. + std::optional bluetoothAddress; + /** * Return InputDeviceIdentifier.name that has been adjusted as follows: * - all characters besides alphanumerics, dash, @@ -104,12 +109,18 @@ enum class InputDeviceSensorReportingMode : int32_t { }; enum class InputDeviceLightType : int32_t { - MONO = 0, + INPUT = 0, PLAYER_ID = 1, - RGB = 2, - MULTI_COLOR = 3, + KEYBOARD_BACKLIGHT = 2, - ftl_last = MULTI_COLOR + ftl_last = KEYBOARD_BACKLIGHT +}; + +enum class InputDeviceLightCapability : uint32_t { + /** Capability to change brightness of the light */ + BRIGHTNESS = 0x00000001, + /** Capability to change color of the light */ + RGB = 0x00000002, }; struct InputDeviceSensorInfo { @@ -170,14 +181,17 @@ struct InputDeviceSensorInfo { struct InputDeviceLightInfo { explicit InputDeviceLightInfo(std::string name, int32_t id, InputDeviceLightType type, + ftl::Flags capabilityFlags, int32_t ordinal) - : name(name), id(id), type(type), ordinal(ordinal) {} + : name(name), id(id), type(type), capabilityFlags(capabilityFlags), ordinal(ordinal) {} // Name string of the light. std::string name; // Light id int32_t id; // Type of the light. InputDeviceLightType type; + // Light capabilities. + ftl::Flags capabilityFlags; // Ordinal of the light int32_t ordinal; }; @@ -190,6 +204,27 @@ struct InputDeviceBatteryInfo { int32_t id; }; +struct KeyboardLayoutInfo { + explicit KeyboardLayoutInfo(std::string languageTag, std::string layoutType) + : languageTag(languageTag), layoutType(layoutType) {} + + // A BCP 47 conformant language tag such as "en-US". + std::string languageTag; + // The layout type such as QWERTY or AZERTY. + std::string layoutType; + + inline bool operator==(const KeyboardLayoutInfo& other) const { + return languageTag == other.languageTag && layoutType == other.layoutType; + } + inline bool operator!=(const KeyboardLayoutInfo& other) const { return !(*this == other); } +}; + +// The version of the Universal Stylus Initiative (USI) protocol supported by the input device. +struct InputDeviceUsiVersion { + int32_t majorVersion = -1; + int32_t minorVersion = -1; +}; + /* * Describes the characteristics and capabilities of an input device. */ @@ -210,8 +245,8 @@ public: }; void initialize(int32_t id, int32_t generation, int32_t controllerNumber, - const InputDeviceIdentifier& identifier, const std::string& alias, bool isExternal, - bool hasMic); + const InputDeviceIdentifier& identifier, const std::string& alias, + bool isExternal, bool hasMic, int32_t associatedDisplayId); inline int32_t getId() const { return mId; } inline int32_t getControllerNumber() const { return mControllerNumber; } @@ -238,6 +273,11 @@ public: void setKeyboardType(int32_t keyboardType); inline int32_t getKeyboardType() const { return mKeyboardType; } + void setKeyboardLayoutInfo(KeyboardLayoutInfo keyboardLayoutInfo); + inline const std::optional& getKeyboardLayoutInfo() const { + return mKeyboardLayoutInfo; + } + inline void setKeyCharacterMap(const std::shared_ptr value) { mKeyCharacterMap = value; } @@ -266,6 +306,13 @@ public: std::vector getLights(); + inline void setUsiVersion(std::optional usiVersion) { + mUsiVersion = std::move(usiVersion); + } + inline std::optional getUsiVersion() const { return mUsiVersion; } + + inline int32_t getAssociatedDisplayId() const { return mAssociatedDisplayId; } + private: int32_t mId; int32_t mGeneration; @@ -274,9 +321,13 @@ private: std::string mAlias; bool mIsExternal; bool mHasMic; + std::optional mKeyboardLayoutInfo; uint32_t mSources; int32_t mKeyboardType; std::shared_ptr mKeyCharacterMap; + std::optional mUsiVersion; + int32_t mAssociatedDisplayId; + bool mHasVibrator; bool mHasBattery; bool mHasButtonUnderPad; @@ -325,6 +376,8 @@ extern std::string getInputDeviceConfigurationFilePathByName( const std::string& name, InputDeviceConfigurationFileType type); enum ReservedInputDeviceId : int32_t { + // Device id representing an invalid device + INVALID_INPUT_DEVICE_ID = android::os::IInputConstants::INVALID_INPUT_DEVICE_ID, // Device id of a special "virtual" keyboard that is always present. VIRTUAL_KEYBOARD_ID = -1, // Device id of the "built-in" keyboard if there is one. @@ -334,5 +387,3 @@ enum ReservedInputDeviceId : int32_t { }; } // namespace android - -#endif // _LIBINPUT_INPUT_DEVICE_H diff --git a/include/input/InputEventLabels.h b/include/input/InputEventLabels.h index 2a742f9cf473704d17cc77023f1810b365e6bac3..9dedd2b2dac7e935bec88c3752a243274dda193b 100644 --- a/include/input/InputEventLabels.h +++ b/include/input/InputEventLabels.h @@ -14,8 +14,7 @@ * limitations under the License. */ -#ifndef _LIBINPUT_INPUT_EVENT_LABELS_H -#define _LIBINPUT_INPUT_EVENT_LABELS_H +#pragma once #include #include @@ -31,27 +30,35 @@ struct InputEventLabel { int value; }; +struct EvdevEventLabel { + std::string type; + std::string code; + std::string value; +}; + // NOTE: If you want a new key code, axis code, led code or flag code in keylayout file, // then you must add it to InputEventLabels.cpp. class InputEventLookup { public: - static int lookupValueByLabel(const std::unordered_map& map, - const char* literal); + static std::optional lookupValueByLabel(const std::unordered_map& map, + const char* literal); static const char* lookupLabelByValue(const std::vector& vec, int value); - static int32_t getKeyCodeByLabel(const char* label); + static std::optional getKeyCodeByLabel(const char* label); static const char* getLabelByKeyCode(int32_t keyCode); - static uint32_t getKeyFlagByLabel(const char* label); + static std::optional getKeyFlagByLabel(const char* label); - static int32_t getAxisByLabel(const char* label); + static std::optional getAxisByLabel(const char* label); static const char* getAxisLabel(int32_t axisId); - static int32_t getLedByLabel(const char* label); + static std::optional getLedByLabel(const char* label); + + static EvdevEventLabel getLinuxEvdevLabel(int32_t type, int32_t code, int32_t value); private: static const std::unordered_map KEYCODES; @@ -68,4 +75,3 @@ private: }; } // namespace android -#endif // _LIBINPUT_INPUT_EVENT_LABELS_H diff --git a/include/input/InputTransport.h b/include/input/InputTransport.h index 5f9a37d69ccd0679b3cc238ba64e643d0deb7d50..4f53c36d6f2508560872c6d35b8789aa63291573 100644 --- a/include/input/InputTransport.h +++ b/include/input/InputTransport.h @@ -14,8 +14,7 @@ * limitations under the License. */ -#ifndef _LIBINPUT_INPUT_TRANSPORT_H -#define _LIBINPUT_INPUT_TRANSPORT_H +#pragma once #pragma GCC system_header @@ -39,6 +38,7 @@ #include #include #include +#include #include #include #include @@ -445,6 +445,7 @@ public: private: std::shared_ptr mChannel; + InputVerifier mInputVerifier; }; /* @@ -452,8 +453,11 @@ private: */ class InputConsumer { public: - /* Creates a consumer associated with an input channel. */ + /* 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(); @@ -665,11 +669,8 @@ private: 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); - static bool shouldResampleTool(int32_t toolType); static bool isTouchResamplingEnabled(); }; } // namespace android - -#endif // _LIBINPUT_INPUT_TRANSPORT_H diff --git a/include/input/InputVerifier.h b/include/input/InputVerifier.h new file mode 100644 index 0000000000000000000000000000000000000000..d4589f53b5d6f7979a3b7f919e9c47740bf958c1 --- /dev/null +++ b/include/input/InputVerifier.h @@ -0,0 +1,49 @@ +/* + * 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 + +namespace android { + +/* + * Crash if the provided touch stream is inconsistent. + * + * TODO(b/211379801): Add support for hover events: + * - No hover move without enter + * - No touching pointers when hover enter + * - No hovering pointers when touching + * - Only 1 hovering pointer max + */ +class InputVerifier { +public: + InputVerifier(const std::string& name); + + void processMovement(int32_t deviceId, int32_t action, uint32_t pointerCount, + const PointerProperties* pointerProperties, + const PointerCoords* pointerCoords, int32_t flags); + +private: + const std::string mName; + std::map> mTouchingPointerIdsByDevice; + void ensureTouchingPointersMatch(int32_t deviceId, uint32_t pointerCount, + const PointerProperties* pointerProperties, + const char* action) const; +}; + +} // namespace android diff --git a/include/input/KeyCharacterMap.h b/include/input/KeyCharacterMap.h index f6f8939b7af430fb5e10d53b29f62ad58ab7d13c..b2e8baade364f65f857240fe7902f4870985c1f0 100644 --- a/include/input/KeyCharacterMap.h +++ b/include/input/KeyCharacterMap.h @@ -14,10 +14,10 @@ * limitations under the License. */ -#ifndef _LIBINPUT_KEY_CHARACTER_MAP_H -#define _LIBINPUT_KEY_CHARACTER_MAP_H +#pragma once #include +#include #ifdef __linux__ #include @@ -26,9 +26,9 @@ #include #include #include -#include #include #include +#include // Maximum number of keys supported by KeyCharacterMaps #define MAX_KEYS 8192 @@ -87,6 +87,9 @@ public: /* Combines this key character map with the provided overlay. */ void combine(const KeyCharacterMap& overlay); + /* Clears already applied layout overlay */ + void clearLayoutOverlay(); + /* Gets the keyboard type. */ KeyboardType getKeyboardType() const; @@ -125,14 +128,21 @@ public: bool getEvents(int32_t deviceId, const char16_t* chars, size_t numChars, Vector& outEvents) const; + /* Maps an Android key code to another Android key code. This mapping is applied after scanCode + * and usageCodes are mapped to corresponding Android Keycode */ + void addKeyRemapping(int32_t fromKeyCode, int32_t toKeyCode); + /* Maps a scan code and usage code to a key code, in case this key map overrides * the mapping in some way. */ status_t mapKey(int32_t scanCode, int32_t usageCode, int32_t* outKeyCode) const; - /* Tries to find a replacement key code for a given key code and meta state - * in character map. */ - void tryRemapKey(int32_t scanCode, int32_t metaState, - int32_t* outKeyCode, int32_t* outMetaState) const; + /* Returns keycode after applying Android key code remapping defined in mKeyRemapping */ + int32_t applyKeyRemapping(int32_t fromKeyCode) const; + + /* Returns the pair after applying key behavior defined in the kcm file, + * that tries to find a replacement key code based on current meta state */ + std::pair applyKeyBehavior(int32_t keyCode, + int32_t metaState) const; #ifdef __linux__ /* Reads a key map from a parcel. */ @@ -142,49 +152,39 @@ public: void writeToParcel(Parcel* parcel) const; #endif - bool operator==(const KeyCharacterMap& other) const; - - bool operator!=(const KeyCharacterMap& other) const; - - KeyCharacterMap(const KeyCharacterMap& other); + bool operator==(const KeyCharacterMap& other) const = default; - virtual ~KeyCharacterMap(); + KeyCharacterMap(const KeyCharacterMap& other) = default; private: struct Behavior { - Behavior(); - Behavior(const Behavior& other); - - /* The next behavior in the list, or NULL if none. */ - Behavior* next; - /* The meta key modifiers for this behavior. */ - int32_t metaState; + int32_t metaState = 0; /* The character to insert. */ - char16_t character; + char16_t character = 0; /* The fallback keycode if the key is not handled. */ - int32_t fallbackKeyCode; + int32_t fallbackKeyCode = 0; /* The replacement keycode if the key has to be replaced outright. */ - int32_t replacementKeyCode; + int32_t replacementKeyCode = 0; + + bool operator==(const Behavior&) const = default; }; struct Key { - Key(); - Key(const Key& other); - ~Key(); + bool operator==(const Key&) const = default; /* The single character label printed on the key, or 0 if none. */ - char16_t label; + char16_t label = 0; /* The number or symbol character generated by the key, or 0 if none. */ - char16_t number; + char16_t number = 0; /* The list of key behaviors sorted from most specific to least specific * meta key binding. */ - Behavior* firstBehavior; + std::list behaviors; }; class Parser { @@ -215,7 +215,6 @@ private: public: Parser(KeyCharacterMap* map, Tokenizer* tokenizer, Format format); - ~Parser(); status_t parse(); private: @@ -224,24 +223,24 @@ private: status_t parseMapKey(); status_t parseKey(); status_t parseKeyProperty(); - status_t finishKey(Key* key); + status_t finishKey(Key& key); status_t parseModifier(const std::string& token, int32_t* outMetaState); status_t parseCharacterLiteral(char16_t* outCharacter); }; - KeyedVector mKeys; - KeyboardType mType; + std::map mKeys; + KeyboardType mType = KeyboardType::UNKNOWN; std::string mLoadFileName; - bool mLayoutOverlayApplied; + bool mLayoutOverlayApplied = false; - KeyedVector mKeysByScanCode; - KeyedVector mKeysByUsageCode; + std::map mKeyRemapping; + std::map mKeysByScanCode; + std::map mKeysByUsageCode; KeyCharacterMap(const std::string& filename); - bool getKey(int32_t keyCode, const Key** outKey) const; - bool getKeyBehavior(int32_t keyCode, int32_t metaState, - const Key** outKey, const Behavior** outBehavior) const; + const Key* getKey(int32_t keyCode) const; + const Behavior* getKeyBehavior(int32_t keyCode, int32_t metaState) const; static bool matchesMetaState(int32_t eventMetaState, int32_t behaviorMetaState); bool findKey(char16_t ch, int32_t* outKeyCode, int32_t* outMetaState) const; @@ -277,5 +276,3 @@ private: }; } // namespace android - -#endif // _LIBINPUT_KEY_CHARACTER_MAP_H diff --git a/include/input/KeyLayoutMap.h b/include/input/KeyLayoutMap.h index d36d28cac93abe34fef2750f1a0bad8318f3bffe..8c3c74af205f46191a1f40c41387942bf91c7dd9 100644 --- a/include/input/KeyLayoutMap.h +++ b/include/input/KeyLayoutMap.h @@ -14,8 +14,7 @@ * limitations under the License. */ -#ifndef _LIBINPUT_KEY_LAYOUT_MAP_H -#define _LIBINPUT_KEY_LAYOUT_MAP_H +#pragma once #include #include @@ -79,7 +78,7 @@ public: std::optional mapAxis(int32_t scanCode) const; const std::string getLoadFileName() const; // Return pair of sensor type and sensor data index, for the input device abs code - base::Result> mapSensor(int32_t absCode); + base::Result> mapSensor(int32_t absCode) const; virtual ~KeyLayoutMap(); @@ -132,5 +131,3 @@ private: }; } // namespace android - -#endif // _LIBINPUT_KEY_LAYOUT_MAP_H diff --git a/include/input/Keyboard.h b/include/input/Keyboard.h index 9a3e15f1cd1c348989e2e1f148f7e91296bd0cec..f7f960f8e69ba0842fbfe064045c12540e85e512 100644 --- a/include/input/Keyboard.h +++ b/include/input/Keyboard.h @@ -14,8 +14,7 @@ * limitations under the License. */ -#ifndef _LIBINPUT_KEYBOARD_H -#define _LIBINPUT_KEYBOARD_H +#pragma once #include #include @@ -88,5 +87,3 @@ extern int32_t normalizeMetaState(int32_t oldMetaState); extern bool isMetaKey(int32_t keyCode); } // namespace android - -#endif // _LIBINPUT_KEYBOARD_H diff --git a/include/input/MotionPredictor.h b/include/input/MotionPredictor.h new file mode 100644 index 0000000000000000000000000000000000000000..de8ddcabeb76df1de9f1c7d3703175b915f757e3 --- /dev/null +++ b/include/input/MotionPredictor.h @@ -0,0 +1,93 @@ +/* + * 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. + */ + +#pragma once + +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +namespace android { + +static inline bool isMotionPredictionEnabled() { + return sysprop::InputProperties::enable_motion_prediction().value_or(true); +} + +/** + * Given a set of MotionEvents for the current gesture, predict the motion. The returned MotionEvent + * contains a set of samples in the future. + * + * The typical usage is like this: + * + * MotionPredictor predictor(offset = MY_OFFSET); + * predictor.record(DOWN_MOTION_EVENT); + * predictor.record(MOVE_MOTION_EVENT); + * prediction = predictor.predict(futureTime); + * + * The resulting motion event will have eventTime <= (futureTime + MY_OFFSET). It might contain + * historical data, which are additional samples from the latest recorded MotionEvent's eventTime + * to the futureTime + MY_OFFSET. + * + * The offset is used to provide additional flexibility to the caller, in case the default present + * time (typically provided by the choreographer) does not account for some delays, or to simply + * reduce the aggressiveness of the prediction. Offset can be positive or negative. + */ +class MotionPredictor { +public: + /** + * Parameters: + * predictionTimestampOffsetNanos: additional, constant shift to apply to the target + * prediction time. The prediction will target the time t=(prediction time + + * predictionTimestampOffsetNanos). + * + * modelPath: filesystem path to a TfLiteMotionPredictorModel flatbuffer, or nullptr to use the + * default model path. + * + * checkEnableMotionPredition: the function to check whether the prediction should run. Used to + * provide an additional way of turning prediction on and off. Can be toggled at runtime. + */ + MotionPredictor(nsecs_t predictionTimestampOffsetNanos, + std::function checkEnableMotionPrediction = isMotionPredictionEnabled); + /** + * Record the actual motion received by the view. This event will be used for calculating the + * predictions. + * + * @return empty result if the event was processed correctly, error if the event is not + * consistent with the previously recorded events. + */ + android::base::Result record(const MotionEvent& event); + std::unique_ptr predict(nsecs_t timestamp); + bool isPredictionAvailable(int32_t deviceId, int32_t source); + +private: + const nsecs_t mPredictionTimestampOffsetNanos; + const std::function mCheckMotionPredictionEnabled; + + std::unique_ptr mModel; + + std::unique_ptr mBuffers; + std::optional mLastEvent; +}; + +} // namespace android diff --git a/include/input/PrintTools.h b/include/input/PrintTools.h index 55f730b28705833d0762c710798fda7ea46f50dc..02bc2010db213e997c8cabe3b66ddcd63c809664 100644 --- a/include/input/PrintTools.h +++ b/include/input/PrintTools.h @@ -16,24 +16,45 @@ #pragma once +#include #include #include #include #include +#include namespace android { +template +std::string bitsetToString(const std::bitset& bitset) { + return bitset.to_string(); +} + template -std::string constToString(const T& v) { +inline std::string constToString(const T& v) { return std::to_string(v); } +template <> +inline std::string constToString(const bool& value) { + return value ? "true" : "false"; +} + +template <> +inline std::string constToString(const std::vector::reference& value) { + return value ? "true" : "false"; +} + +inline std::string constToString(const std::string& s) { + return s; +} + /** * Convert an optional type to string. */ template -std::string toString(const std::optional& optional, - std::string (*toString)(const T&) = constToString) { +inline std::string toString(const std::optional& optional, + std::string (*toString)(const T&) = constToString) { return optional ? toString(*optional) : ""; } @@ -66,6 +87,19 @@ std::string dumpMap(const std::map& map, std::string (*keyToString)(const return out; } +/** + * Convert a vector to a string. The values of the vector should be of a type supported by + * constToString. + */ +template +std::string dumpVector(std::vector values) { + std::string dump = constToString(values[0]); + for (size_t i = 1; i < values.size(); i++) { + dump += ", " + constToString(values[i]); + } + return dump; +} + const char* toString(bool value); /** @@ -77,4 +111,4 @@ const char* toString(bool value); */ std::string addLinePrefix(std::string str, const std::string& prefix); -} // namespace android \ No newline at end of file +} // namespace android diff --git a/include/input/PropertyMap.h b/include/input/PropertyMap.h index 451918bb46e1b0d5aa60afa239ee92618ff23e95..2e4414292745d8c411e664517d3e9d573696e709 100644 --- a/include/input/PropertyMap.h +++ b/include/input/PropertyMap.h @@ -14,15 +14,16 @@ * limitations under the License. */ -#ifndef _UTILS_PROPERTY_MAP_H -#define _UTILS_PROPERTY_MAP_H +#pragma once #include -#include -#include -#include #include +#include +#include +#include +#include + namespace android { /* @@ -58,30 +59,31 @@ public: /* Adds a property. * Replaces the property with the same key if it is already present. */ - void addProperty(const String8& key, const String8& value); + void addProperty(const std::string& key, const std::string& value); - /* Returns true if the property map contains the specified key. */ - bool hasProperty(const String8& key) const; + /* Returns a set of all property keys starting with the given prefix. */ + std::unordered_set getKeysWithPrefix(const std::string& prefix) const; - /* Gets the value of a property and parses it. - * Returns true and sets outValue if the key was found and its value was parsed successfully. - * Otherwise returns false and does not modify outValue. (Also logs a warning.) + /* Gets the value of a property and parses it. Returns nullopt if the key wasn't found or + * couldn't be parsed as the requested type. (Warnings are also logged in the case of parsing + * failures.) */ - bool tryGetProperty(const String8& key, String8& outValue) const; - bool tryGetProperty(const String8& key, bool& outValue) const; - bool tryGetProperty(const String8& key, int32_t& outValue) const; - bool tryGetProperty(const String8& key, float& outValue) const; + std::optional getString(const std::string& key) const; + std::optional getBool(const std::string& key) const; + std::optional getInt(const std::string& key) const; + std::optional getFloat(const std::string& key) const; + std::optional getDouble(const std::string& key) const; /* Adds all values from the specified property map. */ void addAll(const PropertyMap* map); - /* Gets the underlying property map. */ - inline const KeyedVector& getProperties() const { return mProperties; } - /* Loads a property map from a file. */ static android::base::Result> load(const char* filename); private: + /* Returns true if the property map contains the specified key. */ + bool hasProperty(const std::string& key) const; + class Parser { PropertyMap* mMap; Tokenizer* mTokenizer; @@ -95,13 +97,11 @@ private: status_t parseType(); status_t parseKey(); status_t parseKeyProperty(); - status_t parseModifier(const String8& token, int32_t* outMetaState); + status_t parseModifier(const std::string& token, int32_t* outMetaState); status_t parseCharacterLiteral(char16_t* outCharacter); }; - KeyedVector mProperties; + std::unordered_map mProperties; }; } // namespace android - -#endif // _UTILS_PROPERTY_MAP_H diff --git a/include/input/RingBuffer.h b/include/input/RingBuffer.h new file mode 100644 index 0000000000000000000000000000000000000000..37fe5afeeacc754aaf6550bae99d029dabb13b2b --- /dev/null +++ b/include/input/RingBuffer.h @@ -0,0 +1,298 @@ +/* + * 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 +#include +#include +#include +#include +#include + +#include +#include + +namespace android { + +// A fixed-size ring buffer of elements. +// +// Elements can only be removed from the front/back or added to the front/back, but with O(1) +// performance. Elements from the opposing side are evicted when new elements are pushed onto a full +// buffer. +template +class RingBuffer { +public: + using value_type = T; + using size_type = size_t; + using difference_type = ptrdiff_t; + using reference = value_type&; + using const_reference = const value_type&; + using pointer = value_type*; + using const_pointer = const value_type*; + + template + class Iterator; + using iterator = Iterator; + using const_iterator = Iterator; + + // Creates an empty ring buffer that can hold some capacity. + explicit RingBuffer(size_type capacity) + : mBuffer(std::allocator().allocate(capacity)), mCapacity(capacity) {} + + // Creates a full ring buffer holding a fixed number of elements initialised to some value. + explicit RingBuffer(size_type count, const_reference value) : RingBuffer(count) { + while (count) { + pushBack(value); + --count; + } + } + + RingBuffer(const RingBuffer& other) : RingBuffer(other.capacity()) { + for (const auto& element : other) { + pushBack(element); + } + } + + RingBuffer(RingBuffer&& other) noexcept { *this = std::move(other); } + + ~RingBuffer() { + if (mBuffer) { + clear(); + std::allocator().deallocate(mBuffer, mCapacity); + } + } + + RingBuffer& operator=(const RingBuffer& other) { return *this = RingBuffer(other); } + + RingBuffer& operator=(RingBuffer&& other) noexcept { + if (this == &other) { + return *this; + } + if (mBuffer) { + clear(); + std::allocator().deallocate(mBuffer, mCapacity); + } + mBuffer = std::move(other.mBuffer); + mCapacity = other.mCapacity; + mBegin = other.mBegin; + mSize = other.mSize; + other.mBuffer = nullptr; + other.mCapacity = 0; + other.mBegin = 0; + other.mSize = 0; + return *this; + } + + iterator begin() { return {*this, 0}; } + const_iterator begin() const { return {*this, 0}; } + iterator end() { return {*this, mSize}; } + const_iterator end() const { return {*this, mSize}; } + + reference front() { return mBuffer[mBegin]; } + const_reference front() const { return mBuffer[mBegin]; } + reference back() { return mBuffer[bufferIndex(mSize - 1)]; } + const_reference back() const { return mBuffer[bufferIndex(mSize - 1)]; } + + reference operator[](size_type i) { return mBuffer[bufferIndex(i)]; } + const_reference operator[](size_type i) const { return mBuffer[bufferIndex(i)]; } + + // Removes all elements from the buffer. + void clear() { + std::destroy(begin(), end()); + mSize = 0; + } + + // Removes and returns the first element from the buffer. + value_type popFront() { + value_type element = mBuffer[mBegin]; + std::destroy_at(std::addressof(mBuffer[mBegin])); + mBegin = next(mBegin); + --mSize; + return element; + } + + // Removes and returns the last element from the buffer. + value_type popBack() { + size_type backIndex = bufferIndex(mSize - 1); + value_type element = mBuffer[backIndex]; + std::destroy_at(std::addressof(mBuffer[backIndex])); + --mSize; + return element; + } + + // Adds an element to the front of the buffer. + void pushFront(const value_type& element) { pushFront(value_type(element)); } + void pushFront(value_type&& element) { + mBegin = previous(mBegin); + if (size() == capacity()) { + mBuffer[mBegin] = std::forward(element); + } else { + // The space at mBuffer[mBegin] is uninitialised. + // TODO: Use std::construct_at when it becomes available. + new (std::addressof(mBuffer[mBegin])) value_type(std::forward(element)); + ++mSize; + } + } + + // Adds an element to the back of the buffer. + void pushBack(const value_type& element) { pushBack(value_type(element)); } + void pushBack(value_type&& element) { + if (size() == capacity()) { + mBuffer[mBegin] = std::forward(element); + mBegin = next(mBegin); + } else { + // The space at mBuffer[...] is uninitialised. + // TODO: Use std::construct_at when it becomes available. + new (std::addressof(mBuffer[bufferIndex(mSize)])) + value_type(std::forward(element)); + ++mSize; + } + } + + bool empty() const { return mSize == 0; } + size_type capacity() const { return mCapacity; } + size_type size() const { return mSize; } + + void swap(RingBuffer& other) noexcept { + using std::swap; + swap(mBuffer, other.mBuffer); + swap(mCapacity, other.mCapacity); + swap(mBegin, other.mBegin); + swap(mSize, other.mSize); + } + + friend void swap(RingBuffer& lhs, RingBuffer& rhs) noexcept { lhs.swap(rhs); } + + template + class Iterator { + private: + using ContainerType = std::conditional_t, const RingBuffer, RingBuffer>; + + public: + using iterator_category = std::random_access_iterator_tag; + using size_type = ContainerType::size_type; + using difference_type = ContainerType::difference_type; + using value_type = std::remove_cv_t; + using pointer = U*; + using reference = U&; + + Iterator(ContainerType& container, size_type index) + : mContainer(container), mIndex(index) {} + + Iterator(const Iterator&) = default; + Iterator& operator=(const Iterator&) = default; + + Iterator& operator++() { + ++mIndex; + return *this; + } + + Iterator operator++(int) { + Iterator iterator(*this); + ++(*this); + return iterator; + } + + Iterator& operator--() { + --mIndex; + return *this; + } + + Iterator operator--(int) { + Iterator iterator(*this); + --(*this); + return iterator; + } + + Iterator& operator+=(difference_type n) { + mIndex += n; + return *this; + } + + Iterator operator+(difference_type n) { + Iterator iterator(*this); + return iterator += n; + } + + Iterator& operator-=(difference_type n) { return *this += -n; } + + Iterator operator-(difference_type n) { + Iterator iterator(*this); + return iterator -= n; + } + + difference_type operator-(const Iterator& other) { return mIndex - other.mIndex; } + + bool operator==(const Iterator& rhs) const { return mIndex == rhs.mIndex; } + + bool operator!=(const Iterator& rhs) const { return !(*this == rhs); } + + friend auto operator<=>(const Iterator& lhs, const Iterator& rhs) { + return lhs.mIndex <=> rhs.mIndex; + } + + reference operator[](difference_type n) { return *(*this + n); } + + reference operator*() const { return mContainer[mIndex]; } + pointer operator->() const { return std::addressof(mContainer[mIndex]); } + + private: + ContainerType& mContainer; + size_type mIndex = 0; + }; + +private: + // Returns the index of the next element in mBuffer. + size_type next(size_type index) const { + if (index == capacity() - 1) { + return 0; + } else { + return index + 1; + } + } + + // Returns the index of the previous element in mBuffer. + size_type previous(size_type index) const { + if (index == 0) { + return capacity() - 1; + } else { + return index - 1; + } + } + + // Converts the index of an element in [0, size()] to its corresponding index in mBuffer. + size_type bufferIndex(size_type elementIndex) const { + CHECK_LE(elementIndex, size()); + size_type index = mBegin + elementIndex; + if (index >= capacity()) { + index -= capacity(); + } + CHECK_LT(index, capacity()) + << android::base::StringPrintf("Invalid index calculated for element (%zu) " + "in buffer of size %zu", + elementIndex, size()); + return index; + } + + pointer mBuffer = nullptr; + size_type mCapacity = 0; // Total capacity of mBuffer. + size_type mBegin = 0; // Index of the first initialised element in mBuffer. + size_type mSize = 0; // Total number of initialised elements. +}; + +} // namespace android diff --git a/include/input/TfLiteMotionPredictor.h b/include/input/TfLiteMotionPredictor.h new file mode 100644 index 0000000000000000000000000000000000000000..a340bd0575f7259719280f6d3d5a4c173b0daf20 --- /dev/null +++ b/include/input/TfLiteMotionPredictor.h @@ -0,0 +1,153 @@ +/* + * 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 +#include +#include +#include +#include + +#include +#include + +#include +#include +#include +#include + +namespace android { + +struct TfLiteMotionPredictorSample { + // The untransformed AMOTION_EVENT_AXIS_X and AMOTION_EVENT_AXIS_Y of the sample. + struct Point { + float x; + float y; + } position; + // The AMOTION_EVENT_AXIS_PRESSURE, _TILT, and _ORIENTATION. + float pressure; + float tilt; + float orientation; +}; + +inline TfLiteMotionPredictorSample::Point operator-(const TfLiteMotionPredictorSample::Point& lhs, + const TfLiteMotionPredictorSample::Point& rhs) { + return {.x = lhs.x - rhs.x, .y = lhs.y - rhs.y}; +} + +class TfLiteMotionPredictorModel; + +// Buffer storage for a TfLiteMotionPredictorModel. +class TfLiteMotionPredictorBuffers { +public: + // Creates buffer storage for a model with the given input length. + TfLiteMotionPredictorBuffers(size_t inputLength); + + // Adds a motion sample to the buffers. + void pushSample(int64_t timestamp, TfLiteMotionPredictorSample sample); + + // Returns true if the buffers are complete enough to generate a prediction. + bool isReady() const { + // Predictions can't be applied unless there are at least two points to determine + // the direction to apply them in. + return mAxisFrom && mAxisTo; + } + + // Resets all buffers to their initial state. + void reset(); + + // Copies the buffers to those of a model for prediction. + void copyTo(TfLiteMotionPredictorModel& model) const; + + // Returns the current axis of the buffer's samples. Only valid if isReady(). + TfLiteMotionPredictorSample axisFrom() const { return *mAxisFrom; } + TfLiteMotionPredictorSample axisTo() const { return *mAxisTo; } + + // Returns the timestamp of the last sample. + int64_t lastTimestamp() const { return mTimestamp; } + +private: + int64_t mTimestamp = 0; + + RingBuffer mInputR; + RingBuffer mInputPhi; + RingBuffer mInputPressure; + RingBuffer mInputTilt; + RingBuffer mInputOrientation; + + // The samples defining the current polar axis. + std::optional mAxisFrom; + std::optional mAxisTo; +}; + +// A TFLite model for generating motion predictions. +class TfLiteMotionPredictorModel { +public: + // Creates a model from an encoded Flatbuffer model. + static std::unique_ptr create(); + + ~TfLiteMotionPredictorModel(); + + // Returns the length of the model's input buffers. + size_t inputLength() const; + + // Returns the length of the model's output buffers. + size_t outputLength() const; + + // Executes the model. + // Returns true if the model successfully executed and the output tensors can be read. + bool invoke(); + + // Returns mutable buffers to the input tensors of inputLength() elements. + std::span inputR(); + std::span inputPhi(); + std::span inputPressure(); + std::span inputOrientation(); + std::span inputTilt(); + + // Returns immutable buffers to the output tensors of identical length. Only valid after a + // successful call to invoke(). + std::span outputR() const; + std::span outputPhi() const; + std::span outputPressure() const; + +private: + explicit TfLiteMotionPredictorModel(std::unique_ptr model); + + void allocateTensors(); + void attachInputTensors(); + void attachOutputTensors(); + + TfLiteTensor* mInputR = nullptr; + TfLiteTensor* mInputPhi = nullptr; + TfLiteTensor* mInputPressure = nullptr; + TfLiteTensor* mInputTilt = nullptr; + TfLiteTensor* mInputOrientation = nullptr; + + const TfLiteTensor* mOutputR = nullptr; + const TfLiteTensor* mOutputPhi = nullptr; + const TfLiteTensor* mOutputPressure = nullptr; + + std::unique_ptr mFlatBuffer; + std::unique_ptr mErrorReporter; + std::unique_ptr mModel; + std::unique_ptr mInterpreter; + tflite::SignatureRunner* mRunner = nullptr; +}; + +} // namespace android diff --git a/include/input/TouchVideoFrame.h b/include/input/TouchVideoFrame.h index eda628e233a69097b924e9a48b1ec895fc39d80a..1e4f6e7a4c3b8b29661038fe44d54f5fe4648aa3 100644 --- a/include/input/TouchVideoFrame.h +++ b/include/input/TouchVideoFrame.h @@ -14,8 +14,9 @@ * limitations under the License. */ -#ifndef _LIBINPUT_TOUCHVIDEOFRAME_H -#define _LIBINPUT_TOUCHVIDEOFRAME_H +#pragma once + +#include #include #include @@ -59,7 +60,7 @@ public: * Rotate the video frame. * The rotation value is an enum from ui/Rotation.h */ - void rotate(int32_t orientation); + void rotate(ui::Rotation orientation); private: uint32_t mHeight; @@ -75,5 +76,3 @@ private: }; } // namespace android - -#endif // _LIBINPUT_TOUCHVIDEOFRAME_H diff --git a/include/input/VelocityControl.h b/include/input/VelocityControl.h index 1acc2aef702ad50b3693e5da6fc2f7cac96c2834..f3c201e7c43fe087bc350faea094236c8eb659a5 100644 --- a/include/input/VelocityControl.h +++ b/include/input/VelocityControl.h @@ -14,13 +14,15 @@ * limitations under the License. */ -#ifndef _LIBINPUT_VELOCITY_CONTROL_H -#define _LIBINPUT_VELOCITY_CONTROL_H +#pragma once +#include #include #include #include +using android::base::StringPrintf; + namespace android { /* @@ -70,6 +72,12 @@ struct VelocityControlParameters { scale(scale), lowThreshold(lowThreshold), highThreshold(highThreshold), acceleration(acceleration) { } + + std::string dump() const { + return StringPrintf("scale=%0.3f, lowThreshold=%0.3f, highThreshold=%0.3f, " + "acceleration=%0.3f\n", + scale, lowThreshold, highThreshold, acceleration); + } }; /* @@ -79,6 +87,9 @@ class VelocityControl { public: VelocityControl(); + /* Gets the various parameters. */ + VelocityControlParameters& getParameters(); + /* Sets the various parameters. */ void setParameters(const VelocityControlParameters& parameters); @@ -98,10 +109,8 @@ private: VelocityControlParameters mParameters; nsecs_t mLastMovementTime; - VelocityTracker::Position mRawPosition; + float mRawPositionX, mRawPositionY; VelocityTracker mVelocityTracker; }; } // namespace android - -#endif // _LIBINPUT_VELOCITY_CONTROL_H diff --git a/include/input/VelocityTracker.h b/include/input/VelocityTracker.h index 886f1f7753759c8beb6af69783dfc70988e33bd1..da97c3e85562ddeeb4c3bed8c0f4163c90b05c41 100644 --- a/include/input/VelocityTracker.h +++ b/include/input/VelocityTracker.h @@ -14,12 +14,13 @@ * limitations under the License. */ -#ifndef _LIBINPUT_VELOCITY_TRACKER_H -#define _LIBINPUT_VELOCITY_TRACKER_H +#pragma once #include #include #include +#include +#include namespace android { @@ -46,93 +47,113 @@ public: MAX = LEGACY, }; - struct Position { - float x, y; - }; - struct Estimator { static const size_t MAX_DEGREE = 4; // Estimator time base. - nsecs_t time; + nsecs_t time = 0; - // Polynomial coefficients describing motion in X and Y. - float xCoeff[MAX_DEGREE + 1], yCoeff[MAX_DEGREE + 1]; + // Polynomial coefficients describing motion. + std::array coeff{}; // Polynomial degree (number of coefficients), or zero if no information is // available. - uint32_t degree; + uint32_t degree = 0; // Confidence (coefficient of determination), between 0 (no fit) and 1 (perfect fit). - float confidence; - - inline void clear() { - time = 0; - degree = 0; - confidence = 0; - for (size_t i = 0; i <= MAX_DEGREE; i++) { - xCoeff[i] = 0; - yCoeff[i] = 0; + float confidence = 0; + }; + + /* + * Contains all available velocity data from a VelocityTracker. + */ + struct ComputedVelocity { + inline std::optional getVelocity(int32_t axis, int32_t id) const { + const auto& axisVelocities = mVelocities.find(axis); + if (axisVelocities == mVelocities.end()) { + return {}; + } + + const auto& axisIdVelocity = axisVelocities->second.find(id); + if (axisIdVelocity == axisVelocities->second.end()) { + return {}; } + + return axisIdVelocity->second; + } + + inline void addVelocity(int32_t axis, int32_t id, float velocity) { + mVelocities[axis][id] = velocity; } + + private: + std::map> mVelocities; }; - // Creates a velocity tracker using the specified strategy. + // Creates a velocity tracker using the specified strategy for each supported axis. // If strategy is not provided, uses the default strategy for the platform. + // TODO(b/32830165): support axis-specific strategies. VelocityTracker(const Strategy strategy = Strategy::DEFAULT); ~VelocityTracker(); + /** Return true if the axis is supported for velocity tracking, false otherwise. */ + static bool isAxisSupported(int32_t axis); + // Resets the velocity tracker state. void clear(); - // Resets the velocity tracker state for specific pointers. + // Resets the velocity tracker state for a specific pointer. // Call this method when some pointers have changed and may be reusing // an id that was assigned to a different pointer earlier. - void clearPointers(BitSet32 idBits); + void clearPointer(int32_t pointerId); - // Adds movement information for a set of pointers. - // The idBits bitfield specifies the pointer ids of the pointers whose positions - // are included in the movement. - // The positions array contains position information for each pointer in order by - // increasing id. Its size should be equal to the number of one bits in idBits. - void addMovement(nsecs_t eventTime, BitSet32 idBits, const std::vector& positions); + // Adds movement information for a pointer for a specific axis + void addMovement(nsecs_t eventTime, int32_t pointerId, int32_t axis, float position); // Adds movement information for all pointers in a MotionEvent, including historical samples. void addMovement(const MotionEvent* event); - // Gets the velocity of the specified pointer id in position units per second. - // Returns false and sets the velocity components to zero if there is - // insufficient movement information for the pointer. - bool getVelocity(uint32_t id, float* outVx, float* outVy) const; + // Returns the velocity of the specified pointer id and axis in position units per second. + // Returns empty optional if there is insufficient movement information for the pointer, or if + // the given axis is not supported for velocity tracking. + std::optional getVelocity(int32_t axis, int32_t pointerId) const; + + // Returns a ComputedVelocity instance with all available velocity data, using the given units + // (reference: units == 1 means "per millisecond"), and clamping each velocity between + // [-maxVelocity, maxVelocity], inclusive. + ComputedVelocity getComputedVelocity(int32_t units, float maxVelocity); - // Gets an estimator for the recent movements of the specified pointer id. + // Gets an estimator for the recent movements of the specified pointer id for the given axis. // Returns false and clears the estimator if there is no information available // about the pointer. - bool getEstimator(uint32_t id, Estimator* outEstimator) const; + std::optional getEstimator(int32_t axis, int32_t pointerId) const; // Gets the active pointer id, or -1 if none. - inline int32_t getActivePointerId() const { return mActivePointerId; } - - // Gets a bitset containing all pointer ids from the most recent movement. - inline BitSet32 getCurrentPointerIdBits() const { return mCurrentPointerIdBits; } + inline int32_t getActivePointerId() const { return mActivePointerId.value_or(-1); } private: - // The default velocity tracker strategy. - // Although other strategies are available for testing and comparison purposes, - // this is the strategy that applications will actually use. Be very careful - // when adjusting the default strategy because it can dramatically affect - // (often in a bad way) the user experience. - static const Strategy DEFAULT_STRATEGY = Strategy::LSQ2; - nsecs_t mLastEventTime; BitSet32 mCurrentPointerIdBits; - int32_t mActivePointerId; - std::unique_ptr mStrategy; - - bool configureStrategy(const Strategy strategy); - - static std::unique_ptr createStrategy(const Strategy strategy); + std::optional mActivePointerId; + + // An override strategy passed in the constructor to be used for all axes. + // This strategy will apply to all axes, unless the default strategy is specified here. + // When default strategy is specified, then each axis will use a potentially different strategy + // based on a hardcoded mapping. + const Strategy mOverrideStrategy; + // Maps axes to their respective VelocityTrackerStrategy instances. + // Note that, only axes that have had MotionEvents (and not all supported axes) will be here. + std::map> mConfiguredStrategies; + + void configureStrategy(int32_t axis); + + // Generates a VelocityTrackerStrategy instance for the given Strategy type. + // The `deltaValues` parameter indicates whether or not the created strategy should treat motion + // values as deltas (and not as absolute values). This the parameter is applicable only for + // strategies that support differential axes. + static std::unique_ptr createStrategy(const Strategy strategy, + bool deltaValues); }; @@ -146,11 +167,9 @@ protected: public: virtual ~VelocityTrackerStrategy() { } - virtual void clear() = 0; - virtual void clearPointers(BitSet32 idBits) = 0; - virtual void addMovement(nsecs_t eventTime, BitSet32 idBits, - const std::vector& positions) = 0; - virtual bool getEstimator(uint32_t id, VelocityTracker::Estimator* outEstimator) const = 0; + virtual void clearPointer(int32_t pointerId) = 0; + virtual void addMovement(nsecs_t eventTime, int32_t pointerId, float position) = 0; + virtual std::optional getEstimator(int32_t pointerId) const = 0; }; @@ -159,30 +178,28 @@ public: */ class LeastSquaresVelocityTrackerStrategy : public VelocityTrackerStrategy { public: - enum Weighting { + enum class Weighting { // No weights applied. All data points are equally reliable. - WEIGHTING_NONE, + NONE, // Weight by time delta. Data points clustered together are weighted less. - WEIGHTING_DELTA, + DELTA, // Weight such that points within a certain horizon are weighed more than those // outside of that horizon. - WEIGHTING_CENTRAL, + CENTRAL, // Weight such that points older than a certain amount are weighed less. - WEIGHTING_RECENT, + RECENT, }; // Degree must be no greater than Estimator::MAX_DEGREE. - LeastSquaresVelocityTrackerStrategy(uint32_t degree, Weighting weighting = WEIGHTING_NONE); - virtual ~LeastSquaresVelocityTrackerStrategy(); + LeastSquaresVelocityTrackerStrategy(uint32_t degree, Weighting weighting = Weighting::NONE); + ~LeastSquaresVelocityTrackerStrategy() override; - virtual void clear(); - virtual void clearPointers(BitSet32 idBits); - void addMovement(nsecs_t eventTime, BitSet32 idBits, - const std::vector& positions) override; - virtual bool getEstimator(uint32_t id, VelocityTracker::Estimator* outEstimator) const; + void clearPointer(int32_t pointerId) override; + void addMovement(nsecs_t eventTime, int32_t pointerId, float position) override; + std::optional getEstimator(int32_t pointerId) const override; private: // Sample horizon. @@ -195,20 +212,15 @@ private: struct Movement { nsecs_t eventTime; - BitSet32 idBits; - VelocityTracker::Position positions[MAX_POINTERS]; - - inline const VelocityTracker::Position& getPosition(uint32_t id) const { - return positions[idBits.getIndexOfBit(id)]; - } + float position; }; - float chooseWeight(uint32_t index) const; + float chooseWeight(int32_t pointerId, uint32_t index) const; const uint32_t mDegree; const Weighting mWeighting; - uint32_t mIndex; - Movement mMovements[HISTORY_SIZE]; + std::map mIndex; + std::map> mMovements; }; @@ -219,13 +231,11 @@ class IntegratingVelocityTrackerStrategy : public VelocityTrackerStrategy { public: // Degree must be 1 or 2. IntegratingVelocityTrackerStrategy(uint32_t degree); - ~IntegratingVelocityTrackerStrategy(); + ~IntegratingVelocityTrackerStrategy() override; - virtual void clear(); - virtual void clearPointers(BitSet32 idBits); - void addMovement(nsecs_t eventTime, BitSet32 idBits, - const std::vector& positions) override; - virtual bool getEstimator(uint32_t id, VelocityTracker::Estimator* outEstimator) const; + void clearPointer(int32_t pointerId) override; + void addMovement(nsecs_t eventTime, int32_t pointerId, float positions) override; + std::optional getEstimator(int32_t pointerId) const override; private: // Current state estimate for a particular pointer. @@ -233,16 +243,15 @@ private: nsecs_t updateTime; uint32_t degree; - float xpos, xvel, xaccel; - float ypos, yvel, yaccel; + float pos, vel, accel; }; const uint32_t mDegree; BitSet32 mPointerIdBits; State mPointerState[MAX_POINTER_ID + 1]; - void initState(State& state, nsecs_t eventTime, float xpos, float ypos) const; - void updateState(State& state, nsecs_t eventTime, float xpos, float ypos) const; + void initState(State& state, nsecs_t eventTime, float pos) const; + void updateState(State& state, nsecs_t eventTime, float pos) const; void populateEstimator(const State& state, VelocityTracker::Estimator* outEstimator) const; }; @@ -253,13 +262,11 @@ private: class LegacyVelocityTrackerStrategy : public VelocityTrackerStrategy { public: LegacyVelocityTrackerStrategy(); - virtual ~LegacyVelocityTrackerStrategy(); + ~LegacyVelocityTrackerStrategy() override; - virtual void clear(); - virtual void clearPointers(BitSet32 idBits); - void addMovement(nsecs_t eventTime, BitSet32 idBits, - const std::vector& positions) override; - virtual bool getEstimator(uint32_t id, VelocityTracker::Estimator* outEstimator) const; + void clearPointer(int32_t pointerId) override; + void addMovement(nsecs_t eventTime, int32_t pointerId, float position) override; + std::optional getEstimator(int32_t pointerId) const override; private: // Oldest sample to consider when calculating the velocity. @@ -273,28 +280,21 @@ private: struct Movement { nsecs_t eventTime; - BitSet32 idBits; - VelocityTracker::Position positions[MAX_POINTERS]; - - inline const VelocityTracker::Position& getPosition(uint32_t id) const { - return positions[idBits.getIndexOfBit(id)]; - } + float position; }; - uint32_t mIndex; - Movement mMovements[HISTORY_SIZE]; + std::map mIndex; + std::map> mMovements; }; class ImpulseVelocityTrackerStrategy : public VelocityTrackerStrategy { public: - ImpulseVelocityTrackerStrategy(); - virtual ~ImpulseVelocityTrackerStrategy(); + ImpulseVelocityTrackerStrategy(bool deltaValues); + ~ImpulseVelocityTrackerStrategy() override; - virtual void clear(); - virtual void clearPointers(BitSet32 idBits); - void addMovement(nsecs_t eventTime, BitSet32 idBits, - const std::vector& positions) override; - virtual bool getEstimator(uint32_t id, VelocityTracker::Estimator* outEstimator) const; + void clearPointer(int32_t pointerId) override; + void addMovement(nsecs_t eventTime, int32_t pointerId, float position) override; + std::optional getEstimator(int32_t pointerId) const override; private: // Sample horizon. @@ -307,18 +307,16 @@ private: struct Movement { nsecs_t eventTime; - BitSet32 idBits; - VelocityTracker::Position positions[MAX_POINTERS]; - - inline const VelocityTracker::Position& getPosition(uint32_t id) const { - return positions[idBits.getIndexOfBit(id)]; - } + float position; }; - size_t mIndex; - Movement mMovements[HISTORY_SIZE]; + // Whether or not the input movement values for the strategy come in the form of delta values. + // If the input values are not deltas, the strategy needs to calculate deltas as part of its + // velocity calculation. + const bool mDeltaValues; + + std::map mIndex; + std::map> mMovements; }; } // namespace android - -#endif // _LIBINPUT_VELOCITY_TRACKER_H diff --git a/include/input/VirtualInputDevice.h b/include/input/VirtualInputDevice.h new file mode 100644 index 0000000000000000000000000000000000000000..21a28770b60e3ea737eb114d78aa7698e82c93fb --- /dev/null +++ b/include/input/VirtualInputDevice.h @@ -0,0 +1,104 @@ +/* + * 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 + +namespace android { + +enum class UinputAction { + RELEASE = 0, + PRESS = 1, + MOVE = 2, + CANCEL = 3, +}; + +class VirtualInputDevice { +public: + VirtualInputDevice(android::base::unique_fd fd); + virtual ~VirtualInputDevice(); + +protected: + const android::base::unique_fd mFd; + bool writeInputEvent(uint16_t type, uint16_t code, int32_t value, + std::chrono::nanoseconds eventTime); + bool writeEvKeyEvent(int32_t androidCode, int32_t androidAction, + const std::map& evKeyCodeMapping, + const std::map& actionMapping, + std::chrono::nanoseconds eventTime); +}; + +class VirtualKeyboard : public VirtualInputDevice { +public: + static const std::map KEY_CODE_MAPPING; + // Expose to share with VirtualDpad. + static const std::map KEY_ACTION_MAPPING; + VirtualKeyboard(android::base::unique_fd fd); + virtual ~VirtualKeyboard() override; + bool writeKeyEvent(int32_t androidKeyCode, int32_t androidAction, + std::chrono::nanoseconds eventTime); +}; + +class VirtualDpad : public VirtualInputDevice { +public: + static const std::map DPAD_KEY_CODE_MAPPING; + VirtualDpad(android::base::unique_fd fd); + virtual ~VirtualDpad() override; + bool writeDpadKeyEvent(int32_t androidKeyCode, int32_t androidAction, + std::chrono::nanoseconds eventTime); +}; + +class VirtualMouse : public VirtualInputDevice { +public: + VirtualMouse(android::base::unique_fd fd); + virtual ~VirtualMouse() override; + bool writeButtonEvent(int32_t androidButtonCode, int32_t androidAction, + std::chrono::nanoseconds eventTime); + // TODO(b/259554911): changing float parameters to int32_t. + bool writeRelativeEvent(float relativeX, float relativeY, std::chrono::nanoseconds eventTime); + bool writeScrollEvent(float xAxisMovement, float yAxisMovement, + std::chrono::nanoseconds eventTime); + +private: + static const std::map BUTTON_ACTION_MAPPING; + static const std::map BUTTON_CODE_MAPPING; +}; + +class VirtualTouchscreen : public VirtualInputDevice { +public: + VirtualTouchscreen(android::base::unique_fd fd); + virtual ~VirtualTouchscreen() override; + // TODO(b/259554911): changing float parameters to int32_t. + bool writeTouchEvent(int32_t pointerId, int32_t toolType, int32_t action, float locationX, + float locationY, float pressure, float majorAxisSize, + std::chrono::nanoseconds eventTime); + +private: + static const std::map TOUCH_ACTION_MAPPING; + static const std::map TOOL_TYPE_MAPPING; + + /* The set of active touch pointers on this device. + * We only allow pointer id to go up to MAX_POINTERS because the maximum slots of virtual + * touchscreen is set up with MAX_POINTERS. Note that in other cases Android allows pointer id + * to go up to MAX_POINTERS_ID. + */ + std::bitset mActivePointers{}; + bool isValidPointerId(int32_t pointerId, UinputAction uinputAction); + bool handleTouchDown(int32_t pointerId, std::chrono::nanoseconds eventTime); + bool handleTouchUp(int32_t pointerId, std::chrono::nanoseconds eventTime); +}; +} // namespace android diff --git a/include/input/VirtualKeyMap.h b/include/input/VirtualKeyMap.h index 6e8e2c9cf45c9d9d67a5d31e8040e71a9f0bb436..a4381eaab981d882133ac8a3673fe04852791be6 100644 --- a/include/input/VirtualKeyMap.h +++ b/include/input/VirtualKeyMap.h @@ -14,8 +14,7 @@ * limitations under the License. */ -#ifndef _LIBINPUT_VIRTUAL_KEY_MAP_H -#define _LIBINPUT_VIRTUAL_KEY_MAP_H +#pragma once #include @@ -77,5 +76,3 @@ private: }; } // namespace android - -#endif // _LIBINPUT_KEY_CHARACTER_MAP_H diff --git a/include/powermanager/PowerHalLoader.h b/include/powermanager/PowerHalLoader.h index ed6f6f35f507cf86ff81e57b22e335e89b93b54e..e0384f31dba2a0c1ad10ce92d68ba2da6f8d74b4 100644 --- a/include/powermanager/PowerHalLoader.h +++ b/include/powermanager/PowerHalLoader.h @@ -19,6 +19,8 @@ #include #include +#include +#include #include namespace android { @@ -32,12 +34,16 @@ public: static sp loadAidl(); static sp loadHidlV1_0(); static sp loadHidlV1_1(); + static sp loadHidlV1_2(); + static sp loadHidlV1_3(); private: static std::mutex gHalMutex; static sp gHalAidl GUARDED_BY(gHalMutex); static sp gHalHidlV1_0 GUARDED_BY(gHalMutex); static sp gHalHidlV1_1 GUARDED_BY(gHalMutex); + static sp gHalHidlV1_2 GUARDED_BY(gHalMutex); + static sp gHalHidlV1_3 GUARDED_BY(gHalMutex); static sp loadHidlV1_0Locked() EXCLUSIVE_LOCKS_REQUIRED(gHalMutex); diff --git a/include/powermanager/PowerHalWrapper.h b/include/powermanager/PowerHalWrapper.h index dfb0ff59a0432c693ac43ca39b577d69449196c3..8028aa86e1455c3ca8ca7aac94e71d6bc27ae626 100644 --- a/include/powermanager/PowerHalWrapper.h +++ b/include/powermanager/PowerHalWrapper.h @@ -19,6 +19,8 @@ #include #include +#include +#include #include #include #include @@ -142,8 +144,8 @@ public: // Wrapper for the HIDL Power HAL v1.0. class HidlHalWrapperV1_0 : public HalWrapper { public: - explicit HidlHalWrapperV1_0(sp Hal) - : mHandleV1_0(std::move(Hal)) {} + explicit HidlHalWrapperV1_0(sp handleV1_0) + : mHandleV1_0(std::move(handleV1_0)) {} virtual ~HidlHalWrapperV1_0() = default; virtual HalResult setBoost(hardware::power::Boost boost, int32_t durationMs) override; @@ -154,10 +156,10 @@ public: virtual HalResult getHintSessionPreferredRate() override; protected: - virtual HalResult sendPowerHint(hardware::power::V1_0::PowerHint hintId, uint32_t data); + const sp mHandleV1_0; + virtual HalResult sendPowerHint(hardware::power::V1_3::PowerHint hintId, uint32_t data); private: - sp mHandleV1_0; HalResult setInteractive(bool enabled); HalResult setFeature(hardware::power::V1_0::Feature feature, bool enabled); }; @@ -165,17 +167,40 @@ private: // Wrapper for the HIDL Power HAL v1.1. class HidlHalWrapperV1_1 : public HidlHalWrapperV1_0 { public: - HidlHalWrapperV1_1(sp handleV1_0, - sp handleV1_1) - : HidlHalWrapperV1_0(std::move(handleV1_0)), mHandleV1_1(std::move(handleV1_1)) {} + HidlHalWrapperV1_1(sp handleV1_1) + : HidlHalWrapperV1_0(std::move(handleV1_1)) {} virtual ~HidlHalWrapperV1_1() = default; protected: - virtual HalResult sendPowerHint(hardware::power::V1_0::PowerHint hintId, + virtual HalResult sendPowerHint(hardware::power::V1_3::PowerHint hintId, uint32_t data) override; +}; -private: - sp mHandleV1_1; +// Wrapper for the HIDL Power HAL v1.2. +class HidlHalWrapperV1_2 : public HidlHalWrapperV1_1 { +public: + virtual HalResult setBoost(hardware::power::Boost boost, int32_t durationMs) override; + virtual HalResult setMode(hardware::power::Mode mode, bool enabled) override; + HidlHalWrapperV1_2(sp handleV1_2) + : HidlHalWrapperV1_1(std::move(handleV1_2)) {} + virtual ~HidlHalWrapperV1_2() = default; + +protected: + virtual HalResult sendPowerHint(hardware::power::V1_3::PowerHint hintId, + uint32_t data) override; +}; + +// Wrapper for the HIDL Power HAL v1.3. +class HidlHalWrapperV1_3 : public HidlHalWrapperV1_2 { +public: + virtual HalResult setMode(hardware::power::Mode mode, bool enabled) override; + HidlHalWrapperV1_3(sp handleV1_3) + : HidlHalWrapperV1_2(std::move(handleV1_3)) {} + virtual ~HidlHalWrapperV1_3() = default; + +protected: + virtual HalResult sendPowerHint(hardware::power::V1_3::PowerHint hintId, + uint32_t data) override; }; // Wrapper for the AIDL Power HAL. diff --git a/include/private/performance_hint_private.h b/include/private/performance_hint_private.h index f27f5f150f032acf2a829dc1e425b4ba22ea8889..d50c5f846e943008f902cafb3a0c9f18e2269f19 100644 --- a/include/private/performance_hint_private.h +++ b/include/private/performance_hint_private.h @@ -17,6 +17,8 @@ #ifndef ANDROID_PRIVATE_NATIVE_PERFORMANCE_HINT_PRIVATE_H #define ANDROID_PRIVATE_NATIVE_PERFORMANCE_HINT_PRIVATE_H +#include + __BEGIN_DECLS /** @@ -24,6 +26,51 @@ __BEGIN_DECLS */ void APerformanceHint_setIHintManagerForTesting(void* iManager); +/** + * Hints for the session used to signal upcoming changes in the mode or workload. + */ +enum SessionHint: int32_t { + /** + * This hint indicates a sudden increase in CPU workload intensity. It means + * that this hint session needs extra CPU resources immediately to meet the + * target duration for the current work cycle. + */ + CPU_LOAD_UP = 0, + /** + * This hint indicates a decrease in CPU workload intensity. It means that + * this hint session can reduce CPU resources and still meet the target duration. + */ + CPU_LOAD_DOWN = 1, + /* + * This hint indicates an upcoming CPU workload that is completely changed and + * unknown. It means that the hint session should reset CPU resources to a known + * baseline to prepare for an arbitrary load, and must wake up if inactive. + */ + CPU_LOAD_RESET = 2, + /* + * This hint indicates that the most recent CPU workload is resuming after a + * period of inactivity. It means that the hint session should allocate similar + * CPU resources to what was used previously, and must wake up if inactive. + */ + CPU_LOAD_RESUME = 3, +}; + +/** + * Sends performance hints to inform the hint session of changes in the workload. + * + * @param session The performance hint session instance to update. + * @param hint The hint to send to the session. + * @return 0 on success + * EPIPE if communication with the system service has failed. + */ +int APerformanceHint_sendHint(void* session, SessionHint hint); + +/** + * Return the list of thread ids, this API should only be used for testing only. + */ +int APerformanceHint_getThreadIds(void* aPerformanceHintSession, + int32_t* const threadIds, size_t* const size); + __END_DECLS #endif // ANDROID_PRIVATE_NATIVE_PERFORMANCE_HINT_PRIVATE_H diff --git a/include/private/surface_control_private.h b/include/private/surface_control_private.h index 7e6c51587db2ed0447873a68ac9df1e08fe6174f..138926e55b2221739c9c67ec978ea32c8a443e9e 100644 --- a/include/private/surface_control_private.h +++ b/include/private/surface_control_private.h @@ -19,6 +19,8 @@ #include +#include + __BEGIN_DECLS struct ASurfaceControl; @@ -55,6 +57,13 @@ void ASurfaceControl_registerSurfaceStatsListener(ASurfaceControl* control, int3 void ASurfaceControl_unregisterSurfaceStatsListener(void* context, ASurfaceControl_SurfaceStatsListener func); +/** + * Gets the attached AChoreographer instance from the given \c surfaceControl. If there is no + * choreographer associated with the surface control, then a new instance of choreographer is + * created. The new choreographer is associated with the current thread's Looper. + */ +AChoreographer* ASurfaceControl_getChoreographer(ASurfaceControl* surfaceControl); + /** * Returns the timestamp of when the buffer was acquired for a specific frame with frame number * obtained from ASurfaceControlStats_getFrameNumber. diff --git a/libs/attestation/Android.bp b/libs/attestation/Android.bp index 2bf15d45eb3eede2561192709024300841393aab..fddecc0ceb4a2cb3eb171a276b5763867945284b 100644 --- a/libs/attestation/Android.bp +++ b/libs/attestation/Android.bp @@ -22,6 +22,7 @@ package { cc_library_static { name: "libattestation", + host_supported: true, cflags: [ "-Wall", "-Wextra", diff --git a/libs/binder/ActivityManager.cpp b/libs/binder/ActivityManager.cpp index e45a656d29f829746863e7353441873a6dceb43c..aca5009148a771f20e5382cf65773d73b609e1ec 100644 --- a/libs/binder/ActivityManager.cpp +++ b/libs/binder/ActivityManager.cpp @@ -75,6 +75,20 @@ status_t ActivityManager::registerUidObserver(const sp& observer, return DEAD_OBJECT; } +status_t ActivityManager::registerUidObserverForUids(const sp& observer, + const int32_t event, const int32_t cutpoint, + const String16& callingPackage, + const int32_t uids[], size_t nUids, + /*out*/ sp& observerToken) { + sp service = getService(); + if (service != nullptr) { + return service->registerUidObserverForUids(observer, event, cutpoint, callingPackage, uids, + nUids, observerToken); + } + // ActivityManagerService appears dead. Return usual error code for dead service. + return DEAD_OBJECT; +} + status_t ActivityManager::unregisterUidObserver(const sp& observer) { sp service = getService(); @@ -85,6 +99,26 @@ status_t ActivityManager::unregisterUidObserver(const sp& observer return DEAD_OBJECT; } +status_t ActivityManager::addUidToObserver(const sp& observerToken, + const String16& callingPackage, int32_t uid) { + sp service = getService(); + if (service != nullptr) { + return service->addUidToObserver(observerToken, callingPackage, uid); + } + // ActivityManagerService appears dead. Return usual error code for dead service. + return DEAD_OBJECT; +} + +status_t ActivityManager::removeUidFromObserver(const sp& observerToken, + const String16& callingPackage, int32_t uid) { + sp service = getService(); + if (service != nullptr) { + return service->removeUidFromObserver(observerToken, callingPackage, uid); + } + // ActivityManagerService appears dead. Return usual error code for dead service. + return DEAD_OBJECT; +} + bool ActivityManager::isUidActive(const uid_t uid, const String16& callingPackage) { sp service = getService(); diff --git a/libs/binder/Android.bp b/libs/binder/Android.bp index f634c1dcde98513070d4fb394a8e93b026c5e9a9..6c2b313f8a91fd3bde421bb25eef51dc5ac22e64 100644 --- a/libs/binder/Android.bp +++ b/libs/binder/Android.bp @@ -342,10 +342,6 @@ cc_library { }, afdo: true, - - header_abi_checker: { - diff_flags: ["-allow-adding-removing-weak-symbols"], - }, } cc_library_static { @@ -486,9 +482,7 @@ aidl_interface { local_include_dir: "aidl", host_supported: true, srcs: [ - "aidl/android/content/pm/IPackageChangeObserver.aidl", "aidl/android/content/pm/IPackageManagerNative.aidl", - "aidl/android/content/pm/PackageChangeEvent.aidl", "aidl/android/content/pm/IStagedApexObserver.aidl", "aidl/android/content/pm/ApexStagedEvent.aidl", "aidl/android/content/pm/StagedApexInfo.aidl", diff --git a/libs/binder/IActivityManager.cpp b/libs/binder/IActivityManager.cpp index 5ec4e8b25a42b215598fa972ea36dbd87813dab9..28975618e11ea1c826d2f2cd99264c44d65b29ba 100644 --- a/libs/binder/IActivityManager.cpp +++ b/libs/binder/IActivityManager.cpp @@ -77,6 +77,30 @@ public: return OK; } + virtual status_t registerUidObserverForUids(const sp& observer, + const int32_t event, const int32_t cutpoint, + const String16& callingPackage, + const int32_t uids[], size_t nUids, + /*out*/ sp& observerToken) { + Parcel data, reply; + data.writeInterfaceToken(IActivityManager::getInterfaceDescriptor()); + data.writeStrongBinder(IInterface::asBinder(observer)); + data.writeInt32(event); + data.writeInt32(cutpoint); + data.writeString16(callingPackage); + data.writeInt32Array(nUids, uids); + status_t err = + remote()->transact(REGISTER_UID_OBSERVER_FOR_UIDS_TRANSACTION, data, &reply); + if (err != NO_ERROR || ((err = reply.readExceptionCode()) != NO_ERROR)) { + return err; + } + err = reply.readStrongBinder(&observerToken); + if (err != NO_ERROR || ((err = reply.readExceptionCode()) != NO_ERROR)) { + return err; + } + return OK; + } + virtual status_t unregisterUidObserver(const sp& observer) { Parcel data, reply; @@ -89,6 +113,34 @@ public: return OK; } + virtual status_t addUidToObserver(const sp& observerToken, + const String16& callingPackage, int32_t uid) { + Parcel data, reply; + data.writeInterfaceToken(IActivityManager::getInterfaceDescriptor()); + data.writeStrongBinder(observerToken); + data.writeString16(callingPackage); + data.writeInt32(uid); + status_t err = remote()->transact(ADD_UID_TO_OBSERVER_TRANSACTION, data, &reply); + if (err != NO_ERROR || ((err = reply.readExceptionCode()) != NO_ERROR)) { + return err; + } + return OK; + } + + virtual status_t removeUidFromObserver(const sp& observerToken, + const String16& callingPackage, int32_t uid) { + Parcel data, reply; + data.writeInterfaceToken(IActivityManager::getInterfaceDescriptor()); + data.writeStrongBinder(observerToken); + data.writeString16(callingPackage); + data.writeInt32(uid); + status_t err = remote()->transact(REMOVE_UID_FROM_OBSERVER_TRANSACTION, data, &reply); + if (err != NO_ERROR || ((err = reply.readExceptionCode()) != NO_ERROR)) { + return err; + } + return OK; + } + virtual bool isUidActive(const uid_t uid, const String16& callingPackage) { Parcel data, reply; @@ -131,6 +183,56 @@ public: *outResult = reply.readInt32(); return NO_ERROR; } + + virtual status_t logFgsApiBegin(int32_t apiType, int32_t appUid, int32_t appPid) { + Parcel data, reply; + data.writeInterfaceToken(IActivityManager::getInterfaceDescriptor()); + data.writeInt32(apiType); + data.writeInt32(appUid); + data.writeInt32(appPid); + status_t err = remote()->transact(LOG_FGS_API_BEGIN_TRANSACTION, data, &reply, + IBinder::FLAG_ONEWAY); + if (err != NO_ERROR || ((err = reply.readExceptionCode()) != NO_ERROR)) { + ALOGD("FGS Logger Transaction failed"); + ALOGD("%d", err); + return err; + } + return NO_ERROR; + } + + virtual status_t logFgsApiEnd(int32_t apiType, int32_t appUid, int32_t appPid) { + Parcel data, reply; + data.writeInterfaceToken(IActivityManager::getInterfaceDescriptor()); + data.writeInt32(apiType); + data.writeInt32(appUid); + data.writeInt32(appPid); + status_t err = + remote()->transact(LOG_FGS_API_END_TRANSACTION, data, &reply, IBinder::FLAG_ONEWAY); + if (err != NO_ERROR || ((err = reply.readExceptionCode()) != NO_ERROR)) { + ALOGD("FGS Logger Transaction failed"); + ALOGD("%d", err); + return err; + } + return NO_ERROR; + } + + virtual status_t logFgsApiStateChanged(int32_t apiType, int32_t state, int32_t appUid, + int32_t appPid) { + Parcel data, reply; + data.writeInterfaceToken(IActivityManager::getInterfaceDescriptor()); + data.writeInt32(apiType); + data.writeInt32(state); + data.writeInt32(appUid); + data.writeInt32(appPid); + status_t err = remote()->transact(LOG_FGS_API_BEGIN_TRANSACTION, data, &reply, + IBinder::FLAG_ONEWAY); + if (err != NO_ERROR || ((err = reply.readExceptionCode()) != NO_ERROR)) { + ALOGD("FGS Logger Transaction failed"); + ALOGD("%d", err); + return err; + } + return NO_ERROR; + } }; // ------------------------------------------------------------------------------------ diff --git a/libs/binder/IBatteryStats.cpp b/libs/binder/IBatteryStats.cpp index 0de804c3c2bb7a65f9c9e975c3cb727b09922cdd..69b11c0ee926838ea98a992c8d0a114ef45ec846 100644 --- a/libs/binder/IBatteryStats.cpp +++ b/libs/binder/IBatteryStats.cpp @@ -128,6 +128,15 @@ public: remote()->transact(NOTE_RESET_FLASHLIGHT_TRANSACTION, data, &reply); } + virtual binder::Status noteWakeupSensorEvent(int64_t elapsedNanos, int uid, int handle) { + Parcel data, reply; + data.writeInterfaceToken(IBatteryStats::getInterfaceDescriptor()); + data.writeInt64(elapsedNanos); + data.writeInt32(uid); + data.writeInt32(handle); + status_t ret = remote()->transact(NOTE_WAKEUP_SENSOR_EVENT_TRANSACTION, data, &reply); + return binder::Status::fromStatusT(ret); + } }; IMPLEMENT_META_INTERFACE(BatteryStats, "com.android.internal.app.IBatteryStats") @@ -235,6 +244,16 @@ status_t BnBatteryStats::onTransact( reply->writeNoException(); return NO_ERROR; } break; + case NOTE_WAKEUP_SENSOR_EVENT_TRANSACTION: { + CHECK_INTERFACE(IBatteryStats, data, reply); + int64_t elapsedNanos = data.readInt64(); + int uid = data.readInt32(); + int handle = data.readInt32(); + noteWakeupSensorEvent(elapsedNanos, uid, handle); + reply->writeNoException(); + return NO_ERROR; + } break; + default: return BBinder::onTransact(code, data, reply, flags); } diff --git a/libs/binder/IUidObserver.cpp b/libs/binder/IUidObserver.cpp index d952dc71f928c3fc41b2cef7e6e8b64e0957c026..1c35f5359b6bcff30c85a597cbbc024bf5486861 100644 --- a/libs/binder/IUidObserver.cpp +++ b/libs/binder/IUidObserver.cpp @@ -67,9 +67,10 @@ public: remote()->transact(ON_UID_STATE_CHANGED_TRANSACTION, data, &reply, IBinder::FLAG_ONEWAY); } - virtual void onUidProcAdjChanged(uid_t uid) { + virtual void onUidProcAdjChanged(uid_t uid, int32_t adj) { Parcel data, reply; data.writeInt32((int32_t)uid); + data.writeInt32((int32_t)adj); remote()->transact(ON_UID_PROC_ADJ_CHANGED_TRANSACTION, data, &reply, IBinder::FLAG_ONEWAY); } }; @@ -121,7 +122,8 @@ status_t BnUidObserver::onTransact( case ON_UID_PROC_ADJ_CHANGED_TRANSACTION: { CHECK_INTERFACE(IUidObserver, data, reply); uid_t uid = data.readInt32(); - onUidProcAdjChanged(uid); + int32_t adj = data.readInt32(); + onUidProcAdjChanged(uid, adj); return NO_ERROR; } break; diff --git a/libs/binder/aidl/android/content/pm/IPackageManagerNative.aidl b/libs/binder/aidl/android/content/pm/IPackageManagerNative.aidl index 7c99f76ec6d819d0f2088e799934e27edf272bcf..f8a88433098e6001ee3455e39689f2141d8e8fc0 100644 --- a/libs/binder/aidl/android/content/pm/IPackageManagerNative.aidl +++ b/libs/binder/aidl/android/content/pm/IPackageManagerNative.aidl @@ -17,7 +17,6 @@ package android.content.pm; -import android.content.pm.IPackageChangeObserver; import android.content.pm.IStagedApexObserver; import android.content.pm.StagedApexInfo; @@ -92,18 +91,6 @@ interface IPackageManagerNative { */ @utf8InCpp String getModuleMetadataPackageName(); - /* Returns the names of all packages. */ - @utf8InCpp String[] getAllPackages(); - - /** Register an extra package change observer to receive the multi-cast. */ - void registerPackageChangeObserver(in IPackageChangeObserver observer); - - /** - * Unregister an existing package change observer. - * This does nothing if this observer was not already registered. - */ - void unregisterPackageChangeObserver(in IPackageChangeObserver observer); - /** * Returns true if the package has the SHA 256 version of the signing certificate. * @see PackageManager#hasSigningCertificate(String, byte[], int), where type diff --git a/libs/binder/include/binder/IInterface.h b/libs/binder/include/binder/IInterface.h index 9f7e2c87b8776ae1d60bfc7d1654172d8a195a7a..dc572ac953470e1aeedefb8c82f50d4c42516e59 100644 --- a/libs/binder/include/binder/IInterface.h +++ b/libs/binder/include/binder/IInterface.h @@ -219,80 +219,79 @@ inline IBinder* BpInterface::onAsBinder() namespace internal { constexpr const char* const kManualInterfaces[] = { - "android.app.IActivityManager", - "android.app.IUidObserver", - "android.drm.IDrm", - "android.dvr.IVsyncCallback", - "android.dvr.IVsyncService", - "android.gfx.tests.ICallback", - "android.gfx.tests.IIPCTest", - "android.gfx.tests.ISafeInterfaceTest", - "android.graphicsenv.IGpuService", - "android.gui.IConsumerListener", - "android.gui.IGraphicBufferConsumer", - "android.gui.ITransactionComposerListener", - "android.gui.SensorEventConnection", - "android.gui.SensorServer", - "android.hardware.ICamera", - "android.hardware.ICameraClient", - "android.hardware.ICameraRecordingProxy", - "android.hardware.ICameraRecordingProxyListener", - "android.hardware.ICrypto", - "android.hardware.IOMXObserver", - "android.hardware.IStreamListener", - "android.hardware.IStreamSource", - "android.media.IAudioService", - "android.media.IDataSource", - "android.media.IDrmClient", - "android.media.IMediaCodecList", - "android.media.IMediaDrmService", - "android.media.IMediaExtractor", - "android.media.IMediaExtractorService", - "android.media.IMediaHTTPConnection", - "android.media.IMediaHTTPService", - "android.media.IMediaLogService", - "android.media.IMediaMetadataRetriever", - "android.media.IMediaMetricsService", - "android.media.IMediaPlayer", - "android.media.IMediaPlayerClient", - "android.media.IMediaPlayerService", - "android.media.IMediaRecorder", - "android.media.IMediaRecorderClient", - "android.media.IMediaResourceMonitor", - "android.media.IMediaSource", - "android.media.IRemoteDisplay", - "android.media.IRemoteDisplayClient", - "android.media.IResourceManagerClient", - "android.media.IResourceManagerService", - "android.os.IComplexTypeInterface", - "android.os.IPermissionController", - "android.os.IPingResponder", - "android.os.IProcessInfoService", - "android.os.ISchedulingPolicyService", - "android.os.IStringConstants", - "android.os.storage.IObbActionListener", - "android.os.storage.IStorageEventListener", - "android.os.storage.IStorageManager", - "android.os.storage.IStorageShutdownObserver", - "android.service.vr.IPersistentVrStateCallbacks", - "android.service.vr.IVrManager", - "android.service.vr.IVrStateCallbacks", - "android.ui.ISurfaceComposer", - "android.ui.ISurfaceComposerClient", - "android.utils.IMemory", - "android.utils.IMemoryHeap", - "com.android.car.procfsinspector.IProcfsInspector", - "com.android.internal.app.IAppOpsCallback", - "com.android.internal.app.IAppOpsService", - "com.android.internal.app.IBatteryStats", - "com.android.internal.os.IResultReceiver", - "com.android.internal.os.IShellCallback", - "drm.IDrmManagerService", - "drm.IDrmServiceListener", - "IAAudioClient", - "IAAudioService", - "VtsFuzzer", - nullptr, + "android.app.IActivityManager", + "android.app.IUidObserver", + "android.drm.IDrm", + "android.dvr.IVsyncCallback", + "android.dvr.IVsyncService", + "android.gfx.tests.ICallback", + "android.gfx.tests.IIPCTest", + "android.gfx.tests.ISafeInterfaceTest", + "android.graphicsenv.IGpuService", + "android.gui.IConsumerListener", + "android.gui.IGraphicBufferConsumer", + "android.gui.ITransactionComposerListener", + "android.gui.SensorEventConnection", + "android.gui.SensorServer", + "android.hardware.ICamera", + "android.hardware.ICameraClient", + "android.hardware.ICameraRecordingProxy", + "android.hardware.ICameraRecordingProxyListener", + "android.hardware.ICrypto", + "android.hardware.IOMXObserver", + "android.hardware.IStreamListener", + "android.hardware.IStreamSource", + "android.media.IAudioService", + "android.media.IDataSource", + "android.media.IDrmClient", + "android.media.IMediaCodecList", + "android.media.IMediaDrmService", + "android.media.IMediaExtractor", + "android.media.IMediaExtractorService", + "android.media.IMediaHTTPConnection", + "android.media.IMediaHTTPService", + "android.media.IMediaLogService", + "android.media.IMediaMetadataRetriever", + "android.media.IMediaMetricsService", + "android.media.IMediaPlayer", + "android.media.IMediaPlayerClient", + "android.media.IMediaPlayerService", + "android.media.IMediaRecorder", + "android.media.IMediaRecorderClient", + "android.media.IMediaResourceMonitor", + "android.media.IMediaSource", + "android.media.IRemoteDisplay", + "android.media.IRemoteDisplayClient", + "android.media.IResourceManagerClient", + "android.media.IResourceManagerService", + "android.os.IComplexTypeInterface", + "android.os.IPermissionController", + "android.os.IPingResponder", + "android.os.IProcessInfoService", + "android.os.ISchedulingPolicyService", + "android.os.IStringConstants", + "android.os.storage.IObbActionListener", + "android.os.storage.IStorageEventListener", + "android.os.storage.IStorageManager", + "android.os.storage.IStorageShutdownObserver", + "android.service.vr.IPersistentVrStateCallbacks", + "android.service.vr.IVrManager", + "android.service.vr.IVrStateCallbacks", + "android.ui.ISurfaceComposer", + "android.utils.IMemory", + "android.utils.IMemoryHeap", + "com.android.car.procfsinspector.IProcfsInspector", + "com.android.internal.app.IAppOpsCallback", + "com.android.internal.app.IAppOpsService", + "com.android.internal.app.IBatteryStats", + "com.android.internal.os.IResultReceiver", + "com.android.internal.os.IShellCallback", + "drm.IDrmManagerService", + "drm.IDrmServiceListener", + "IAAudioClient", + "IAAudioService", + "VtsFuzzer", + nullptr, }; constexpr const char* const kDownstreamManualInterfaces[] = { diff --git a/libs/binder/include_activitymanager/binder/ActivityManager.h b/libs/binder/include_activitymanager/binder/ActivityManager.h index 5dfbd44dc4c5ca96804f1be39a18c085f8e57f2b..9c634c72387bb0b967b27ff11e5172bdf060f44f 100644 --- a/libs/binder/include_activitymanager/binder/ActivityManager.h +++ b/libs/binder/include_activitymanager/binder/ActivityManager.h @@ -82,7 +82,15 @@ public: const int32_t event, const int32_t cutpoint, const String16& callingPackage); + status_t registerUidObserverForUids(const sp& observer, const int32_t event, + const int32_t cutpoint, const String16& callingPackage, + const int32_t uids[], size_t nUids, + /*out*/ sp& observerToken); status_t unregisterUidObserver(const sp& observer); + status_t addUidToObserver(const sp& observerToken, const String16& callingPackage, + int32_t uid); + status_t removeUidFromObserver(const sp& observerToken, const String16& callingPackage, + int32_t uid); bool isUidActive(const uid_t uid, const String16& callingPackage); int getUidProcessState(const uid_t uid, const String16& callingPackage); status_t checkPermission(const String16& permission, const pid_t pid, const uid_t uid, int32_t* outResult); diff --git a/libs/binder/include_activitymanager/binder/IActivityManager.h b/libs/binder/include_activitymanager/binder/IActivityManager.h index 4632b2eb0f3a070128778a76bd10803402b0af11..07450c6af92a2726370f7bc7260bf8402beeeacb 100644 --- a/libs/binder/include_activitymanager/binder/IActivityManager.h +++ b/libs/binder/include_activitymanager/binder/IActivityManager.h @@ -35,21 +35,40 @@ public: const int32_t event, const int32_t cutpoint, const String16& callingPackage) = 0; + virtual status_t registerUidObserverForUids(const sp& observer, + const int32_t event, const int32_t cutpoint, + const String16& callingPackage, + const int32_t uids[], size_t nUids, + /*out*/ sp& observerToken) = 0; virtual status_t unregisterUidObserver(const sp& observer) = 0; + virtual status_t addUidToObserver(const sp& observerToken, + const String16& callingPackage, int32_t uid) = 0; + virtual status_t removeUidFromObserver(const sp& observerToken, + const String16& callingPackage, int32_t uid) = 0; virtual bool isUidActive(const uid_t uid, const String16& callingPackage) = 0; virtual int32_t getUidProcessState(const uid_t uid, const String16& callingPackage) = 0; virtual status_t checkPermission(const String16& permission, const pid_t pid, const uid_t uid, int32_t* outResult) = 0; + virtual status_t logFgsApiBegin(int32_t apiType, int32_t appUid, int32_t appPid) = 0; + virtual status_t logFgsApiEnd(int32_t apiType, int32_t appUid, int32_t appPid) = 0; + virtual status_t logFgsApiStateChanged(int32_t apiType, int32_t state, int32_t appUid, + int32_t appPid) = 0; enum { OPEN_CONTENT_URI_TRANSACTION = IBinder::FIRST_CALL_TRANSACTION, REGISTER_UID_OBSERVER_TRANSACTION, UNREGISTER_UID_OBSERVER_TRANSACTION, + REGISTER_UID_OBSERVER_FOR_UIDS_TRANSACTION, + ADD_UID_TO_OBSERVER_TRANSACTION, + REMOVE_UID_FROM_OBSERVER_TRANSACTION, IS_UID_ACTIVE_TRANSACTION, GET_UID_PROCESS_STATE_TRANSACTION, CHECK_PERMISSION_TRANSACTION, + LOG_FGS_API_BEGIN_TRANSACTION, + LOG_FGS_API_END_TRANSACTION, + LOG_FGS_API_STATE_CHANGED_TRANSACTION }; }; diff --git a/libs/binder/include_activitymanager/binder/IUidObserver.h b/libs/binder/include_activitymanager/binder/IUidObserver.h index 17f03a92017bbb2b359fd8b5fe0bbc6fc64e48af..5ea7447ef285e5ed864e07902f4d89ea607d2129 100644 --- a/libs/binder/include_activitymanager/binder/IUidObserver.h +++ b/libs/binder/include_activitymanager/binder/IUidObserver.h @@ -34,7 +34,7 @@ public: virtual void onUidIdle(uid_t uid, bool disabled) = 0; virtual void onUidStateChanged(uid_t uid, int32_t procState, int64_t procStateSeq, int32_t capability) = 0; - virtual void onUidProcAdjChanged(uid_t uid) = 0; + virtual void onUidProcAdjChanged(uid_t uid, int32_t adj) = 0; enum { ON_UID_GONE_TRANSACTION = IBinder::FIRST_CALL_TRANSACTION, diff --git a/libs/binder/include_batterystats/batterystats/IBatteryStats.h b/libs/binder/include_batterystats/batterystats/IBatteryStats.h index 6defc7fb0b3739575a8f65eaed38fdd596d70de1..5bb01dd86ba775cf73842328f84c331faf5d459c 100644 --- a/libs/binder/include_batterystats/batterystats/IBatteryStats.h +++ b/libs/binder/include_batterystats/batterystats/IBatteryStats.h @@ -19,6 +19,7 @@ #ifndef __ANDROID_VNDK__ #include +#include namespace android { @@ -43,6 +44,7 @@ public: virtual void noteStopCamera(int uid) = 0; virtual void noteResetCamera() = 0; virtual void noteResetFlashlight() = 0; + virtual binder::Status noteWakeupSensorEvent(int64_t elapsedNanos, int uid, int sensor) = 0; enum { NOTE_START_SENSOR_TRANSACTION = IBinder::FIRST_CALL_TRANSACTION, @@ -58,7 +60,8 @@ public: NOTE_START_CAMERA_TRANSACTION, NOTE_STOP_CAMERA_TRANSACTION, NOTE_RESET_CAMERA_TRANSACTION, - NOTE_RESET_FLASHLIGHT_TRANSACTION + NOTE_RESET_FLASHLIGHT_TRANSACTION, + NOTE_WAKEUP_SENSOR_EVENT_TRANSACTION }; }; diff --git a/libs/bufferqueueconverter/Android.bp b/libs/bufferqueueconverter/Android.bp index c5d3a3207c93264ccc433703f18761882fe76ef2..d4605ea13a32821ab4947af31278b9e282cb37f8 100644 --- a/libs/bufferqueueconverter/Android.bp +++ b/libs/bufferqueueconverter/Android.bp @@ -13,7 +13,7 @@ cc_library_headers { export_include_dirs: ["include"], } -cc_library_shared { +cc_library { name: "libbufferqueueconverter", vendor_available: true, vndk: { @@ -22,6 +22,7 @@ cc_library_shared { double_loadable: true, srcs: [ + ":libgui_frame_event_aidl", "BufferQueueConverter.cpp", ], diff --git a/libs/dumputils/dump_utils.cpp b/libs/dumputils/dump_utils.cpp index 97cb810404b62ed699c9e42255f1238d9ab109d3..5eb33087a2c8e99fe0d07c8b0a7cd5580812351a 100644 --- a/libs/dumputils/dump_utils.cpp +++ b/libs/dumputils/dump_utils.cpp @@ -62,7 +62,10 @@ static const char* hidl_hal_interfaces_to_dump[] { "android.hardware.audio@7.0::IDevicesFactory", "android.hardware.automotive.audiocontrol@1.0::IAudioControl", "android.hardware.automotive.audiocontrol@2.0::IAudioControl", + "android.hardware.automotive.can@1.0::ICanBus", + "android.hardware.automotive.can@1.0::ICanController", "android.hardware.automotive.evs@1.0::IEvsCamera", + "android.hardware.automotive.sv@1.0::ISurroundViewService", "android.hardware.automotive.vehicle@2.0::IVehicle", "android.hardware.biometrics.face@1.0::IBiometricsFace", "android.hardware.biometrics.fingerprint@2.1::IBiometricsFingerprint", @@ -87,7 +90,12 @@ static const char* hidl_hal_interfaces_to_dump[] { /* list of hal interface to dump containing process during native dumps */ static const std::vector aidl_interfaces_to_dump { "android.hardware.automotive.audiocontrol.IAudioControl", + "android.hardware.automotive.can.ICanController", "android.hardware.automotive.evs.IEvsEnumerator", + "android.hardware.automotive.ivn.IIvnAndroidDevice", + "android.hardware.automotive.occupant_awareness.IOccupantAwareness", + "android.hardware.automotive.remoteaccess.IRemoteAccess", + "android.hardware.automotive.vehicle.IVehicle", "android.hardware.biometrics.face.IBiometricsFace", "android.hardware.biometrics.fingerprint.IBiometricsFingerprint", "android.hardware.camera.provider.ICameraProvider", diff --git a/libs/ftl/Android.bp b/libs/ftl/Android.bp index 09422d389202155b01b2839c026772e7006beb3d..ea1b5e4998d02550df5578e0ffe4f5cec434eeca 100644 --- a/libs/ftl/Android.bp +++ b/libs/ftl/Android.bp @@ -11,12 +11,18 @@ cc_test { name: "ftl_test", test_suites: ["device-tests"], srcs: [ + "algorithm_test.cpp", "cast_test.cpp", "concat_test.cpp", "enum_test.cpp", "fake_guard_test.cpp", "flags_test.cpp", "future_test.cpp", + "match_test.cpp", + "mixins_test.cpp", + "non_null_test.cpp", + "optional_test.cpp", + "shared_mutex_test.cpp", "small_map_test.cpp", "small_vector_test.cpp", "static_vector_test.cpp", diff --git a/libs/ftl/algorithm_test.cpp b/libs/ftl/algorithm_test.cpp new file mode 100644 index 0000000000000000000000000000000000000000..487b1b875950fc5b941d1cf8a16924c8f28c5617 --- /dev/null +++ b/libs/ftl/algorithm_test.cpp @@ -0,0 +1,66 @@ +/* + * 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::test { + +// Keep in sync with example usage in header file. +TEST(Algorithm, FindIf) { + using namespace std::string_view_literals; + + const ftl::StaticVector vector = {"upside"sv, "down"sv, "cake"sv}; + EXPECT_EQ(ftl::find_if(vector, [](const auto& str) { return str.front() == 'c'; }), "cake"sv); + + const ftl::SmallMap map = ftl::init::map>( + 12, "snow"sv, "cone"sv)(13, "tiramisu"sv)(14, "upside"sv, "down"sv, "cake"sv); + + using Map = decltype(map); + + EXPECT_EQ(14, ftl::find_if(map, [](const auto& pair) { + return pair.second.size() == 3; + }).transform(ftl::to_key)); + + const auto opt = ftl::find_if(map, [](const auto& pair) { + return pair.second.size() == 1; + }).transform(ftl::to_mapped_ref); + + ASSERT_TRUE(opt); + EXPECT_EQ(opt->get(), ftl::StaticVector("tiramisu"sv)); +} + +TEST(Algorithm, StaticRef) { + using namespace std::string_view_literals; + + const ftl::SmallMap map = ftl::init::map(13, "tiramisu"sv)(14, "upside-down cake"sv); + ASSERT_EQ("???"sv, + map.get(20).or_else(ftl::static_ref([] { return "???"sv; }))->get()); + + using Map = decltype(map); + + ASSERT_EQ("snow cone"sv, + ftl::find_if(map, [](const auto& pair) { return pair.second.front() == 's'; }) + .transform(ftl::to_mapped_ref) + .or_else(ftl::static_ref([] { return "snow cone"sv; })) + ->get()); +} + +} // namespace android::test diff --git a/libs/ftl/concat_test.cpp b/libs/ftl/concat_test.cpp index 8ecb1b252d681c7ec4509b6253f3b38ddfbe45d4..771f05478a2e4bbdc57df171a842aac657eecabe 100644 --- a/libs/ftl/concat_test.cpp +++ b/libs/ftl/concat_test.cpp @@ -28,8 +28,25 @@ TEST(Concat, Example) { EXPECT_EQ(string.c_str()[string.size()], '\0'); } +TEST(Concat, Characters) { + EXPECT_EQ(ftl::Concat(u'a', ' ', U'b').str(), "97 98"); +} + +TEST(Concat, References) { + int i[] = {-1, 2}; + unsigned u = 3; + EXPECT_EQ(ftl::Concat(i[0], std::as_const(i[1]), u).str(), "-123"); + + const bool b = false; + const char c = 'o'; + EXPECT_EQ(ftl::Concat(b, "tt", c).str(), "falsetto"); +} + namespace { +static_assert(ftl::Concat{true, false, true}.str() == "truefalsetrue"); +static_assert(ftl::Concat{':', '-', ')'}.str() == ":-)"); + static_assert(ftl::Concat{"foo"}.str() == "foo"); static_assert(ftl::Concat{ftl::truncated<3>("foobar")}.str() == "foo"); diff --git a/libs/ftl/flags_test.cpp b/libs/ftl/flags_test.cpp index eea052ba33e8eb8e25c833da5daa87e0ab032390..1279d1147da62b81ebeecadb65315e8dadf15791 100644 --- a/libs/ftl/flags_test.cpp +++ b/libs/ftl/flags_test.cpp @@ -35,6 +35,7 @@ TEST(Flags, Test) { TEST(Flags, Any) { Flags flags = TestFlags::ONE | TestFlags::TWO; + ASSERT_TRUE(flags.any()); ASSERT_TRUE(flags.any(TestFlags::ONE)); ASSERT_TRUE(flags.any(TestFlags::TWO)); ASSERT_FALSE(flags.any(TestFlags::THREE)); @@ -42,6 +43,9 @@ TEST(Flags, Any) { ASSERT_TRUE(flags.any(TestFlags::TWO | TestFlags::THREE)); ASSERT_TRUE(flags.any(TestFlags::ONE | TestFlags::THREE)); ASSERT_TRUE(flags.any(TestFlags::ONE | TestFlags::TWO | TestFlags::THREE)); + + Flags emptyFlags; + ASSERT_FALSE(emptyFlags.any()); } TEST(Flags, All) { diff --git a/libs/ftl/match_test.cpp b/libs/ftl/match_test.cpp new file mode 100644 index 0000000000000000000000000000000000000000..a6cff2eed6adb73ac2abed2262e9a79a887dfd07 --- /dev/null +++ b/libs/ftl/match_test.cpp @@ -0,0 +1,48 @@ +/* + * 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::test { + +// Keep in sync with example usage in header file. +TEST(Match, Example) { + using namespace std::chrono; + using namespace std::chrono_literals; + using namespace std::string_literals; + + std::variant duration = 119min; + + // Mutable match. + ftl::match(duration, [](auto& d) { ++d; }); + + // Immutable match. Exhaustive due to minutes being convertible to seconds. + EXPECT_EQ("2 hours"s, + ftl::match( + duration, + [](const seconds& s) { + const auto h = duration_cast(s); + return std::to_string(h.count()) + " hours"s; + }, + [](const hours& h) { return std::to_string(h.count() / 24) + " days"s; })); +} + +} // namespace android::test diff --git a/libs/ftl/mixins_test.cpp b/libs/ftl/mixins_test.cpp new file mode 100644 index 0000000000000000000000000000000000000000..2c9f9dfd8a9a4356f7bfe3f9b5d9445b41d76487 --- /dev/null +++ b/libs/ftl/mixins_test.cpp @@ -0,0 +1,185 @@ +/* + * 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 + +namespace android::test { +namespace { + +// Keep in sync with example usage in header file. + +struct Id : ftl::Constructible, ftl::Equatable { + using Constructible::Constructible; +}; + +static_assert(!std::is_default_constructible_v); + +struct Color : ftl::DefaultConstructible, + ftl::Equatable, + ftl::Orderable { + using DefaultConstructible::DefaultConstructible; +}; + +static_assert(Color() == Color(0u)); +static_assert(ftl::to_underlying(Color(-1)) == 255u); +static_assert(Color(1u) < Color(2u)); + +struct Sequence : ftl::DefaultConstructible, + ftl::Equatable, + ftl::Orderable, + ftl::Incrementable { + using DefaultConstructible::DefaultConstructible; +}; + +static_assert(Sequence() == Sequence(-1)); + +struct Timeout : ftl::DefaultConstructible, + ftl::Equatable, + ftl::Addable { + using DefaultConstructible::DefaultConstructible; +}; + +using namespace std::chrono_literals; +static_assert(Timeout() + Timeout(5s) == Timeout(15s)); + +// Construction. +constexpr Id kId{1234}; +constexpr Sequence kSequence; + +// Underlying value. +static_assert(ftl::to_underlying(Id(-42)) == -42); +static_assert(ftl::to_underlying(kSequence) == -1); + +// Casting. +static_assert(static_cast(Id(-1)) == -1); +static_assert(static_cast(kSequence) == -1); + +static_assert(!std::is_convertible_v); +static_assert(!std::is_convertible_v); + +// Equality. +static_assert(kId == Id(1234)); +static_assert(kId != Id(123)); +static_assert(kSequence == Sequence(-1)); + +// Ordering. +static_assert(Sequence(1) < Sequence(2)); +static_assert(Sequence(2) > Sequence(1)); +static_assert(Sequence(3) <= Sequence(4)); +static_assert(Sequence(4) >= Sequence(3)); +static_assert(Sequence(5) <= Sequence(5)); +static_assert(Sequence(6) >= Sequence(6)); + +// Incrementing. +template +constexpr auto mutable_op(Op op, T lhs, Ts... rhs) { + const T result = op(lhs, rhs...); + return std::make_pair(lhs, result); +} + +static_assert(mutable_op([](auto& lhs) { return ++lhs; }, Sequence()) == + std::make_pair(Sequence(0), Sequence(0))); + +static_assert(mutable_op([](auto& lhs) { return lhs++; }, Sequence()) == + std::make_pair(Sequence(0), Sequence(-1))); + +// Addition. + +// `Addable` implies `Incrementable`. +static_assert(mutable_op([](auto& lhs) { return ++lhs; }, Timeout()) == + std::make_pair(Timeout(11s), Timeout(11s))); + +static_assert(mutable_op([](auto& lhs) { return lhs++; }, Timeout()) == + std::make_pair(Timeout(11s), Timeout(10s))); + +static_assert(Timeout(5s) + Timeout(6s) == Timeout(11s)); + +static_assert(mutable_op([](auto& lhs, const auto& rhs) { return lhs += rhs; }, Timeout(7s), + Timeout(8s)) == std::make_pair(Timeout(15s), Timeout(15s))); + +// Type safety. + +namespace traits { + +template +struct is_incrementable : std::false_type {}; + +template +struct is_incrementable())>> : std::true_type {}; + +template +constexpr bool is_incrementable_v = is_incrementable{}; + +template +struct has_binary_op : std::false_type {}; + +template +struct has_binary_op(), std::declval()))>> + : std::true_type {}; + +template +constexpr bool is_equatable_v = + has_binary_op, T, U>{} && has_binary_op, T, U>{}; + +template +constexpr bool is_orderable_v = + has_binary_op, T, U>{} && has_binary_op, T, U>{} && + has_binary_op, T, U>{} && has_binary_op, T, U>{}; + +template +constexpr bool is_addable_v = has_binary_op, T, U>{}; + +} // namespace traits + +struct Real : ftl::Constructible { + using Constructible::Constructible; +}; + +static_assert(traits::is_equatable_v); +static_assert(!traits::is_equatable_v); +static_assert(!traits::is_equatable_v); +static_assert(!traits::is_equatable_v); +static_assert(!traits::is_equatable_v); +static_assert(!traits::is_equatable_v); + +static_assert(traits::is_orderable_v); +static_assert(!traits::is_orderable_v); +static_assert(!traits::is_orderable_v); +static_assert(!traits::is_orderable_v); +static_assert(!traits::is_orderable_v); +static_assert(!traits::is_orderable_v); + +static_assert(traits::is_incrementable_v); +static_assert(traits::is_incrementable_v); +static_assert(!traits::is_incrementable_v); +static_assert(!traits::is_incrementable_v); +static_assert(!traits::is_incrementable_v); + +static_assert(traits::is_addable_v); +static_assert(!traits::is_addable_v); +static_assert(!traits::is_addable_v); +static_assert(!traits::is_addable_v); +static_assert(!traits::is_addable_v); +static_assert(!traits::is_addable_v); + +} // namespace +} // namespace android::test diff --git a/libs/ftl/non_null_test.cpp b/libs/ftl/non_null_test.cpp new file mode 100644 index 0000000000000000000000000000000000000000..bd0462b3b62f7399712d9c7880ff36ce61bd68a3 --- /dev/null +++ b/libs/ftl/non_null_test.cpp @@ -0,0 +1,75 @@ +/* + * 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::test { +namespace { + +void get_length(const ftl::NonNull>& string_ptr, + ftl::NonNull length_ptr) { + // No need for `nullptr` checks. + *length_ptr = string_ptr->length(); +} + +using Pair = std::pair>, std::shared_ptr>; + +Pair dupe_if(ftl::NonNull> non_null_ptr, bool condition) { + // Move the underlying pointer out, so `non_null_ptr` must not be accessed after this point. + auto unique_ptr = std::move(non_null_ptr).take(); + + auto non_null_shared_ptr = ftl::as_non_null(std::shared_ptr(std::move(unique_ptr))); + auto nullable_shared_ptr = condition ? non_null_shared_ptr.get() : nullptr; + + return {std::move(non_null_shared_ptr), std::move(nullable_shared_ptr)}; +} + +} // namespace + +// 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; + get_length(string_ptr, ftl::as_non_null(&size)); + EXPECT_EQ(size, 7u); + + auto ptr = ftl::as_non_null(std::make_unique(42)); + const auto [ptr1, ptr2] = dupe_if(std::move(ptr), true); + EXPECT_EQ(ptr1.get(), ptr2); +} + +namespace { + +constexpr std::string_view kApple = "apple"; +constexpr std::string_view kOrange = "orange"; + +using StringViewPtr = ftl::NonNull; +constexpr StringViewPtr kApplePtr = ftl::as_non_null(&kApple); +constexpr StringViewPtr kOrangePtr = ftl::as_non_null(&kOrange); + +constexpr StringViewPtr longest(StringViewPtr ptr1, StringViewPtr ptr2) { + return ptr1->length() > ptr2->length() ? ptr1 : ptr2; +} + +static_assert(longest(kApplePtr, kOrangePtr) == kOrangePtr); + +} // namespace +} // namespace android::test diff --git a/libs/ftl/optional_test.cpp b/libs/ftl/optional_test.cpp new file mode 100644 index 0000000000000000000000000000000000000000..91bf7bc5b6558b1f464e58c1bcfa39e571356eea --- /dev/null +++ b/libs/ftl/optional_test.cpp @@ -0,0 +1,238 @@ +/* + * 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 + +using namespace std::placeholders; +using namespace std::string_literals; + +namespace android::test { + +using ftl::Optional; +using ftl::StaticVector; + +TEST(Optional, Construct) { + // Empty. + EXPECT_EQ(std::nullopt, Optional()); + EXPECT_EQ(std::nullopt, Optional(std::nullopt)); + + // Value. + EXPECT_EQ('?', Optional('?')); + EXPECT_EQ(""s, Optional(std::string())); + + // In place. + EXPECT_EQ("???"s, Optional(std::in_place, 3u, '?')); + EXPECT_EQ("abc"s, Optional(std::in_place, {'a', 'b', 'c'})); + + // Implicit downcast. + { + Optional opt = std::optional("test"s); + static_assert(std::is_same_v>); + + ASSERT_TRUE(opt); + EXPECT_EQ(opt.value(), "test"s); + } +} + +TEST(Optional, Transform) { + // Empty. + EXPECT_EQ(std::nullopt, Optional().transform([](int) { return 0; })); + + // By value. + EXPECT_EQ(0, Optional(0).transform([](int x) { return x; })); + EXPECT_EQ(100, Optional(99).transform([](int x) { return x + 1; })); + EXPECT_EQ("0b100"s, Optional(4).transform(std::bind(ftl::to_string, _1, ftl::Radix::kBin))); + + // By reference. + { + Optional opt = 'x'; + EXPECT_EQ('z', opt.transform([](char& c) { + c = 'y'; + return 'z'; + })); + + EXPECT_EQ('y', opt); + } + + // By rvalue reference. + { + std::string out; + EXPECT_EQ("xyz"s, Optional("abc"s).transform([&out](std::string&& str) { + out = std::move(str); + return "xyz"s; + })); + + EXPECT_EQ(out, "abc"s); + } + + // No return value. + { + Optional opt = "food"s; + EXPECT_EQ(ftl::unit, opt.transform(ftl::unit_fn([](std::string& str) { str.pop_back(); }))); + EXPECT_EQ(opt, "foo"s); + } + + // Chaining. + EXPECT_EQ(14u, Optional(StaticVector{"upside"s, "down"s}) + .transform([](StaticVector&& v) { + v.push_back("cake"s); + return v; + }) + .transform([](const StaticVector& v) { + return std::accumulate(v.begin(), v.end(), std::string()); + }) + .transform([](const std::string& s) { return s.length(); })); +} + +namespace { + +Optional parse_int(const std::string& str) { + if (const int i = std::atoi(str.c_str())) return i; + return std::nullopt; +} + +} // namespace + +TEST(Optional, AndThen) { + // Empty. + EXPECT_EQ(std::nullopt, Optional().and_then([](int) -> Optional { return 0; })); + EXPECT_EQ(std::nullopt, Optional().and_then([](int) { return Optional(); })); + + // By value. + EXPECT_EQ(0, Optional(0).and_then([](int x) { return Optional(x); })); + EXPECT_EQ(123, Optional("123").and_then(parse_int)); + EXPECT_EQ(std::nullopt, Optional("abc").and_then(parse_int)); + + // By reference. + { + Optional opt = 'x'; + EXPECT_EQ('z', opt.and_then([](char& c) { + c = 'y'; + return Optional('z'); + })); + + EXPECT_EQ('y', opt); + } + + // By rvalue reference. + { + std::string out; + EXPECT_EQ("xyz"s, Optional("abc"s).and_then([&out](std::string&& str) { + out = std::move(str); + return Optional("xyz"s); + })); + + EXPECT_EQ(out, "abc"s); + } + + // Chaining. + using StringVector = StaticVector; + EXPECT_EQ(14u, Optional(StaticVector{"-"s, "1"s}) + .and_then([](StringVector&& v) -> Optional { + if (v.push_back("4"s)) return v; + return {}; + }) + .and_then([](const StringVector& v) -> Optional { + if (v.full()) return std::accumulate(v.begin(), v.end(), std::string()); + return {}; + }) + .and_then(parse_int) + .and_then([](int i) { + return i > 0 ? std::nullopt : std::make_optional(static_cast(-i)); + })); +} + +TEST(Optional, OrElse) { + // Non-empty. + { + const Optional opt = false; + EXPECT_EQ(false, opt.or_else([] { return Optional(true); })); + EXPECT_EQ('x', Optional('x').or_else([] { return std::make_optional('y'); })); + } + + // Empty. + { + const Optional opt; + EXPECT_EQ(123, opt.or_else([]() -> Optional { return 123; })); + EXPECT_EQ("abc"s, Optional().or_else([] { return Optional("abc"s); })); + } + { + bool empty = false; + EXPECT_EQ(Optional(), Optional().or_else([&empty]() -> Optional { + empty = true; + return std::nullopt; + })); + EXPECT_TRUE(empty); + } + + // Chaining. + using StringVector = StaticVector; + EXPECT_EQ(999, Optional(StaticVector{"1"s, "0"s, "0"s}) + .and_then([](StringVector&& v) -> Optional { + if (v.push_back("0"s)) return v; + return {}; + }) + .or_else([] { + return Optional(StaticVector{"9"s, "9"s, "9"s}); + }) + .transform([](const StringVector& v) { + return std::accumulate(v.begin(), v.end(), std::string()); + }) + .and_then(parse_int) + .or_else([] { return Optional(-1); })); +} + +// Comparison. +namespace { + +constexpr Optional kOptional1 = 1; +constexpr Optional kAnotherOptional1 = 1; +constexpr Optional kOptional2 = 2; +constexpr Optional kOptionalEmpty, kAnotherOptionalEmpty; + +constexpr std::optional kStdOptional1 = 1; + +static_assert(kOptional1 == kAnotherOptional1); + +static_assert(kOptional1 != kOptional2); +static_assert(kOptional2 != kOptional1); + +static_assert(kOptional1 != kOptionalEmpty); +static_assert(kOptionalEmpty != kOptional1); + +static_assert(kOptionalEmpty == kAnotherOptionalEmpty); + +static_assert(kOptional1 == kStdOptional1); +static_assert(kStdOptional1 == kOptional1); + +static_assert(kOptional2 != kStdOptional1); +static_assert(kStdOptional1 != kOptional2); + +static_assert(kOptional2 != kOptionalEmpty); +static_assert(kOptionalEmpty != kOptional2); + +} // namespace + +} // namespace android::test diff --git a/libs/ftl/shared_mutex_test.cpp b/libs/ftl/shared_mutex_test.cpp new file mode 100644 index 0000000000000000000000000000000000000000..6da7061ae0e28c4e704488cee9892b3d724ceb30 --- /dev/null +++ b/libs/ftl/shared_mutex_test.cpp @@ -0,0 +1,60 @@ +/* + * 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::test { + +TEST(SharedMutex, SharedLock) { + ftl::SharedMutex mutex; + std::shared_lock shared_lock(mutex); + + { std::shared_lock shared_lock2(mutex); } +} + +TEST(SharedMutex, ExclusiveLock) { + ftl::SharedMutex mutex; + std::unique_lock unique_lock(mutex); +} + +TEST(SharedMutex, Annotations) { + struct { + void foo() FTL_ATTRIBUTE(requires_shared_capability(mutex)) { num++; } + void bar() FTL_ATTRIBUTE(requires_capability(mutex)) { num++; } + void baz() { + std::shared_lock shared_lock(mutex); + num++; + } + ftl::SharedMutex mutex; + int num = 0; + + } s; + + { + // TODO(b/257958323): Use an RAII class instead of locking manually. + s.mutex.lock_shared(); + s.foo(); + s.baz(); + s.mutex.unlock_shared(); + } + s.mutex.lock(); + s.bar(); + s.mutex.unlock(); +} + +} // namespace android::test diff --git a/libs/ftl/small_map_test.cpp b/libs/ftl/small_map_test.cpp index 1740a2b54cf2ed61bff3f0d7747d311065bea730..634877f67207a4a1da60f4aed058b3d763cc363a 100644 --- a/libs/ftl/small_map_test.cpp +++ b/libs/ftl/small_map_test.cpp @@ -15,12 +15,15 @@ */ #include +#include #include #include #include +#include using namespace std::string_literals; +using namespace std::string_view_literals; namespace android::test { @@ -38,7 +41,7 @@ TEST(SmallMap, Example) { EXPECT_TRUE(map.contains(123)); - EXPECT_EQ(map.get(42, [](const std::string& s) { return s.size(); }), 3u); + EXPECT_EQ(map.get(42).transform([](const std::string& s) { return s.size(); }), 3u); const auto opt = map.get(-1); ASSERT_TRUE(opt); @@ -50,7 +53,7 @@ TEST(SmallMap, Example) { map.emplace_or_replace(0, "vanilla", 2u, 3u); EXPECT_TRUE(map.dynamic()); - EXPECT_EQ(map, SmallMap(ftl::init::map(-1, "xyz")(0, "nil")(42, "???")(123, "abc"))); + EXPECT_EQ(map, SmallMap(ftl::init::map(-1, "xyz"sv)(0, "nil"sv)(42, "???"sv)(123, "abc"sv))); } TEST(SmallMap, Construct) { @@ -70,7 +73,7 @@ TEST(SmallMap, Construct) { EXPECT_EQ(map.max_size(), 5u); EXPECT_FALSE(map.dynamic()); - EXPECT_EQ(map, SmallMap(ftl::init::map(123, "abc")(456, "def")(789, "ghi"))); + EXPECT_EQ(map, SmallMap(ftl::init::map(123, "abc"sv)(456, "def"sv)(789, "ghi"sv))); } { // In-place constructor with different types. @@ -81,7 +84,7 @@ TEST(SmallMap, Construct) { EXPECT_EQ(map.max_size(), 5u); EXPECT_FALSE(map.dynamic()); - EXPECT_EQ(map, SmallMap(ftl::init::map(42, "???")(123, "abc")(-1, "\0\0\0"))); + EXPECT_EQ(map, SmallMap(ftl::init::map(42, "???"sv)(123, "abc"sv)(-1, ""sv))); } { // In-place constructor with implicit size. @@ -92,7 +95,7 @@ TEST(SmallMap, Construct) { EXPECT_EQ(map.max_size(), 3u); EXPECT_FALSE(map.dynamic()); - EXPECT_EQ(map, SmallMap(ftl::init::map(-1, "\0\0\0")(42, "???")(123, "abc"))); + EXPECT_EQ(map, SmallMap(ftl::init::map(-1, ""sv)(42, "???"sv)(123, "abc"sv))); } } @@ -108,7 +111,7 @@ TEST(SmallMap, Assign) { { // Convertible types; same capacity. SmallMap map1 = ftl::init::map('M', "mega")('G', "giga"); - const SmallMap map2 = ftl::init::map('T', "tera")('P', "peta"); + const SmallMap map2 = ftl::init::map('T', "tera"sv)('P', "peta"sv); map1 = map2; EXPECT_EQ(map1, map2); @@ -147,7 +150,7 @@ TEST(SmallMap, UniqueKeys) { } } -TEST(SmallMap, Find) { +TEST(SmallMap, Get) { { // Constant reference. const SmallMap map = ftl::init::map('a', 'A')('b', 'B')('c', 'C'); @@ -172,14 +175,15 @@ TEST(SmallMap, Find) { EXPECT_EQ(d, 'D'); } { - // Constant unary operation. + // Immutable transform operation. const SmallMap map = ftl::init::map('a', 'x')('b', 'y')('c', 'z'); - EXPECT_EQ(map.get('c', [](char c) { return std::toupper(c); }), 'Z'); + EXPECT_EQ(map.get('c').transform([](char c) { return std::toupper(c); }), 'Z'); } { - // Mutable unary operation. + // Mutable transform operation. SmallMap map = ftl::init::map('a', 'x')('b', 'y')('c', 'z'); - EXPECT_TRUE(map.get('c', [](char& c) { c = std::toupper(c); })); + EXPECT_EQ(map.get('c').transform(ftl::unit_fn([](char& c) { c = std::toupper(c); })), + ftl::unit); EXPECT_EQ(map, SmallMap(ftl::init::map('c', 'Z')('b', 'y')('a', 'x'))); } @@ -247,7 +251,7 @@ TEST(SmallMap, TryReplace) { } { // Replacement arguments can refer to the replaced mapping. - const auto ref = map.get(2, [](const auto& s) { return s.str[0]; }); + const auto ref = map.get(2).transform([](const String& s) { return s.str[0]; }); ASSERT_TRUE(ref); // Construct std::string from one character. @@ -292,7 +296,7 @@ TEST(SmallMap, EmplaceOrReplace) { } { // Replacement arguments can refer to the replaced mapping. - const auto ref = map.get(2, [](const auto& s) { return s.str[0]; }); + const auto ref = map.get(2).transform([](const String& s) { return s.str[0]; }); ASSERT_TRUE(ref); // Construct std::string from one character. diff --git a/libs/gralloc/types/Android.bp b/libs/gralloc/types/Android.bp index f5af425fcafb3ae83e2ddaf05682fdca86f587c7..6d1dfe812435191849fdd77d1e756efda3fd76fe 100644 --- a/libs/gralloc/types/Android.bp +++ b/libs/gralloc/types/Android.bp @@ -23,6 +23,7 @@ package { cc_library { name: "libgralloctypes", + defaults: ["android.hardware.graphics.common-ndk_shared"], cflags: [ "-Wall", "-Werror", @@ -51,7 +52,6 @@ cc_library { ], shared_libs: [ - "android.hardware.graphics.common-V4-ndk", "android.hardware.graphics.mapper@4.0", "libhidlbase", "liblog", diff --git a/libs/graphicsenv/GpuStatsInfo.cpp b/libs/graphicsenv/GpuStatsInfo.cpp index 858739c9dd609c457ffc85958d97078ab4b66087..7b7421424dc49e5b0ff349c8a31605defee33bba 100644 --- a/libs/graphicsenv/GpuStatsInfo.cpp +++ b/libs/graphicsenv/GpuStatsInfo.cpp @@ -89,6 +89,14 @@ status_t GpuStatsAppInfo::writeToParcel(Parcel* parcel) const { if ((status = parcel->writeBool(falsePrerotation)) != OK) return status; if ((status = parcel->writeBool(gles1InUse)) != OK) return status; if ((status = parcel->writeBool(angleInUse)) != OK) return status; + if ((status = parcel->writeBool(createdGlesContext)) != OK) return status; + if ((status = parcel->writeBool(createdVulkanDevice)) != OK) return status; + if ((status = parcel->writeBool(createdVulkanSwapchain)) != OK) return status; + if ((status = parcel->writeUint32(vulkanApiVersion)) != OK) return status; + if ((status = parcel->writeUint64(vulkanDeviceFeaturesEnabled)) != OK) return status; + if ((status = parcel->writeInt32Vector(vulkanInstanceExtensions)) != OK) return status; + if ((status = parcel->writeInt32Vector(vulkanDeviceExtensions)) != OK) return status; + return OK; } @@ -103,6 +111,14 @@ status_t GpuStatsAppInfo::readFromParcel(const Parcel* parcel) { if ((status = parcel->readBool(&falsePrerotation)) != OK) return status; if ((status = parcel->readBool(&gles1InUse)) != OK) return status; if ((status = parcel->readBool(&angleInUse)) != OK) return status; + if ((status = parcel->readBool(&createdGlesContext)) != OK) return status; + if ((status = parcel->readBool(&createdVulkanDevice)) != OK) return status; + if ((status = parcel->readBool(&createdVulkanSwapchain)) != OK) return status; + if ((status = parcel->readUint32(&vulkanApiVersion)) != OK) return status; + if ((status = parcel->readUint64(&vulkanDeviceFeaturesEnabled)) != OK) return status; + if ((status = parcel->readInt32Vector(&vulkanInstanceExtensions)) != OK) return status; + if ((status = parcel->readInt32Vector(&vulkanDeviceExtensions)) != OK) return status; + return OK; } @@ -114,6 +130,12 @@ std::string GpuStatsAppInfo::toString() const { StringAppendF(&result, "falsePrerotation = %d\n", falsePrerotation); StringAppendF(&result, "gles1InUse = %d\n", gles1InUse); StringAppendF(&result, "angleInUse = %d\n", angleInUse); + StringAppendF(&result, "createdGlesContext = %d\n", createdGlesContext); + StringAppendF(&result, "createdVulkanDevice = %d\n", createdVulkanDevice); + StringAppendF(&result, "createdVulkanSwapchain = %d\n", createdVulkanSwapchain); + StringAppendF(&result, "vulkanApiVersion = 0x%" PRIx32 "\n", vulkanApiVersion); + StringAppendF(&result, "vulkanDeviceFeaturesEnabled = 0x%" PRIx64 "\n", + vulkanDeviceFeaturesEnabled); result.append("glDriverLoadingTime:"); for (int32_t loadingTime : glDriverLoadingTime) { StringAppendF(&result, " %d", loadingTime); @@ -129,6 +151,16 @@ std::string GpuStatsAppInfo::toString() const { StringAppendF(&result, " %d", loadingTime); } result.append("\n"); + result.append("vulkanInstanceExtensions:"); + for (int32_t extension : vulkanInstanceExtensions) { + StringAppendF(&result, " 0x%x", extension); + } + result.append("\n"); + result.append("vulkanDeviceExtensions:"); + for (int32_t extension : vulkanDeviceExtensions) { + StringAppendF(&result, " 0x%x", extension); + } + result.append("\n"); return result; } diff --git a/libs/graphicsenv/GraphicsEnv.cpp b/libs/graphicsenv/GraphicsEnv.cpp index 701a3b2f779005689194f030b42fff8ef4abc311..16315ed85854db18666ce50cf5328a59de95f889 100644 --- a/libs/graphicsenv/GraphicsEnv.cpp +++ b/libs/graphicsenv/GraphicsEnv.cpp @@ -277,6 +277,57 @@ void GraphicsEnv::setDriverLoaded(GpuStatsInfo::Api api, bool isDriverLoaded, sendGpuStatsLocked(api, isDriverLoaded, driverLoadingTime); } +// Hash function to calculate hash for null-terminated Vulkan extension names +// We store hash values of the extensions, rather than the actual names or +// indices to be able to support new extensions easily, avoid creating +// a table of 'known' extensions inside Android and reduce the runtime overhead. +static uint64_t calculateExtensionHash(const char* word) { + if (!word) { + return 0; + } + const size_t wordLen = strlen(word); + const uint32_t seed = 167; + uint64_t hash = 0; + for (size_t i = 0; i < wordLen; i++) { + hash = (hash * seed) + word[i]; + } + return hash; +} + +void GraphicsEnv::setVulkanInstanceExtensions(uint32_t enabledExtensionCount, + const char* const* ppEnabledExtensionNames) { + ATRACE_CALL(); + if (enabledExtensionCount == 0 || ppEnabledExtensionNames == nullptr) { + return; + } + + const uint32_t maxNumStats = android::GpuStatsAppInfo::MAX_NUM_EXTENSIONS; + uint64_t extensionHashes[maxNumStats]; + const uint32_t numStats = std::min(enabledExtensionCount, maxNumStats); + for(uint32_t i = 0; i < numStats; i++) { + extensionHashes[i] = calculateExtensionHash(ppEnabledExtensionNames[i]); + } + setTargetStatsArray(android::GpuStatsInfo::Stats::VULKAN_INSTANCE_EXTENSION, + extensionHashes, numStats); +} + +void GraphicsEnv::setVulkanDeviceExtensions(uint32_t enabledExtensionCount, + const char* const* ppEnabledExtensionNames) { + ATRACE_CALL(); + if (enabledExtensionCount == 0 || ppEnabledExtensionNames == nullptr) { + return; + } + + const uint32_t maxNumStats = android::GpuStatsAppInfo::MAX_NUM_EXTENSIONS; + uint64_t extensionHashes[maxNumStats]; + const uint32_t numStats = std::min(enabledExtensionCount, maxNumStats); + for(uint32_t i = 0; i < numStats; i++) { + extensionHashes[i] = calculateExtensionHash(ppEnabledExtensionNames[i]); + } + setTargetStatsArray(android::GpuStatsInfo::Stats::VULKAN_DEVICE_EXTENSION, + extensionHashes, numStats); +} + static sp getGpuService() { static const sp binder = defaultServiceManager()->checkService(String16("gpu")); if (!binder) { @@ -294,6 +345,11 @@ bool GraphicsEnv::readyToSendGpuStatsLocked() { } void GraphicsEnv::setTargetStats(const GpuStatsInfo::Stats stats, const uint64_t value) { + return setTargetStatsArray(stats, &value, 1); +} + +void GraphicsEnv::setTargetStatsArray(const GpuStatsInfo::Stats stats, const uint64_t* values, + const uint32_t valueCount) { ATRACE_CALL(); std::lock_guard lock(mStatsLock); @@ -301,8 +357,8 @@ void GraphicsEnv::setTargetStats(const GpuStatsInfo::Stats stats, const uint64_t const sp gpuService = getGpuService(); if (gpuService) { - gpuService->setTargetStats(mGpuStats.appPackageName, mGpuStats.driverVersionCode, stats, - value); + gpuService->setTargetStatsArray(mGpuStats.appPackageName, mGpuStats.driverVersionCode, + stats, values, valueCount); } } @@ -395,61 +451,24 @@ bool GraphicsEnv::shouldUseAngle() { return (mUseAngle == YES) ? true : false; } -bool GraphicsEnv::angleIsSystemDriver() { - // Make sure we are init'ed - if (mAngleAppName.empty()) { - ALOGV("App name is empty. setAngleInfo() has not been called to enable ANGLE."); - return false; - } - - return (mAngleIsSystemDriver == YES) ? true : false; -} - -bool GraphicsEnv::shouldForceLegacyDriver() { - // Make sure we are init'ed - if (mAngleAppName.empty()) { - ALOGV("App name is empty. setAngleInfo() has not been called to enable ANGLE."); - return false; - } - - return (mAngleIsSystemDriver == YES && mUseAngle == NO) ? true : false; -} - -std::string GraphicsEnv::getLegacySuffix() { - return mLegacyDriverSuffix; -} - void GraphicsEnv::updateUseAngle() { - mUseAngle = NO; - const char* ANGLE_PREFER_ANGLE = "angle"; - const char* ANGLE_PREFER_LEGACY = "legacy"; - // The following is a deprecated version of "legacy" const char* ANGLE_PREFER_NATIVE = "native"; mUseAngle = NO; if (mAngleDeveloperOptIn == ANGLE_PREFER_ANGLE) { - ALOGI("Using ANGLE, the %s GLES driver for package '%s'", - mAngleIsSystemDriver == YES ? "system" : "optional", mAngleAppName.c_str()); + ALOGV("User set \"Developer Options\" to force the use of ANGLE"); mUseAngle = YES; - } else if (mAngleDeveloperOptIn == ANGLE_PREFER_LEGACY || - mAngleDeveloperOptIn == ANGLE_PREFER_NATIVE) { - ALOGI("Using the (%s) Legacy GLES driver for package '%s'", - mAngleIsSystemDriver == YES ? "optional" : "system", mAngleAppName.c_str()); + } else if (mAngleDeveloperOptIn == ANGLE_PREFER_NATIVE) { + ALOGV("User set \"Developer Options\" to force the use of Native"); } else { ALOGV("User set invalid \"Developer Options\": '%s'", mAngleDeveloperOptIn.c_str()); } } void GraphicsEnv::setAngleInfo(const std::string path, const std::string appName, - const bool angleIsSystemDriver, const std::string developerOptIn, + const std::string developerOptIn, const std::vector eglFeatures) { - // Set whether ANGLE is the system driver: - mAngleIsSystemDriver = angleIsSystemDriver ? YES : NO; - - // Note: Given the current logic and lack of the old rules file processing, - // there seems to be little chance that mUseAngle != UNKNOWN. Leave this - // for now, even though it seems outdated. if (mUseAngle != UNKNOWN) { // We've already figured out an answer for this app, so just return. ALOGV("Already evaluated the rules file for '%s': use ANGLE = %s", appName.c_str(), @@ -470,25 +489,6 @@ void GraphicsEnv::setAngleInfo(const std::string path, const std::string appName updateUseAngle(); } -void GraphicsEnv::setLegacyDriverInfo(const std::string appName, const bool angleIsSystemDriver, - const std::string legacyDriverName) { - ALOGV("setting legacy app name to '%s'", appName.c_str()); - mAngleAppName = appName; - - // Force the use of the legacy driver instead of ANGLE - const char* ANGLE_PREFER_LEGACY = "legacy"; - mAngleDeveloperOptIn = ANGLE_PREFER_LEGACY; - ALOGV("setting ANGLE application opt-in to 'legacy'"); - - // Set whether ANGLE is the system driver: - mAngleIsSystemDriver = angleIsSystemDriver ? YES : NO; - - mLegacyDriverSuffix = legacyDriverName; - - // Update the current status of whether we should use ANGLE or not - updateUseAngle(); -} - void GraphicsEnv::setLayerPaths(NativeLoaderNamespace* appNamespace, const std::string layerPaths) { if (mLayerPaths.empty()) { mLayerPaths = layerPaths; @@ -651,4 +651,13 @@ android_namespace_t* GraphicsEnv::getAngleNamespace() { return mAngleNamespace; } +void GraphicsEnv::nativeToggleAngleAsSystemDriver(bool enabled) { + const sp gpuService = getGpuService(); + if (!gpuService) { + ALOGE("No GPU service"); + return; + } + gpuService->toggleAngleAsSystemDriver(enabled); +} + } // namespace android diff --git a/libs/graphicsenv/IGpuService.cpp b/libs/graphicsenv/IGpuService.cpp index fa25c5516dc1691697384804155e74312f6a0cae..4c070aec01ba20057f5f18fa59440587af4534a1 100644 --- a/libs/graphicsenv/IGpuService.cpp +++ b/libs/graphicsenv/IGpuService.cpp @@ -61,6 +61,14 @@ public: remote()->transact(BnGpuService::SET_TARGET_STATS, data, &reply, IBinder::FLAG_ONEWAY); } + void setTargetStatsArray(const std::string& appPackageName, const uint64_t driverVersionCode, + const GpuStatsInfo::Stats stats, const uint64_t* values, + const uint32_t valueCount) override { + for (uint32_t i = 0; i < valueCount; i++) { + setTargetStats(appPackageName, driverVersionCode, stats, values[i]); + } + } + void setUpdatableDriverPath(const std::string& driverPath) override { Parcel data, reply; data.writeInterfaceToken(IGpuService::getInterfaceDescriptor()); @@ -70,6 +78,15 @@ public: IBinder::FLAG_ONEWAY); } + void toggleAngleAsSystemDriver(bool enabled) override { + Parcel data, reply; + data.writeInterfaceToken(IGpuService::getInterfaceDescriptor()); + data.writeBool(enabled); + + remote()->transact(BnGpuService::TOGGLE_ANGLE_AS_SYSTEM_DRIVER, data, &reply, + IBinder::FLAG_ONEWAY); + } + std::string getUpdatableDriverPath() override { Parcel data, reply; data.writeInterfaceToken(IGpuService::getInterfaceDescriptor()); @@ -181,6 +198,15 @@ status_t BnGpuService::onTransact(uint32_t code, const Parcel& data, Parcel* rep return OK; } + case TOGGLE_ANGLE_AS_SYSTEM_DRIVER: { + CHECK_INTERFACE(IGpuService, data, reply); + + bool enableAngleAsSystemDriver; + if ((status = data.readBool(&enableAngleAsSystemDriver)) != OK) return status; + + toggleAngleAsSystemDriver(enableAngleAsSystemDriver); + return OK; + } default: return BBinder::onTransact(code, data, reply, flags); } diff --git a/libs/graphicsenv/include/graphicsenv/GpuStatsInfo.h b/libs/graphicsenv/include/graphicsenv/GpuStatsInfo.h index 5b513d2a79b4efecbaf5fab743621a2c9837fcbd..47607a0ab9340391593781dcaed3626c5998d2f7 100644 --- a/libs/graphicsenv/include/graphicsenv/GpuStatsInfo.h +++ b/libs/graphicsenv/include/graphicsenv/GpuStatsInfo.h @@ -58,6 +58,9 @@ public: */ class GpuStatsAppInfo : public Parcelable { public: + // This limits the worst case number of extensions to be tracked. + static const uint32_t MAX_NUM_EXTENSIONS = 100; + GpuStatsAppInfo() = default; GpuStatsAppInfo(const GpuStatsAppInfo&) = default; virtual ~GpuStatsAppInfo() = default; @@ -74,6 +77,13 @@ public: bool falsePrerotation = false; bool gles1InUse = false; bool angleInUse = false; + bool createdGlesContext = false; + bool createdVulkanDevice = false; + bool createdVulkanSwapchain = false; + uint32_t vulkanApiVersion = 0; + uint64_t vulkanDeviceFeaturesEnabled = 0; + std::vector vulkanInstanceExtensions = {}; + std::vector vulkanDeviceExtensions = {}; std::chrono::time_point lastAccessTime; }; @@ -101,6 +111,13 @@ public: CPU_VULKAN_IN_USE = 0, FALSE_PREROTATION = 1, GLES_1_IN_USE = 2, + CREATED_GLES_CONTEXT = 3, + CREATED_VULKAN_API_VERSION = 4, + CREATED_VULKAN_DEVICE = 5, + CREATED_VULKAN_SWAPCHAIN = 6, + VULKAN_DEVICE_FEATURES_ENABLED = 7, + VULKAN_INSTANCE_EXTENSION = 8, + VULKAN_DEVICE_EXTENSION = 9, }; GpuStatsInfo() = default; diff --git a/libs/graphicsenv/include/graphicsenv/GraphicsEnv.h b/libs/graphicsenv/include/graphicsenv/GraphicsEnv.h index 73d3196948573902de1fdcf812d9d624ffb30367..f9b234a04793248c1fc7fe1eb9fab313137b4af1 100644 --- a/libs/graphicsenv/include/graphicsenv/GraphicsEnv.h +++ b/libs/graphicsenv/include/graphicsenv/GraphicsEnv.h @@ -71,10 +71,19 @@ public: const std::string& appPackageName, const int32_t vulkanVersion); // Set stats for target GpuStatsInfo::Stats type. void setTargetStats(const GpuStatsInfo::Stats stats, const uint64_t value = 0); + // Set array of stats for target GpuStatsInfo::Stats type. + void setTargetStatsArray(const GpuStatsInfo::Stats stats, const uint64_t* values, + const uint32_t valueCount); // Set which driver is intended to load. void setDriverToLoad(GpuStatsInfo::Driver driver); // Set which driver is actually loaded. void setDriverLoaded(GpuStatsInfo::Api api, bool isDriverLoaded, int64_t driverLoadingTime); + // Set which instance extensions are enabled for the app. + void setVulkanInstanceExtensions(uint32_t enabledExtensionCount, + const char* const* ppEnabledExtensionNames); + // Set which device extensions are enabled for the app. + void setVulkanDeviceExtensions(uint32_t enabledExtensionCount, + const char* const* ppEnabledExtensionNames); /* * Api for Vk/GL layer injection. Presently, drivers enable certain @@ -91,28 +100,17 @@ public: bool shouldUseAngle(std::string appName); // Check if this app process should use ANGLE. bool shouldUseAngle(); - // If ANGLE is the system GLES driver - bool angleIsSystemDriver(); - // If should use legacy driver instead of a system ANGLE driver - bool shouldForceLegacyDriver(); // Set a search path for loading ANGLE libraries. The path is a list of // directories separated by ':'. A directory can be contained in a zip file // (libraries must be stored uncompressed and page aligned); such elements // in the search path must have a '!' after the zip filename, e.g. // /system/app/ANGLEPrebuilt/ANGLEPrebuilt.apk!/lib/arm64-v8a - void setAngleInfo(const std::string path, const std::string appName, - const bool angleIsSystemDriver, std::string devOptIn, + void setAngleInfo(const std::string path, const std::string appName, std::string devOptIn, const std::vector eglFeatures); - // Set the state so that the legacy driver will be used, and in case ANGLE - // is the system driver, provide the name of the legacy driver. - void setLegacyDriverInfo(const std::string appName, const bool angleIsSystemDriver, - const std::string legacyDriverName); // Get the ANGLE driver namespace. android_namespace_t* getAngleNamespace(); // Get the app name for ANGLE debug message. std::string& getAngleAppName(); - // Get the legacy driver's suffix name. - std::string getLegacySuffix(); const std::vector& getAngleEglFeatures(); @@ -133,6 +131,8 @@ public: const std::string& getDebugLayers(); // Get the debug layers to load. const std::string& getDebugLayersGLES(); + // Set the persist.graphics.egl system property value. + void nativeToggleAngleAsSystemDriver(bool enabled); private: enum UseAngle { UNKNOWN, YES, NO }; @@ -167,10 +167,6 @@ private: std::string mAngleDeveloperOptIn; // ANGLE EGL features; std::vector mAngleEglFeatures; - // ANGLE is System Driver flag. - UseAngle mAngleIsSystemDriver = UNKNOWN; - // Legacy driver name to use when ANGLE is the system driver. - std::string mLegacyDriverSuffix; // Use ANGLE flag. UseAngle mUseAngle = UNKNOWN; // Vulkan debug layers libs. diff --git a/libs/graphicsenv/include/graphicsenv/IGpuService.h b/libs/graphicsenv/include/graphicsenv/IGpuService.h index 2d59fa0165a2d7b82c9d679959e0b6f9259d5821..e3857d2ec0ab71351ad17200b071f2c92aced883 100644 --- a/libs/graphicsenv/include/graphicsenv/IGpuService.h +++ b/libs/graphicsenv/include/graphicsenv/IGpuService.h @@ -42,10 +42,17 @@ public: // set target stats. virtual void setTargetStats(const std::string& appPackageName, const uint64_t driverVersionCode, const GpuStatsInfo::Stats stats, const uint64_t value = 0) = 0; + virtual void setTargetStatsArray(const std::string& appPackageName, + const uint64_t driverVersionCode, + const GpuStatsInfo::Stats stats, const uint64_t* values, + const uint32_t valueCount) = 0; // setter and getter for updatable driver path. virtual void setUpdatableDriverPath(const std::string& driverPath) = 0; virtual std::string getUpdatableDriverPath() = 0; + + // sets ANGLE as system GLES driver if enabled==true by setting persist.graphics.egl to true. + virtual void toggleAngleAsSystemDriver(bool enabled) = 0; }; class BnGpuService : public BnInterface { @@ -55,6 +62,7 @@ public: SET_TARGET_STATS, SET_UPDATABLE_DRIVER_PATH, GET_UPDATABLE_DRIVER_PATH, + TOGGLE_ANGLE_AS_SYSTEM_DRIVER, // Always append new enum to the end. }; diff --git a/libs/gui/Android.bp b/libs/gui/Android.bp index 0fe6f24842ac24b27ac7324778aa15c04940ea3f..d7e7eb8ea1a4d3e3fa5a1e7087c8edd9c1245440 100644 --- a/libs/gui/Android.bp +++ b/libs/gui/Android.bp @@ -66,6 +66,20 @@ filegroup { ], } +filegroup { + name: "android_gui_aidl", + srcs: [ + "android/gui/DisplayInfo.aidl", + "android/gui/FocusRequest.aidl", + "android/gui/InputApplicationInfo.aidl", + "android/gui/IWindowInfosListener.aidl", + "android/gui/IWindowInfosPublisher.aidl", + "android/gui/IWindowInfosReportedListener.aidl", + "android/gui/WindowInfo.aidl", + "android/gui/WindowInfosUpdate.aidl", + ], +} + cc_library_static { name: "libgui_window_info_static", vendor_available: true, @@ -77,10 +91,13 @@ cc_library_static { "android/gui/FocusRequest.aidl", "android/gui/InputApplicationInfo.aidl", "android/gui/IWindowInfosListener.aidl", + "android/gui/IWindowInfosPublisher.aidl", "android/gui/IWindowInfosReportedListener.aidl", + "android/gui/WindowInfosUpdate.aidl", "android/gui/WindowInfo.aidl", "DisplayInfo.cpp", "WindowInfo.cpp", + "WindowInfosUpdate.cpp", ], shared_libs: [ @@ -114,18 +131,36 @@ cc_library_static { }, } -filegroup { +aidl_library { + name: "libgui_aidl_hdrs", + hdrs: [ + "android/gui/DisplayInfo.aidl", + "android/gui/FocusRequest.aidl", + "android/gui/InputApplicationInfo.aidl", + "android/gui/IWindowInfosListener.aidl", + "android/gui/IWindowInfosPublisher.aidl", + "android/gui/IWindowInfosReportedListener.aidl", + "android/gui/WindowInfo.aidl", + "android/gui/WindowInfosUpdate.aidl", + ], +} + +aidl_library { name: "libgui_aidl", srcs: ["aidl/**/*.aidl"], + strip_import_prefix: "aidl", + deps: ["libgui_aidl_hdrs"], +} + +filegroup { + name: "libgui_frame_event_aidl", + srcs: ["aidl/android/gui/FrameEvent.aidl"], path: "aidl/", } cc_library_static { name: "libgui_aidl_static", vendor_available: true, - srcs: [ - ":libgui_aidl", - ], shared_libs: [ "libbinder", @@ -136,16 +171,22 @@ cc_library_static { "include", ], + include_dirs: [ + "frameworks/native/include", + ], + export_shared_lib_headers: [ "libbinder", ], static_libs: [ "libui-types", + "libgui_window_info_static", ], aidl: { export_aidl_headers: true, + libs: ["libgui_aidl"], }, } @@ -178,22 +219,24 @@ cc_library_shared { "BitTube.cpp", "BLASTBufferQueue.cpp", "BufferItemConsumer.cpp", + "Choreographer.cpp", + "CompositorTiming.cpp", "ConsumerBase.cpp", "CpuConsumer.cpp", "DebugEGLImageTracker.cpp", "DisplayEventDispatcher.cpp", "DisplayEventReceiver.cpp", - "FrameTimelineInfo.cpp", + "FenceMonitor.cpp", "GLConsumer.cpp", "IConsumerListener.cpp", "IGraphicBufferConsumer.cpp", "IGraphicBufferProducer.cpp", "IProducerListener.cpp", "ISurfaceComposer.cpp", - "ISurfaceComposerClient.cpp", "ITransactionCompletedListener.cpp", "LayerDebugInfo.cpp", "LayerMetadata.cpp", + "LayerStatePermissions.cpp", "LayerState.cpp", "OccupancyTracker.cpp", "StreamSplitter.cpp", @@ -202,7 +245,6 @@ cc_library_shared { "SurfaceControl.cpp", "SurfaceComposerClient.cpp", "SyncFeatures.cpp", - "TransactionTracing.cpp", "VsyncEventData.cpp", "view/Surface.cpp", "WindowInfosListenerReporter.cpp", @@ -223,6 +265,7 @@ cc_library_shared { export_header_lib_headers: [ "libgui_aidl_headers", + "jni_headers", ], aidl: { @@ -230,6 +273,7 @@ cc_library_shared { }, header_libs: [ + "jni_headers", "libdvr_headers", "libgui_aidl_headers", "libpdx_headers", @@ -240,6 +284,10 @@ cc_library_shared { lto: { thin: true, }, + + cflags: [ + "-Wthread-safety", + ], } // Used by media codec services exclusively as a static lib for @@ -260,10 +308,16 @@ cc_library_static { defaults: ["libgui_bufferqueue-defaults"], srcs: [ + ":libgui_frame_event_aidl", ":inputconstants_aidl", ":libgui_bufferqueue_sources", - ":libgui_aidl", ], + + aidl: { + include_dirs: [ + "frameworks/native/libs/gui", + ], + }, } filegroup { @@ -294,6 +348,8 @@ filegroup { cc_defaults { name: "libgui_bufferqueue-defaults", + defaults: ["android.hardware.graphics.common-ndk_shared"], + cflags: [ "-Wall", "-Werror", @@ -322,7 +378,6 @@ cc_defaults { "android.hardware.graphics.bufferqueue@2.0", "android.hardware.graphics.common@1.1", "android.hardware.graphics.common@1.2", - "android.hardware.graphics.common-V4-ndk", "android.hidl.token@1.0-utils", "libbase", "libcutils", @@ -381,6 +436,7 @@ cc_library_static { ], srcs: [ + ":libgui_frame_event_aidl", "mock/GraphicBufferConsumer.cpp", "mock/GraphicBufferProducer.cpp", ], diff --git a/libs/gui/BLASTBufferQueue.cpp b/libs/gui/BLASTBufferQueue.cpp index 000f458fd1a4fb4efa9b27659b5bcf71d3fb9172..5c324b29cd984923e6bce8150c699b5419c740c8 100644 --- a/libs/gui/BLASTBufferQueue.cpp +++ b/libs/gui/BLASTBufferQueue.cpp @@ -20,6 +20,7 @@ #define ATRACE_TAG ATRACE_TAG_GRAPHICS //#define LOG_NDEBUG 0 +#include #include #include #include @@ -33,7 +34,9 @@ #include #include +#include +#include #include using namespace std::chrono_literals; @@ -62,6 +65,10 @@ namespace android { ATRACE_FORMAT("%s - %s(f:%u,a:%u)" x, __FUNCTION__, mName.c_str(), mNumFrameAvailable, \ mNumAcquired, ##__VA_ARGS__) +#define UNIQUE_LOCK_WITH_ASSERTION(mutex) \ + std::unique_lock _lock{mutex}; \ + base::ScopedLockAssertion assumeLocked(mutex); + void BLASTBufferItemConsumer::onDisconnect() { Mutex::Autolock lock(mMutex); mPreviouslyConnected = mCurrentlyConnected; @@ -156,30 +163,30 @@ BLASTBufferQueue::BLASTBufferQueue(const std::string& name, bool updateDestinati GraphicBuffer::USAGE_HW_COMPOSER | GraphicBuffer::USAGE_HW_TEXTURE, 1, false, this); - static int32_t id = 0; - mName = name + "#" + std::to_string(id); - auto consumerName = mName + "(BLAST Consumer)" + std::to_string(id); - mQueuedBufferTrace = "QueuedBuffer - " + mName + "BLAST#" + std::to_string(id); - id++; + static std::atomic nextId = 0; + mProducerId = nextId++; + mName = name + "#" + std::to_string(mProducerId); + auto consumerName = mName + "(BLAST Consumer)" + std::to_string(mProducerId); + mQueuedBufferTrace = "QueuedBuffer - " + mName + "BLAST#" + std::to_string(mProducerId); mBufferItemConsumer->setName(String8(consumerName.c_str())); mBufferItemConsumer->setFrameAvailableListener(this); - mBufferItemConsumer->setBufferFreedListener(this); - ComposerService::getComposerService()->getMaxAcquiredBufferCount(&mMaxAcquiredBuffers); + ComposerServiceAIDL::getComposerService()->getMaxAcquiredBufferCount(&mMaxAcquiredBuffers); mBufferItemConsumer->setMaxAcquiredBufferCount(mMaxAcquiredBuffers); mCurrentMaxAcquiredBufferCount = mMaxAcquiredBuffers; mNumAcquired = 0; mNumFrameAvailable = 0; TransactionCompletedListener::getInstance()->addQueueStallListener( - [&]() { - std::function callbackCopy; - { - std::unique_lock _lock{mMutex}; - callbackCopy = mTransactionHangCallback; - } - if (callbackCopy) callbackCopy(true); - }, this); + [&](const std::string& reason) { + std::function callbackCopy; + { + std::unique_lock _lock{mMutex}; + callbackCopy = mTransactionHangCallback; + } + if (callbackCopy) callbackCopy(reason); + }, + this); BQA_LOGV("BLASTBufferQueue created"); } @@ -211,7 +218,7 @@ void BLASTBufferQueue::update(const sp& surface, uint32_t width, int32_t format) { LOG_ALWAYS_FATAL_IF(surface == nullptr, "BLASTBufferQueue: mSurfaceControl must not be NULL"); - std::unique_lock _lock{mMutex}; + std::lock_guard _lock{mMutex}; if (mFormat != format) { mFormat = format; mBufferItemConsumer->setDefaultBufferFormat(convertBufferFormat(format)); @@ -281,7 +288,7 @@ void BLASTBufferQueue::transactionCommittedCallback(nsecs_t /*latchTime*/, const sp& /*presentFence*/, const std::vector& stats) { { - std::unique_lock _lock{mMutex}; + std::lock_guard _lock{mMutex}; BBQ_TRACE(); BQA_LOGV("transactionCommittedCallback"); if (!mSurfaceControlsWithPendingCallback.empty()) { @@ -329,7 +336,7 @@ static void transactionCallbackThunk(void* context, nsecs_t latchTime, void BLASTBufferQueue::transactionCallback(nsecs_t /*latchTime*/, const sp& /*presentFence*/, const std::vector& stats) { { - std::unique_lock _lock{mMutex}; + std::lock_guard _lock{mMutex}; BBQ_TRACE(); BQA_LOGV("transactionCallback"); @@ -339,9 +346,11 @@ void BLASTBufferQueue::transactionCallback(nsecs_t /*latchTime*/, const sp statsOptional = findMatchingStat(stats, pendingSC); if (statsOptional) { SurfaceControlStats stat = *statsOptional; - mTransformHint = stat.transformHint; - mBufferItemConsumer->setTransformHint(mTransformHint); - BQA_LOGV("updated mTransformHint=%d", mTransformHint); + if (stat.transformHint) { + mTransformHint = *stat.transformHint; + mBufferItemConsumer->setTransformHint(mTransformHint); + BQA_LOGV("updated mTransformHint=%d", mTransformHint); + } // Update frametime stamps if the frame was latched and presented, indicated by a // valid latch time. if (stat.latchTime > 0) { @@ -408,9 +417,8 @@ void BLASTBufferQueue::flushShadowQueue() { void BLASTBufferQueue::releaseBufferCallback( const ReleaseCallbackId& id, const sp& releaseFence, std::optional currentMaxAcquiredBufferCount) { + std::lock_guard _lock{mMutex}; BBQ_TRACE(); - - std::unique_lock _lock{mMutex}; releaseBufferCallbackLocked(id, releaseFence, currentMaxAcquiredBufferCount, false /* fakeRelease */); } @@ -425,10 +433,8 @@ void BLASTBufferQueue::releaseBufferCallbackLocked( // to the buffer queue. This will prevent higher latency when we are running // on a lower refresh rate than the max supported. We only do that for EGL // clients as others don't care about latency - const bool isEGL = [&] { - const auto it = mSubmitted.find(id); - return it != mSubmitted.end() && it->second.mApi == NATIVE_WINDOW_API_EGL; - }(); + const auto it = mSubmitted.find(id); + const bool isEGL = it != mSubmitted.end() && it->second.mApi == NATIVE_WINDOW_API_EGL; if (currentMaxAcquiredBufferCount) { mCurrentMaxAcquiredBufferCount = *currentMaxAcquiredBufferCount; @@ -485,6 +491,17 @@ void BLASTBufferQueue::releaseBuffer(const ReleaseCallbackId& callbackId, mSyncedFrameNumbers.erase(callbackId.framenumber); } +static ui::Size getBufferSize(const BufferItem& item) { + uint32_t bufWidth = item.mGraphicBuffer->getWidth(); + uint32_t bufHeight = item.mGraphicBuffer->getHeight(); + + // Take the buffer's orientation into account + if (item.mTransform & ui::Transform::ROT_90) { + std::swap(bufWidth, bufHeight); + } + return ui::Size(bufWidth, bufHeight); +} + status_t BLASTBufferQueue::acquireNextBufferLocked( const std::optional transaction) { // Check if we have frames available and we have not acquired the maximum number of buffers. @@ -562,7 +579,13 @@ status_t BLASTBufferQueue::acquireNextBufferLocked( // Ensure BLASTBufferQueue stays alive until we receive the transaction complete callback. incStrong((void*)transactionCallbackThunk); - mSize = mRequestedSize; + // Only update mSize for destination bounds if the incoming buffer matches the requested size. + // Otherwise, it could cause stretching since the destination bounds will update before the + // buffer with the new size is acquired. + if (mRequestedSize == getBufferSize(bufferItem) || + bufferItem.mScalingMode != NATIVE_WINDOW_SCALING_MODE_FREEZE) { + mSize = mRequestedSize; + } Rect crop = computeCrop(bufferItem); mLastBufferInfo.update(true /* hasBuffer */, bufferItem.mGraphicBuffer->getWidth(), bufferItem.mGraphicBuffer->getHeight(), bufferItem.mTransform, @@ -572,7 +595,8 @@ 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; - t->setBuffer(mSurfaceControl, buffer, fence, bufferItem.mFrameNumber, releaseBufferCallback); + t->setBuffer(mSurfaceControl, buffer, fence, bufferItem.mFrameNumber, mProducerId, + releaseBufferCallback); t->setDataspace(mSurfaceControl, static_cast(bufferItem.mDataSpace)); t->setHdrMetadata(mSurfaceControl, bufferItem.mHdrMetadata); t->setSurfaceDamageRegion(mSurfaceControl, bufferItem.mSurfaceDamage); @@ -617,12 +641,12 @@ status_t BLASTBufferQueue::acquireNextBufferLocked( } { - std::unique_lock _lock{mTimestampMutex}; + std::lock_guard _lock{mTimestampMutex}; auto dequeueTime = mDequeueTimestamps.find(buffer->getId()); if (dequeueTime != mDequeueTimestamps.end()) { Parcel p; p.writeInt64(dequeueTime->second); - t->setMetadata(mSurfaceControl, METADATA_DEQUEUE_TIME, p); + t->setMetadata(mSurfaceControl, gui::METADATA_DEQUEUE_TIME, p); mDequeueTimestamps.erase(dequeueTime); } } @@ -656,6 +680,7 @@ Rect BLASTBufferQueue::computeCrop(const BufferItem& item) { } void BLASTBufferQueue::acquireAndReleaseBuffer() { + BBQ_TRACE(); BufferItem bufferItem; status_t status = mBufferItemConsumer->acquireBuffer(&bufferItem, 0 /* expectedPresent */, false); @@ -673,10 +698,10 @@ void BLASTBufferQueue::onFrameAvailable(const BufferItem& item) { SurfaceComposerClient::Transaction* prevTransaction = nullptr; { - std::unique_lock _lock{mMutex}; + UNIQUE_LOCK_WITH_ASSERTION(mMutex); BBQ_TRACE(); - bool waitForTransactionCallback = !mSyncedFrameNumbers.empty(); + const bool syncTransactionSet = mTransactionReadyCallback != nullptr; BQA_LOGV("onFrameAvailable-start syncTransactionSet=%s", boolToString(syncTransactionSet)); @@ -767,44 +792,33 @@ void BLASTBufferQueue::onFrameReplaced(const BufferItem& item) { } void BLASTBufferQueue::onFrameDequeued(const uint64_t bufferId) { - std::unique_lock _lock{mTimestampMutex}; + std::lock_guard _lock{mTimestampMutex}; mDequeueTimestamps[bufferId] = systemTime(); }; void BLASTBufferQueue::onFrameCancelled(const uint64_t bufferId) { - std::unique_lock _lock{mTimestampMutex}; + std::lock_guard _lock{mTimestampMutex}; mDequeueTimestamps.erase(bufferId); }; -void BLASTBufferQueue::syncNextTransaction( +bool BLASTBufferQueue::syncNextTransaction( std::function callback, bool acquireSingleBuffer) { - BBQ_TRACE(); - - std::function prevCallback = nullptr; - SurfaceComposerClient::Transaction* prevTransaction = nullptr; - - { - std::lock_guard _lock{mMutex}; - // We're about to overwrite the previous call so we should invoke that callback - // immediately. - if (mTransactionReadyCallback) { - prevCallback = mTransactionReadyCallback; - prevTransaction = mSyncTransaction; - } + LOG_ALWAYS_FATAL_IF(!callback, + "BLASTBufferQueue: callback passed in to syncNextTransaction must not be " + "NULL"); - mTransactionReadyCallback = callback; - if (callback) { - mSyncTransaction = new SurfaceComposerClient::Transaction(); - } else { - mSyncTransaction = nullptr; - } - mAcquireSingleBuffer = mTransactionReadyCallback ? acquireSingleBuffer : true; + std::lock_guard _lock{mMutex}; + BBQ_TRACE(); + if (mTransactionReadyCallback) { + ALOGW("Attempting to overwrite transaction callback in syncNextTransaction"); + return false; } - if (prevCallback) { - prevCallback(prevTransaction); - } + mTransactionReadyCallback = callback; + mSyncTransaction = new SurfaceComposerClient::Transaction(); + mAcquireSingleBuffer = acquireSingleBuffer; + return true; } void BLASTBufferQueue::stopContinuousSyncTransaction() { @@ -812,34 +826,42 @@ void BLASTBufferQueue::stopContinuousSyncTransaction() { SurfaceComposerClient::Transaction* prevTransaction = nullptr; { std::lock_guard _lock{mMutex}; - bool invokeCallback = mTransactionReadyCallback && !mAcquireSingleBuffer; - if (invokeCallback) { - prevCallback = mTransactionReadyCallback; - prevTransaction = mSyncTransaction; + if (mAcquireSingleBuffer || !mTransactionReadyCallback) { + ALOGW("Attempting to stop continuous sync when none are active"); + return; } + + prevCallback = mTransactionReadyCallback; + prevTransaction = mSyncTransaction; + mTransactionReadyCallback = nullptr; mSyncTransaction = nullptr; mAcquireSingleBuffer = true; } + if (prevCallback) { prevCallback(prevTransaction); } } +void BLASTBufferQueue::clearSyncTransaction() { + std::lock_guard _lock{mMutex}; + if (!mAcquireSingleBuffer) { + ALOGW("Attempting to clear sync transaction when none are active"); + return; + } + + mTransactionReadyCallback = nullptr; + mSyncTransaction = nullptr; +} + bool BLASTBufferQueue::rejectBuffer(const BufferItem& item) { if (item.mScalingMode != NATIVE_WINDOW_SCALING_MODE_FREEZE) { // Only reject buffers if scaling mode is freeze. return false; } - uint32_t bufWidth = item.mGraphicBuffer->getWidth(); - uint32_t bufHeight = item.mGraphicBuffer->getHeight(); - - // Take the buffer's orientation into account - if (item.mTransform & ui::Transform::ROT_90) { - std::swap(bufWidth, bufHeight); - } - ui::Size bufferSize(bufWidth, bufHeight); + ui::Size bufferSize = getBufferSize(item); if (mRequestedSize != mSize && mRequestedSize == bufferSize) { return false; } @@ -851,8 +873,8 @@ bool BLASTBufferQueue::rejectBuffer(const BufferItem& item) { class BBQSurface : public Surface { private: std::mutex mMutex; - sp mBbq; - bool mDestroyed = false; + sp mBbq GUARDED_BY(mMutex); + bool mDestroyed GUARDED_BY(mMutex) = false; public: BBQSurface(const sp& igbp, bool controlledByApp, @@ -873,7 +895,7 @@ public: status_t setFrameRate(float frameRate, int8_t compatibility, int8_t changeFrameRateStrategy) override { - std::unique_lock _lock{mMutex}; + std::lock_guard _lock{mMutex}; if (mDestroyed) { return DEAD_OBJECT; } @@ -886,7 +908,7 @@ public: status_t setFrameTimelineInfo(uint64_t frameNumber, const FrameTimelineInfo& frameTimelineInfo) override { - std::unique_lock _lock{mMutex}; + std::lock_guard _lock{mMutex}; if (mDestroyed) { return DEAD_OBJECT; } @@ -896,7 +918,7 @@ public: void destroy() override { Surface::destroy(); - std::unique_lock _lock{mMutex}; + std::lock_guard _lock{mMutex}; mDestroyed = true; mBbq = nullptr; } @@ -906,7 +928,7 @@ public: // no timing issues. status_t BLASTBufferQueue::setFrameRate(float frameRate, int8_t compatibility, bool shouldBeSeamless) { - std::unique_lock _lock{mMutex}; + std::lock_guard _lock{mMutex}; SurfaceComposerClient::Transaction t; return t.setFrameRate(mSurfaceControl, frameRate, compatibility, shouldBeSeamless).apply(); @@ -916,20 +938,20 @@ status_t BLASTBufferQueue::setFrameTimelineInfo(uint64_t frameNumber, const FrameTimelineInfo& frameTimelineInfo) { ATRACE_FORMAT("%s(%s) frameNumber: %" PRIu64 " vsyncId: %" PRId64, __func__, mName.c_str(), frameNumber, frameTimelineInfo.vsyncId); - std::unique_lock _lock{mMutex}; + std::lock_guard _lock{mMutex}; mPendingFrameTimelines.push({frameNumber, frameTimelineInfo}); return OK; } void BLASTBufferQueue::setSidebandStream(const sp& stream) { - std::unique_lock _lock{mMutex}; + std::lock_guard _lock{mMutex}; SurfaceComposerClient::Transaction t; t.setSidebandStream(mSurfaceControl, stream).apply(); } sp BLASTBufferQueue::getSurface(bool includeSurfaceControlHandle) { - std::unique_lock _lock{mMutex}; + std::lock_guard _lock{mMutex}; sp scHandle = nullptr; if (includeSurfaceControlHandle && mSurfaceControl) { scHandle = mSurfaceControl->getHandle(); @@ -1154,6 +1176,7 @@ PixelFormat BLASTBufferQueue::convertBufferFormat(PixelFormat& format) { } uint32_t BLASTBufferQueue::getLastTransformHint() const { + std::lock_guard _lock{mMutex}; if (mSurfaceControl != nullptr) { return mSurfaceControl->getTransformHint(); } else { @@ -1162,62 +1185,18 @@ uint32_t BLASTBufferQueue::getLastTransformHint() const { } uint64_t BLASTBufferQueue::getLastAcquiredFrameNum() { - std::unique_lock _lock{mMutex}; + std::lock_guard _lock{mMutex}; return mLastAcquiredFrameNumber; } -void BLASTBufferQueue::abandon() { - std::unique_lock _lock{mMutex}; - // flush out the shadow queue - while (mNumFrameAvailable > 0) { - acquireAndReleaseBuffer(); - } - - // Clear submitted buffer states - mNumAcquired = 0; - mSubmitted.clear(); - mPendingRelease.clear(); - - if (!mPendingTransactions.empty()) { - BQA_LOGD("Applying pending transactions on abandon %d", - static_cast(mPendingTransactions.size())); - SurfaceComposerClient::Transaction t; - mergePendingTransactions(&t, std::numeric_limits::max() /* frameNumber */); - // All transactions on our apply token are one-way. See comment on mAppliedLastTransaction - t.setApplyToken(mApplyToken).apply(false, true); - } - - // Clear sync states - if (!mSyncedFrameNumbers.empty()) { - BQA_LOGD("mSyncedFrameNumbers cleared"); - mSyncedFrameNumbers.clear(); - } - - if (mSyncTransaction != nullptr) { - BQA_LOGD("mSyncTransaction cleared mAcquireSingleBuffer=%s", - mAcquireSingleBuffer ? "true" : "false"); - mSyncTransaction = nullptr; - mAcquireSingleBuffer = false; - } - - // abandon buffer queue - if (mBufferItemConsumer != nullptr) { - mBufferItemConsumer->abandon(); - mBufferItemConsumer->setFrameAvailableListener(nullptr); - mBufferItemConsumer->setBufferFreedListener(nullptr); - } - mBufferItemConsumer = nullptr; - mConsumer = nullptr; - mProducer = nullptr; -} - bool BLASTBufferQueue::isSameSurfaceControl(const sp& surfaceControl) const { - std::unique_lock _lock{mMutex}; + std::lock_guard _lock{mMutex}; return SurfaceControl::isSameSurface(mSurfaceControl, surfaceControl); } -void BLASTBufferQueue::setTransactionHangCallback(std::function callback) { - std::unique_lock _lock{mMutex}; +void BLASTBufferQueue::setTransactionHangCallback( + std::function callback) { + std::lock_guard _lock{mMutex}; mTransactionHangCallback = callback; } diff --git a/libs/gui/BufferQueueConsumer.cpp b/libs/gui/BufferQueueConsumer.cpp index db513561fbe6e743dd49e26c06de0e85a1710763..5b34ba12c8368f2c123ebb29374f7a7f608c5c74 100644 --- a/libs/gui/BufferQueueConsumer.cpp +++ b/libs/gui/BufferQueueConsumer.cpp @@ -33,6 +33,7 @@ #include #include #include +#include #include #ifndef __ANDROID_VNDK__ @@ -645,7 +646,7 @@ status_t BufferQueueConsumer::setMaxBufferCount(int bufferCount) { status_t BufferQueueConsumer::setMaxAcquiredBufferCount( int maxAcquiredBuffers) { - ATRACE_CALL(); + ATRACE_FORMAT("%s(%d)", __func__, maxAcquiredBuffers); if (maxAcquiredBuffers < 1 || maxAcquiredBuffers > BufferQueueCore::MAX_MAX_ACQUIRED_BUFFERS) { diff --git a/libs/gui/BufferQueueProducer.cpp b/libs/gui/BufferQueueProducer.cpp index 36c2e5891ea8db95a7f02cab4690cff4f663302c..ce5d5d382eacc8d8fd87646335a732929367d93c 100644 --- a/libs/gui/BufferQueueProducer.cpp +++ b/libs/gui/BufferQueueProducer.cpp @@ -35,6 +35,7 @@ #include #include #include +#include #include #include @@ -125,7 +126,7 @@ status_t BufferQueueProducer::setMaxDequeuedBufferCount( status_t BufferQueueProducer::setMaxDequeuedBufferCount(int maxDequeuedBuffers, int* maxBufferCount) { - ATRACE_CALL(); + ATRACE_FORMAT("%s(%d)", __func__, maxDequeuedBuffers); BQ_LOGV("setMaxDequeuedBufferCount: maxDequeuedBuffers = %d", maxDequeuedBuffers); @@ -502,6 +503,20 @@ status_t BufferQueueProducer::dequeueBuffer(int* outSlot, sp* ou if ((buffer == nullptr) || buffer->needsReallocation(width, height, format, BQ_LAYER_COUNT, usage)) { + if (CC_UNLIKELY(ATRACE_ENABLED())) { + if (buffer == nullptr) { + ATRACE_FORMAT_INSTANT("%s buffer reallocation: null", mConsumerName.c_str()); + } else { + ATRACE_FORMAT_INSTANT("%s buffer reallocation actual %dx%d format:%d " + "layerCount:%d " + "usage:%d requested: %dx%d format:%d layerCount:%d " + "usage:%d ", + mConsumerName.c_str(), width, height, format, + BQ_LAYER_COUNT, usage, buffer->getWidth(), + buffer->getHeight(), buffer->getPixelFormat(), + buffer->getLayerCount(), buffer->getUsage()); + } + } mSlots[found].mAcquireCalled = false; mSlots[found].mGraphicBuffer = nullptr; mSlots[found].mRequestBufferCalled = false; diff --git a/libs/gui/Choreographer.cpp b/libs/gui/Choreographer.cpp new file mode 100644 index 0000000000000000000000000000000000000000..46fb068dee9c563310b7d6016056ba466e5ea997 --- /dev/null +++ b/libs/gui/Choreographer.cpp @@ -0,0 +1,397 @@ +/* + * 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. + */ + +// #define LOG_NDEBUG 0 +#define ATRACE_TAG ATRACE_TAG_GRAPHICS + +#include +#include +#include + +#undef LOG_TAG +#define LOG_TAG "AChoreographer" + +namespace { +struct { + // Global JVM that is provided by zygote + JavaVM* jvm = nullptr; + struct { + jclass clazz; + jmethodID getInstance; + jmethodID registerNativeChoreographerForRefreshRateCallbacks; + jmethodID unregisterNativeChoreographerForRefreshRateCallbacks; + } displayManagerGlobal; +} gJni; + +// Gets the JNIEnv* for this thread, and performs one-off initialization if we +// have never retrieved a JNIEnv* pointer before. +JNIEnv* getJniEnv() { + if (gJni.jvm == nullptr) { + ALOGW("AChoreographer: No JVM provided!"); + return nullptr; + } + + JNIEnv* env = nullptr; + if (gJni.jvm->GetEnv((void**)&env, JNI_VERSION_1_4) != JNI_OK) { + ALOGD("Attaching thread to JVM for AChoreographer"); + JavaVMAttachArgs args = {JNI_VERSION_1_4, "AChoreographer_env", NULL}; + jint attachResult = gJni.jvm->AttachCurrentThreadAsDaemon(&env, (void*)&args); + if (attachResult != JNI_OK) { + ALOGE("Unable to attach thread. Error: %d", attachResult); + return nullptr; + } + } + if (env == nullptr) { + ALOGW("AChoreographer: No JNI env available!"); + } + return env; +} + +inline const char* toString(bool value) { + return value ? "true" : "false"; +} +} // namespace + +namespace android { + +Choreographer::Context Choreographer::gChoreographers; + +static thread_local Choreographer* gChoreographer; + +void Choreographer::initJVM(JNIEnv* env) { + env->GetJavaVM(&gJni.jvm); + // Now we need to find the java classes. + jclass dmgClass = env->FindClass("android/hardware/display/DisplayManagerGlobal"); + gJni.displayManagerGlobal.clazz = static_cast(env->NewGlobalRef(dmgClass)); + gJni.displayManagerGlobal.getInstance = + env->GetStaticMethodID(dmgClass, "getInstance", + "()Landroid/hardware/display/DisplayManagerGlobal;"); + gJni.displayManagerGlobal.registerNativeChoreographerForRefreshRateCallbacks = + env->GetMethodID(dmgClass, "registerNativeChoreographerForRefreshRateCallbacks", "()V"); + gJni.displayManagerGlobal.unregisterNativeChoreographerForRefreshRateCallbacks = + env->GetMethodID(dmgClass, "unregisterNativeChoreographerForRefreshRateCallbacks", + "()V"); +} + +Choreographer* Choreographer::getForThread() { + if (gChoreographer == nullptr) { + sp looper = Looper::getForThread(); + if (!looper.get()) { + ALOGW("No looper prepared for thread"); + return nullptr; + } + gChoreographer = new Choreographer(looper); + status_t result = gChoreographer->initialize(); + if (result != OK) { + ALOGW("Failed to initialize"); + return nullptr; + } + } + return gChoreographer; +} + +Choreographer::Choreographer(const sp& looper, const sp& layerHandle) + : DisplayEventDispatcher(looper, gui::ISurfaceComposer::VsyncSource::eVsyncSourceApp, {}, + layerHandle), + mLooper(looper), + mThreadId(std::this_thread::get_id()) { + std::lock_guard _l(gChoreographers.lock); + gChoreographers.ptrs.push_back(this); +} + +Choreographer::~Choreographer() { + std::lock_guard _l(gChoreographers.lock); + gChoreographers.ptrs.erase(std::remove_if(gChoreographers.ptrs.begin(), + gChoreographers.ptrs.end(), + [=](Choreographer* c) { return c == this; }), + gChoreographers.ptrs.end()); + // Only poke DisplayManagerGlobal to unregister if we previously registered + // callbacks. + if (gChoreographers.ptrs.empty() && gChoreographers.registeredToDisplayManager) { + gChoreographers.registeredToDisplayManager = false; + JNIEnv* env = getJniEnv(); + if (env == nullptr) { + ALOGW("JNI environment is unavailable, skipping choreographer cleanup"); + return; + } + jobject dmg = env->CallStaticObjectMethod(gJni.displayManagerGlobal.clazz, + gJni.displayManagerGlobal.getInstance); + if (dmg == nullptr) { + ALOGW("DMS is not initialized yet, skipping choreographer cleanup"); + } else { + env->CallVoidMethod(dmg, + gJni.displayManagerGlobal + .unregisterNativeChoreographerForRefreshRateCallbacks); + env->DeleteLocalRef(dmg); + } + } +} + +void Choreographer::postFrameCallbackDelayed(AChoreographer_frameCallback cb, + AChoreographer_frameCallback64 cb64, + AChoreographer_vsyncCallback vsyncCallback, void* data, + nsecs_t delay) { + nsecs_t now = systemTime(SYSTEM_TIME_MONOTONIC); + FrameCallback callback{cb, cb64, vsyncCallback, data, now + delay}; + { + std::lock_guard _l{mLock}; + mFrameCallbacks.push(callback); + } + if (callback.dueTime <= now) { + if (std::this_thread::get_id() != mThreadId) { + if (mLooper != nullptr) { + Message m{MSG_SCHEDULE_VSYNC}; + mLooper->sendMessage(this, m); + } else { + scheduleVsync(); + } + } else { + scheduleVsync(); + } + } else { + if (mLooper != nullptr) { + Message m{MSG_SCHEDULE_CALLBACKS}; + mLooper->sendMessageDelayed(delay, this, m); + } else { + scheduleCallbacks(); + } + } +} + +void Choreographer::registerRefreshRateCallback(AChoreographer_refreshRateCallback cb, void* data) { + std::lock_guard _l{mLock}; + for (const auto& callback : mRefreshRateCallbacks) { + // Don't re-add callbacks. + if (cb == callback.callback && data == callback.data) { + return; + } + } + mRefreshRateCallbacks.emplace_back( + RefreshRateCallback{.callback = cb, .data = data, .firstCallbackFired = false}); + bool needsRegistration = false; + { + std::lock_guard _l2(gChoreographers.lock); + needsRegistration = !gChoreographers.registeredToDisplayManager; + } + if (needsRegistration) { + JNIEnv* env = getJniEnv(); + if (env == nullptr) { + ALOGW("JNI environment is unavailable, skipping registration"); + return; + } + jobject dmg = env->CallStaticObjectMethod(gJni.displayManagerGlobal.clazz, + gJni.displayManagerGlobal.getInstance); + if (dmg == nullptr) { + ALOGW("DMS is not initialized yet: skipping registration"); + return; + } else { + env->CallVoidMethod(dmg, + gJni.displayManagerGlobal + .registerNativeChoreographerForRefreshRateCallbacks, + reinterpret_cast(this)); + env->DeleteLocalRef(dmg); + { + std::lock_guard _l2(gChoreographers.lock); + gChoreographers.registeredToDisplayManager = true; + } + } + } else { + scheduleLatestConfigRequest(); + } +} + +void Choreographer::unregisterRefreshRateCallback(AChoreographer_refreshRateCallback cb, + void* data) { + std::lock_guard _l{mLock}; + mRefreshRateCallbacks.erase(std::remove_if(mRefreshRateCallbacks.begin(), + mRefreshRateCallbacks.end(), + [&](const RefreshRateCallback& callback) { + return cb == callback.callback && + data == callback.data; + }), + mRefreshRateCallbacks.end()); +} + +void Choreographer::scheduleLatestConfigRequest() { + if (mLooper != nullptr) { + Message m{MSG_HANDLE_REFRESH_RATE_UPDATES}; + mLooper->sendMessage(this, m); + } else { + // If the looper thread is detached from Choreographer, then refresh rate + // changes will be handled in AChoreographer_handlePendingEvents, so we + // need to wake up the looper thread by writing to the write-end of the + // socket the looper is listening on. + // Fortunately, these events are small so sending packets across the + // socket should be atomic across processes. + DisplayEventReceiver::Event event; + event.header = + DisplayEventReceiver::Event::Header{DisplayEventReceiver::DISPLAY_EVENT_NULL, + PhysicalDisplayId::fromPort(0), systemTime()}; + injectEvent(event); + } +} + +void Choreographer::scheduleCallbacks() { + const nsecs_t now = systemTime(SYSTEM_TIME_MONOTONIC); + nsecs_t dueTime; + { + std::lock_guard _l{mLock}; + // If there are no pending callbacks then don't schedule a vsync + if (mFrameCallbacks.empty()) { + return; + } + dueTime = mFrameCallbacks.top().dueTime; + } + + if (dueTime <= now) { + ALOGV("choreographer %p ~ scheduling vsync", this); + scheduleVsync(); + return; + } +} + +void Choreographer::handleRefreshRateUpdates() { + std::vector callbacks{}; + const nsecs_t pendingPeriod = gChoreographers.mLastKnownVsync.load(); + const nsecs_t lastPeriod = mLatestVsyncPeriod; + if (pendingPeriod > 0) { + mLatestVsyncPeriod = pendingPeriod; + } + { + std::lock_guard _l{mLock}; + for (auto& cb : mRefreshRateCallbacks) { + callbacks.push_back(cb); + cb.firstCallbackFired = true; + } + } + + for (auto& cb : callbacks) { + if (!cb.firstCallbackFired || (pendingPeriod > 0 && pendingPeriod != lastPeriod)) { + cb.callback(pendingPeriod, cb.data); + } + } +} + +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; + for (const auto& cb : callbacks) { + if (cb.vsyncCallback != nullptr) { + ATRACE_FORMAT("AChoreographer_vsyncCallback %" PRId64, + vsyncEventData.preferredVsyncId()); + const ChoreographerFrameCallbackDataImpl frameCallbackData = + createFrameCallbackData(timestamp); + registerStartTime(); + mInCallback = true; + cb.vsyncCallback(reinterpret_cast( + &frameCallbackData), + cb.data); + mInCallback = false; + } else if (cb.callback64 != nullptr) { + ATRACE_FORMAT("AChoreographer_frameCallback64"); + cb.callback64(timestamp, cb.data); + } else if (cb.callback != nullptr) { + ATRACE_FORMAT("AChoreographer_frameCallback"); + cb.callback(timestamp, cb.data); + } + } +} + +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)); +} + +void Choreographer::dispatchModeChanged(nsecs_t, PhysicalDisplayId, int32_t, nsecs_t) { + LOG_ALWAYS_FATAL("dispatchModeChanged was called but was never registered"); +} + +void Choreographer::dispatchFrameRateOverrides(nsecs_t, PhysicalDisplayId, + std::vector) { + LOG_ALWAYS_FATAL("dispatchFrameRateOverrides was called but was never registered"); +} + +void Choreographer::dispatchNullEvent(nsecs_t, PhysicalDisplayId) { + ALOGV("choreographer %p ~ received null event.", this); + handleRefreshRateUpdates(); +} + +void Choreographer::handleMessage(const Message& message) { + switch (message.what) { + case MSG_SCHEDULE_CALLBACKS: + scheduleCallbacks(); + break; + case MSG_SCHEDULE_VSYNC: + scheduleVsync(); + break; + case MSG_HANDLE_REFRESH_RATE_UPDATES: + handleRefreshRateUpdates(); + break; + } +} + +int64_t Choreographer::getFrameInterval() const { + return mLastVsyncEventData.frameInterval; +} + +bool Choreographer::inCallback() const { + return mInCallback; +} + +ChoreographerFrameCallbackDataImpl Choreographer::createFrameCallbackData(nsecs_t timestamp) const { + return {.frameTimeNanos = timestamp, + .vsyncEventData = mLastVsyncEventData, + .choreographer = this}; +} + +void Choreographer::registerStartTime() const { + std::scoped_lock _l(gChoreographers.lock); + for (VsyncEventData::FrameTimeline frameTimeline : mLastVsyncEventData.frameTimelines) { + while (gChoreographers.startTimes.size() >= kMaxStartTimes) { + gChoreographers.startTimes.erase(gChoreographers.startTimes.begin()); + } + gChoreographers.startTimes[frameTimeline.vsyncId] = systemTime(SYSTEM_TIME_MONOTONIC); + } +} + +void Choreographer::signalRefreshRateCallbacks(nsecs_t vsyncPeriod) { + std::lock_guard _l(gChoreographers.lock); + gChoreographers.mLastKnownVsync.store(vsyncPeriod); + for (auto c : gChoreographers.ptrs) { + c->scheduleLatestConfigRequest(); + } +} + +int64_t Choreographer::getStartTimeNanosForVsyncId(AVsyncId vsyncId) { + std::scoped_lock _l(gChoreographers.lock); + const auto iter = gChoreographers.startTimes.find(vsyncId); + if (iter == gChoreographers.startTimes.end()) { + ALOGW("Start time was not found for vsync id: %" PRId64, vsyncId); + return 0; + } + return iter->second; +} + +} // namespace android \ No newline at end of file diff --git a/libs/gui/CompositorTiming.cpp b/libs/gui/CompositorTiming.cpp new file mode 100644 index 0000000000000000000000000000000000000000..50f7b252b6570517184a31cbe37da14dc1d8a0f7 --- /dev/null +++ b/libs/gui/CompositorTiming.cpp @@ -0,0 +1,54 @@ +/* + * 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. + */ + +#define LOG_TAG "CompositorTiming" + +#include +#include +#include + +namespace android::gui { + +CompositorTiming::CompositorTiming(nsecs_t vsyncDeadline, nsecs_t vsyncPeriod, nsecs_t vsyncPhase, + nsecs_t presentLatency) { + if (CC_UNLIKELY(vsyncPeriod <= 0)) { + ALOGE("Invalid VSYNC period"); + return; + } + + const nsecs_t idealLatency = [=] { + // Modulo rounds toward 0 not INT64_MIN, so treat signs separately. + if (vsyncPhase < 0) return -vsyncPhase % vsyncPeriod; + + const nsecs_t latency = (vsyncPeriod - vsyncPhase) % vsyncPeriod; + return latency > 0 ? latency : vsyncPeriod; + }(); + + // Snap the latency to a value that removes scheduling jitter from the composite and present + // times, which often have >1ms of jitter. Reducing jitter is important if an app attempts to + // extrapolate something like user input to an accurate present time. Snapping also allows an + // app to precisely calculate vsyncPhase with (presentLatency % interval). + const nsecs_t bias = vsyncPeriod / 2; + const nsecs_t extraVsyncs = (presentLatency - idealLatency + bias) / vsyncPeriod; + const nsecs_t snappedLatency = + extraVsyncs > 0 ? idealLatency + extraVsyncs * vsyncPeriod : idealLatency; + + this->deadline = vsyncDeadline - idealLatency; + this->interval = vsyncPeriod; + this->presentLatency = snappedLatency; +} + +} // namespace android::gui diff --git a/libs/gui/DisplayEventDispatcher.cpp b/libs/gui/DisplayEventDispatcher.cpp index dfdce2043882829bdaf739d74ddb943c3d019187..8a883770d851f855496c588e7ef3e3ca97834a48 100644 --- a/libs/gui/DisplayEventDispatcher.cpp +++ b/libs/gui/DisplayEventDispatcher.cpp @@ -35,11 +35,15 @@ static const size_t EVENT_BUFFER_SIZE = 100; static constexpr nsecs_t WAITING_FOR_VSYNC_TIMEOUT = ms2ns(300); -DisplayEventDispatcher::DisplayEventDispatcher( - const sp& looper, ISurfaceComposer::VsyncSource vsyncSource, - ISurfaceComposer::EventRegistrationFlags eventRegistration) - : mLooper(looper), mReceiver(vsyncSource, eventRegistration), mWaitingForVsync(false), - mLastVsyncCount(0), mLastScheduleVsyncTime(0) { +DisplayEventDispatcher::DisplayEventDispatcher(const sp& looper, + gui::ISurfaceComposer::VsyncSource vsyncSource, + EventRegistrationFlags eventRegistration, + const sp& layerHandle) + : mLooper(looper), + mReceiver(vsyncSource, eventRegistration, layerHandle), + mWaitingForVsync(false), + mLastVsyncCount(0), + mLastScheduleVsyncTime(0) { ALOGV("dispatcher %p ~ Initializing display event dispatcher.", this); } diff --git a/libs/gui/DisplayEventReceiver.cpp b/libs/gui/DisplayEventReceiver.cpp index bfb77699c08e7084a23fee23c2435e85693ee313..6849a95d1ecd5ea575f42895489757417af26200 100644 --- a/libs/gui/DisplayEventReceiver.cpp +++ b/libs/gui/DisplayEventReceiver.cpp @@ -14,15 +14,16 @@ * limitations under the License. */ +#define LOG_TAG "DisplayEventReceiver" + #include #include #include -#include #include -#include +#include #include @@ -32,21 +33,29 @@ namespace android { // --------------------------------------------------------------------------- -DisplayEventReceiver::DisplayEventReceiver( - ISurfaceComposer::VsyncSource vsyncSource, - ISurfaceComposer::EventRegistrationFlags eventRegistration) { - sp sf(ComposerService::getComposerService()); +DisplayEventReceiver::DisplayEventReceiver(gui::ISurfaceComposer::VsyncSource vsyncSource, + EventRegistrationFlags eventRegistration, + const sp& layerHandle) { + sp sf(ComposerServiceAIDL::getComposerService()); if (sf != nullptr) { - mEventConnection = sf->createDisplayEventConnection(vsyncSource, eventRegistration); - if (mEventConnection != nullptr) { + mEventConnection = nullptr; + binder::Status status = + sf->createDisplayEventConnection(vsyncSource, + static_cast< + gui::ISurfaceComposer::EventRegistration>( + eventRegistration.get()), + layerHandle, &mEventConnection); + if (status.isOk() && mEventConnection != nullptr) { mDataChannel = std::make_unique(); - const auto status = mEventConnection->stealReceiveChannel(mDataChannel.get()); + status = mEventConnection->stealReceiveChannel(mDataChannel.get()); if (!status.isOk()) { ALOGE("stealReceiveChannel failed: %s", status.toString8().c_str()); mInitError = std::make_optional(status.transactionError()); mDataChannel.reset(); mEventConnection.clear(); } + } else { + ALOGE("DisplayEventConnection creation failed: status=%s", status.toString8().c_str()); } } } diff --git a/libs/gui/DisplayInfo.cpp b/libs/gui/DisplayInfo.cpp index 52d9540eeb5049d3ceac87bd9cb743ec6da96a7d..bd640df81eaf64eb7322a2fe38860ace290241ae 100644 --- a/libs/gui/DisplayInfo.cpp +++ b/libs/gui/DisplayInfo.cpp @@ -20,8 +20,13 @@ #include #include +#include #include +#include + +#define INDENT " " + namespace android::gui { // --- DisplayInfo --- @@ -67,4 +72,17 @@ status_t DisplayInfo::writeToParcel(android::Parcel* parcel) const { return OK; } +void DisplayInfo::dump(std::string& out, const char* prefix) const { + using android::base::StringAppendF; + + out += prefix; + StringAppendF(&out, "DisplayViewport[id=%" PRId32 "]\n", displayId); + out += prefix; + StringAppendF(&out, INDENT "Width=%" PRId32 ", Height=%" PRId32 "\n", logicalWidth, + logicalHeight); + std::string transformPrefix(prefix); + transformPrefix.append(INDENT); + transform.dump(out, "Transform", transformPrefix.c_str()); +} + } // namespace android::gui diff --git a/libs/gui/FenceMonitor.cpp b/libs/gui/FenceMonitor.cpp new file mode 100644 index 0000000000000000000000000000000000000000..230c81a0b3a5170b3a0dd0c67a550b75f346835a --- /dev/null +++ b/libs/gui/FenceMonitor.cpp @@ -0,0 +1,89 @@ +/* + * 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. + */ + +#define ATRACE_TAG ATRACE_TAG_GRAPHICS + +#include +#include +#include + +#include + +namespace android::gui { + +FenceMonitor::FenceMonitor(const char* name) : mName(name), mFencesQueued(0), mFencesSignaled(0) { + std::thread thread(&FenceMonitor::loop, this); + pthread_setname_np(thread.native_handle(), mName); + thread.detach(); +} + +void FenceMonitor::queueFence(const sp& fence) { + char message[64]; + + std::lock_guard lock(mMutex); + if (fence->getSignalTime() != Fence::SIGNAL_TIME_PENDING) { + snprintf(message, sizeof(message), "%s fence %u has signaled", mName, mFencesQueued); + ATRACE_NAME(message); + // Need an increment on both to make the trace number correct. + mFencesQueued++; + mFencesSignaled++; + return; + } + snprintf(message, sizeof(message), "Trace %s fence %u", mName, mFencesQueued); + ATRACE_NAME(message); + + mQueue.push_back(fence); + mCondition.notify_one(); + mFencesQueued++; + ATRACE_INT(mName, int32_t(mQueue.size())); +} + +void FenceMonitor::loop() { + while (true) { + threadLoop(); + } +} + +void FenceMonitor::threadLoop() { + sp fence; + uint32_t fenceNum; + { + std::unique_lock lock(mMutex); + while (mQueue.empty()) { + mCondition.wait(lock); + } + fence = mQueue[0]; + fenceNum = mFencesSignaled; + } + { + char message[64]; + snprintf(message, sizeof(message), "waiting for %s %u", mName, fenceNum); + ATRACE_NAME(message); + + status_t result = fence->waitForever(message); + if (result != OK) { + ALOGE("Error waiting for fence: %d", result); + } + } + { + std::lock_guard lock(mMutex); + mQueue.pop_front(); + mFencesSignaled++; + ATRACE_INT(mName, int32_t(mQueue.size())); + } +} + +} // namespace android::gui \ No newline at end of file diff --git a/libs/gui/FrameTimelineInfo.cpp b/libs/gui/FrameTimelineInfo.cpp deleted file mode 100644 index 3800b88ab0ddbff709a7f937bb1d288e6f78be77..0000000000000000000000000000000000000000 --- a/libs/gui/FrameTimelineInfo.cpp +++ /dev/null @@ -1,68 +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. - */ - -#define LOG_TAG "FrameTimelineInfo" - -#include - -#include -#include -#include -#include -#include - -#include - -using android::os::IInputConstants; - -namespace android { - -status_t FrameTimelineInfo::write(Parcel& output) const { - SAFE_PARCEL(output.writeInt64, vsyncId); - SAFE_PARCEL(output.writeInt32, inputEventId); - SAFE_PARCEL(output.writeInt64, startTimeNanos); - return NO_ERROR; -} - -status_t FrameTimelineInfo::read(const Parcel& input) { - SAFE_PARCEL(input.readInt64, &vsyncId); - SAFE_PARCEL(input.readInt32, &inputEventId); - SAFE_PARCEL(input.readInt64, &startTimeNanos); - return NO_ERROR; -} - -void FrameTimelineInfo::merge(const FrameTimelineInfo& other) { - // When merging vsync Ids we take the oldest valid one - if (vsyncId != INVALID_VSYNC_ID && other.vsyncId != INVALID_VSYNC_ID) { - if (other.vsyncId > vsyncId) { - vsyncId = other.vsyncId; - inputEventId = other.inputEventId; - startTimeNanos = other.startTimeNanos; - } - } else if (vsyncId == INVALID_VSYNC_ID) { - vsyncId = other.vsyncId; - inputEventId = other.inputEventId; - startTimeNanos = other.startTimeNanos; - } -} - -void FrameTimelineInfo::clear() { - vsyncId = INVALID_VSYNC_ID; - inputEventId = IInputConstants::INVALID_INPUT_EVENT_ID; - startTimeNanos = 0; -} - -}; // namespace android diff --git a/libs/gui/GLConsumerUtils.cpp b/libs/gui/GLConsumerUtils.cpp index 7a06c3d801699dc051b6e0a7a2c4c3267762dda3..a1c69e7d6d5680fcb501e8852563969703ae0494 100644 --- a/libs/gui/GLConsumerUtils.cpp +++ b/libs/gui/GLConsumerUtils.cpp @@ -27,6 +27,13 @@ namespace android { void GLConsumer::computeTransformMatrix(float outTransform[16], const sp& buf, const Rect& cropRect, uint32_t transform, bool filtering) { + computeTransformMatrix(outTransform, buf->getWidth(), buf->getHeight(), buf->getPixelFormat(), + cropRect, transform, filtering); +} + +void GLConsumer::computeTransformMatrix(float outTransform[16], float bufferWidth, + float bufferHeight, PixelFormat pixelFormat, + const Rect& cropRect, uint32_t transform, bool filtering) { // Transform matrices static const mat4 mtxFlipH( -1, 0, 0, 0, @@ -60,8 +67,6 @@ void GLConsumer::computeTransformMatrix(float outTransform[16], if (!cropRect.isEmpty()) { float tx = 0.0f, ty = 0.0f, sx = 1.0f, sy = 1.0f; - float bufferWidth = buf->getWidth(); - float bufferHeight = buf->getHeight(); float shrinkAmount = 0.0f; if (filtering) { // In order to prevent bilinear sampling beyond the edge of the @@ -70,7 +75,7 @@ void GLConsumer::computeTransformMatrix(float outTransform[16], // off each end, but because the chroma channels of YUV420 images // are subsampled we may need to shrink the crop region by a whole // texel on each side. - switch (buf->getPixelFormat()) { + switch (pixelFormat) { case PIXEL_FORMAT_RGBA_8888: case PIXEL_FORMAT_RGBX_8888: case PIXEL_FORMAT_RGBA_FP16: diff --git a/libs/gui/ISurfaceComposer.cpp b/libs/gui/ISurfaceComposer.cpp index 24d39fe86ac35105b0bc7c44edf1d23e395e5941..b526a6c92cabdcbdab7f560552e1f08dfbb98b0d 100644 --- a/libs/gui/ISurfaceComposer.cpp +++ b/libs/gui/ISurfaceComposer.cpp @@ -19,14 +19,11 @@ #include #include -#include #include #include #include #include #include -#include -#include #include #include #include @@ -37,7 +34,6 @@ #include #include #include -#include #include // --------------------------------------------------------------------------- @@ -63,26 +59,17 @@ public: virtual ~BpSurfaceComposer(); - virtual sp createConnection() - { - Parcel data, reply; - data.writeInterfaceToken(ISurfaceComposer::getInterfaceDescriptor()); - remote()->transact(BnSurfaceComposer::CREATE_CONNECTION, data, &reply); - return interface_cast(reply.readStrongBinder()); - } - - status_t setTransactionState(const FrameTimelineInfo& frameTimelineInfo, - const Vector& state, - const Vector& displays, uint32_t flags, - const sp& applyToken, const InputWindowCommands& commands, - int64_t desiredPresentTime, bool isAutoTimestamp, - const client_cache_t& uncacheBuffer, bool hasListenerCallbacks, - const std::vector& listenerCallbacks, - uint64_t transactionId) override { + status_t setTransactionState( + const FrameTimelineInfo& frameTimelineInfo, Vector& state, + const Vector& displays, uint32_t flags, const sp& applyToken, + InputWindowCommands commands, int64_t desiredPresentTime, bool isAutoTimestamp, + const std::vector& uncacheBuffers, bool hasListenerCallbacks, + const std::vector& listenerCallbacks, uint64_t transactionId, + const std::vector& mergedTransactionIds) override { Parcel data, reply; data.writeInterfaceToken(ISurfaceComposer::getInterfaceDescriptor()); - SAFE_PARCEL(frameTimelineInfo.write, data); + frameTimelineInfo.writeToParcel(&data); SAFE_PARCEL(data.writeUint32, static_cast(state.size())); for (const auto& s : state) { @@ -99,8 +86,11 @@ public: SAFE_PARCEL(commands.write, data); SAFE_PARCEL(data.writeInt64, desiredPresentTime); SAFE_PARCEL(data.writeBool, isAutoTimestamp); - SAFE_PARCEL(data.writeStrongBinder, uncacheBuffer.token.promote()); - SAFE_PARCEL(data.writeUint64, uncacheBuffer.id); + SAFE_PARCEL(data.writeUint32, static_cast(uncacheBuffers.size())); + for (const client_cache_t& uncacheBuffer : uncacheBuffers) { + SAFE_PARCEL(data.writeStrongBinder, uncacheBuffer.token.promote()); + SAFE_PARCEL(data.writeUint64, uncacheBuffer.id); + } SAFE_PARCEL(data.writeBool, hasListenerCallbacks); SAFE_PARCEL(data.writeVectorSize, listenerCallbacks); @@ -111,6 +101,11 @@ public: SAFE_PARCEL(data.writeUint64, transactionId); + SAFE_PARCEL(data.writeUint32, static_cast(mergedTransactionIds.size())); + for (auto mergedTransactionId : mergedTransactionIds) { + SAFE_PARCEL(data.writeUint64, mergedTransactionId); + } + if (flags & ISurfaceComposer::eOneWay) { return remote()->transact(BnSurfaceComposer::SET_TRANSACTION_STATE, data, &reply, IBinder::FLAG_ONEWAY); @@ -119,905 +114,6 @@ public: data, &reply); } } - - void bootFinished() override { - Parcel data, reply; - data.writeInterfaceToken(ISurfaceComposer::getInterfaceDescriptor()); - remote()->transact(BnSurfaceComposer::BOOT_FINISHED, data, &reply); - } - - bool authenticateSurfaceTexture( - const sp& bufferProducer) const override { - Parcel data, reply; - int err = NO_ERROR; - err = data.writeInterfaceToken( - ISurfaceComposer::getInterfaceDescriptor()); - if (err != NO_ERROR) { - ALOGE("ISurfaceComposer::authenticateSurfaceTexture: error writing " - "interface descriptor: %s (%d)", strerror(-err), -err); - return false; - } - err = data.writeStrongBinder(IInterface::asBinder(bufferProducer)); - if (err != NO_ERROR) { - ALOGE("ISurfaceComposer::authenticateSurfaceTexture: error writing " - "strong binder to parcel: %s (%d)", strerror(-err), -err); - return false; - } - err = remote()->transact(BnSurfaceComposer::AUTHENTICATE_SURFACE, data, - &reply); - if (err != NO_ERROR) { - ALOGE("ISurfaceComposer::authenticateSurfaceTexture: error " - "performing transaction: %s (%d)", strerror(-err), -err); - return false; - } - int32_t result = 0; - err = reply.readInt32(&result); - if (err != NO_ERROR) { - ALOGE("ISurfaceComposer::authenticateSurfaceTexture: error " - "retrieving result: %s (%d)", strerror(-err), -err); - return false; - } - return result != 0; - } - - status_t getSupportedFrameTimestamps(std::vector* outSupported) const override { - if (!outSupported) { - return UNEXPECTED_NULL; - } - outSupported->clear(); - - Parcel data, reply; - - status_t err = data.writeInterfaceToken( - ISurfaceComposer::getInterfaceDescriptor()); - if (err != NO_ERROR) { - return err; - } - - err = remote()->transact( - BnSurfaceComposer::GET_SUPPORTED_FRAME_TIMESTAMPS, - data, &reply); - if (err != NO_ERROR) { - return err; - } - - int32_t result = 0; - err = reply.readInt32(&result); - if (err != NO_ERROR) { - return err; - } - if (result != NO_ERROR) { - return result; - } - - std::vector supported; - err = reply.readInt32Vector(&supported); - if (err != NO_ERROR) { - return err; - } - - outSupported->reserve(supported.size()); - for (int32_t s : supported) { - outSupported->push_back(static_cast(s)); - } - return NO_ERROR; - } - - sp createDisplayEventConnection( - VsyncSource vsyncSource, EventRegistrationFlags eventRegistration) override { - Parcel data, reply; - sp result; - int err = data.writeInterfaceToken( - ISurfaceComposer::getInterfaceDescriptor()); - if (err != NO_ERROR) { - return result; - } - data.writeInt32(static_cast(vsyncSource)); - data.writeUint32(eventRegistration.get()); - err = remote()->transact( - BnSurfaceComposer::CREATE_DISPLAY_EVENT_CONNECTION, - data, &reply); - if (err != NO_ERROR) { - ALOGE("ISurfaceComposer::createDisplayEventConnection: error performing " - "transaction: %s (%d)", strerror(-err), -err); - return result; - } - result = interface_cast(reply.readStrongBinder()); - return result; - } - - status_t getStaticDisplayInfo(const sp& display, - ui::StaticDisplayInfo* info) override { - Parcel data, reply; - data.writeInterfaceToken(ISurfaceComposer::getInterfaceDescriptor()); - data.writeStrongBinder(display); - remote()->transact(BnSurfaceComposer::GET_STATIC_DISPLAY_INFO, data, &reply); - const status_t result = reply.readInt32(); - if (result != NO_ERROR) return result; - return reply.read(*info); - } - - status_t getDynamicDisplayInfo(const sp& display, - ui::DynamicDisplayInfo* info) override { - Parcel data, reply; - data.writeInterfaceToken(ISurfaceComposer::getInterfaceDescriptor()); - data.writeStrongBinder(display); - remote()->transact(BnSurfaceComposer::GET_DYNAMIC_DISPLAY_INFO, data, &reply); - const status_t result = reply.readInt32(); - if (result != NO_ERROR) return result; - return reply.read(*info); - } - - status_t getDisplayNativePrimaries(const sp& display, - ui::DisplayPrimaries& primaries) override { - Parcel data, reply; - status_t result = data.writeInterfaceToken(ISurfaceComposer::getInterfaceDescriptor()); - if (result != NO_ERROR) { - ALOGE("getDisplayNativePrimaries failed to writeInterfaceToken: %d", result); - return result; - } - result = data.writeStrongBinder(display); - if (result != NO_ERROR) { - ALOGE("getDisplayNativePrimaries failed to writeStrongBinder: %d", result); - return result; - } - result = remote()->transact(BnSurfaceComposer::GET_DISPLAY_NATIVE_PRIMARIES, data, &reply); - if (result != NO_ERROR) { - ALOGE("getDisplayNativePrimaries failed to transact: %d", result); - return result; - } - result = reply.readInt32(); - if (result == NO_ERROR) { - memcpy(&primaries, reply.readInplace(sizeof(ui::DisplayPrimaries)), - sizeof(ui::DisplayPrimaries)); - } - return result; - } - - status_t setActiveColorMode(const sp& display, ColorMode colorMode) override { - Parcel data, reply; - status_t result = data.writeInterfaceToken(ISurfaceComposer::getInterfaceDescriptor()); - if (result != NO_ERROR) { - ALOGE("setActiveColorMode failed to writeInterfaceToken: %d", result); - return result; - } - result = data.writeStrongBinder(display); - if (result != NO_ERROR) { - ALOGE("setActiveColorMode failed to writeStrongBinder: %d", result); - return result; - } - result = data.writeInt32(static_cast(colorMode)); - if (result != NO_ERROR) { - ALOGE("setActiveColorMode failed to writeInt32: %d", result); - return result; - } - result = remote()->transact(BnSurfaceComposer::SET_ACTIVE_COLOR_MODE, data, &reply); - if (result != NO_ERROR) { - ALOGE("setActiveColorMode failed to transact: %d", result); - return result; - } - return static_cast(reply.readInt32()); - } - - status_t setBootDisplayMode(const sp& display, - ui::DisplayModeId displayModeId) override { - Parcel data, reply; - status_t result = data.writeInterfaceToken(ISurfaceComposer::getInterfaceDescriptor()); - if (result != NO_ERROR) { - ALOGE("setBootDisplayMode failed to writeInterfaceToken: %d", result); - return result; - } - result = data.writeStrongBinder(display); - if (result != NO_ERROR) { - ALOGE("setBootDisplayMode failed to writeStrongBinder: %d", result); - return result; - } - result = data.writeInt32(displayModeId); - if (result != NO_ERROR) { - ALOGE("setBootDisplayMode failed to writeIint32: %d", result); - return result; - } - result = remote()->transact(BnSurfaceComposer::SET_BOOT_DISPLAY_MODE, data, &reply); - if (result != NO_ERROR) { - ALOGE("setBootDisplayMode failed to transact: %d", result); - } - return result; - } - - status_t clearAnimationFrameStats() override { - Parcel data, reply; - status_t result = data.writeInterfaceToken(ISurfaceComposer::getInterfaceDescriptor()); - if (result != NO_ERROR) { - ALOGE("clearAnimationFrameStats failed to writeInterfaceToken: %d", result); - return result; - } - result = remote()->transact(BnSurfaceComposer::CLEAR_ANIMATION_FRAME_STATS, data, &reply); - if (result != NO_ERROR) { - ALOGE("clearAnimationFrameStats failed to transact: %d", result); - return result; - } - return reply.readInt32(); - } - - status_t getAnimationFrameStats(FrameStats* outStats) const override { - Parcel data, reply; - data.writeInterfaceToken(ISurfaceComposer::getInterfaceDescriptor()); - remote()->transact(BnSurfaceComposer::GET_ANIMATION_FRAME_STATS, data, &reply); - reply.read(*outStats); - return reply.readInt32(); - } - - virtual status_t overrideHdrTypes(const sp& display, - const std::vector& hdrTypes) { - Parcel data, reply; - SAFE_PARCEL(data.writeInterfaceToken, ISurfaceComposer::getInterfaceDescriptor()); - SAFE_PARCEL(data.writeStrongBinder, display); - - std::vector hdrTypesVector; - for (ui::Hdr i : hdrTypes) { - hdrTypesVector.push_back(static_cast(i)); - } - SAFE_PARCEL(data.writeInt32Vector, hdrTypesVector); - - status_t result = remote()->transact(BnSurfaceComposer::OVERRIDE_HDR_TYPES, data, &reply); - if (result != NO_ERROR) { - ALOGE("overrideHdrTypes failed to transact: %d", result); - return result; - } - return result; - } - - status_t onPullAtom(const int32_t atomId, std::string* pulledData, bool* success) { - Parcel data, reply; - SAFE_PARCEL(data.writeInterfaceToken, ISurfaceComposer::getInterfaceDescriptor()); - SAFE_PARCEL(data.writeInt32, atomId); - - status_t err = remote()->transact(BnSurfaceComposer::ON_PULL_ATOM, data, &reply); - if (err != NO_ERROR) { - ALOGE("onPullAtom failed to transact: %d", err); - return err; - } - - int32_t size = 0; - SAFE_PARCEL(reply.readInt32, &size); - const void* dataPtr = reply.readInplace(size); - if (dataPtr == nullptr) { - return UNEXPECTED_NULL; - } - pulledData->assign((const char*)dataPtr, size); - SAFE_PARCEL(reply.readBool, success); - return NO_ERROR; - } - - status_t enableVSyncInjections(bool enable) override { - Parcel data, reply; - status_t result = data.writeInterfaceToken(ISurfaceComposer::getInterfaceDescriptor()); - if (result != NO_ERROR) { - ALOGE("enableVSyncInjections failed to writeInterfaceToken: %d", result); - return result; - } - result = data.writeBool(enable); - if (result != NO_ERROR) { - ALOGE("enableVSyncInjections failed to writeBool: %d", result); - return result; - } - result = remote()->transact(BnSurfaceComposer::ENABLE_VSYNC_INJECTIONS, data, &reply, - IBinder::FLAG_ONEWAY); - if (result != NO_ERROR) { - ALOGE("enableVSyncInjections failed to transact: %d", result); - return result; - } - return result; - } - - status_t injectVSync(nsecs_t when) override { - Parcel data, reply; - status_t result = data.writeInterfaceToken(ISurfaceComposer::getInterfaceDescriptor()); - if (result != NO_ERROR) { - ALOGE("injectVSync failed to writeInterfaceToken: %d", result); - return result; - } - result = data.writeInt64(when); - if (result != NO_ERROR) { - ALOGE("injectVSync failed to writeInt64: %d", result); - return result; - } - result = remote()->transact(BnSurfaceComposer::INJECT_VSYNC, data, &reply, - IBinder::FLAG_ONEWAY); - if (result != NO_ERROR) { - ALOGE("injectVSync failed to transact: %d", result); - return result; - } - return result; - } - - status_t getLayerDebugInfo(std::vector* outLayers) override { - if (!outLayers) { - return UNEXPECTED_NULL; - } - - Parcel data, reply; - - status_t err = data.writeInterfaceToken(ISurfaceComposer::getInterfaceDescriptor()); - if (err != NO_ERROR) { - return err; - } - - err = remote()->transact(BnSurfaceComposer::GET_LAYER_DEBUG_INFO, data, &reply); - if (err != NO_ERROR) { - return err; - } - - int32_t result = 0; - err = reply.readInt32(&result); - if (err != NO_ERROR) { - return err; - } - if (result != NO_ERROR) { - return result; - } - - outLayers->clear(); - return reply.readParcelableVector(outLayers); - } - - status_t getCompositionPreference(ui::Dataspace* defaultDataspace, - ui::PixelFormat* defaultPixelFormat, - ui::Dataspace* wideColorGamutDataspace, - ui::PixelFormat* wideColorGamutPixelFormat) const override { - Parcel data, reply; - status_t error = data.writeInterfaceToken(ISurfaceComposer::getInterfaceDescriptor()); - if (error != NO_ERROR) { - return error; - } - error = remote()->transact(BnSurfaceComposer::GET_COMPOSITION_PREFERENCE, data, &reply); - if (error != NO_ERROR) { - return error; - } - error = static_cast(reply.readInt32()); - if (error == NO_ERROR) { - *defaultDataspace = static_cast(reply.readInt32()); - *defaultPixelFormat = static_cast(reply.readInt32()); - *wideColorGamutDataspace = static_cast(reply.readInt32()); - *wideColorGamutPixelFormat = static_cast(reply.readInt32()); - } - return error; - } - - status_t getColorManagement(bool* outGetColorManagement) const override { - Parcel data, reply; - data.writeInterfaceToken(ISurfaceComposer::getInterfaceDescriptor()); - remote()->transact(BnSurfaceComposer::GET_COLOR_MANAGEMENT, data, &reply); - bool result; - status_t err = reply.readBool(&result); - if (err == NO_ERROR) { - *outGetColorManagement = result; - } - return err; - } - - status_t getDisplayedContentSamplingAttributes(const sp& display, - ui::PixelFormat* outFormat, - ui::Dataspace* outDataspace, - uint8_t* outComponentMask) const override { - if (!outFormat || !outDataspace || !outComponentMask) return BAD_VALUE; - Parcel data, reply; - data.writeInterfaceToken(ISurfaceComposer::getInterfaceDescriptor()); - data.writeStrongBinder(display); - - status_t error = - remote()->transact(BnSurfaceComposer::GET_DISPLAYED_CONTENT_SAMPLING_ATTRIBUTES, - data, &reply); - if (error != NO_ERROR) { - return error; - } - - uint32_t value = 0; - error = reply.readUint32(&value); - if (error != NO_ERROR) { - return error; - } - *outFormat = static_cast(value); - - error = reply.readUint32(&value); - if (error != NO_ERROR) { - return error; - } - *outDataspace = static_cast(value); - - error = reply.readUint32(&value); - if (error != NO_ERROR) { - return error; - } - *outComponentMask = static_cast(value); - return error; - } - - status_t setDisplayContentSamplingEnabled(const sp& display, bool enable, - uint8_t componentMask, uint64_t maxFrames) override { - Parcel data, reply; - data.writeInterfaceToken(ISurfaceComposer::getInterfaceDescriptor()); - data.writeStrongBinder(display); - data.writeBool(enable); - data.writeByte(static_cast(componentMask)); - data.writeUint64(maxFrames); - status_t result = - remote()->transact(BnSurfaceComposer::SET_DISPLAY_CONTENT_SAMPLING_ENABLED, data, - &reply); - return result; - } - - status_t getDisplayedContentSample(const sp& display, uint64_t maxFrames, - uint64_t timestamp, - DisplayedFrameStats* outStats) const override { - if (!outStats) return BAD_VALUE; - - Parcel data, reply; - data.writeInterfaceToken(ISurfaceComposer::getInterfaceDescriptor()); - data.writeStrongBinder(display); - data.writeUint64(maxFrames); - data.writeUint64(timestamp); - - status_t result = - remote()->transact(BnSurfaceComposer::GET_DISPLAYED_CONTENT_SAMPLE, data, &reply); - - if (result != NO_ERROR) { - return result; - } - - result = reply.readUint64(&outStats->numFrames); - if (result != NO_ERROR) { - return result; - } - - result = reply.readUint64Vector(&outStats->component_0_sample); - if (result != NO_ERROR) { - return result; - } - result = reply.readUint64Vector(&outStats->component_1_sample); - if (result != NO_ERROR) { - return result; - } - result = reply.readUint64Vector(&outStats->component_2_sample); - if (result != NO_ERROR) { - return result; - } - result = reply.readUint64Vector(&outStats->component_3_sample); - return result; - } - - status_t getProtectedContentSupport(bool* outSupported) const override { - Parcel data, reply; - data.writeInterfaceToken(ISurfaceComposer::getInterfaceDescriptor()); - status_t error = - remote()->transact(BnSurfaceComposer::GET_PROTECTED_CONTENT_SUPPORT, data, &reply); - if (error != NO_ERROR) { - return error; - } - error = reply.readBool(outSupported); - return error; - } - - status_t addRegionSamplingListener(const Rect& samplingArea, const sp& stopLayerHandle, - const sp& listener) override { - Parcel data, reply; - status_t error = data.writeInterfaceToken(ISurfaceComposer::getInterfaceDescriptor()); - if (error != NO_ERROR) { - ALOGE("addRegionSamplingListener: Failed to write interface token"); - return error; - } - error = data.write(samplingArea); - if (error != NO_ERROR) { - ALOGE("addRegionSamplingListener: Failed to write sampling area"); - return error; - } - error = data.writeStrongBinder(stopLayerHandle); - if (error != NO_ERROR) { - ALOGE("addRegionSamplingListener: Failed to write stop layer handle"); - return error; - } - error = data.writeStrongBinder(IInterface::asBinder(listener)); - if (error != NO_ERROR) { - ALOGE("addRegionSamplingListener: Failed to write listener"); - return error; - } - error = remote()->transact(BnSurfaceComposer::ADD_REGION_SAMPLING_LISTENER, data, &reply); - if (error != NO_ERROR) { - ALOGE("addRegionSamplingListener: Failed to transact"); - } - return error; - } - - status_t removeRegionSamplingListener(const sp& listener) override { - Parcel data, reply; - status_t error = data.writeInterfaceToken(ISurfaceComposer::getInterfaceDescriptor()); - if (error != NO_ERROR) { - ALOGE("removeRegionSamplingListener: Failed to write interface token"); - return error; - } - error = data.writeStrongBinder(IInterface::asBinder(listener)); - if (error != NO_ERROR) { - ALOGE("removeRegionSamplingListener: Failed to write listener"); - return error; - } - error = remote()->transact(BnSurfaceComposer::REMOVE_REGION_SAMPLING_LISTENER, data, - &reply); - if (error != NO_ERROR) { - ALOGE("removeRegionSamplingListener: Failed to transact"); - } - return error; - } - - virtual status_t addFpsListener(int32_t taskId, const sp& listener) { - Parcel data, reply; - SAFE_PARCEL(data.writeInterfaceToken, ISurfaceComposer::getInterfaceDescriptor()); - SAFE_PARCEL(data.writeInt32, taskId); - SAFE_PARCEL(data.writeStrongBinder, IInterface::asBinder(listener)); - const status_t error = - remote()->transact(BnSurfaceComposer::ADD_FPS_LISTENER, data, &reply); - if (error != OK) { - ALOGE("addFpsListener: Failed to transact"); - } - return error; - } - - virtual status_t removeFpsListener(const sp& listener) { - Parcel data, reply; - SAFE_PARCEL(data.writeInterfaceToken, ISurfaceComposer::getInterfaceDescriptor()); - SAFE_PARCEL(data.writeStrongBinder, IInterface::asBinder(listener)); - - const status_t error = - remote()->transact(BnSurfaceComposer::REMOVE_FPS_LISTENER, data, &reply); - if (error != OK) { - ALOGE("removeFpsListener: Failed to transact"); - } - return error; - } - - virtual status_t addTunnelModeEnabledListener( - const sp& listener) { - Parcel data, reply; - SAFE_PARCEL(data.writeInterfaceToken, ISurfaceComposer::getInterfaceDescriptor()); - SAFE_PARCEL(data.writeStrongBinder, IInterface::asBinder(listener)); - - const status_t error = - remote()->transact(BnSurfaceComposer::ADD_TUNNEL_MODE_ENABLED_LISTENER, data, - &reply); - if (error != NO_ERROR) { - ALOGE("addTunnelModeEnabledListener: Failed to transact"); - } - return error; - } - - virtual status_t removeTunnelModeEnabledListener( - const sp& listener) { - Parcel data, reply; - SAFE_PARCEL(data.writeInterfaceToken, ISurfaceComposer::getInterfaceDescriptor()); - SAFE_PARCEL(data.writeStrongBinder, IInterface::asBinder(listener)); - - const status_t error = - remote()->transact(BnSurfaceComposer::REMOVE_TUNNEL_MODE_ENABLED_LISTENER, data, - &reply); - if (error != NO_ERROR) { - ALOGE("removeTunnelModeEnabledListener: Failed to transact"); - } - return error; - } - - status_t setDesiredDisplayModeSpecs(const sp& displayToken, - ui::DisplayModeId defaultMode, bool allowGroupSwitching, - float primaryRefreshRateMin, float primaryRefreshRateMax, - float appRequestRefreshRateMin, - float appRequestRefreshRateMax) override { - Parcel data, reply; - status_t result = data.writeInterfaceToken(ISurfaceComposer::getInterfaceDescriptor()); - if (result != NO_ERROR) { - ALOGE("setDesiredDisplayModeSpecs: failed to writeInterfaceToken: %d", result); - return result; - } - result = data.writeStrongBinder(displayToken); - if (result != NO_ERROR) { - ALOGE("setDesiredDisplayModeSpecs: failed to write display token: %d", result); - return result; - } - result = data.writeInt32(defaultMode); - if (result != NO_ERROR) { - ALOGE("setDesiredDisplayModeSpecs failed to write defaultMode: %d", result); - return result; - } - result = data.writeBool(allowGroupSwitching); - if (result != NO_ERROR) { - ALOGE("setDesiredDisplayModeSpecs failed to write allowGroupSwitching: %d", result); - return result; - } - result = data.writeFloat(primaryRefreshRateMin); - if (result != NO_ERROR) { - ALOGE("setDesiredDisplayModeSpecs failed to write primaryRefreshRateMin: %d", result); - return result; - } - result = data.writeFloat(primaryRefreshRateMax); - if (result != NO_ERROR) { - ALOGE("setDesiredDisplayModeSpecs failed to write primaryRefreshRateMax: %d", result); - return result; - } - result = data.writeFloat(appRequestRefreshRateMin); - if (result != NO_ERROR) { - ALOGE("setDesiredDisplayModeSpecs failed to write appRequestRefreshRateMin: %d", - result); - return result; - } - result = data.writeFloat(appRequestRefreshRateMax); - if (result != NO_ERROR) { - ALOGE("setDesiredDisplayModeSpecs failed to write appRequestRefreshRateMax: %d", - result); - return result; - } - - result = - remote()->transact(BnSurfaceComposer::SET_DESIRED_DISPLAY_MODE_SPECS, data, &reply); - if (result != NO_ERROR) { - ALOGE("setDesiredDisplayModeSpecs failed to transact: %d", result); - return result; - } - return reply.readInt32(); - } - - status_t getDesiredDisplayModeSpecs(const sp& displayToken, - ui::DisplayModeId* outDefaultMode, - bool* outAllowGroupSwitching, - float* outPrimaryRefreshRateMin, - float* outPrimaryRefreshRateMax, - float* outAppRequestRefreshRateMin, - float* outAppRequestRefreshRateMax) override { - if (!outDefaultMode || !outAllowGroupSwitching || !outPrimaryRefreshRateMin || - !outPrimaryRefreshRateMax || !outAppRequestRefreshRateMin || - !outAppRequestRefreshRateMax) { - return BAD_VALUE; - } - Parcel data, reply; - status_t result = data.writeInterfaceToken(ISurfaceComposer::getInterfaceDescriptor()); - if (result != NO_ERROR) { - ALOGE("getDesiredDisplayModeSpecs failed to writeInterfaceToken: %d", result); - return result; - } - result = data.writeStrongBinder(displayToken); - if (result != NO_ERROR) { - ALOGE("getDesiredDisplayModeSpecs failed to writeStrongBinder: %d", result); - return result; - } - result = - remote()->transact(BnSurfaceComposer::GET_DESIRED_DISPLAY_MODE_SPECS, data, &reply); - if (result != NO_ERROR) { - ALOGE("getDesiredDisplayModeSpecs failed to transact: %d", result); - return result; - } - - result = reply.readInt32(outDefaultMode); - if (result != NO_ERROR) { - ALOGE("getDesiredDisplayModeSpecs failed to read defaultMode: %d", result); - return result; - } - if (*outDefaultMode < 0) { - ALOGE("%s: defaultMode must be non-negative but it was %d", __func__, *outDefaultMode); - return BAD_VALUE; - } - - result = reply.readBool(outAllowGroupSwitching); - if (result != NO_ERROR) { - ALOGE("getDesiredDisplayModeSpecs failed to read allowGroupSwitching: %d", result); - return result; - } - result = reply.readFloat(outPrimaryRefreshRateMin); - if (result != NO_ERROR) { - ALOGE("getDesiredDisplayModeSpecs failed to read primaryRefreshRateMin: %d", result); - return result; - } - result = reply.readFloat(outPrimaryRefreshRateMax); - if (result != NO_ERROR) { - ALOGE("getDesiredDisplayModeSpecs failed to read primaryRefreshRateMax: %d", result); - return result; - } - result = reply.readFloat(outAppRequestRefreshRateMin); - if (result != NO_ERROR) { - ALOGE("getDesiredDisplayModeSpecs failed to read appRequestRefreshRateMin: %d", result); - return result; - } - result = reply.readFloat(outAppRequestRefreshRateMax); - if (result != NO_ERROR) { - ALOGE("getDesiredDisplayModeSpecs failed to read appRequestRefreshRateMax: %d", result); - return result; - } - return reply.readInt32(); - } - - status_t setGlobalShadowSettings(const half4& ambientColor, const half4& spotColor, - float lightPosY, float lightPosZ, float lightRadius) override { - Parcel data, reply; - status_t error = data.writeInterfaceToken(ISurfaceComposer::getInterfaceDescriptor()); - if (error != NO_ERROR) { - ALOGE("setGlobalShadowSettings: failed to write interface token: %d", error); - return error; - } - - std::vector shadowConfig = {ambientColor.r, ambientColor.g, ambientColor.b, - ambientColor.a, spotColor.r, spotColor.g, - spotColor.b, spotColor.a, lightPosY, - lightPosZ, lightRadius}; - - error = data.writeFloatVector(shadowConfig); - if (error != NO_ERROR) { - ALOGE("setGlobalShadowSettings: failed to write shadowConfig: %d", error); - return error; - } - - error = remote()->transact(BnSurfaceComposer::SET_GLOBAL_SHADOW_SETTINGS, data, &reply, - IBinder::FLAG_ONEWAY); - if (error != NO_ERROR) { - ALOGE("setGlobalShadowSettings: failed to transact: %d", error); - return error; - } - return NO_ERROR; - } - - status_t getDisplayDecorationSupport( - const sp& displayToken, - std::optional* outSupport) const override { - Parcel data, reply; - status_t error = data.writeInterfaceToken(ISurfaceComposer::getInterfaceDescriptor()); - if (error != NO_ERROR) { - ALOGE("getDisplayDecorationSupport: failed to write interface token: %d", error); - return error; - } - error = data.writeStrongBinder(displayToken); - if (error != NO_ERROR) { - ALOGE("getDisplayDecorationSupport: failed to write display token: %d", error); - return error; - } - error = remote()->transact(BnSurfaceComposer::GET_DISPLAY_DECORATION_SUPPORT, data, &reply); - if (error != NO_ERROR) { - ALOGE("getDisplayDecorationSupport: failed to transact: %d", error); - return error; - } - bool support; - error = reply.readBool(&support); - if (error != NO_ERROR) { - ALOGE("getDisplayDecorationSupport: failed to read support: %d", error); - return error; - } - - if (support) { - int32_t format, alphaInterpretation; - error = reply.readInt32(&format); - if (error != NO_ERROR) { - ALOGE("getDisplayDecorationSupport: failed to read format: %d", error); - return error; - } - error = reply.readInt32(&alphaInterpretation); - if (error != NO_ERROR) { - ALOGE("getDisplayDecorationSupport: failed to read alphaInterpretation: %d", error); - return error; - } - outSupport->emplace(); - outSupport->value().format = static_cast(format); - outSupport->value().alphaInterpretation = - static_cast(alphaInterpretation); - } else { - outSupport->reset(); - } - return NO_ERROR; - } - - status_t setFrameRate(const sp& surface, float frameRate, - int8_t compatibility, int8_t changeFrameRateStrategy) override { - Parcel data, reply; - SAFE_PARCEL(data.writeInterfaceToken, ISurfaceComposer::getInterfaceDescriptor()); - SAFE_PARCEL(data.writeStrongBinder, IInterface::asBinder(surface)); - SAFE_PARCEL(data.writeFloat, frameRate); - SAFE_PARCEL(data.writeByte, compatibility); - SAFE_PARCEL(data.writeByte, changeFrameRateStrategy); - - status_t err = remote()->transact(BnSurfaceComposer::SET_FRAME_RATE, data, &reply); - if (err != NO_ERROR) { - ALOGE("setFrameRate: failed to transact: %s (%d)", strerror(-err), err); - return err; - } - - return reply.readInt32(); - } - - status_t setFrameTimelineInfo(const sp& surface, - const FrameTimelineInfo& frameTimelineInfo) override { - Parcel data, reply; - status_t err = data.writeInterfaceToken(ISurfaceComposer::getInterfaceDescriptor()); - if (err != NO_ERROR) { - ALOGE("%s: failed writing interface token: %s (%d)", __func__, strerror(-err), -err); - return err; - } - - err = data.writeStrongBinder(IInterface::asBinder(surface)); - if (err != NO_ERROR) { - ALOGE("%s: failed writing strong binder: %s (%d)", __func__, strerror(-err), -err); - return err; - } - - SAFE_PARCEL(frameTimelineInfo.write, data); - - err = remote()->transact(BnSurfaceComposer::SET_FRAME_TIMELINE_INFO, data, &reply); - if (err != NO_ERROR) { - ALOGE("%s: failed to transact: %s (%d)", __func__, strerror(-err), err); - return err; - } - - return reply.readInt32(); - } - - status_t addTransactionTraceListener( - const sp& listener) override { - Parcel data, reply; - SAFE_PARCEL(data.writeInterfaceToken, ISurfaceComposer::getInterfaceDescriptor()); - SAFE_PARCEL(data.writeStrongBinder, IInterface::asBinder(listener)); - - return remote()->transact(BnSurfaceComposer::ADD_TRANSACTION_TRACE_LISTENER, data, &reply); - } - - /** - * Get priority of the RenderEngine in surface flinger. - */ - int getGPUContextPriority() override { - Parcel data, reply; - data.writeInterfaceToken(ISurfaceComposer::getInterfaceDescriptor()); - status_t err = - remote()->transact(BnSurfaceComposer::GET_GPU_CONTEXT_PRIORITY, data, &reply); - if (err != NO_ERROR) { - ALOGE("getGPUContextPriority failed to read data: %s (%d)", strerror(-err), err); - return 0; - } - return reply.readInt32(); - } - - status_t getMaxAcquiredBufferCount(int* buffers) const override { - Parcel data, reply; - data.writeInterfaceToken(ISurfaceComposer::getInterfaceDescriptor()); - status_t err = - remote()->transact(BnSurfaceComposer::GET_MAX_ACQUIRED_BUFFER_COUNT, data, &reply); - if (err != NO_ERROR) { - ALOGE("getMaxAcquiredBufferCount failed to read data: %s (%d)", strerror(-err), err); - return err; - } - - return reply.readInt32(buffers); - } - - status_t addWindowInfosListener( - const sp& windowInfosListener) const override { - Parcel data, reply; - SAFE_PARCEL(data.writeInterfaceToken, ISurfaceComposer::getInterfaceDescriptor()); - SAFE_PARCEL(data.writeStrongBinder, IInterface::asBinder(windowInfosListener)); - return remote()->transact(BnSurfaceComposer::ADD_WINDOW_INFOS_LISTENER, data, &reply); - } - - status_t removeWindowInfosListener( - const sp& windowInfosListener) const override { - Parcel data, reply; - SAFE_PARCEL(data.writeInterfaceToken, ISurfaceComposer::getInterfaceDescriptor()); - SAFE_PARCEL(data.writeStrongBinder, IInterface::asBinder(windowInfosListener)); - return remote()->transact(BnSurfaceComposer::REMOVE_WINDOW_INFOS_LISTENER, data, &reply); - } - - status_t setOverrideFrameRate(uid_t uid, float frameRate) override { - Parcel data, reply; - SAFE_PARCEL(data.writeInterfaceToken, ISurfaceComposer::getInterfaceDescriptor()); - SAFE_PARCEL(data.writeUint32, uid); - SAFE_PARCEL(data.writeFloat, frameRate); - - status_t err = remote()->transact(BnSurfaceComposer::SET_OVERRIDE_FRAME_RATE, data, &reply); - if (err != NO_ERROR) { - ALOGE("setOverrideFrameRate: failed to transact %s (%d)", strerror(-err), err); - return err; - } - - return NO_ERROR; - } }; // Out-of-line virtual method definition to trigger vtable emission in this @@ -1031,18 +127,12 @@ IMPLEMENT_META_INTERFACE(SurfaceComposer, "android.ui.ISurfaceComposer"); status_t BnSurfaceComposer::onTransact( uint32_t code, const Parcel& data, Parcel* reply, uint32_t flags) { - switch(code) { - case CREATE_CONNECTION: { - CHECK_INTERFACE(ISurfaceComposer, data, reply); - sp b = IInterface::asBinder(createConnection()); - reply->writeStrongBinder(b); - return NO_ERROR; - } + switch (code) { case SET_TRANSACTION_STATE: { CHECK_INTERFACE(ISurfaceComposer, data, reply); FrameTimelineInfo frameTimelineInfo; - SAFE_PARCEL(frameTimelineInfo.read, data); + frameTimelineInfo.readFromParcel(&data); uint32_t count = 0; SAFE_PARCEL_READ_SIZE(data.readUint32, &count, data.dataSize()); @@ -1075,11 +165,14 @@ status_t BnSurfaceComposer::onTransact( SAFE_PARCEL(data.readInt64, &desiredPresentTime); SAFE_PARCEL(data.readBool, &isAutoTimestamp); - client_cache_t uncachedBuffer; + SAFE_PARCEL_READ_SIZE(data.readUint32, &count, data.dataSize()); + std::vector uncacheBuffers(count); sp tmpBinder; - SAFE_PARCEL(data.readNullableStrongBinder, &tmpBinder); - uncachedBuffer.token = tmpBinder; - SAFE_PARCEL(data.readUint64, &uncachedBuffer.id); + for (size_t i = 0; i < count; i++) { + SAFE_PARCEL(data.readNullableStrongBinder, &tmpBinder); + uncacheBuffers[i].token = tmpBinder; + SAFE_PARCEL(data.readUint64, &uncacheBuffers[i].id); + } bool hasListenerCallbacks = false; SAFE_PARCEL(data.readBool, &hasListenerCallbacks); @@ -1097,646 +190,16 @@ status_t BnSurfaceComposer::onTransact( uint64_t transactionId = -1; SAFE_PARCEL(data.readUint64, &transactionId); - return setTransactionState(frameTimelineInfo, state, displays, stateFlags, applyToken, - inputWindowCommands, desiredPresentTime, isAutoTimestamp, - uncachedBuffer, hasListenerCallbacks, listenerCallbacks, - transactionId); - } - case BOOT_FINISHED: { - CHECK_INTERFACE(ISurfaceComposer, data, reply); - bootFinished(); - return NO_ERROR; - } - case AUTHENTICATE_SURFACE: { - CHECK_INTERFACE(ISurfaceComposer, data, reply); - sp bufferProducer = - interface_cast(data.readStrongBinder()); - int32_t result = authenticateSurfaceTexture(bufferProducer) ? 1 : 0; - reply->writeInt32(result); - return NO_ERROR; - } - case GET_SUPPORTED_FRAME_TIMESTAMPS: { - CHECK_INTERFACE(ISurfaceComposer, data, reply); - std::vector supportedTimestamps; - status_t result = getSupportedFrameTimestamps(&supportedTimestamps); - status_t err = reply->writeInt32(result); - if (err != NO_ERROR) { - return err; - } - if (result != NO_ERROR) { - return result; - } - - std::vector supported; - supported.reserve(supportedTimestamps.size()); - for (FrameEvent s : supportedTimestamps) { - supported.push_back(static_cast(s)); - } - return reply->writeInt32Vector(supported); - } - case CREATE_DISPLAY_EVENT_CONNECTION: { - CHECK_INTERFACE(ISurfaceComposer, data, reply); - auto vsyncSource = static_cast(data.readInt32()); - EventRegistrationFlags eventRegistration = - static_cast(data.readUint32()); - - sp connection( - createDisplayEventConnection(vsyncSource, eventRegistration)); - reply->writeStrongBinder(IInterface::asBinder(connection)); - return NO_ERROR; - } - case GET_STATIC_DISPLAY_INFO: { - CHECK_INTERFACE(ISurfaceComposer, data, reply); - ui::StaticDisplayInfo info; - const sp display = data.readStrongBinder(); - const status_t result = getStaticDisplayInfo(display, &info); - SAFE_PARCEL(reply->writeInt32, result); - if (result != NO_ERROR) return result; - SAFE_PARCEL(reply->write, info); - return NO_ERROR; - } - case GET_DYNAMIC_DISPLAY_INFO: { - CHECK_INTERFACE(ISurfaceComposer, data, reply); - ui::DynamicDisplayInfo info; - const sp display = data.readStrongBinder(); - const status_t result = getDynamicDisplayInfo(display, &info); - SAFE_PARCEL(reply->writeInt32, result); - if (result != NO_ERROR) return result; - SAFE_PARCEL(reply->write, info); - return NO_ERROR; - } - case GET_DISPLAY_NATIVE_PRIMARIES: { - CHECK_INTERFACE(ISurfaceComposer, data, reply); - ui::DisplayPrimaries primaries; - sp display = nullptr; - - status_t result = data.readStrongBinder(&display); - if (result != NO_ERROR) { - ALOGE("getDisplayNativePrimaries failed to readStrongBinder: %d", result); - return result; - } - - result = getDisplayNativePrimaries(display, primaries); - reply->writeInt32(result); - if (result == NO_ERROR) { - memcpy(reply->writeInplace(sizeof(ui::DisplayPrimaries)), &primaries, - sizeof(ui::DisplayPrimaries)); - } - - return NO_ERROR; - } - case SET_ACTIVE_COLOR_MODE: { - CHECK_INTERFACE(ISurfaceComposer, data, reply); - sp display = nullptr; - status_t result = data.readStrongBinder(&display); - if (result != NO_ERROR) { - ALOGE("getActiveColorMode failed to readStrongBinder: %d", result); - return result; - } - int32_t colorModeInt = 0; - result = data.readInt32(&colorModeInt); - if (result != NO_ERROR) { - ALOGE("setActiveColorMode failed to readInt32: %d", result); - return result; - } - result = setActiveColorMode(display, - static_cast(colorModeInt)); - result = reply->writeInt32(result); - return result; - } - case SET_BOOT_DISPLAY_MODE: { - CHECK_INTERFACE(ISurfaceComposer, data, reply); - sp display = nullptr; - status_t result = data.readStrongBinder(&display); - if (result != NO_ERROR) { - ALOGE("setBootDisplayMode failed to readStrongBinder: %d", result); - return result; - } - ui::DisplayModeId displayModeId; - result = data.readInt32(&displayModeId); - if (result != NO_ERROR) { - ALOGE("setBootDisplayMode failed to readInt32: %d", result); - return result; - } - return setBootDisplayMode(display, displayModeId); - } - case CLEAR_ANIMATION_FRAME_STATS: { - CHECK_INTERFACE(ISurfaceComposer, data, reply); - status_t result = clearAnimationFrameStats(); - reply->writeInt32(result); - return NO_ERROR; - } - case GET_ANIMATION_FRAME_STATS: { - CHECK_INTERFACE(ISurfaceComposer, data, reply); - FrameStats stats; - status_t result = getAnimationFrameStats(&stats); - reply->write(stats); - reply->writeInt32(result); - return NO_ERROR; - } - case ENABLE_VSYNC_INJECTIONS: { - CHECK_INTERFACE(ISurfaceComposer, data, reply); - bool enable = false; - status_t result = data.readBool(&enable); - if (result != NO_ERROR) { - ALOGE("enableVSyncInjections failed to readBool: %d", result); - return result; - } - return enableVSyncInjections(enable); - } - case INJECT_VSYNC: { - CHECK_INTERFACE(ISurfaceComposer, data, reply); - int64_t when = 0; - status_t result = data.readInt64(&when); - if (result != NO_ERROR) { - ALOGE("enableVSyncInjections failed to readInt64: %d", result); - return result; - } - return injectVSync(when); - } - case GET_LAYER_DEBUG_INFO: { - CHECK_INTERFACE(ISurfaceComposer, data, reply); - std::vector outLayers; - status_t result = getLayerDebugInfo(&outLayers); - reply->writeInt32(result); - if (result == NO_ERROR) - { - result = reply->writeParcelableVector(outLayers); - } - return result; - } - case GET_COMPOSITION_PREFERENCE: { - CHECK_INTERFACE(ISurfaceComposer, data, reply); - ui::Dataspace defaultDataspace; - ui::PixelFormat defaultPixelFormat; - ui::Dataspace wideColorGamutDataspace; - ui::PixelFormat wideColorGamutPixelFormat; - status_t error = - getCompositionPreference(&defaultDataspace, &defaultPixelFormat, - &wideColorGamutDataspace, &wideColorGamutPixelFormat); - reply->writeInt32(error); - if (error == NO_ERROR) { - reply->writeInt32(static_cast(defaultDataspace)); - reply->writeInt32(static_cast(defaultPixelFormat)); - reply->writeInt32(static_cast(wideColorGamutDataspace)); - reply->writeInt32(static_cast(wideColorGamutPixelFormat)); - } - return error; - } - case GET_COLOR_MANAGEMENT: { - CHECK_INTERFACE(ISurfaceComposer, data, reply); - bool result; - status_t error = getColorManagement(&result); - if (error == NO_ERROR) { - reply->writeBool(result); - } - return error; - } - case GET_DISPLAYED_CONTENT_SAMPLING_ATTRIBUTES: { - CHECK_INTERFACE(ISurfaceComposer, data, reply); - - sp display = data.readStrongBinder(); - ui::PixelFormat format; - ui::Dataspace dataspace; - uint8_t component = 0; - auto result = - getDisplayedContentSamplingAttributes(display, &format, &dataspace, &component); - if (result == NO_ERROR) { - reply->writeUint32(static_cast(format)); - reply->writeUint32(static_cast(dataspace)); - reply->writeUint32(static_cast(component)); - } - return result; - } - case SET_DISPLAY_CONTENT_SAMPLING_ENABLED: { - CHECK_INTERFACE(ISurfaceComposer, data, reply); - - sp display = nullptr; - bool enable = false; - int8_t componentMask = 0; - uint64_t maxFrames = 0; - status_t result = data.readStrongBinder(&display); - if (result != NO_ERROR) { - ALOGE("setDisplayContentSamplingEnabled failure in reading Display token: %d", - result); - return result; - } - - result = data.readBool(&enable); - if (result != NO_ERROR) { - ALOGE("setDisplayContentSamplingEnabled failure in reading enable: %d", result); - return result; - } - - result = data.readByte(static_cast(&componentMask)); - if (result != NO_ERROR) { - ALOGE("setDisplayContentSamplingEnabled failure in reading component mask: %d", - result); - return result; - } - - result = data.readUint64(&maxFrames); - if (result != NO_ERROR) { - ALOGE("setDisplayContentSamplingEnabled failure in reading max frames: %d", result); - return result; - } - - return setDisplayContentSamplingEnabled(display, enable, - static_cast(componentMask), maxFrames); - } - case GET_DISPLAYED_CONTENT_SAMPLE: { - CHECK_INTERFACE(ISurfaceComposer, data, reply); - - sp display = data.readStrongBinder(); - uint64_t maxFrames = 0; - uint64_t timestamp = 0; - - status_t result = data.readUint64(&maxFrames); - if (result != NO_ERROR) { - ALOGE("getDisplayedContentSample failure in reading max frames: %d", result); - return result; - } - - result = data.readUint64(×tamp); - if (result != NO_ERROR) { - ALOGE("getDisplayedContentSample failure in reading timestamp: %d", result); - return result; - } - - DisplayedFrameStats stats; - result = getDisplayedContentSample(display, maxFrames, timestamp, &stats); - if (result == NO_ERROR) { - reply->writeUint64(stats.numFrames); - reply->writeUint64Vector(stats.component_0_sample); - reply->writeUint64Vector(stats.component_1_sample); - reply->writeUint64Vector(stats.component_2_sample); - reply->writeUint64Vector(stats.component_3_sample); - } - return result; - } - case GET_PROTECTED_CONTENT_SUPPORT: { - CHECK_INTERFACE(ISurfaceComposer, data, reply); - bool result; - status_t error = getProtectedContentSupport(&result); - if (error == NO_ERROR) { - reply->writeBool(result); - } - return error; - } - case ADD_REGION_SAMPLING_LISTENER: { - CHECK_INTERFACE(ISurfaceComposer, data, reply); - Rect samplingArea; - status_t result = data.read(samplingArea); - if (result != NO_ERROR) { - ALOGE("addRegionSamplingListener: Failed to read sampling area"); - return result; - } - sp stopLayerHandle; - result = data.readNullableStrongBinder(&stopLayerHandle); - if (result != NO_ERROR) { - ALOGE("addRegionSamplingListener: Failed to read stop layer handle"); - return result; - } - sp listener; - result = data.readNullableStrongBinder(&listener); - if (result != NO_ERROR) { - ALOGE("addRegionSamplingListener: Failed to read listener"); - return result; - } - return addRegionSamplingListener(samplingArea, stopLayerHandle, listener); - } - case REMOVE_REGION_SAMPLING_LISTENER: { - CHECK_INTERFACE(ISurfaceComposer, data, reply); - sp listener; - status_t result = data.readNullableStrongBinder(&listener); - if (result != NO_ERROR) { - ALOGE("removeRegionSamplingListener: Failed to read listener"); - return result; - } - return removeRegionSamplingListener(listener); - } - case ADD_FPS_LISTENER: { - CHECK_INTERFACE(ISurfaceComposer, data, reply); - int32_t taskId; - status_t result = data.readInt32(&taskId); - if (result != NO_ERROR) { - ALOGE("addFpsListener: Failed to read layer handle"); - return result; - } - sp listener; - result = data.readNullableStrongBinder(&listener); - if (result != NO_ERROR) { - ALOGE("addFpsListener: Failed to read listener"); - return result; - } - return addFpsListener(taskId, listener); - } - case REMOVE_FPS_LISTENER: { - CHECK_INTERFACE(ISurfaceComposer, data, reply); - sp listener; - status_t result = data.readNullableStrongBinder(&listener); - if (result != NO_ERROR) { - ALOGE("removeFpsListener: Failed to read listener"); - return result; - } - return removeFpsListener(listener); - } - case ADD_TUNNEL_MODE_ENABLED_LISTENER: { - CHECK_INTERFACE(ISurfaceComposer, data, reply); - sp listener; - status_t result = data.readNullableStrongBinder(&listener); - if (result != NO_ERROR) { - ALOGE("addTunnelModeEnabledListener: Failed to read listener"); - return result; - } - return addTunnelModeEnabledListener(listener); - } - case REMOVE_TUNNEL_MODE_ENABLED_LISTENER: { - CHECK_INTERFACE(ISurfaceComposer, data, reply); - sp listener; - status_t result = data.readNullableStrongBinder(&listener); - if (result != NO_ERROR) { - ALOGE("removeTunnelModeEnabledListener: Failed to read listener"); - return result; - } - return removeTunnelModeEnabledListener(listener); - } - case SET_DESIRED_DISPLAY_MODE_SPECS: { - CHECK_INTERFACE(ISurfaceComposer, data, reply); - sp displayToken = data.readStrongBinder(); - ui::DisplayModeId defaultMode; - status_t result = data.readInt32(&defaultMode); - if (result != NO_ERROR) { - ALOGE("setDesiredDisplayModeSpecs: failed to read defaultMode: %d", result); - return result; - } - if (defaultMode < 0) { - ALOGE("%s: defaultMode must be non-negative but it was %d", __func__, defaultMode); - return BAD_VALUE; - } - bool allowGroupSwitching; - result = data.readBool(&allowGroupSwitching); - if (result != NO_ERROR) { - ALOGE("setDesiredDisplayModeSpecs: failed to read allowGroupSwitching: %d", result); - return result; - } - float primaryRefreshRateMin; - result = data.readFloat(&primaryRefreshRateMin); - if (result != NO_ERROR) { - ALOGE("setDesiredDisplayModeSpecs: failed to read primaryRefreshRateMin: %d", - result); - return result; - } - float primaryRefreshRateMax; - result = data.readFloat(&primaryRefreshRateMax); - if (result != NO_ERROR) { - ALOGE("setDesiredDisplayModeSpecs: failed to read primaryRefreshRateMax: %d", - result); - return result; - } - float appRequestRefreshRateMin; - result = data.readFloat(&appRequestRefreshRateMin); - if (result != NO_ERROR) { - ALOGE("setDesiredDisplayModeSpecs: failed to read appRequestRefreshRateMin: %d", - result); - return result; - } - float appRequestRefreshRateMax; - result = data.readFloat(&appRequestRefreshRateMax); - if (result != NO_ERROR) { - ALOGE("setDesiredDisplayModeSpecs: failed to read appRequestRefreshRateMax: %d", - result); - return result; - } - result = setDesiredDisplayModeSpecs(displayToken, defaultMode, allowGroupSwitching, - primaryRefreshRateMin, primaryRefreshRateMax, - appRequestRefreshRateMin, appRequestRefreshRateMax); - if (result != NO_ERROR) { - ALOGE("setDesiredDisplayModeSpecs: failed to call setDesiredDisplayModeSpecs: " - "%d", - result); - return result; - } - reply->writeInt32(result); - return result; - } - case GET_DESIRED_DISPLAY_MODE_SPECS: { - CHECK_INTERFACE(ISurfaceComposer, data, reply); - sp displayToken = data.readStrongBinder(); - ui::DisplayModeId defaultMode; - bool allowGroupSwitching; - float primaryRefreshRateMin; - float primaryRefreshRateMax; - float appRequestRefreshRateMin; - float appRequestRefreshRateMax; - - status_t result = - getDesiredDisplayModeSpecs(displayToken, &defaultMode, &allowGroupSwitching, - &primaryRefreshRateMin, &primaryRefreshRateMax, - &appRequestRefreshRateMin, - &appRequestRefreshRateMax); - if (result != NO_ERROR) { - ALOGE("getDesiredDisplayModeSpecs: failed to get getDesiredDisplayModeSpecs: " - "%d", - result); - return result; - } - - result = reply->writeInt32(defaultMode); - if (result != NO_ERROR) { - ALOGE("getDesiredDisplayModeSpecs: failed to write defaultMode: %d", result); - return result; - } - result = reply->writeBool(allowGroupSwitching); - if (result != NO_ERROR) { - ALOGE("getDesiredDisplayModeSpecs: failed to write allowGroupSwitching: %d", - result); - return result; - } - result = reply->writeFloat(primaryRefreshRateMin); - if (result != NO_ERROR) { - ALOGE("getDesiredDisplayModeSpecs: failed to write primaryRefreshRateMin: %d", - result); - return result; - } - result = reply->writeFloat(primaryRefreshRateMax); - if (result != NO_ERROR) { - ALOGE("getDesiredDisplayModeSpecs: failed to write primaryRefreshRateMax: %d", - result); - return result; - } - result = reply->writeFloat(appRequestRefreshRateMin); - if (result != NO_ERROR) { - ALOGE("getDesiredDisplayModeSpecs: failed to write appRequestRefreshRateMin: %d", - result); - return result; - } - result = reply->writeFloat(appRequestRefreshRateMax); - if (result != NO_ERROR) { - ALOGE("getDesiredDisplayModeSpecs: failed to write appRequestRefreshRateMax: %d", - result); - return result; - } - reply->writeInt32(result); - return result; - } - case SET_GLOBAL_SHADOW_SETTINGS: { - CHECK_INTERFACE(ISurfaceComposer, data, reply); - - std::vector shadowConfig; - status_t error = data.readFloatVector(&shadowConfig); - if (error != NO_ERROR || shadowConfig.size() != 11) { - ALOGE("setGlobalShadowSettings: failed to read shadowConfig: %d", error); - return error; - } - - half4 ambientColor = {shadowConfig[0], shadowConfig[1], shadowConfig[2], - shadowConfig[3]}; - half4 spotColor = {shadowConfig[4], shadowConfig[5], shadowConfig[6], shadowConfig[7]}; - float lightPosY = shadowConfig[8]; - float lightPosZ = shadowConfig[9]; - float lightRadius = shadowConfig[10]; - return setGlobalShadowSettings(ambientColor, spotColor, lightPosY, lightPosZ, - lightRadius); - } - case GET_DISPLAY_DECORATION_SUPPORT: { - CHECK_INTERFACE(ISurfaceComposer, data, reply); - sp displayToken; - SAFE_PARCEL(data.readNullableStrongBinder, &displayToken); - std::optional support; - auto error = getDisplayDecorationSupport(displayToken, &support); - if (error != NO_ERROR) { - ALOGE("getDisplayDecorationSupport failed with error %d", error); - return error; - } - reply->writeBool(support.has_value()); - if (support) { - reply->writeInt32(static_cast(support.value().format)); - reply->writeInt32(static_cast(support.value().alphaInterpretation)); - } - return error; - } - case SET_FRAME_RATE: { - CHECK_INTERFACE(ISurfaceComposer, data, reply); - sp binder; - SAFE_PARCEL(data.readStrongBinder, &binder); - - sp surface = interface_cast(binder); - if (!surface) { - ALOGE("setFrameRate: failed to cast to IGraphicBufferProducer"); - return BAD_VALUE; - } - float frameRate; - SAFE_PARCEL(data.readFloat, &frameRate); - - int8_t compatibility; - SAFE_PARCEL(data.readByte, &compatibility); - - int8_t changeFrameRateStrategy; - SAFE_PARCEL(data.readByte, &changeFrameRateStrategy); - - status_t result = - setFrameRate(surface, frameRate, compatibility, changeFrameRateStrategy); - reply->writeInt32(result); - return NO_ERROR; - } - case SET_FRAME_TIMELINE_INFO: { - CHECK_INTERFACE(ISurfaceComposer, data, reply); - sp binder; - status_t err = data.readStrongBinder(&binder); - if (err != NO_ERROR) { - ALOGE("setFrameTimelineInfo: failed to read strong binder: %s (%d)", strerror(-err), - -err); - return err; - } - sp surface = interface_cast(binder); - if (!surface) { - ALOGE("setFrameTimelineInfo: failed to cast to IGraphicBufferProducer: %s (%d)", - strerror(-err), -err); - return err; - } - - FrameTimelineInfo frameTimelineInfo; - SAFE_PARCEL(frameTimelineInfo.read, data); - - status_t result = setFrameTimelineInfo(surface, frameTimelineInfo); - reply->writeInt32(result); - return NO_ERROR; - } - case ADD_TRANSACTION_TRACE_LISTENER: { - CHECK_INTERFACE(ISurfaceComposer, data, reply); - sp listener; - SAFE_PARCEL(data.readStrongBinder, &listener); - - return addTransactionTraceListener(listener); - } - case GET_GPU_CONTEXT_PRIORITY: { - CHECK_INTERFACE(ISurfaceComposer, data, reply); - int priority = getGPUContextPriority(); - SAFE_PARCEL(reply->writeInt32, priority); - return NO_ERROR; - } - case GET_MAX_ACQUIRED_BUFFER_COUNT: { - CHECK_INTERFACE(ISurfaceComposer, data, reply); - int buffers = 0; - int err = getMaxAcquiredBufferCount(&buffers); - if (err != NO_ERROR) { - return err; + SAFE_PARCEL_READ_SIZE(data.readUint32, &count, data.dataSize()); + std::vector mergedTransactions(count); + for (size_t i = 0; i < count; i++) { + SAFE_PARCEL(data.readUint64, &mergedTransactions[i]); } - SAFE_PARCEL(reply->writeInt32, buffers); - return NO_ERROR; - } - case OVERRIDE_HDR_TYPES: { - CHECK_INTERFACE(ISurfaceComposer, data, reply); - sp display = nullptr; - SAFE_PARCEL(data.readStrongBinder, &display); - std::vector hdrTypes; - SAFE_PARCEL(data.readInt32Vector, &hdrTypes); - - std::vector hdrTypesVector; - for (int i : hdrTypes) { - hdrTypesVector.push_back(static_cast(i)); - } - return overrideHdrTypes(display, hdrTypesVector); - } - case ON_PULL_ATOM: { - CHECK_INTERFACE(ISurfaceComposer, data, reply); - int32_t atomId = 0; - SAFE_PARCEL(data.readInt32, &atomId); - - std::string pulledData; - bool success; - status_t err = onPullAtom(atomId, &pulledData, &success); - SAFE_PARCEL(reply->writeByteArray, pulledData.size(), - reinterpret_cast(pulledData.data())); - SAFE_PARCEL(reply->writeBool, success); - return err; - } - case ADD_WINDOW_INFOS_LISTENER: { - CHECK_INTERFACE(ISurfaceComposer, data, reply); - sp listener; - SAFE_PARCEL(data.readStrongBinder, &listener); - - return addWindowInfosListener(listener); - } - case REMOVE_WINDOW_INFOS_LISTENER: { - CHECK_INTERFACE(ISurfaceComposer, data, reply); - sp listener; - SAFE_PARCEL(data.readStrongBinder, &listener); - - return removeWindowInfosListener(listener); - } - case SET_OVERRIDE_FRAME_RATE: { - CHECK_INTERFACE(ISurfaceComposer, data, reply); - - uid_t uid; - SAFE_PARCEL(data.readUint32, &uid); - - float frameRate; - SAFE_PARCEL(data.readFloat, &frameRate); - - return setOverrideFrameRate(uid, frameRate); + return setTransactionState(frameTimelineInfo, state, displays, stateFlags, applyToken, + std::move(inputWindowCommands), desiredPresentTime, + isAutoTimestamp, uncacheBuffers, hasListenerCallbacks, + listenerCallbacks, transactionId, mergedTransactions); } default: { return BBinder::onTransact(code, data, reply, flags); diff --git a/libs/gui/ISurfaceComposerClient.cpp b/libs/gui/ISurfaceComposerClient.cpp deleted file mode 100644 index 5e7a7ec67b217afa4d6e45194d13257c3c1ab090..0000000000000000000000000000000000000000 --- a/libs/gui/ISurfaceComposerClient.cpp +++ /dev/null @@ -1,124 +0,0 @@ -/* - * Copyright (C) 2007 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. - */ - -// tag as surfaceflinger -#define LOG_TAG "SurfaceFlinger" - -#include - -#include - -#include - -#include - -namespace android { - -namespace { // Anonymous - -enum class Tag : uint32_t { - CREATE_SURFACE = IBinder::FIRST_CALL_TRANSACTION, - CREATE_WITH_SURFACE_PARENT, - CLEAR_LAYER_FRAME_STATS, - GET_LAYER_FRAME_STATS, - MIRROR_SURFACE, - LAST = MIRROR_SURFACE, -}; - -} // Anonymous namespace - -class BpSurfaceComposerClient : public SafeBpInterface { -public: - explicit BpSurfaceComposerClient(const sp& impl) - : SafeBpInterface(impl, "BpSurfaceComposerClient") {} - - ~BpSurfaceComposerClient() override; - - status_t createSurface(const String8& name, uint32_t width, uint32_t height, PixelFormat format, - uint32_t flags, const sp& parent, LayerMetadata metadata, - sp* handle, sp* gbp, - int32_t* outLayerId, uint32_t* outTransformHint) override { - return callRemote(Tag::CREATE_SURFACE, - name, width, height, - format, flags, parent, - std::move(metadata), - handle, gbp, outLayerId, - outTransformHint); - } - - status_t createWithSurfaceParent(const String8& name, uint32_t width, uint32_t height, - PixelFormat format, uint32_t flags, - const sp& parent, - LayerMetadata metadata, sp* handle, - sp* gbp, int32_t* outLayerId, - uint32_t* outTransformHint) override { - return callRemote(Tag::CREATE_WITH_SURFACE_PARENT, - name, width, height, format, - flags, parent, - std::move(metadata), handle, gbp, - outLayerId, outTransformHint); - } - - status_t clearLayerFrameStats(const sp& handle) const override { - return callRemote(Tag::CLEAR_LAYER_FRAME_STATS, - handle); - } - - status_t getLayerFrameStats(const sp& handle, FrameStats* outStats) const override { - return callRemote(Tag::GET_LAYER_FRAME_STATS, handle, - outStats); - } - - status_t mirrorSurface(const sp& mirrorFromHandle, sp* outHandle, - int32_t* outLayerId) override { - return callRemote(Tag::MIRROR_SURFACE, - mirrorFromHandle, - outHandle, outLayerId); - } -}; - -// Out-of-line virtual method definition to trigger vtable emission in this -// translation unit (see clang warning -Wweak-vtables) -BpSurfaceComposerClient::~BpSurfaceComposerClient() {} - -IMPLEMENT_META_INTERFACE(SurfaceComposerClient, "android.ui.ISurfaceComposerClient"); - -// ---------------------------------------------------------------------- - -status_t BnSurfaceComposerClient::onTransact(uint32_t code, const Parcel& data, Parcel* reply, - uint32_t flags) { - if (code < IBinder::FIRST_CALL_TRANSACTION || code > static_cast(Tag::LAST)) { - return BBinder::onTransact(code, data, reply, flags); - } - auto tag = static_cast(code); - switch (tag) { - case Tag::CREATE_SURFACE: - return callLocal(data, reply, &ISurfaceComposerClient::createSurface); - case Tag::CREATE_WITH_SURFACE_PARENT: - return callLocal(data, reply, &ISurfaceComposerClient::createWithSurfaceParent); - case Tag::CLEAR_LAYER_FRAME_STATS: - return callLocal(data, reply, &ISurfaceComposerClient::clearLayerFrameStats); - case Tag::GET_LAYER_FRAME_STATS: - return callLocal(data, reply, &ISurfaceComposerClient::getLayerFrameStats); - case Tag::MIRROR_SURFACE: - return callLocal(data, reply, &ISurfaceComposerClient::mirrorSurface); - } -} - -} // namespace android diff --git a/libs/gui/ITransactionCompletedListener.cpp b/libs/gui/ITransactionCompletedListener.cpp index e4b8bad8f8d0dbaf60b39910156af2b01830ba94..ffe79a3a039c97f3f7d2f3773029b632cb681eab 100644 --- a/libs/gui/ITransactionCompletedListener.cpp +++ b/libs/gui/ITransactionCompletedListener.cpp @@ -17,6 +17,9 @@ #define LOG_TAG "ITransactionCompletedListener" //#define LOG_NDEBUG 0 +#include +#include + #include #include #include @@ -30,11 +33,18 @@ enum class Tag : uint32_t { ON_TRANSACTION_COMPLETED = IBinder::FIRST_CALL_TRANSACTION, ON_RELEASE_BUFFER, ON_TRANSACTION_QUEUE_STALLED, - LAST = ON_RELEASE_BUFFER, + ON_TRUSTED_PRESENTATION_CHANGED, + LAST = ON_TRUSTED_PRESENTATION_CHANGED, }; } // Anonymous namespace +namespace { // Anonymous + +constexpr int32_t kSerializedCallbackTypeOnCompelteWithJankData = 2; + +} // Anonymous namespace + status_t FrameEventHistoryStats::writeToParcel(Parcel* output) const { status_t err = output->writeUint64(frameNumber); if (err != NO_ERROR) return err; @@ -126,7 +136,12 @@ status_t SurfaceStats::writeToParcel(Parcel* output) const { } else { SAFE_PARCEL(output->writeBool, false); } - SAFE_PARCEL(output->writeUint32, transformHint); + + SAFE_PARCEL(output->writeBool, transformHint.has_value()); + if (transformHint.has_value()) { + output->writeUint32(transformHint.value()); + } + SAFE_PARCEL(output->writeUint32, currentMaxAcquiredBufferCount); SAFE_PARCEL(output->writeParcelable, eventStats); SAFE_PARCEL(output->writeInt32, static_cast(jankData.size())); @@ -156,7 +171,16 @@ status_t SurfaceStats::readFromParcel(const Parcel* input) { previousReleaseFence = new Fence(); SAFE_PARCEL(input->read, *previousReleaseFence); } - SAFE_PARCEL(input->readUint32, &transformHint); + bool hasTransformHint = false; + SAFE_PARCEL(input->readBool, &hasTransformHint); + if (hasTransformHint) { + uint32_t tempTransformHint; + SAFE_PARCEL(input->readUint32, &tempTransformHint); + transformHint = std::make_optional(tempTransformHint); + } else { + transformHint = std::nullopt; + } + SAFE_PARCEL(input->readUint32, ¤tMaxAcquiredBufferCount); SAFE_PARCEL(input->readParcelable, &eventStats); @@ -273,15 +297,22 @@ public: void onReleaseBuffer(ReleaseCallbackId callbackId, sp releaseFence, uint32_t currentMaxAcquiredBufferCount) override { - callRemoteAsync(Tag::ON_RELEASE_BUFFER, - callbackId, releaseFence, - currentMaxAcquiredBufferCount); + callRemoteAsync(Tag::ON_RELEASE_BUFFER, callbackId, + releaseFence, + currentMaxAcquiredBufferCount); + } + + void onTransactionQueueStalled(const String8& reason) override { + callRemoteAsync< + decltype(&ITransactionCompletedListener:: + onTransactionQueueStalled)>(Tag::ON_TRANSACTION_QUEUE_STALLED, + reason); } - void onTransactionQueueStalled() override { - callRemoteAsync( - Tag::ON_TRANSACTION_QUEUE_STALLED); + void onTrustedPresentationChanged(int id, bool inTrustedPresentationState) override { + callRemoteAsync( + Tag::ON_TRUSTED_PRESENTATION_CHANGED, id, inTrustedPresentationState); } }; @@ -306,6 +337,9 @@ status_t BnTransactionCompletedListener::onTransact(uint32_t code, const Parcel& case Tag::ON_TRANSACTION_QUEUE_STALLED: return callLocalAsync(data, reply, &ITransactionCompletedListener::onTransactionQueueStalled); + case Tag::ON_TRUSTED_PRESENTATION_CHANGED: + return callLocalAsync(data, reply, + &ITransactionCompletedListener::onTrustedPresentationChanged); } } @@ -321,7 +355,11 @@ ListenerCallbacks ListenerCallbacks::filter(CallbackId::Type type) const { status_t CallbackId::writeToParcel(Parcel* output) const { SAFE_PARCEL(output->writeInt64, id); - SAFE_PARCEL(output->writeInt32, static_cast(type)); + if (type == Type::ON_COMPLETE && includeJankData) { + SAFE_PARCEL(output->writeInt32, kSerializedCallbackTypeOnCompelteWithJankData); + } else { + SAFE_PARCEL(output->writeInt32, static_cast(type)); + } return NO_ERROR; } @@ -329,7 +367,13 @@ status_t CallbackId::readFromParcel(const Parcel* input) { SAFE_PARCEL(input->readInt64, &id); int32_t typeAsInt; SAFE_PARCEL(input->readInt32, &typeAsInt); - type = static_cast(typeAsInt); + if (typeAsInt == kSerializedCallbackTypeOnCompelteWithJankData) { + type = Type::ON_COMPLETE; + includeJankData = true; + } else { + type = static_cast(typeAsInt); + includeJankData = false; + } return NO_ERROR; } diff --git a/libs/gui/LayerDebugInfo.cpp b/libs/gui/LayerDebugInfo.cpp index ea5fb293a6fc99de3c0eda2d4ba6425303537fdf..15b2221464d5ed649d477af79fce1fb98ae7db9c 100644 --- a/libs/gui/LayerDebugInfo.cpp +++ b/libs/gui/LayerDebugInfo.cpp @@ -27,7 +27,7 @@ using android::base::StringAppendF; #define RETURN_ON_ERROR(X) do {status_t res = (X); if (res != NO_ERROR) return res;} while(false) -namespace android { +namespace android::gui { status_t LayerDebugInfo::writeToParcel(Parcel* parcel) const { RETURN_ON_ERROR(parcel->writeCString(mName.c_str())); @@ -149,4 +149,4 @@ std::string to_string(const LayerDebugInfo& info) { return result; } -} // android +} // namespace android::gui diff --git a/libs/gui/LayerMetadata.cpp b/libs/gui/LayerMetadata.cpp index 189d51a4c14521c4a00d4739a14ab45836c27f38..4e12fd330c116c42ff854652147fdc5065656017 100644 --- a/libs/gui/LayerMetadata.cpp +++ b/libs/gui/LayerMetadata.cpp @@ -23,7 +23,7 @@ using android::base::StringPrintf; -namespace android { +namespace android::gui { LayerMetadata::LayerMetadata() = default; @@ -144,4 +144,4 @@ std::string LayerMetadata::itemToString(uint32_t key, const char* separator) con } } -} // namespace android +} // namespace android::gui diff --git a/libs/gui/LayerState.cpp b/libs/gui/LayerState.cpp index 74e6ae6a9b1729448b9ec0663cc2d64f81af9c2f..2322b70d1ce5286d78524429da867d3309ba8141 100644 --- a/libs/gui/LayerState.cpp +++ b/libs/gui/LayerState.cpp @@ -19,15 +19,36 @@ #include #include +#include #include #include #include -#include #include +#include #include #include #include +#define CHECK_DIFF(DIFF_RESULT, CHANGE_FLAG, OTHER, FIELD) \ + { \ + if ((OTHER.what & CHANGE_FLAG) && (FIELD != OTHER.FIELD)) { \ + DIFF_RESULT |= CHANGE_FLAG; \ + } \ + } + +#define CHECK_DIFF2(DIFF_RESULT, CHANGE_FLAG, OTHER, FIELD1, FIELD2) \ + { \ + CHECK_DIFF(DIFF_RESULT, CHANGE_FLAG, OTHER, FIELD1) \ + CHECK_DIFF(DIFF_RESULT, CHANGE_FLAG, OTHER, FIELD2) \ + } + +#define CHECK_DIFF3(DIFF_RESULT, CHANGE_FLAG, OTHER, FIELD1, FIELD2, FIELD3) \ + { \ + CHECK_DIFF(DIFF_RESULT, CHANGE_FLAG, OTHER, FIELD1) \ + CHECK_DIFF(DIFF_RESULT, CHANGE_FLAG, OTHER, FIELD2) \ + CHECK_DIFF(DIFF_RESULT, CHANGE_FLAG, OTHER, FIELD3) \ + } + namespace android { using gui::FocusRequest; @@ -40,22 +61,20 @@ layer_state_t::layer_state_t() x(0), y(0), z(0), - w(0), - h(0), - alpha(0), flags(0), mask(0), reserved(0), cornerRadius(0.0f), backgroundBlurRadius(0), - transform(0), + color(0), + bufferTransform(0), transformToDisplayInverse(false), crop(Rect::INVALID_RECT), dataspace(ui::Dataspace::UNKNOWN), surfaceDamageRegion(), api(-1), colorTransform(mat4()), - bgColorAlpha(0), + bgColor(0), bgColorDataspace(ui::Dataspace::UNKNOWN), colorSpaceAgnostic(false), shadowRadius(0.0f), @@ -63,9 +82,11 @@ layer_state_t::layer_state_t() frameRate(0.0f), frameRateCompatibility(ANATIVEWINDOW_FRAME_RATE_COMPATIBILITY_DEFAULT), changeFrameRateStrategy(ANATIVEWINDOW_CHANGE_FRAME_RATE_ONLY_IF_SEAMLESS), + defaultFrameRateCompatibility(ANATIVEWINDOW_FRAME_RATE_COMPATIBILITY_DEFAULT), fixedTransformHint(ui::Transform::ROT_INVALID), autoRefresh(false), isTrustedOverlay(false), + borderEnabled(false), bufferCrop(Rect::INVALID_RECT), destinationFrame(Rect::INVALID_RECT), dropInputMode(gui::DropInputMode::NONE) { @@ -82,25 +103,27 @@ status_t layer_state_t::write(Parcel& output) const SAFE_PARCEL(output.writeFloat, x); SAFE_PARCEL(output.writeFloat, y); SAFE_PARCEL(output.writeInt32, z); - SAFE_PARCEL(output.writeUint32, w); - SAFE_PARCEL(output.writeUint32, h); SAFE_PARCEL(output.writeUint32, layerStack.id); - SAFE_PARCEL(output.writeFloat, alpha); SAFE_PARCEL(output.writeUint32, flags); SAFE_PARCEL(output.writeUint32, mask); SAFE_PARCEL(matrix.write, output); SAFE_PARCEL(output.write, crop); - SAFE_PARCEL(SurfaceControl::writeNullableToParcel, output, reparentSurfaceControl); SAFE_PARCEL(SurfaceControl::writeNullableToParcel, output, relativeLayerSurfaceControl); SAFE_PARCEL(SurfaceControl::writeNullableToParcel, output, parentSurfaceControlForChild); SAFE_PARCEL(output.writeFloat, color.r); SAFE_PARCEL(output.writeFloat, color.g); SAFE_PARCEL(output.writeFloat, color.b); + SAFE_PARCEL(output.writeFloat, color.a); SAFE_PARCEL(windowInfoHandle->writeToParcel, &output); SAFE_PARCEL(output.write, transparentRegion); - SAFE_PARCEL(output.writeUint32, transform); + 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); @@ -117,7 +140,10 @@ status_t layer_state_t::write(Parcel& output) const SAFE_PARCEL(output.writeFloat, cornerRadius); SAFE_PARCEL(output.writeUint32, backgroundBlurRadius); SAFE_PARCEL(output.writeParcelable, metadata); - SAFE_PARCEL(output.writeFloat, bgColorAlpha); + SAFE_PARCEL(output.writeFloat, bgColor.r); + SAFE_PARCEL(output.writeFloat, bgColor.g); + SAFE_PARCEL(output.writeFloat, bgColor.b); + SAFE_PARCEL(output.writeFloat, bgColor.a); SAFE_PARCEL(output.writeUint32, static_cast(bgColorDataspace)); SAFE_PARCEL(output.writeBool, colorSpaceAgnostic); SAFE_PARCEL(output.writeVectorSize, listeners); @@ -131,6 +157,7 @@ status_t layer_state_t::write(Parcel& output) const SAFE_PARCEL(output.writeFloat, frameRate); SAFE_PARCEL(output.writeByte, frameRateCompatibility); SAFE_PARCEL(output.writeByte, changeFrameRateStrategy); + SAFE_PARCEL(output.writeByte, defaultFrameRateCompatibility); SAFE_PARCEL(output.writeUint32, fixedTransformHint); SAFE_PARCEL(output.writeBool, autoRefresh); SAFE_PARCEL(output.writeBool, dimmingEnabled); @@ -161,6 +188,11 @@ status_t layer_state_t::write(Parcel& output) const if (hasBufferData) { SAFE_PARCEL(output.writeParcelable, *bufferData); } + SAFE_PARCEL(output.writeParcelable, trustedPresentationThresholds); + SAFE_PARCEL(output.writeParcelable, trustedPresentationListener); + SAFE_PARCEL(output.writeFloat, currentHdrSdrRatio); + SAFE_PARCEL(output.writeFloat, desiredHdrSdrRatio); + SAFE_PARCEL(output.writeInt32, static_cast(cachingHint)) return NO_ERROR; } @@ -172,10 +204,7 @@ status_t layer_state_t::read(const Parcel& input) SAFE_PARCEL(input.readFloat, &x); SAFE_PARCEL(input.readFloat, &y); SAFE_PARCEL(input.readInt32, &z); - SAFE_PARCEL(input.readUint32, &w); - SAFE_PARCEL(input.readUint32, &h); SAFE_PARCEL(input.readUint32, &layerStack.id); - SAFE_PARCEL(input.readFloat, &alpha); SAFE_PARCEL(input.readUint32, &flags); @@ -183,7 +212,6 @@ status_t layer_state_t::read(const Parcel& input) SAFE_PARCEL(matrix.read, input); SAFE_PARCEL(input.read, crop); - SAFE_PARCEL(SurfaceControl::readNullableFromParcel, input, &reparentSurfaceControl); SAFE_PARCEL(SurfaceControl::readNullableFromParcel, input, &relativeLayerSurfaceControl); SAFE_PARCEL(SurfaceControl::readNullableFromParcel, input, &parentSurfaceControlForChild); @@ -195,11 +223,25 @@ status_t layer_state_t::read(const Parcel& input) color.g = tmpFloat; SAFE_PARCEL(input.readFloat, &tmpFloat); color.b = tmpFloat; + SAFE_PARCEL(input.readFloat, &tmpFloat); + color.a = tmpFloat; + SAFE_PARCEL(windowInfoHandle->readFromParcel, &input); SAFE_PARCEL(input.read, transparentRegion); - SAFE_PARCEL(input.readUint32, &transform); + 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); @@ -220,7 +262,14 @@ status_t layer_state_t::read(const Parcel& input) SAFE_PARCEL(input.readUint32, &backgroundBlurRadius); SAFE_PARCEL(input.readParcelable, &metadata); - SAFE_PARCEL(input.readFloat, &bgColorAlpha); + SAFE_PARCEL(input.readFloat, &tmpFloat); + bgColor.r = tmpFloat; + SAFE_PARCEL(input.readFloat, &tmpFloat); + bgColor.g = tmpFloat; + SAFE_PARCEL(input.readFloat, &tmpFloat); + bgColor.b = tmpFloat; + SAFE_PARCEL(input.readFloat, &tmpFloat); + bgColor.a = tmpFloat; SAFE_PARCEL(input.readUint32, &tmpUint32); bgColorDataspace = static_cast(tmpUint32); SAFE_PARCEL(input.readBool, &colorSpaceAgnostic); @@ -240,6 +289,7 @@ status_t layer_state_t::read(const Parcel& input) SAFE_PARCEL(input.readFloat, &frameRate); SAFE_PARCEL(input.readByte, &frameRateCompatibility); SAFE_PARCEL(input.readByte, &changeFrameRateStrategy); + SAFE_PARCEL(input.readByte, &defaultFrameRateCompatibility); SAFE_PARCEL(input.readUint32, &tmpUint32); fixedTransformHint = static_cast(tmpUint32); SAFE_PARCEL(input.readBool, &autoRefresh); @@ -280,6 +330,19 @@ status_t layer_state_t::read(const Parcel& input) } else { bufferData = nullptr; } + + SAFE_PARCEL(input.readParcelable, &trustedPresentationThresholds); + SAFE_PARCEL(input.readParcelable, &trustedPresentationListener); + + SAFE_PARCEL(input.readFloat, &tmpFloat); + currentHdrSdrRatio = tmpFloat; + SAFE_PARCEL(input.readFloat, &tmpFloat); + desiredHdrSdrRatio = tmpFloat; + + int32_t tmpInt32; + SAFE_PARCEL(input.readInt32, &tmpInt32); + cachingHint = static_cast(tmpInt32); + return NO_ERROR; } @@ -458,14 +521,9 @@ void layer_state_t::merge(const layer_state_t& other) { what &= ~eRelativeLayerChanged; z = other.z; } - if (other.what & eSizeChanged) { - what |= eSizeChanged; - w = other.w; - h = other.h; - } if (other.what & eAlphaChanged) { what |= eAlphaChanged; - alpha = other.alpha; + color.a = other.color.a; } if (other.what & eMatrixChanged) { what |= eMatrixChanged; @@ -507,12 +565,9 @@ void layer_state_t::merge(const layer_state_t& other) { what |= eReparent; parentSurfaceControlForChild = other.parentSurfaceControlForChild; } - if (other.what & eDestroySurface) { - what |= eDestroySurface; - } - if (other.what & eTransformChanged) { - what |= eTransformChanged; - transform = other.transform; + if (other.what & eBufferTransformChanged) { + what |= eBufferTransformChanged; + bufferTransform = other.bufferTransform; } if (other.what & eTransformToDisplayInverseChanged) { what |= eTransformToDisplayInverseChanged; @@ -526,10 +581,24 @@ void layer_state_t::merge(const layer_state_t& other) { what |= eBufferChanged; bufferData = other.bufferData; } + if (other.what & eTrustedPresentationInfoChanged) { + what |= eTrustedPresentationInfoChanged; + trustedPresentationListener = other.trustedPresentationListener; + trustedPresentationThresholds = other.trustedPresentationThresholds; + } if (other.what & eDataspaceChanged) { what |= eDataspaceChanged; dataspace = other.dataspace; } + if (other.what & eExtendedRangeBrightnessChanged) { + what |= eExtendedRangeBrightnessChanged; + desiredHdrSdrRatio = other.desiredHdrSdrRatio; + currentHdrSdrRatio = other.currentHdrSdrRatio; + } + if (other.what & eCachingHintChanged) { + what |= eCachingHintChanged; + cachingHint = other.cachingHint; + } if (other.what & eHdrMetadataChanged) { what |= eHdrMetadataChanged; hdrMetadata = other.hdrMetadata; @@ -559,8 +628,7 @@ void layer_state_t::merge(const layer_state_t& other) { } if (other.what & eBackgroundColorChanged) { what |= eBackgroundColorChanged; - color = other.color; - bgColorAlpha = other.bgColorAlpha; + bgColor = other.bgColor; bgColorDataspace = other.bgColorDataspace; } if (other.what & eMetadataChanged) { @@ -571,6 +639,16 @@ 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; + } if (other.what & eFrameRateSelectionPriority) { what |= eFrameRateSelectionPriority; frameRateSelectionPriority = other.frameRateSelectionPriority; @@ -614,7 +692,7 @@ void layer_state_t::merge(const layer_state_t& other) { } if (other.what & eColorChanged) { what |= eColorChanged; - color = other.color; + color.rgb = other.color.rgb; } if (other.what & eColorSpaceAgnosticChanged) { what |= eColorSpaceAgnosticChanged; @@ -624,6 +702,9 @@ void layer_state_t::merge(const layer_state_t& other) { what |= eDimmingEnabledChanged; dimmingEnabled = other.dimmingEnabled; } + if (other.what & eFlushJankData) { + what |= eFlushJankData; + } if ((other.what & what) != other.what) { ALOGE("Unmerged SurfaceComposer Transaction properties. LayerState::merge needs updating? " "other.what=0x%" PRIX64 " what=0x%" PRIX64 " unmerged flags=0x%" PRIX64, @@ -631,12 +712,83 @@ void layer_state_t::merge(const layer_state_t& other) { } } +uint64_t layer_state_t::diff(const layer_state_t& other) const { + uint64_t diff = 0; + CHECK_DIFF2(diff, ePositionChanged, other, x, y); + if (other.what & eLayerChanged) { + diff |= eLayerChanged; + diff &= ~eRelativeLayerChanged; + } + CHECK_DIFF(diff, eAlphaChanged, other, color.a); + CHECK_DIFF(diff, eMatrixChanged, other, matrix); + if (other.what & eTransparentRegionChanged && + (!transparentRegion.hasSameRects(other.transparentRegion))) { + diff |= eTransparentRegionChanged; + } + if (other.what & eFlagsChanged) { + uint64_t changedFlags = (flags & other.mask) ^ (other.flags & other.mask); + if (changedFlags) diff |= eFlagsChanged; + } + CHECK_DIFF(diff, eLayerStackChanged, other, layerStack); + CHECK_DIFF(diff, eCornerRadiusChanged, other, cornerRadius); + CHECK_DIFF(diff, eBackgroundBlurRadiusChanged, other, backgroundBlurRadius); + if (other.what & eBlurRegionsChanged) diff |= eBlurRegionsChanged; + if (other.what & eRelativeLayerChanged) { + diff |= eRelativeLayerChanged; + diff &= ~eLayerChanged; + } + if (other.what & eReparent && + !SurfaceControl::isSameSurface(parentSurfaceControlForChild, + other.parentSurfaceControlForChild)) { + diff |= eReparent; + } + CHECK_DIFF(diff, eBufferTransformChanged, other, bufferTransform); + CHECK_DIFF(diff, eTransformToDisplayInverseChanged, other, transformToDisplayInverse); + CHECK_DIFF(diff, eCropChanged, other, crop); + if (other.what & eBufferChanged) diff |= eBufferChanged; + CHECK_DIFF(diff, eDataspaceChanged, other, dataspace); + CHECK_DIFF2(diff, eExtendedRangeBrightnessChanged, other, currentHdrSdrRatio, + desiredHdrSdrRatio); + CHECK_DIFF(diff, eCachingHintChanged, other, cachingHint); + CHECK_DIFF(diff, eHdrMetadataChanged, other, hdrMetadata); + if (other.what & eSurfaceDamageRegionChanged && + (!surfaceDamageRegion.hasSameRects(other.surfaceDamageRegion))) { + diff |= eSurfaceDamageRegionChanged; + } + CHECK_DIFF(diff, eApiChanged, other, api); + if (other.what & eSidebandStreamChanged) diff |= eSidebandStreamChanged; + CHECK_DIFF(diff, eApiChanged, other, api); + CHECK_DIFF(diff, eColorTransformChanged, other, colorTransform); + if (other.what & eHasListenerCallbacksChanged) diff |= eHasListenerCallbacksChanged; + if (other.what & eInputInfoChanged) diff |= eInputInfoChanged; + 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, + changeFrameRateStrategy); + CHECK_DIFF(diff, eFixedTransformHintChanged, other, fixedTransformHint); + CHECK_DIFF(diff, eAutoRefreshChanged, other, autoRefresh); + CHECK_DIFF(diff, eTrustedOverlayChanged, other, isTrustedOverlay); + CHECK_DIFF(diff, eStretchChanged, other, stretchEffect); + CHECK_DIFF(diff, eBufferCropChanged, other, bufferCrop); + CHECK_DIFF(diff, eDestinationFrameChanged, other, destinationFrame); + if (other.what & eProducerDisconnect) diff |= eProducerDisconnect; + CHECK_DIFF(diff, eDropInputModeChanged, other, dropInputMode); + CHECK_DIFF(diff, eColorChanged, other, color.rgb); + CHECK_DIFF(diff, eColorSpaceAgnosticChanged, other, colorSpaceAgnostic); + CHECK_DIFF(diff, eDimmingEnabledChanged, other, dimmingEnabled); + return diff; +} + bool layer_state_t::hasBufferChanges() const { return what & layer_state_t::eBufferChanged; } bool layer_state_t::hasValidBuffer() const { - return bufferData && (bufferData->buffer || bufferData->cachedBuffer.isValid()); + return bufferData && (bufferData->hasBuffer() || bufferData->cachedBuffer.isValid()); } status_t layer_state_t::matrix22_t::write(Parcel& output) const { @@ -662,29 +814,44 @@ bool InputWindowCommands::merge(const InputWindowCommands& other) { changes |= !other.focusRequests.empty(); focusRequests.insert(focusRequests.end(), std::make_move_iterator(other.focusRequests.begin()), std::make_move_iterator(other.focusRequests.end())); - changes |= other.syncInputWindows && !syncInputWindows; - syncInputWindows |= other.syncInputWindows; + changes |= !other.windowInfosReportedListeners.empty(); + windowInfosReportedListeners.insert(other.windowInfosReportedListeners.begin(), + other.windowInfosReportedListeners.end()); return changes; } bool InputWindowCommands::empty() const { - return focusRequests.empty() && !syncInputWindows; + return focusRequests.empty() && windowInfosReportedListeners.empty(); } void InputWindowCommands::clear() { focusRequests.clear(); - syncInputWindows = false; + windowInfosReportedListeners.clear(); } status_t InputWindowCommands::write(Parcel& output) const { SAFE_PARCEL(output.writeParcelableVector, focusRequests); - SAFE_PARCEL(output.writeBool, syncInputWindows); + + SAFE_PARCEL(output.writeInt32, windowInfosReportedListeners.size()); + for (const auto& listener : windowInfosReportedListeners) { + SAFE_PARCEL(output.writeStrongBinder, listener); + } + return NO_ERROR; } status_t InputWindowCommands::read(const Parcel& input) { SAFE_PARCEL(input.readParcelableVector, &focusRequests); - SAFE_PARCEL(input.readBool, &syncInputWindows); + + int listenerSize = 0; + SAFE_PARCEL_READ_SIZE(input.readInt32, &listenerSize, input.dataSize()); + windowInfosReportedListeners.reserve(listenerSize); + for (int i = 0; i < listenerSize; i++) { + sp listener; + SAFE_PARCEL(input.readStrongBinder, &listener); + windowInfosReportedListeners.insert(listener); + } + return NO_ERROR; } @@ -730,6 +897,11 @@ status_t CaptureArgs::writeToParcel(Parcel* output) const { SAFE_PARCEL(output->writeInt32, static_cast(dataspace)); SAFE_PARCEL(output->writeBool, allowProtected); SAFE_PARCEL(output->writeBool, grayscale); + SAFE_PARCEL(output->writeInt32, excludeHandles.size()); + for (auto& excludeHandle : excludeHandles) { + SAFE_PARCEL(output->writeStrongBinder, excludeHandle); + } + SAFE_PARCEL(output->writeBool, hintForSeamlessTransition); return NO_ERROR; } @@ -746,6 +918,15 @@ status_t CaptureArgs::readFromParcel(const Parcel* input) { dataspace = static_cast(value); SAFE_PARCEL(input->readBool, &allowProtected); SAFE_PARCEL(input->readBool, &grayscale); + int32_t numExcludeHandles = 0; + SAFE_PARCEL_READ_SIZE(input->readInt32, &numExcludeHandles, input->dataSize()); + excludeHandles.reserve(numExcludeHandles); + for (int i = 0; i < numExcludeHandles; i++) { + sp binder; + SAFE_PARCEL(input->readStrongBinder, &binder); + excludeHandles.emplace(binder); + } + SAFE_PARCEL(input->readBool, &hintForSeamlessTransition); return NO_ERROR; } @@ -773,10 +954,6 @@ status_t LayerCaptureArgs::writeToParcel(Parcel* output) const { SAFE_PARCEL(CaptureArgs::writeToParcel, output); SAFE_PARCEL(output->writeStrongBinder, layerHandle); - SAFE_PARCEL(output->writeInt32, excludeHandles.size()); - for (auto el : excludeHandles) { - SAFE_PARCEL(output->writeStrongBinder, el); - } SAFE_PARCEL(output->writeBool, childrenOnly); return NO_ERROR; } @@ -786,15 +963,6 @@ status_t LayerCaptureArgs::readFromParcel(const Parcel* input) { SAFE_PARCEL(input->readStrongBinder, &layerHandle); - int32_t numExcludeHandles = 0; - SAFE_PARCEL_READ_SIZE(input->readInt32, &numExcludeHandles, input->dataSize()); - excludeHandles.reserve(numExcludeHandles); - for (int i = 0; i < numExcludeHandles; i++) { - sp binder; - SAFE_PARCEL(input->readStrongBinder, &binder); - excludeHandles.emplace(binder); - } - SAFE_PARCEL(input->readBool, &childrenOnly); return NO_ERROR; } @@ -836,6 +1004,7 @@ status_t BufferData::writeToParcel(Parcel* output) const { SAFE_PARCEL(output->writeUint64, cachedBuffer.id); SAFE_PARCEL(output->writeBool, hasBarrier); SAFE_PARCEL(output->writeUint64, barrierFrameNumber); + SAFE_PARCEL(output->writeUint32, producerId); return NO_ERROR; } @@ -874,8 +1043,25 @@ status_t BufferData::readFromParcel(const Parcel* input) { SAFE_PARCEL(input->readBool, &hasBarrier); SAFE_PARCEL(input->readUint64, &barrierFrameNumber); + SAFE_PARCEL(input->readUint32, &producerId); return NO_ERROR; } +status_t TrustedPresentationListener::writeToParcel(Parcel* parcel) const { + SAFE_PARCEL(parcel->writeStrongBinder, callbackInterface); + SAFE_PARCEL(parcel->writeInt32, callbackId); + return NO_ERROR; +} + +status_t TrustedPresentationListener::readFromParcel(const Parcel* parcel) { + sp tmpBinder = nullptr; + SAFE_PARCEL(parcel->readNullableStrongBinder, &tmpBinder); + if (tmpBinder) { + callbackInterface = checked_interface_cast(tmpBinder); + } + SAFE_PARCEL(parcel->readInt32, &callbackId); + return NO_ERROR; +} + }; // namespace android diff --git a/libs/gui/LayerStatePermissions.cpp b/libs/gui/LayerStatePermissions.cpp new file mode 100644 index 0000000000000000000000000000000000000000..28697ca953028e9dccc78b99da5c01e14b0430dc --- /dev/null +++ b/libs/gui/LayerStatePermissions.cpp @@ -0,0 +1,58 @@ +/* + * 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. + */ + +#include +#include +#include +#ifndef __ANDROID_VNDK__ +#include +#endif // __ANDROID_VNDK__ +#include + +namespace android { +std::unordered_map LayerStatePermissions::mPermissionMap = { + // If caller has ACCESS_SURFACE_FLINGER, they automatically get ROTATE_SURFACE_FLINGER + // permission, as well + {"android.permission.ACCESS_SURFACE_FLINGER", + layer_state_t::Permission::ACCESS_SURFACE_FLINGER | + layer_state_t::Permission::ROTATE_SURFACE_FLINGER}, + {"android.permission.ROTATE_SURFACE_FLINGER", + layer_state_t::Permission::ROTATE_SURFACE_FLINGER}, + {"android.permission.INTERNAL_SYSTEM_WINDOW", + layer_state_t::Permission::INTERNAL_SYSTEM_WINDOW}, +}; + +static bool callingThreadHasPermission(const std::string& permission __attribute__((unused)), + int pid __attribute__((unused)), + int uid __attribute__((unused))) { +#ifndef __ANDROID_VNDK__ + return uid == AID_GRAPHICS || uid == AID_SYSTEM || + PermissionCache::checkPermission(String16(permission.c_str()), pid, uid); +#endif // __ANDROID_VNDK__ + return false; +} + +uint32_t LayerStatePermissions::getTransactionPermissions(int pid, int uid) { + uint32_t permissions = 0; + for (auto [permissionName, permissionVal] : mPermissionMap) { + if (callingThreadHasPermission(permissionName, pid, uid)) { + permissions |= permissionVal; + } + } + + return permissions; +} +} // namespace android diff --git a/libs/gui/ScreenCaptureResults.cpp b/libs/gui/ScreenCaptureResults.cpp index fe387064bc5b74e90e845c92f263ea089b547226..601a5f9b333c6b0ab616df2ba35bff331b5b9419 100644 --- a/libs/gui/ScreenCaptureResults.cpp +++ b/libs/gui/ScreenCaptureResults.cpp @@ -17,6 +17,7 @@ #include #include +#include namespace android::gui { @@ -28,17 +29,17 @@ status_t ScreenCaptureResults::writeToParcel(android::Parcel* parcel) const { SAFE_PARCEL(parcel->writeBool, false); } - if (fence != Fence::NO_FENCE) { + if (fenceResult.ok() && fenceResult.value() != Fence::NO_FENCE) { SAFE_PARCEL(parcel->writeBool, true); - SAFE_PARCEL(parcel->write, *fence); + SAFE_PARCEL(parcel->write, *fenceResult.value()); } else { SAFE_PARCEL(parcel->writeBool, false); + SAFE_PARCEL(parcel->writeInt32, fenceStatus(fenceResult)); } SAFE_PARCEL(parcel->writeBool, capturedSecureLayers); SAFE_PARCEL(parcel->writeBool, capturedHdrLayers); SAFE_PARCEL(parcel->writeUint32, static_cast(capturedDataspace)); - SAFE_PARCEL(parcel->writeInt32, result); return NO_ERROR; } @@ -53,8 +54,13 @@ status_t ScreenCaptureResults::readFromParcel(const android::Parcel* parcel) { bool hasFence; SAFE_PARCEL(parcel->readBool, &hasFence); if (hasFence) { - fence = new Fence(); - SAFE_PARCEL(parcel->read, *fence); + fenceResult = sp::make(); + SAFE_PARCEL(parcel->read, *fenceResult.value()); + } else { + status_t status; + SAFE_PARCEL(parcel->readInt32, &status); + fenceResult = status == NO_ERROR ? FenceResult(Fence::NO_FENCE) + : FenceResult(base::unexpected(status)); } SAFE_PARCEL(parcel->readBool, &capturedSecureLayers); @@ -62,7 +68,6 @@ status_t ScreenCaptureResults::readFromParcel(const android::Parcel* parcel) { uint32_t dataspace = 0; SAFE_PARCEL(parcel->readUint32, &dataspace); capturedDataspace = static_cast(dataspace); - SAFE_PARCEL(parcel->readInt32, &result); return NO_ERROR; } diff --git a/libs/gui/Surface.cpp b/libs/gui/Surface.cpp index 16edfd42676fbba609c729e61e7ea2e6e73d5c7b..ed691006e98bc99ced3f2dcd7fc7acbd229cfdf4 100644 --- a/libs/gui/Surface.cpp +++ b/libs/gui/Surface.cpp @@ -30,15 +30,18 @@ #include #include +#include +#include #include -#include #include +#include #include #include #include #include +#include #include #include @@ -49,10 +52,17 @@ namespace android { +using gui::aidl_utils::statusTFromBinderStatus; using ui::Dataspace; namespace { +enum { + // moved from nativewindow/include/system/window.h, to be removed + NATIVE_WINDOW_GET_WIDE_COLOR_SUPPORT = 28, + NATIVE_WINDOW_GET_HDR_SUPPORT = 29, +}; + bool isInterceptorRegistrationOp(int op) { return op == NATIVE_WINDOW_SET_CANCEL_INTERCEPTOR || op == NATIVE_WINDOW_SET_DEQUEUE_INTERCEPTOR || @@ -182,7 +192,7 @@ status_t Surface::getDisplayRefreshCycleDuration(nsecs_t* outRefreshDuration) { gui::DisplayStatInfo stats; binder::Status status = composerServiceAIDL()->getDisplayStats(nullptr, &stats); if (!status.isOk()) { - return status.transactionError(); + return statusTFromBinderStatus(status); } *outRefreshDuration = stats.vsyncPeriod; @@ -345,33 +355,25 @@ status_t Surface::getFrameTimestamps(uint64_t frameNumber, return NO_ERROR; } +// Deprecated(b/242763577): to be removed, this method should not be used +// The reason this method still exists here is to support compiled vndk +// Surface support should not be tied to the display +// Return true since most displays should have this support status_t Surface::getWideColorSupport(bool* supported) { ATRACE_CALL(); - const sp display = ComposerServiceAIDL::getInstance().getInternalDisplayToken(); - if (display == nullptr) { - return NAME_NOT_FOUND; - } - - *supported = false; - binder::Status status = composerServiceAIDL()->isWideColorDisplay(display, supported); - return status.transactionError(); + *supported = true; + return NO_ERROR; } +// Deprecated(b/242763577): to be removed, this method should not be used +// The reason this method still exists here is to support compiled vndk +// Surface support should not be tied to the display +// Return true since most displays should have this support status_t Surface::getHdrSupport(bool* supported) { ATRACE_CALL(); - const sp display = ComposerServiceAIDL::getInstance().getInternalDisplayToken(); - if (display == nullptr) { - return NAME_NOT_FOUND; - } - - ui::DynamicDisplayInfo info; - if (status_t err = composerService()->getDynamicDisplayInfo(display, &info); err != NO_ERROR) { - return err; - } - - *supported = !info.hdrCapabilities.getSupportedHdrTypes().empty(); + *supported = true; return NO_ERROR; } @@ -544,82 +546,6 @@ int Surface::setSwapInterval(int interval) { return NO_ERROR; } -class FenceMonitor { -public: - explicit FenceMonitor(const char* name) : mName(name), mFencesQueued(0), mFencesSignaled(0) { - std::thread thread(&FenceMonitor::loop, this); - pthread_setname_np(thread.native_handle(), mName); - thread.detach(); - } - - void queueFence(const sp& fence) { - char message[64]; - - std::lock_guard lock(mMutex); - if (fence->getSignalTime() != Fence::SIGNAL_TIME_PENDING) { - snprintf(message, sizeof(message), "%s fence %u has signaled", mName, mFencesQueued); - ATRACE_NAME(message); - // Need an increment on both to make the trace number correct. - mFencesQueued++; - mFencesSignaled++; - return; - } - snprintf(message, sizeof(message), "Trace %s fence %u", mName, mFencesQueued); - ATRACE_NAME(message); - - mQueue.push_back(fence); - mCondition.notify_one(); - mFencesQueued++; - ATRACE_INT(mName, int32_t(mQueue.size())); - } - -private: -#pragma clang diagnostic push -#pragma clang diagnostic ignored "-Wmissing-noreturn" - void loop() { - while (true) { - threadLoop(); - } - } -#pragma clang diagnostic pop - - void threadLoop() { - sp fence; - uint32_t fenceNum; - { - std::unique_lock lock(mMutex); - while (mQueue.empty()) { - mCondition.wait(lock); - } - fence = mQueue[0]; - fenceNum = mFencesSignaled; - } - { - char message[64]; - snprintf(message, sizeof(message), "waiting for %s %u", mName, fenceNum); - ATRACE_NAME(message); - - status_t result = fence->waitForever(message); - if (result != OK) { - ALOGE("Error waiting for fence: %d", result); - } - } - { - std::lock_guard lock(mMutex); - mQueue.pop_front(); - mFencesSignaled++; - ATRACE_INT(mName, int32_t(mQueue.size())); - } - } - - const char* mName; - uint32_t mFencesQueued; - uint32_t mFencesSignaled; - std::deque> mQueue; - std::condition_variable mCondition; - std::mutex mMutex; -}; - void Surface::getDequeueBufferInputLocked( IGraphicBufferProducer::DequeueBufferInput* dequeueInput) { LOG_ALWAYS_FATAL_IF(dequeueInput == nullptr, "input is null"); @@ -634,7 +560,7 @@ void Surface::getDequeueBufferInputLocked( } int Surface::dequeueBuffer(android_native_buffer_t** buffer, int* fenceFd) { - ATRACE_CALL(); + ATRACE_FORMAT("dequeueBuffer - %s", getDebugName()); ALOGV("Surface::dequeueBuffer"); IGraphicBufferProducer::DequeueBufferInput dqInput; @@ -693,7 +619,7 @@ int Surface::dequeueBuffer(android_native_buffer_t** buffer, int* fenceFd) { ALOGE_IF(fence == nullptr, "Surface::dequeueBuffer: received null Fence! buf=%d", buf); if (CC_UNLIKELY(atrace_is_tag_enabled(ATRACE_TAG_GRAPHICS))) { - static FenceMonitor hwcReleaseThread("HWC release"); + static gui::FenceMonitor hwcReleaseThread("HWC release"); hwcReleaseThread.queueFence(fence); } @@ -892,7 +818,7 @@ int Surface::dequeueBuffers(std::vector* buffers) { sp& gbuf(mSlots[slot].buffer); if (CC_UNLIKELY(atrace_is_tag_enabled(ATRACE_TAG_GRAPHICS))) { - static FenceMonitor hwcReleaseThread("HWC release"); + static gui::FenceMonitor hwcReleaseThread("HWC release"); hwcReleaseThread.queueFence(output.fence); } @@ -1162,7 +1088,7 @@ void Surface::onBufferQueuedLocked(int slot, sp fence, mQueueBufferCondition.broadcast(); if (CC_UNLIKELY(atrace_is_tag_enabled(ATRACE_TAG_GRAPHICS))) { - static FenceMonitor gpuCompletionThread("GPU completion"); + static gui::FenceMonitor gpuCompletionThread("GPU completion"); gpuCompletionThread.queueFence(fence); } } @@ -1263,10 +1189,10 @@ void Surface::querySupportedTimestampsLocked() const { mQueriedSupportedTimestamps = true; std::vector supportedFrameTimestamps; - status_t err = composerService()->getSupportedFrameTimestamps( - &supportedFrameTimestamps); + binder::Status status = + composerServiceAIDL()->getSupportedFrameTimestamps(&supportedFrameTimestamps); - if (err != NO_ERROR) { + if (!status.isOk()) { return; } @@ -1294,15 +1220,12 @@ int Surface::query(int what, int* value) const { if (err == NO_ERROR) { return NO_ERROR; } - sp surfaceComposer = composerService(); + sp surfaceComposer = composerServiceAIDL(); if (surfaceComposer == nullptr) { return -EPERM; // likely permissions error } - if (surfaceComposer->authenticateSurfaceTexture(mGraphicBufferProducer)) { - *value = 1; - } else { - *value = 0; - } + // ISurfaceComposer no longer supports authenticateSurfaceTexture + *value = 0; return NO_ERROR; } case NATIVE_WINDOW_CONCRETE_TYPE: @@ -1873,9 +1796,15 @@ int Surface::dispatchSetFrameTimelineInfo(va_list args) { auto frameTimelineVsyncId = static_cast(va_arg(args, int64_t)); auto inputEventId = static_cast(va_arg(args, int32_t)); auto startTimeNanos = static_cast(va_arg(args, int64_t)); + auto useForRefreshRateSelection = static_cast(va_arg(args, int32_t)); ALOGV("Surface::%s", __func__); - return setFrameTimelineInfo(frameNumber, {frameTimelineVsyncId, inputEventId, startTimeNanos}); + FrameTimelineInfo ftlInfo; + ftlInfo.vsyncId = frameTimelineVsyncId; + ftlInfo.inputEventId = inputEventId; + ftlInfo.startTimeNanos = startTimeNanos; + ftlInfo.useForRefreshRateSelection = useForRefreshRateSelection; + return setFrameTimelineInfo(frameNumber, ftlInfo); } bool Surface::transformToDisplayInverse() const { @@ -2635,23 +2564,19 @@ void Surface::ProducerListenerProxy::onBuffersDiscarded(const std::vectoronBuffersDiscarded(discardedBufs); } -status_t Surface::setFrameRate(float frameRate, int8_t compatibility, - int8_t changeFrameRateStrategy) { - ATRACE_CALL(); - ALOGV("Surface::setFrameRate"); - - if (!ValidateFrameRate(frameRate, compatibility, changeFrameRateStrategy, - "Surface::setFrameRate")) { - return BAD_VALUE; - } - - return composerService()->setFrameRate(mGraphicBufferProducer, frameRate, compatibility, - changeFrameRateStrategy); +[[deprecated]] status_t Surface::setFrameRate(float /*frameRate*/, int8_t /*compatibility*/, + int8_t /*changeFrameRateStrategy*/) { + ALOGI("Surface::setFrameRate is deprecated, setFrameRate hint is dropped as destination is not " + "SurfaceFlinger"); + // ISurfaceComposer no longer supports setFrameRate, we will return NO_ERROR when the api is + // called to avoid apps crashing, as BAD_VALUE can generate fatal exception in apps. + return NO_ERROR; } status_t Surface::setFrameTimelineInfo(uint64_t /*frameNumber*/, - const FrameTimelineInfo& frameTimelineInfo) { - return composerService()->setFrameTimelineInfo(mGraphicBufferProducer, frameTimelineInfo); + const FrameTimelineInfo& /*frameTimelineInfo*/) { + // ISurfaceComposer no longer supports setFrameTimelineInfo + return BAD_VALUE; } sp Surface::getSurfaceControlHandle() const { @@ -2664,4 +2589,12 @@ void Surface::destroy() { mSurfaceControlHandle = nullptr; } +const char* Surface::getDebugName() { + std::unique_lock lock{mNameMutex}; + if (mName.empty()) { + mName = getConsumerName(); + } + return mName.c_str(); +} + }; // namespace android diff --git a/libs/gui/SurfaceComposerClient.cpp b/libs/gui/SurfaceComposerClient.cpp index 6d44f1079cb912cde9a3f364474e9a93e7eefc7c..8a1f7c6238b4aaf00e450a554d37c18e815e5431 100644 --- a/libs/gui/SurfaceComposerClient.cpp +++ b/libs/gui/SurfaceComposerClient.cpp @@ -16,11 +16,17 @@ #define LOG_TAG "SurfaceComposerClient" +#include #include #include +#include #include +#include #include +#include +#include +#include #include #include #include @@ -33,11 +39,11 @@ #include +#include #include #include #include #include -#include #include #include #include @@ -47,6 +53,8 @@ #include #include +#include +#include #include #include @@ -58,9 +66,11 @@ namespace android { using aidl::android::hardware::graphics::common::DisplayDecorationSupport; using gui::FocusRequest; using gui::IRegionSamplingListener; +using gui::TrustedPresentationThresholds; using gui::WindowInfo; using gui::WindowInfoHandle; using gui::WindowInfosListener; +using gui::aidl_utils::statusTFromBinderStatus; using ui::ColorMode; // --------------------------------------------------------------------------- @@ -73,6 +83,8 @@ std::atomic idCounter = 0; int64_t generateId() { return (((int64_t)getpid()) << 32) | ++idCounter; } + +void emptyCallback(nsecs_t, const sp&, const std::vector&) {} } // namespace ComposerService::ComposerService() @@ -111,7 +123,6 @@ bool ComposerService::connectLocked() { if (instance.mComposerService == nullptr) { if (ComposerService::getInstance().connectLocked()) { ALOGD("ComposerService reconnected"); - WindowInfosListenerReporter::getInstance()->reconnect(instance.mComposerService); } } return instance.mComposerService; @@ -159,6 +170,7 @@ bool ComposerServiceAIDL::connectLocked() { if (instance.mComposerService == nullptr) { if (ComposerServiceAIDL::getInstance().connectLocked()) { ALOGD("ComposerServiceAIDL reconnected"); + WindowInfosListenerReporter::getInstance()->reconnect(instance.mComposerService); } } return instance.mComposerService; @@ -240,6 +252,14 @@ CallbackId TransactionCompletedListener::addCallbackFunction( surfaceControls, CallbackId::Type callbackType) { std::lock_guard lock(mMutex); + return addCallbackFunctionLocked(callbackFunction, surfaceControls, callbackType); +} + +CallbackId TransactionCompletedListener::addCallbackFunctionLocked( + const TransactionCompletedCallback& callbackFunction, + const std::unordered_set, SurfaceComposerClient::SCHash>& + surfaceControls, + CallbackId::Type callbackType) { startListeningLocked(); CallbackId callbackId(getNextIdLocked(), callbackType); @@ -248,6 +268,11 @@ CallbackId TransactionCompletedListener::addCallbackFunction( for (const auto& surfaceControl : surfaceControls) { callbackSurfaceControls[surfaceControl->getHandle()] = surfaceControl; + + if (callbackType == CallbackId::Type::ON_COMPLETE && + mJankListeners.count(surfaceControl->getLayerId()) != 0) { + callbackId.includeJankData = true; + } } return callbackId; @@ -296,15 +321,26 @@ void TransactionCompletedListener::removeSurfaceStatsListener(void* context, voi } void TransactionCompletedListener::addSurfaceControlToCallbacks( - const sp& surfaceControl, - const std::unordered_set& callbackIds) { + SurfaceComposerClient::CallbackInfo& callbackInfo, + const sp& surfaceControl) { std::lock_guard lock(mMutex); - for (auto callbackId : callbackIds) { + bool includingJankData = false; + for (auto callbackId : callbackInfo.callbackIds) { mCallbacks[callbackId].surfaceControls.emplace(std::piecewise_construct, std::forward_as_tuple( surfaceControl->getHandle()), std::forward_as_tuple(surfaceControl)); + includingJankData = includingJankData || callbackId.includeJankData; + } + + // If no registered callback is requesting jank data, but there is a jank listener registered + // on the new surface control, add a synthetic callback that requests the jank data. + if (!includingJankData && mJankListeners.count(surfaceControl->getLayerId()) != 0) { + CallbackId callbackId = + addCallbackFunctionLocked(&emptyCallback, callbackInfo.surfaceControls, + CallbackId::Type::ON_COMPLETE); + callbackInfo.callbackIds.emplace(callbackId); } } @@ -380,10 +416,11 @@ void TransactionCompletedListener::onTransactionCompleted(ListenerStats listener surfaceStats.previousReleaseFence, surfaceStats.transformHint, surfaceStats.eventStats, surfaceStats.currentMaxAcquiredBufferCount); - if (callbacksMap[callbackId].surfaceControls[surfaceStats.surfaceControl]) { + if (callbacksMap[callbackId].surfaceControls[surfaceStats.surfaceControl] && + surfaceStats.transformHint.has_value()) { callbacksMap[callbackId] .surfaceControls[surfaceStats.surfaceControl] - ->setTransformHint(surfaceStats.transformHint); + ->setTransformHint(*surfaceStats.transformHint); } // If there is buffer id set, we look up any pending client release buffer callbacks // and call them. This is a performance optimization when we have a transaction @@ -449,23 +486,24 @@ void TransactionCompletedListener::onTransactionCompleted(ListenerStats listener } } -void TransactionCompletedListener::onTransactionQueueStalled() { - std::unordered_map> callbackCopy; - { - std::scoped_lock lock(mMutex); - callbackCopy = mQueueStallListeners; - } - for (auto const& it : callbackCopy) { - it.second(); - } +void TransactionCompletedListener::onTransactionQueueStalled(const String8& reason) { + std::unordered_map> callbackCopy; + { + std::scoped_lock lock(mMutex); + callbackCopy = mQueueStallListeners; + } + for (auto const& it : callbackCopy) { + it.second(reason.c_str()); + } } -void TransactionCompletedListener::addQueueStallListener(std::function stallListener, - void* id) { +void TransactionCompletedListener::addQueueStallListener( + std::function stallListener, void* id) { std::scoped_lock lock(mMutex); mQueueStallListeners[id] = stallListener; } -void TransactionCompletedListener::removeQueueStallListener(void *id) { + +void TransactionCompletedListener::removeQueueStallListener(void* id) { std::scoped_lock lock(mMutex); mQueueStallListeners.erase(id); } @@ -510,6 +548,45 @@ void TransactionCompletedListener::removeReleaseBufferCallback( } } +SurfaceComposerClient::PresentationCallbackRAII::PresentationCallbackRAII( + TransactionCompletedListener* tcl, int id) { + mTcl = tcl; + mId = id; +} + +SurfaceComposerClient::PresentationCallbackRAII::~PresentationCallbackRAII() { + mTcl->clearTrustedPresentationCallback(mId); +} + +sp +TransactionCompletedListener::addTrustedPresentationCallback(TrustedPresentationCallback tpc, + int id, void* context) { + std::scoped_lock lock(mMutex); + mTrustedPresentationCallbacks[id] = + std::tuple(tpc, context); + return new SurfaceComposerClient::PresentationCallbackRAII(this, id); +} + +void TransactionCompletedListener::clearTrustedPresentationCallback(int id) { + std::scoped_lock lock(mMutex); + mTrustedPresentationCallbacks.erase(id); +} + +void TransactionCompletedListener::onTrustedPresentationChanged(int id, + bool presentedWithinThresholds) { + TrustedPresentationCallback tpc; + void* context; + { + std::scoped_lock lock(mMutex); + auto it = mTrustedPresentationCallbacks.find(id); + if (it == mTrustedPresentationCallbacks.end()) { + return; + } + std::tie(tpc, context) = it->second; + } + tpc(context, presentedWithinThresholds); +} + // --------------------------------------------------------------------------- void removeDeadBufferCallback(void* /*context*/, uint64_t graphicBufferId); @@ -557,11 +634,13 @@ public: return NO_ERROR; } - uint64_t cache(const sp& buffer) { + uint64_t cache(const sp& buffer, + std::optional& outUncacheBuffer) { std::lock_guard lock(mMutex); if (mBuffers.size() >= BUFFER_CACHE_MAX_SIZE) { - evictLeastRecentlyUsedBuffer(); + outUncacheBuffer = findLeastRecentlyUsedBuffer(); + mBuffers.erase(outUncacheBuffer->id); } buffer->addDeathCallback(removeDeadBufferCallback, nullptr); @@ -572,16 +651,13 @@ public: void uncache(uint64_t cacheId) { std::lock_guard lock(mMutex); - uncacheLocked(cacheId); - } - - void uncacheLocked(uint64_t cacheId) REQUIRES(mMutex) { - mBuffers.erase(cacheId); - SurfaceComposerClient::doUncacheBufferTransaction(cacheId); + if (mBuffers.erase(cacheId)) { + SurfaceComposerClient::doUncacheBufferTransaction(cacheId); + } } private: - void evictLeastRecentlyUsedBuffer() REQUIRES(mMutex) { + client_cache_t findLeastRecentlyUsedBuffer() REQUIRES(mMutex) { auto itr = mBuffers.begin(); uint64_t minCounter = itr->second; auto minBuffer = itr; @@ -595,7 +671,8 @@ private: } itr++; } - uncacheLocked(minBuffer->first); + + return {.token = getToken(), .id = minBuffer->first}; } uint64_t getCounter() REQUIRES(mMutex) { @@ -625,12 +702,11 @@ SurfaceComposerClient::Transaction::Transaction() { SurfaceComposerClient::Transaction::Transaction(const Transaction& other) : mId(other.mId), - mForceSynchronous(other.mForceSynchronous), mTransactionNestCount(other.mTransactionNestCount), mAnimation(other.mAnimation), mEarlyWakeupStart(other.mEarlyWakeupStart), mEarlyWakeupEnd(other.mEarlyWakeupEnd), - mContainsBuffer(other.mContainsBuffer), + mMayContainBuffer(other.mMayContainBuffer), mDesiredPresentTime(other.mDesiredPresentTime), mIsAutoTimestamp(other.mIsAutoTimestamp), mFrameTimelineInfo(other.mFrameTimelineInfo), @@ -641,11 +717,16 @@ SurfaceComposerClient::Transaction::Transaction(const Transaction& other) mListenerCallbacks = other.mListenerCallbacks; } -void SurfaceComposerClient::Transaction::sanitize() { +void SurfaceComposerClient::Transaction::sanitize(int pid, int uid) { + uint32_t permissions = LayerStatePermissions::getTransactionPermissions(pid, uid); for (auto & [handle, composerState] : mComposerStates) { - composerState.state.sanitize(0 /* permissionMask */); + composerState.state.sanitize(permissions); + } + if (!mInputWindowCommands.empty() && + (permissions & layer_state_t::Permission::ACCESS_SURFACE_FLINGER) == 0) { + ALOGE("Only privileged callers are allowed to send input commands."); + mInputWindowCommands.clear(); } - mInputWindowCommands.clear(); } std::unique_ptr @@ -659,16 +740,15 @@ SurfaceComposerClient::Transaction::createFromParcel(const Parcel* parcel) { status_t SurfaceComposerClient::Transaction::readFromParcel(const Parcel* parcel) { - const uint32_t forceSynchronous = parcel->readUint32(); + const uint64_t transactionId = parcel->readUint64(); const uint32_t transactionNestCount = parcel->readUint32(); const bool animation = parcel->readBool(); const bool earlyWakeupStart = parcel->readBool(); const bool earlyWakeupEnd = parcel->readBool(); - const bool containsBuffer = parcel->readBool(); const int64_t desiredPresentTime = parcel->readInt64(); const bool isAutoTimestamp = parcel->readBool(); FrameTimelineInfo frameTimelineInfo; - SAFE_PARCEL(frameTimelineInfo.read, *parcel); + frameTimelineInfo.readFromParcel(parcel); sp applyToken; parcel->readNullableStrongBinder(&applyToken); @@ -735,13 +815,33 @@ status_t SurfaceComposerClient::Transaction::readFromParcel(const Parcel* parcel InputWindowCommands inputWindowCommands; inputWindowCommands.read(*parcel); + count = static_cast(parcel->readUint32()); + if (count > parcel->dataSize()) { + return BAD_VALUE; + } + std::vector uncacheBuffers(count); + for (size_t i = 0; i < count; i++) { + sp tmpBinder; + SAFE_PARCEL(parcel->readStrongBinder, &tmpBinder); + uncacheBuffers[i].token = tmpBinder; + SAFE_PARCEL(parcel->readUint64, &uncacheBuffers[i].id); + } + + count = static_cast(parcel->readUint32()); + if (count > parcel->dataSize()) { + return BAD_VALUE; + } + std::vector mergedTransactionIds(count); + for (size_t i = 0; i < count; i++) { + SAFE_PARCEL(parcel->readUint64, &mergedTransactionIds[i]); + } + // Parsing was successful. Update the object. - mForceSynchronous = forceSynchronous; + mId = transactionId; mTransactionNestCount = transactionNestCount; mAnimation = animation; mEarlyWakeupStart = earlyWakeupStart; mEarlyWakeupEnd = earlyWakeupEnd; - mContainsBuffer = containsBuffer; mDesiredPresentTime = desiredPresentTime; mIsAutoTimestamp = isAutoTimestamp; mFrameTimelineInfo = frameTimelineInfo; @@ -750,6 +850,8 @@ status_t SurfaceComposerClient::Transaction::readFromParcel(const Parcel* parcel mComposerStates = composerStates; mInputWindowCommands = inputWindowCommands; mApplyToken = applyToken; + mUncacheBuffers = std::move(uncacheBuffers); + mMergedTransactionIds = std::move(mergedTransactionIds); return NO_ERROR; } @@ -767,15 +869,14 @@ status_t SurfaceComposerClient::Transaction::writeToParcel(Parcel* parcel) const const_cast(this)->cacheBuffers(); - parcel->writeUint32(mForceSynchronous); + parcel->writeUint64(mId); parcel->writeUint32(mTransactionNestCount); parcel->writeBool(mAnimation); parcel->writeBool(mEarlyWakeupStart); parcel->writeBool(mEarlyWakeupEnd); - parcel->writeBool(mContainsBuffer); parcel->writeInt64(mDesiredPresentTime); parcel->writeBool(mIsAutoTimestamp); - SAFE_PARCEL(mFrameTimelineInfo.write, *parcel); + mFrameTimelineInfo.writeToParcel(parcel); parcel->writeStrongBinder(mApplyToken); parcel->writeUint32(static_cast(mDisplayStates.size())); for (auto const& displayState : mDisplayStates) { @@ -802,11 +903,23 @@ status_t SurfaceComposerClient::Transaction::writeToParcel(Parcel* parcel) const } mInputWindowCommands.write(*parcel); + + SAFE_PARCEL(parcel->writeUint32, static_cast(mUncacheBuffers.size())); + for (const client_cache_t& uncacheBuffer : mUncacheBuffers) { + SAFE_PARCEL(parcel->writeStrongBinder, uncacheBuffer.token.promote()); + SAFE_PARCEL(parcel->writeUint64, uncacheBuffer.id); + } + + SAFE_PARCEL(parcel->writeUint32, static_cast(mMergedTransactionIds.size())); + for (auto mergedTransactionId : mMergedTransactionIds) { + SAFE_PARCEL(parcel->writeUint64, mergedTransactionId); + } + return NO_ERROR; } void SurfaceComposerClient::Transaction::releaseBufferIfOverwriting(const layer_state_t& state) { - if (!(state.what & layer_state_t::eBufferChanged)) { + if (!(state.what & layer_state_t::eBufferChanged) || !state.bufferData->hasBuffer()) { return; } @@ -826,6 +939,22 @@ void SurfaceComposerClient::Transaction::releaseBufferIfOverwriting(const layer_ } SurfaceComposerClient::Transaction& SurfaceComposerClient::Transaction::merge(Transaction&& other) { + while (mMergedTransactionIds.size() + other.mMergedTransactionIds.size() > + MAX_MERGE_HISTORY_LENGTH - 1 && + mMergedTransactionIds.size() > 0) { + mMergedTransactionIds.pop_back(); + } + if (other.mMergedTransactionIds.size() == MAX_MERGE_HISTORY_LENGTH) { + mMergedTransactionIds.insert(mMergedTransactionIds.begin(), + other.mMergedTransactionIds.begin(), + other.mMergedTransactionIds.end() - 1); + } else if (other.mMergedTransactionIds.size() > 0u) { + mMergedTransactionIds.insert(mMergedTransactionIds.begin(), + other.mMergedTransactionIds.begin(), + other.mMergedTransactionIds.end()); + } + mMergedTransactionIds.insert(mMergedTransactionIds.begin(), other.mId); + for (auto const& [handle, composerState] : other.mComposerStates) { if (mComposerStates.count(handle) == 0) { mComposerStates[handle] = composerState; @@ -864,19 +993,22 @@ SurfaceComposerClient::Transaction& SurfaceComposerClient::Transaction::merge(Tr // register all surface controls for all callbackIds for this listener that is merging for (const auto& surfaceControl : currentProcessCallbackInfo.surfaceControls) { TransactionCompletedListener::getInstance() - ->addSurfaceControlToCallbacks(surfaceControl, - currentProcessCallbackInfo.callbackIds); + ->addSurfaceControlToCallbacks(currentProcessCallbackInfo, surfaceControl); } } + for (const auto& cacheId : other.mUncacheBuffers) { + mUncacheBuffers.push_back(cacheId); + } + mInputWindowCommands.merge(other.mInputWindowCommands); - mContainsBuffer |= other.mContainsBuffer; + mMayContainBuffer |= other.mMayContainBuffer; mEarlyWakeupStart = mEarlyWakeupStart || other.mEarlyWakeupStart; mEarlyWakeupEnd = mEarlyWakeupEnd || other.mEarlyWakeupEnd; mApplyToken = other.mApplyToken; - mFrameTimelineInfo.merge(other.mFrameTimelineInfo); + mergeFrameTimelineInfo(mFrameTimelineInfo, other.mFrameTimelineInfo); other.clear(); return *this; @@ -887,36 +1019,46 @@ void SurfaceComposerClient::Transaction::clear() { mDisplayStates.clear(); mListenerCallbacks.clear(); mInputWindowCommands.clear(); - mContainsBuffer = false; - mForceSynchronous = 0; + mUncacheBuffers.clear(); + mMayContainBuffer = false; mTransactionNestCount = 0; mAnimation = false; mEarlyWakeupStart = false; mEarlyWakeupEnd = false; mDesiredPresentTime = 0; mIsAutoTimestamp = true; - mFrameTimelineInfo.clear(); + clearFrameTimelineInfo(mFrameTimelineInfo); mApplyToken = nullptr; + mMergedTransactionIds.clear(); } uint64_t SurfaceComposerClient::Transaction::getId() { return mId; } +std::vector SurfaceComposerClient::Transaction::getMergedTransactionIds() { + return mMergedTransactionIds; +} + void SurfaceComposerClient::doUncacheBufferTransaction(uint64_t cacheId) { sp sf(ComposerService::getComposerService()); client_cache_t uncacheBuffer; uncacheBuffer.token = BufferCache::getInstance().getToken(); uncacheBuffer.id = cacheId; - - sp applyToken = IInterface::asBinder(TransactionCompletedListener::getIInstance()); - sf->setTransactionState(FrameTimelineInfo{}, {}, {}, 0, applyToken, {}, systemTime(), true, - uncacheBuffer, false, {}, generateId()); + Vector composerStates; + status_t status = sf->setTransactionState(FrameTimelineInfo{}, composerStates, {}, + ISurfaceComposer::eOneWay, + Transaction::getDefaultApplyToken(), {}, systemTime(), + true, {uncacheBuffer}, false, {}, generateId(), {}); + if (status != NO_ERROR) { + ALOGE_AND_TRACE("SurfaceComposerClient::doUncacheBufferTransaction - %s", + strerror(-status)); + } } void SurfaceComposerClient::Transaction::cacheBuffers() { - if (!mContainsBuffer) { + if (!mMayContainBuffer) { return; } @@ -946,7 +1088,11 @@ void SurfaceComposerClient::Transaction::cacheBuffers() { s->bufferData->buffer = nullptr; } else { // Cache-miss. Include the buffer and send the new cacheId. - cacheId = BufferCache::getInstance().cache(s->bufferData->buffer); + std::optional uncacheBuffer; + cacheId = BufferCache::getInstance().cache(s->bufferData->buffer, uncacheBuffer); + if (uncacheBuffer) { + mUncacheBuffers.push_back(*uncacheBuffer); + } } s->bufferData->flags |= BufferData::BufferDataChange::cachedBufferChanged; s->bufferData->cachedBuffer.token = BufferCache::getInstance().getToken(); @@ -961,12 +1107,58 @@ void SurfaceComposerClient::Transaction::cacheBuffers() { } } +class SyncCallback { +public: + static auto getCallback(std::shared_ptr& callbackContext) { + return [callbackContext](void* /* unused context */, nsecs_t /* latchTime */, + const sp& /* presentFence */, + const std::vector& /* stats */) { + if (!callbackContext) { + ALOGE("failed to get callback context for SyncCallback"); + return; + } + LOG_ALWAYS_FATAL_IF(sem_post(&callbackContext->mSemaphore), "sem_post failed"); + }; + } + ~SyncCallback() { + if (mInitialized) { + LOG_ALWAYS_FATAL_IF(sem_destroy(&mSemaphore), "sem_destroy failed"); + } + } + void init() { + LOG_ALWAYS_FATAL_IF(clock_gettime(CLOCK_MONOTONIC, &mTimeoutTimespec) == -1, + "clock_gettime() fail! in SyncCallback::init"); + mTimeoutTimespec.tv_sec += 4; + LOG_ALWAYS_FATAL_IF(sem_init(&mSemaphore, 0, 0), "sem_init failed"); + mInitialized = true; + } + void wait() { + int result = sem_clockwait(&mSemaphore, CLOCK_MONOTONIC, &mTimeoutTimespec); + if (result && errno != ETIMEDOUT && errno != EINTR) { + LOG_ALWAYS_FATAL("sem_clockwait failed(%d)", errno); + } else if (errno == ETIMEDOUT) { + ALOGW("Sync transaction timed out waiting for commit callback."); + } + } + void* getContext() { return static_cast(this); } + +private: + sem_t mSemaphore; + bool mInitialized = false; + timespec mTimeoutTimespec; +}; + status_t SurfaceComposerClient::Transaction::apply(bool synchronous, bool oneWay) { if (mStatus != NO_ERROR) { return mStatus; } - sp sf(ComposerService::getComposerService()); + std::shared_ptr syncCallback = std::make_shared(); + if (synchronous) { + syncCallback->init(); + addTransactionCommittedCallback(SyncCallback::getCallback(syncCallback), + /*callbackContext=*/nullptr); + } bool hasListenerCallbacks = !mListenerCallbacks.empty(); std::vector listenerCallbacks; @@ -1001,27 +1193,22 @@ status_t SurfaceComposerClient::Transaction::apply(bool synchronous, bool oneWay Vector displayStates; uint32_t flags = 0; - mForceSynchronous |= synchronous; - - for (auto const& kv : mComposerStates){ + for (auto const& kv : mComposerStates) { composerStates.add(kv.second); } displayStates = std::move(mDisplayStates); - if (mForceSynchronous) { - flags |= ISurfaceComposer::eSynchronous; - } if (mAnimation) { flags |= ISurfaceComposer::eAnimation; } if (oneWay) { - if (mForceSynchronous) { - ALOGE("Transaction attempted to set synchronous and one way at the same time" - " this is an invalid request. Synchronous will win for safety"); - } else { - flags |= ISurfaceComposer::eOneWay; - } + if (synchronous) { + ALOGE("Transaction attempted to set synchronous and one way at the same time" + " this is an invalid request. Synchronous will win for safety"); + } else { + flags |= ISurfaceComposer::eOneWay; + } } // If both mEarlyWakeupStart and mEarlyWakeupEnd are set @@ -1033,31 +1220,58 @@ status_t SurfaceComposerClient::Transaction::apply(bool synchronous, bool oneWay flags |= ISurfaceComposer::eEarlyWakeupEnd; } - sp applyToken = mApplyToken - ? mApplyToken - : IInterface::asBinder(TransactionCompletedListener::getIInstance()); + sp applyToken = mApplyToken ? mApplyToken : sApplyToken; + sp sf(ComposerService::getComposerService()); sf->setTransactionState(mFrameTimelineInfo, composerStates, displayStates, flags, applyToken, mInputWindowCommands, mDesiredPresentTime, mIsAutoTimestamp, - {} /*uncacheBuffer - only set in doUncacheBufferTransaction*/, - hasListenerCallbacks, listenerCallbacks, mId); + mUncacheBuffers, hasListenerCallbacks, listenerCallbacks, mId, + mMergedTransactionIds); mId = generateId(); // Clear the current states and flags clear(); + if (synchronous) { + syncCallback->wait(); + } + mStatus = NO_ERROR; return NO_ERROR; } +sp SurfaceComposerClient::Transaction::sApplyToken = new BBinder(); + +sp SurfaceComposerClient::Transaction::getDefaultApplyToken() { + return sApplyToken; +} + +void SurfaceComposerClient::Transaction::setDefaultApplyToken(sp applyToken) { + sApplyToken = applyToken; +} + +status_t SurfaceComposerClient::Transaction::sendSurfaceFlushJankDataTransaction( + const sp& sc) { + Transaction t; + layer_state_t* s = t.getLayerState(sc); + if (!s) { + return BAD_INDEX; + } + + s->what |= layer_state_t::eFlushJankData; + t.registerSurfaceControlForCallback(sc); + return t.apply(/*sync=*/false, /* oneWay=*/true); +} // --------------------------------------------------------------------------- -sp SurfaceComposerClient::createDisplay(const String8& displayName, bool secure) { +sp SurfaceComposerClient::createDisplay(const String8& displayName, bool secure, + float requestedRefereshRate) { sp display = nullptr; binder::Status status = ComposerServiceAIDL::getComposerService()->createDisplay(std::string( displayName.c_str()), - secure, &display); + secure, requestedRefereshRate, + &display); return status.isOk() ? display : nullptr; } @@ -1080,21 +1294,6 @@ std::vector SurfaceComposerClient::getPhysicalDisplayIds() { return physicalDisplayIds; } -status_t SurfaceComposerClient::getPrimaryPhysicalDisplayId(PhysicalDisplayId* id) { - int64_t displayId; - binder::Status status = - ComposerServiceAIDL::getComposerService()->getPrimaryPhysicalDisplayId(&displayId); - if (status.isOk()) { - *id = *DisplayId::fromValue(static_cast(displayId)); - } - return status.transactionError(); -} - -std::optional SurfaceComposerClient::getInternalDisplayId() { - ComposerServiceAIDL& instance = ComposerServiceAIDL::getInstance(); - return instance.getInternalDisplayId(); -} - sp SurfaceComposerClient::getPhysicalDisplayToken(PhysicalDisplayId displayId) { sp display = nullptr; binder::Status status = @@ -1103,11 +1302,6 @@ sp SurfaceComposerClient::getPhysicalDisplayToken(PhysicalDisplayId dis return status.isOk() ? display : nullptr; } -sp SurfaceComposerClient::getInternalDisplayToken() { - ComposerServiceAIDL& instance = ComposerServiceAIDL::getInstance(); - return instance.getInternalDisplayToken(); -} - void SurfaceComposerClient::Transaction::setAnimationTransaction() { mAnimation = true; } @@ -1141,8 +1335,7 @@ void SurfaceComposerClient::Transaction::registerSurfaceControlForCallback( auto& callbackInfo = mListenerCallbacks[TransactionCompletedListener::getIInstance()]; callbackInfo.surfaceControls.insert(sc); - TransactionCompletedListener::getInstance() - ->addSurfaceControlToCallbacks(sc, callbackInfo.callbackIds); + TransactionCompletedListener::getInstance()->addSurfaceControlToCallbacks(callbackInfo, sc); } SurfaceComposerClient::Transaction& SurfaceComposerClient::Transaction::setPosition( @@ -1170,21 +1363,6 @@ SurfaceComposerClient::Transaction& SurfaceComposerClient::Transaction::hide( return setFlags(sc, layer_state_t::eLayerHidden, layer_state_t::eLayerHidden); } -SurfaceComposerClient::Transaction& SurfaceComposerClient::Transaction::setSize( - const sp& sc, uint32_t w, uint32_t h) { - layer_state_t* s = getLayerState(sc); - if (!s) { - mStatus = BAD_INDEX; - return *this; - } - s->what |= layer_state_t::eSizeChanged; - s->w = w; - s->h = h; - - registerSurfaceControlForCallback(sc); - return *this; -} - SurfaceComposerClient::Transaction& SurfaceComposerClient::Transaction::setLayer( const sp& sc, int32_t z) { layer_state_t* s = getLayerState(sc); @@ -1227,7 +1405,9 @@ SurfaceComposerClient::Transaction& SurfaceComposerClient::Transaction::setFlags if ((mask & layer_state_t::eLayerOpaque) || (mask & layer_state_t::eLayerHidden) || (mask & layer_state_t::eLayerSecure) || (mask & layer_state_t::eLayerSkipScreenshot) || (mask & layer_state_t::eEnableBackpressure) || - (mask & layer_state_t::eLayerIsDisplayDecoration)) { + (mask & layer_state_t::eIgnoreDestinationFrame) || + (mask & layer_state_t::eLayerIsDisplayDecoration) || + (mask & layer_state_t::eLayerIsRefreshRateIndicator)) { s->what |= layer_state_t::eFlagsChanged; } s->flags &= ~mask; @@ -1278,7 +1458,7 @@ SurfaceComposerClient::Transaction& SurfaceComposerClient::Transaction::setAlpha ALOGE("SurfaceComposerClient::Transaction::setAlpha: invalid alpha %f, clamping", alpha); } s->what |= layer_state_t::eAlphaChanged; - s->alpha = std::clamp(alpha, 0.f, 1.f); + s->color.a = std::clamp(alpha, 0.f, 1.f); registerSurfaceControlForCallback(sc); return *this; @@ -1409,7 +1589,7 @@ SurfaceComposerClient::Transaction& SurfaceComposerClient::Transaction::setColor return *this; } s->what |= layer_state_t::eColorChanged; - s->color = color; + s->color.rgb = color; registerSurfaceControlForCallback(sc); return *this; @@ -1424,8 +1604,8 @@ SurfaceComposerClient::Transaction& SurfaceComposerClient::Transaction::setBackg } s->what |= layer_state_t::eBackgroundColorChanged; - s->color = color; - s->bgColorAlpha = alpha; + s->bgColor.rgb = color; + s->bgColor.a = alpha; s->bgColorDataspace = dataspace; registerSurfaceControlForCallback(sc); @@ -1439,8 +1619,8 @@ SurfaceComposerClient::Transaction& SurfaceComposerClient::Transaction::setTrans mStatus = BAD_INDEX; return *this; } - s->what |= layer_state_t::eTransformChanged; - s->transform = transform; + s->what |= layer_state_t::eBufferTransformChanged; + s->bufferTransform = transform; registerSurfaceControlForCallback(sc); return *this; @@ -1478,7 +1658,6 @@ std::shared_ptr SurfaceComposerClient::Transaction::getAndClearBuffe s->what &= ~layer_state_t::eBufferChanged; s->bufferData = nullptr; - mContainsBuffer = false; return bufferData; } @@ -1497,7 +1676,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, - ReleaseBufferCallback callback) { + uint32_t producerId, ReleaseBufferCallback callback) { layer_state_t* s = getLayerState(sc); if (!s) { mStatus = BAD_INDEX; @@ -1506,28 +1685,25 @@ SurfaceComposerClient::Transaction& SurfaceComposerClient::Transaction::setBuffe releaseBufferIfOverwriting(*s); - if (buffer == nullptr) { - s->what &= ~layer_state_t::eBufferChanged; - s->bufferData = nullptr; - mContainsBuffer = false; - return *this; - } - std::shared_ptr bufferData = std::make_shared(); bufferData->buffer = buffer; - uint64_t frameNumber = sc->resolveFrameNumber(optFrameNumber); - bufferData->frameNumber = frameNumber; - bufferData->flags |= BufferData::BufferDataChange::frameNumberChanged; - if (fence) { - bufferData->acquireFence = *fence; - bufferData->flags |= BufferData::BufferDataChange::fenceChanged; - } - bufferData->releaseBufferEndpoint = - IInterface::asBinder(TransactionCompletedListener::getIInstance()); + if (buffer) { + uint64_t frameNumber = sc->resolveFrameNumber(optFrameNumber); + bufferData->frameNumber = frameNumber; + bufferData->producerId = producerId; + bufferData->flags |= BufferData::BufferDataChange::frameNumberChanged; + if (fence) { + bufferData->acquireFence = *fence; + bufferData->flags |= BufferData::BufferDataChange::fenceChanged; + } + bufferData->releaseBufferEndpoint = + IInterface::asBinder(TransactionCompletedListener::getIInstance()); + setReleaseBufferCallback(bufferData.get(), callback); + } + if (mIsAutoTimestamp) { mDesiredPresentTime = systemTime(); } - setReleaseBufferCallback(bufferData.get(), callback); s->what |= layer_state_t::eBufferChanged; s->bufferData = std::move(bufferData); registerSurfaceControlForCallback(sc); @@ -1544,7 +1720,26 @@ SurfaceComposerClient::Transaction& SurfaceComposerClient::Transaction::setBuffe const std::vector&) {}, nullptr); - mContainsBuffer = true; + mMayContainBuffer = true; + return *this; +} + +SurfaceComposerClient::Transaction& SurfaceComposerClient::Transaction::unsetBuffer( + const sp& sc) { + layer_state_t* s = getLayerState(sc); + if (!s) { + mStatus = BAD_INDEX; + return *this; + } + + if (!(s->what & layer_state_t::eBufferChanged)) { + return *this; + } + + releaseBufferIfOverwriting(*s); + + s->what &= ~layer_state_t::eBufferChanged; + s->bufferData = nullptr; return *this; } @@ -1579,6 +1774,35 @@ SurfaceComposerClient::Transaction& SurfaceComposerClient::Transaction::setDatas return *this; } +SurfaceComposerClient::Transaction& SurfaceComposerClient::Transaction::setExtendedRangeBrightness( + const sp& sc, float currentBufferRatio, float desiredRatio) { + layer_state_t* s = getLayerState(sc); + if (!s) { + mStatus = BAD_INDEX; + return *this; + } + s->what |= layer_state_t::eExtendedRangeBrightnessChanged; + s->currentHdrSdrRatio = currentBufferRatio; + s->desiredHdrSdrRatio = desiredRatio; + + registerSurfaceControlForCallback(sc); + return *this; +} + +SurfaceComposerClient::Transaction& SurfaceComposerClient::Transaction::setCachingHint( + const sp& sc, gui::CachingHint cachingHint) { + layer_state_t* s = getLayerState(sc); + if (!s) { + mStatus = BAD_INDEX; + return *this; + } + s->what |= layer_state_t::eCachingHintChanged; + s->cachingHint = cachingHint; + + registerSurfaceControlForCallback(sc); + return *this; +} + SurfaceComposerClient::Transaction& SurfaceComposerClient::Transaction::setHdrMetadata( const sp& sc, const HdrMetadata& hdrMetadata) { layer_state_t* s = getLayerState(sc); @@ -1732,8 +1956,10 @@ SurfaceComposerClient::Transaction& SurfaceComposerClient::Transaction::setFocus return *this; } -SurfaceComposerClient::Transaction& SurfaceComposerClient::Transaction::syncInputWindows() { - mInputWindowCommands.syncInputWindows = true; +SurfaceComposerClient::Transaction& +SurfaceComposerClient::Transaction::addWindowInfosReportedListener( + sp windowInfosReportedListener) { + mInputWindowCommands.windowInfosReportedListeners.insert(windowInfosReportedListener); return *this; } @@ -1840,6 +2066,19 @@ SurfaceComposerClient::Transaction& SurfaceComposerClient::Transaction::setFrame return *this; } +SurfaceComposerClient::Transaction& +SurfaceComposerClient::Transaction::setDefaultFrameRateCompatibility(const sp& sc, + int8_t compatibility) { + layer_state_t* s = getLayerState(sc); + if (!s) { + mStatus = BAD_INDEX; + return *this; + } + s->what |= layer_state_t::eDefaultFrameRateCompatibilityChanged; + s->defaultFrameRateCompatibility = compatibility; + return *this; +} + SurfaceComposerClient::Transaction& SurfaceComposerClient::Transaction::setFixedTransformHint( const sp& sc, int32_t fixedTransformHint) { layer_state_t* s = getLayerState(sc); @@ -1858,7 +2097,7 @@ SurfaceComposerClient::Transaction& SurfaceComposerClient::Transaction::setFixed SurfaceComposerClient::Transaction& SurfaceComposerClient::Transaction::setFrameTimelineInfo( const FrameTimelineInfo& frameTimelineInfo) { - mFrameTimelineInfo.merge(frameTimelineInfo); + mergeFrameTimelineInfo(mFrameTimelineInfo, frameTimelineInfo); return *this; } @@ -1952,6 +2191,23 @@ 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) { @@ -2007,7 +2263,6 @@ void SurfaceComposerClient::Transaction::setDisplayProjection(const sp& s.layerStackSpaceRect = layerStackRect; s.orientedDisplaySpaceRect = displayRect; s.what |= DisplayState::eDisplayProjectionChanged; - mForceSynchronous = true; // TODO: do we actually still need this? } void SurfaceComposerClient::Transaction::setDisplaySize(const sp& token, uint32_t width, uint32_t height) { @@ -2017,6 +2272,73 @@ void SurfaceComposerClient::Transaction::setDisplaySize(const sp& token s.what |= DisplayState::eDisplaySizeChanged; } +// copied from FrameTimelineInfo::merge() +void SurfaceComposerClient::Transaction::mergeFrameTimelineInfo(FrameTimelineInfo& t, + const FrameTimelineInfo& other) { + // When merging vsync Ids we take the oldest valid one + if (t.vsyncId != FrameTimelineInfo::INVALID_VSYNC_ID && + other.vsyncId != FrameTimelineInfo::INVALID_VSYNC_ID) { + if (other.vsyncId > t.vsyncId) { + t.vsyncId = other.vsyncId; + t.inputEventId = other.inputEventId; + t.startTimeNanos = other.startTimeNanos; + t.useForRefreshRateSelection = other.useForRefreshRateSelection; + } + } else if (t.vsyncId == FrameTimelineInfo::INVALID_VSYNC_ID) { + t.vsyncId = other.vsyncId; + t.inputEventId = other.inputEventId; + t.startTimeNanos = other.startTimeNanos; + t.useForRefreshRateSelection = other.useForRefreshRateSelection; + } +} + +// copied from FrameTimelineInfo::clear() +void SurfaceComposerClient::Transaction::clearFrameTimelineInfo(FrameTimelineInfo& t) { + t.vsyncId = FrameTimelineInfo::INVALID_VSYNC_ID; + t.inputEventId = os::IInputConstants::INVALID_INPUT_EVENT_ID; + t.startTimeNanos = 0; + t.useForRefreshRateSelection = false; +} + +SurfaceComposerClient::Transaction& +SurfaceComposerClient::Transaction::setTrustedPresentationCallback( + const sp& sc, TrustedPresentationCallback cb, + const TrustedPresentationThresholds& thresholds, void* context, + sp& outCallbackRef) { + auto listener = TransactionCompletedListener::getInstance(); + outCallbackRef = listener->addTrustedPresentationCallback(cb, sc->getLayerId(), context); + + layer_state_t* s = getLayerState(sc); + if (!s) { + mStatus = BAD_INDEX; + return *this; + } + s->what |= layer_state_t::eTrustedPresentationInfoChanged; + s->trustedPresentationThresholds = thresholds; + s->trustedPresentationListener.callbackInterface = TransactionCompletedListener::getIInstance(); + s->trustedPresentationListener.callbackId = sc->getLayerId(); + + return *this; +} + +SurfaceComposerClient::Transaction& +SurfaceComposerClient::Transaction::clearTrustedPresentationCallback(const sp& sc) { + auto listener = TransactionCompletedListener::getInstance(); + listener->clearTrustedPresentationCallback(sc->getLayerId()); + + layer_state_t* s = getLayerState(sc); + if (!s) { + mStatus = BAD_INDEX; + return *this; + } + s->what |= layer_state_t::eTrustedPresentationInfoChanged; + s->trustedPresentationThresholds = TrustedPresentationThresholds(); + s->trustedPresentationListener.callbackInterface = nullptr; + s->trustedPresentationListener.callbackId = -1; + + return *this; +} + // --------------------------------------------------------------------------- SurfaceComposerClient::SurfaceComposerClient() : mStatus(NO_INIT) {} @@ -2025,11 +2347,11 @@ SurfaceComposerClient::SurfaceComposerClient(const sp& c : mStatus(NO_ERROR), mClient(client) {} void SurfaceComposerClient::onFirstRef() { - sp sf(ComposerService::getComposerService()); + sp sf(ComposerServiceAIDL::getComposerService()); if (sf != nullptr && mStatus == NO_INIT) { sp conn; - conn = sf->createConnection(); - if (conn != nullptr) { + binder::Status status = sf->createConnection(&conn); + if (status.isOk() && conn != nullptr) { mClient = conn; mStatus = NO_ERROR; } @@ -2066,8 +2388,14 @@ void SurfaceComposerClient::dispose() { mStatus = NO_INIT; } +status_t SurfaceComposerClient::bootFinished() { + sp sf(ComposerServiceAIDL::getComposerService()); + binder::Status status = sf->bootFinished(); + return statusTFromBinderStatus(status); +} + sp SurfaceComposerClient::createSurface(const String8& name, uint32_t w, uint32_t h, - PixelFormat format, uint32_t flags, + PixelFormat format, int32_t flags, const sp& parentHandle, LayerMetadata metadata, uint32_t* outTransformHint) { @@ -2077,38 +2405,13 @@ sp SurfaceComposerClient::createSurface(const String8& name, uin return s; } -sp SurfaceComposerClient::createWithSurfaceParent(const String8& name, uint32_t w, - uint32_t h, PixelFormat format, - uint32_t flags, Surface* parent, - LayerMetadata metadata, - uint32_t* outTransformHint) { - sp sur; - status_t err = mStatus; - - if (mStatus == NO_ERROR) { - sp handle; - sp parentGbp = parent->getIGraphicBufferProducer(); - sp gbp; - - uint32_t transformHint = 0; - int32_t id = -1; - err = mClient->createWithSurfaceParent(name, w, h, format, flags, parentGbp, - std::move(metadata), &handle, &gbp, &id, - &transformHint); - if (outTransformHint) { - *outTransformHint = transformHint; - } - ALOGE_IF(err, "SurfaceComposerClient::createWithSurfaceParent error %s", strerror(-err)); - if (err == NO_ERROR) { - return new SurfaceControl(this, handle, gbp, id, transformHint); - } - } - return nullptr; +static std::string toString(const String16& string) { + return std::string(String8(string).c_str()); } status_t SurfaceComposerClient::createSurfaceChecked(const String8& name, uint32_t w, uint32_t h, PixelFormat format, - sp* outSurface, uint32_t flags, + sp* outSurface, int32_t flags, const sp& parentHandle, LayerMetadata metadata, uint32_t* outTransformHint) { @@ -2116,21 +2419,18 @@ status_t SurfaceComposerClient::createSurfaceChecked(const String8& name, uint32 status_t err = mStatus; if (mStatus == NO_ERROR) { - sp handle; - sp gbp; - - uint32_t transformHint = 0; - int32_t id = -1; - err = mClient->createSurface(name, w, h, format, flags, parentHandle, std::move(metadata), - &handle, &gbp, &id, &transformHint); - + gui::CreateSurfaceResult result; + binder::Status status = mClient->createSurface(std::string(name.c_str()), flags, + parentHandle, std::move(metadata), &result); + err = statusTFromBinderStatus(status); if (outTransformHint) { - *outTransformHint = transformHint; + *outTransformHint = result.transformHint; } ALOGE_IF(err, "SurfaceComposerClient::createSurface error %s", strerror(-err)); if (err == NO_ERROR) { - *outSurface = - new SurfaceControl(this, handle, gbp, id, w, h, format, transformHint, flags); + *outSurface = new SurfaceControl(this, result.handle, result.layerId, + toString(result.layerName), w, h, format, + result.transformHint, flags); } } return err; @@ -2141,12 +2441,22 @@ sp SurfaceComposerClient::mirrorSurface(SurfaceControl* mirrorFr return nullptr; } - sp handle; sp mirrorFromHandle = mirrorFromSurface->getHandle(); - int32_t layer_id = -1; - status_t err = mClient->mirrorSurface(mirrorFromHandle, &handle, &layer_id); + gui::CreateSurfaceResult result; + const binder::Status status = mClient->mirrorSurface(mirrorFromHandle, &result); + const status_t err = statusTFromBinderStatus(status); if (err == NO_ERROR) { - return new SurfaceControl(this, handle, nullptr, layer_id, true /* owned */); + return new SurfaceControl(this, result.handle, result.layerId, toString(result.layerName)); + } + return nullptr; +} + +sp SurfaceComposerClient::mirrorDisplay(DisplayId displayId) { + gui::CreateSurfaceResult result; + const binder::Status status = mClient->mirrorDisplay(displayId.value, &result); + const status_t err = statusTFromBinderStatus(status); + if (err == NO_ERROR) { + return new SurfaceControl(this, result.handle, result.layerId, toString(result.layerName)); } return nullptr; } @@ -2155,7 +2465,8 @@ status_t SurfaceComposerClient::clearLayerFrameStats(const sp& token) c if (mStatus != NO_ERROR) { return mStatus; } - return mClient->clearLayerFrameStats(token); + const binder::Status status = mClient->clearLayerFrameStats(token); + return statusTFromBinderStatus(status); } status_t SurfaceComposerClient::getLayerFrameStats(const sp& token, @@ -2163,21 +2474,28 @@ status_t SurfaceComposerClient::getLayerFrameStats(const sp& token, if (mStatus != NO_ERROR) { return mStatus; } - return mClient->getLayerFrameStats(token, outStats); + gui::FrameStats stats; + const binder::Status status = mClient->getLayerFrameStats(token, &stats); + if (status.isOk()) { + outStats->refreshPeriodNano = stats.refreshPeriodNano; + outStats->desiredPresentTimesNano.setCapacity(stats.desiredPresentTimesNano.size()); + for (const auto& t : stats.desiredPresentTimesNano) { + outStats->desiredPresentTimesNano.add(t); + } + outStats->actualPresentTimesNano.setCapacity(stats.actualPresentTimesNano.size()); + for (const auto& t : stats.actualPresentTimesNano) { + outStats->actualPresentTimesNano.add(t); + } + outStats->frameReadyTimesNano.setCapacity(stats.frameReadyTimesNano.size()); + for (const auto& t : stats.frameReadyTimesNano) { + outStats->frameReadyTimesNano.add(t); + } + } + return statusTFromBinderStatus(status); } // ---------------------------------------------------------------------------- -status_t SurfaceComposerClient::enableVSyncInjections(bool enable) { - sp sf(ComposerService::getComposerService()); - return sf->enableVSyncInjections(enable); -} - -status_t SurfaceComposerClient::injectVSync(nsecs_t when) { - sp sf(ComposerService::getComposerService()); - return sf->injectVSync(when); -} - status_t SurfaceComposerClient::getDisplayState(const sp& display, ui::DisplayState* state) { gui::DisplayState ds; @@ -2189,23 +2507,135 @@ status_t SurfaceComposerClient::getDisplayState(const sp& display, state->layerStackSpaceRect = ui::Size(ds.layerStackSpaceRect.width, ds.layerStackSpaceRect.height); } - return status.transactionError(); + return statusTFromBinderStatus(status); +} + +status_t SurfaceComposerClient::getStaticDisplayInfo(int64_t displayId, + ui::StaticDisplayInfo* outInfo) { + using Tag = android::gui::DeviceProductInfo::ManufactureOrModelDate::Tag; + gui::StaticDisplayInfo ginfo; + binder::Status status = + ComposerServiceAIDL::getComposerService()->getStaticDisplayInfo(displayId, &ginfo); + if (status.isOk()) { + // convert gui::StaticDisplayInfo to ui::StaticDisplayInfo + outInfo->connectionType = static_cast(ginfo.connectionType); + outInfo->density = ginfo.density; + outInfo->secure = ginfo.secure; + outInfo->installOrientation = static_cast(ginfo.installOrientation); + + DeviceProductInfo info; + std::optional dpi = ginfo.deviceProductInfo; + gui::DeviceProductInfo::ManufactureOrModelDate& date = dpi->manufactureOrModelDate; + info.name = dpi->name; + if (dpi->manufacturerPnpId.size() > 0) { + // copid from PnpId = std::array in ui/DeviceProductInfo.h + constexpr int kMaxPnpIdSize = 4; + size_t count = std::max(kMaxPnpIdSize, dpi->manufacturerPnpId.size()); + std::copy_n(dpi->manufacturerPnpId.begin(), count, info.manufacturerPnpId.begin()); + } + if (dpi->relativeAddress.size() > 0) { + std::copy(dpi->relativeAddress.begin(), dpi->relativeAddress.end(), + std::back_inserter(info.relativeAddress)); + } + info.productId = dpi->productId; + if (date.getTag() == Tag::modelYear) { + DeviceProductInfo::ModelYear modelYear; + modelYear.year = static_cast(date.get().year); + info.manufactureOrModelDate = modelYear; + } else if (date.getTag() == Tag::manufactureYear) { + DeviceProductInfo::ManufactureYear manufactureYear; + manufactureYear.year = date.get().modelYear.year; + info.manufactureOrModelDate = manufactureYear; + } else if (date.getTag() == Tag::manufactureWeekAndYear) { + DeviceProductInfo::ManufactureWeekAndYear weekAndYear; + weekAndYear.year = + date.get().manufactureYear.modelYear.year; + weekAndYear.week = date.get().week; + info.manufactureOrModelDate = weekAndYear; + } + + outInfo->deviceProductInfo = info; + } + return statusTFromBinderStatus(status); } -status_t SurfaceComposerClient::getStaticDisplayInfo(const sp& display, - ui::StaticDisplayInfo* info) { - return ComposerService::getComposerService()->getStaticDisplayInfo(display, info); +void SurfaceComposerClient::getDynamicDisplayInfoInternal(gui::DynamicDisplayInfo& ginfo, + ui::DynamicDisplayInfo*& outInfo) { + // convert gui::DynamicDisplayInfo to ui::DynamicDisplayInfo + outInfo->supportedDisplayModes.clear(); + outInfo->supportedDisplayModes.reserve(ginfo.supportedDisplayModes.size()); + for (const auto& mode : ginfo.supportedDisplayModes) { + ui::DisplayMode outMode; + outMode.id = mode.id; + outMode.resolution.width = mode.resolution.width; + outMode.resolution.height = mode.resolution.height; + outMode.xDpi = mode.xDpi; + outMode.yDpi = mode.yDpi; + outMode.refreshRate = mode.refreshRate; + outMode.appVsyncOffset = mode.appVsyncOffset; + outMode.sfVsyncOffset = mode.sfVsyncOffset; + outMode.presentationDeadline = mode.presentationDeadline; + outMode.group = mode.group; + std::transform(mode.supportedHdrTypes.begin(), mode.supportedHdrTypes.end(), + std::back_inserter(outMode.supportedHdrTypes), + [](const int32_t& value) { return static_cast(value); }); + outInfo->supportedDisplayModes.push_back(outMode); + } + + outInfo->activeDisplayModeId = ginfo.activeDisplayModeId; + outInfo->renderFrameRate = ginfo.renderFrameRate; + + outInfo->supportedColorModes.clear(); + outInfo->supportedColorModes.reserve(ginfo.supportedColorModes.size()); + for (const auto& cmode : ginfo.supportedColorModes) { + outInfo->supportedColorModes.push_back(static_cast(cmode)); + } + + outInfo->activeColorMode = static_cast(ginfo.activeColorMode); + + std::vector types; + types.reserve(ginfo.hdrCapabilities.supportedHdrTypes.size()); + for (const auto& hdr : ginfo.hdrCapabilities.supportedHdrTypes) { + types.push_back(static_cast(hdr)); + } + outInfo->hdrCapabilities = HdrCapabilities(types, ginfo.hdrCapabilities.maxLuminance, + ginfo.hdrCapabilities.maxAverageLuminance, + ginfo.hdrCapabilities.minLuminance); + + outInfo->autoLowLatencyModeSupported = ginfo.autoLowLatencyModeSupported; + outInfo->gameContentTypeSupported = ginfo.gameContentTypeSupported; + outInfo->preferredBootDisplayMode = ginfo.preferredBootDisplayMode; +} + +status_t SurfaceComposerClient::getDynamicDisplayInfoFromId(int64_t displayId, + ui::DynamicDisplayInfo* outInfo) { + gui::DynamicDisplayInfo ginfo; + binder::Status status = + ComposerServiceAIDL::getComposerService()->getDynamicDisplayInfoFromId(displayId, + &ginfo); + if (status.isOk()) { + getDynamicDisplayInfoInternal(ginfo, outInfo); + } + return statusTFromBinderStatus(status); } -status_t SurfaceComposerClient::getDynamicDisplayInfo(const sp& display, - ui::DynamicDisplayInfo* info) { - return ComposerService::getComposerService()->getDynamicDisplayInfo(display, info); +status_t SurfaceComposerClient::getDynamicDisplayInfoFromToken(const sp& display, + ui::DynamicDisplayInfo* outInfo) { + gui::DynamicDisplayInfo ginfo; + binder::Status status = + ComposerServiceAIDL::getComposerService()->getDynamicDisplayInfoFromToken(display, + &ginfo); + if (status.isOk()) { + getDynamicDisplayInfoInternal(ginfo, outInfo); + } + return statusTFromBinderStatus(status); } status_t SurfaceComposerClient::getActiveDisplayMode(const sp& display, ui::DisplayMode* mode) { ui::DynamicDisplayInfo info; - status_t result = getDynamicDisplayInfo(display, &info); + + status_t result = getDynamicDisplayInfoFromToken(display, &info); if (result != NO_ERROR) { return result; } @@ -2219,58 +2649,109 @@ status_t SurfaceComposerClient::getActiveDisplayMode(const sp& display, return NAME_NOT_FOUND; } -status_t SurfaceComposerClient::setDesiredDisplayModeSpecs( - const sp& displayToken, ui::DisplayModeId defaultMode, bool allowGroupSwitching, - float primaryRefreshRateMin, float primaryRefreshRateMax, float appRequestRefreshRateMin, - float appRequestRefreshRateMax) { - return ComposerService::getComposerService() - ->setDesiredDisplayModeSpecs(displayToken, defaultMode, allowGroupSwitching, - primaryRefreshRateMin, primaryRefreshRateMax, - appRequestRefreshRateMin, appRequestRefreshRateMax); +status_t SurfaceComposerClient::setDesiredDisplayModeSpecs(const sp& displayToken, + const gui::DisplayModeSpecs& specs) { + binder::Status status = + ComposerServiceAIDL::getComposerService()->setDesiredDisplayModeSpecs(displayToken, + specs); + return statusTFromBinderStatus(status); } status_t SurfaceComposerClient::getDesiredDisplayModeSpecs(const sp& displayToken, - ui::DisplayModeId* outDefaultMode, - bool* outAllowGroupSwitching, - float* outPrimaryRefreshRateMin, - float* outPrimaryRefreshRateMax, - float* outAppRequestRefreshRateMin, - float* outAppRequestRefreshRateMax) { - return ComposerService::getComposerService() - ->getDesiredDisplayModeSpecs(displayToken, outDefaultMode, outAllowGroupSwitching, - outPrimaryRefreshRateMin, outPrimaryRefreshRateMax, - outAppRequestRefreshRateMin, outAppRequestRefreshRateMax); + gui::DisplayModeSpecs* outSpecs) { + if (!outSpecs) { + return BAD_VALUE; + } + binder::Status status = + ComposerServiceAIDL::getComposerService()->getDesiredDisplayModeSpecs(displayToken, + outSpecs); + return statusTFromBinderStatus(status); } status_t SurfaceComposerClient::getDisplayNativePrimaries(const sp& display, ui::DisplayPrimaries& outPrimaries) { - return ComposerService::getComposerService()->getDisplayNativePrimaries(display, outPrimaries); + gui::DisplayPrimaries primaries; + binder::Status status = + ComposerServiceAIDL::getComposerService()->getDisplayNativePrimaries(display, + &primaries); + if (status.isOk()) { + outPrimaries.red.X = primaries.red.X; + outPrimaries.red.Y = primaries.red.Y; + outPrimaries.red.Z = primaries.red.Z; + + outPrimaries.green.X = primaries.green.X; + outPrimaries.green.Y = primaries.green.Y; + outPrimaries.green.Z = primaries.green.Z; + + outPrimaries.blue.X = primaries.blue.X; + outPrimaries.blue.Y = primaries.blue.Y; + outPrimaries.blue.Z = primaries.blue.Z; + + outPrimaries.white.X = primaries.white.X; + outPrimaries.white.Y = primaries.white.Y; + outPrimaries.white.Z = primaries.white.Z; + } + return statusTFromBinderStatus(status); } status_t SurfaceComposerClient::setActiveColorMode(const sp& display, ColorMode colorMode) { - return ComposerService::getComposerService()->setActiveColorMode(display, colorMode); + binder::Status status = ComposerServiceAIDL::getComposerService() + ->setActiveColorMode(display, static_cast(colorMode)); + return statusTFromBinderStatus(status); } status_t SurfaceComposerClient::getBootDisplayModeSupport(bool* support) { binder::Status status = ComposerServiceAIDL::getComposerService()->getBootDisplayModeSupport(support); - return status.transactionError(); + return statusTFromBinderStatus(status); +} + +status_t SurfaceComposerClient::getOverlaySupport(gui::OverlayProperties* outProperties) { + binder::Status status = + ComposerServiceAIDL::getComposerService()->getOverlaySupport(outProperties); + return statusTFromBinderStatus(status); } status_t SurfaceComposerClient::setBootDisplayMode(const sp& display, ui::DisplayModeId displayModeId) { - return ComposerService::getComposerService()->setBootDisplayMode(display, displayModeId); + binder::Status status = ComposerServiceAIDL::getComposerService() + ->setBootDisplayMode(display, static_cast(displayModeId)); + return statusTFromBinderStatus(status); } status_t SurfaceComposerClient::clearBootDisplayMode(const sp& display) { binder::Status status = ComposerServiceAIDL::getComposerService()->clearBootDisplayMode(display); - return status.transactionError(); + return statusTFromBinderStatus(status); +} + +status_t SurfaceComposerClient::getHdrConversionCapabilities( + std::vector* hdrConversionCapabilities) { + binder::Status status = ComposerServiceAIDL::getComposerService()->getHdrConversionCapabilities( + hdrConversionCapabilities); + return statusTFromBinderStatus(status); +} + +status_t SurfaceComposerClient::setHdrConversionStrategy( + gui::HdrConversionStrategy hdrConversionStrategy, ui::Hdr* outPreferredHdrOutputType) { + int hdrType; + binder::Status status = ComposerServiceAIDL::getComposerService() + ->setHdrConversionStrategy(hdrConversionStrategy, &hdrType); + *outPreferredHdrOutputType = static_cast(hdrType); + return statusTFromBinderStatus(status); +} + +status_t SurfaceComposerClient::getHdrOutputConversionSupport(bool* isSupported) { + binder::Status status = + ComposerServiceAIDL::getComposerService()->getHdrOutputConversionSupport(isSupported); + return statusTFromBinderStatus(status); } status_t SurfaceComposerClient::setOverrideFrameRate(uid_t uid, float frameRate) { - return ComposerService::getComposerService()->setOverrideFrameRate(uid, frameRate); + binder::Status status = + ComposerServiceAIDL::getComposerService()->setOverrideFrameRate(uid, frameRate); + return statusTFromBinderStatus(status); } void SurfaceComposerClient::setAutoLowLatencyMode(const sp& display, bool on) { @@ -2289,57 +2770,137 @@ void SurfaceComposerClient::setDisplayPowerMode(const sp& token, status_t SurfaceComposerClient::getCompositionPreference( ui::Dataspace* defaultDataspace, ui::PixelFormat* defaultPixelFormat, ui::Dataspace* wideColorGamutDataspace, ui::PixelFormat* wideColorGamutPixelFormat) { - return ComposerService::getComposerService() - ->getCompositionPreference(defaultDataspace, defaultPixelFormat, - wideColorGamutDataspace, wideColorGamutPixelFormat); + gui::CompositionPreference pref; + binder::Status status = + ComposerServiceAIDL::getComposerService()->getCompositionPreference(&pref); + if (status.isOk()) { + *defaultDataspace = static_cast(pref.defaultDataspace); + *defaultPixelFormat = static_cast(pref.defaultPixelFormat); + *wideColorGamutDataspace = static_cast(pref.wideColorGamutDataspace); + *wideColorGamutPixelFormat = static_cast(pref.wideColorGamutPixelFormat); + } + return statusTFromBinderStatus(status); } bool SurfaceComposerClient::getProtectedContentSupport() { bool supported = false; - ComposerService::getComposerService()->getProtectedContentSupport(&supported); + ComposerServiceAIDL::getComposerService()->getProtectedContentSupport(&supported); return supported; } status_t SurfaceComposerClient::clearAnimationFrameStats() { - return ComposerService::getComposerService()->clearAnimationFrameStats(); + binder::Status status = ComposerServiceAIDL::getComposerService()->clearAnimationFrameStats(); + return statusTFromBinderStatus(status); } status_t SurfaceComposerClient::getAnimationFrameStats(FrameStats* outStats) { - return ComposerService::getComposerService()->getAnimationFrameStats(outStats); + gui::FrameStats stats; + binder::Status status = + ComposerServiceAIDL::getComposerService()->getAnimationFrameStats(&stats); + if (status.isOk()) { + outStats->refreshPeriodNano = stats.refreshPeriodNano; + outStats->desiredPresentTimesNano.setCapacity(stats.desiredPresentTimesNano.size()); + for (const auto& t : stats.desiredPresentTimesNano) { + outStats->desiredPresentTimesNano.add(t); + } + outStats->actualPresentTimesNano.setCapacity(stats.actualPresentTimesNano.size()); + for (const auto& t : stats.actualPresentTimesNano) { + outStats->actualPresentTimesNano.add(t); + } + outStats->frameReadyTimesNano.setCapacity(stats.frameReadyTimesNano.size()); + for (const auto& t : stats.frameReadyTimesNano) { + outStats->frameReadyTimesNano.add(t); + } + } + return statusTFromBinderStatus(status); } status_t SurfaceComposerClient::overrideHdrTypes(const sp& display, const std::vector& hdrTypes) { - return ComposerService::getComposerService()->overrideHdrTypes(display, hdrTypes); + std::vector hdrTypesVector; + hdrTypesVector.reserve(hdrTypes.size()); + for (auto t : hdrTypes) { + hdrTypesVector.push_back(static_cast(t)); + } + + binder::Status status = + ComposerServiceAIDL::getComposerService()->overrideHdrTypes(display, hdrTypesVector); + return statusTFromBinderStatus(status); } status_t SurfaceComposerClient::onPullAtom(const int32_t atomId, std::string* outData, bool* success) { - return ComposerService::getComposerService()->onPullAtom(atomId, outData, success); + gui::PullAtomData pad; + binder::Status status = ComposerServiceAIDL::getComposerService()->onPullAtom(atomId, &pad); + if (status.isOk()) { + outData->assign(pad.data.begin(), pad.data.end()); + *success = pad.success; + } + return statusTFromBinderStatus(status); } status_t SurfaceComposerClient::getDisplayedContentSamplingAttributes(const sp& display, ui::PixelFormat* outFormat, ui::Dataspace* outDataspace, uint8_t* outComponentMask) { - return ComposerService::getComposerService() - ->getDisplayedContentSamplingAttributes(display, outFormat, outDataspace, - outComponentMask); + if (!outFormat || !outDataspace || !outComponentMask) { + return BAD_VALUE; + } + + gui::ContentSamplingAttributes attrs; + binder::Status status = ComposerServiceAIDL::getComposerService() + ->getDisplayedContentSamplingAttributes(display, &attrs); + if (status.isOk()) { + *outFormat = static_cast(attrs.format); + *outDataspace = static_cast(attrs.dataspace); + *outComponentMask = static_cast(attrs.componentMask); + } + return statusTFromBinderStatus(status); } status_t SurfaceComposerClient::setDisplayContentSamplingEnabled(const sp& display, bool enable, uint8_t componentMask, uint64_t maxFrames) { - return ComposerService::getComposerService()->setDisplayContentSamplingEnabled(display, enable, - componentMask, - maxFrames); + binder::Status status = + ComposerServiceAIDL::getComposerService() + ->setDisplayContentSamplingEnabled(display, enable, + static_cast(componentMask), + static_cast(maxFrames)); + return statusTFromBinderStatus(status); } status_t SurfaceComposerClient::getDisplayedContentSample(const sp& display, uint64_t maxFrames, uint64_t timestamp, DisplayedFrameStats* outStats) { - return ComposerService::getComposerService()->getDisplayedContentSample(display, maxFrames, - timestamp, outStats); + if (!outStats) { + return BAD_VALUE; + } + + gui::DisplayedFrameStats stats; + binder::Status status = + ComposerServiceAIDL::getComposerService()->getDisplayedContentSample(display, maxFrames, + timestamp, &stats); + if (status.isOk()) { + // convert gui::DisplayedFrameStats to ui::DisplayedFrameStats + outStats->numFrames = static_cast(stats.numFrames); + outStats->component_0_sample.reserve(stats.component_0_sample.size()); + for (const auto& s : stats.component_0_sample) { + outStats->component_0_sample.push_back(static_cast(s)); + } + outStats->component_1_sample.reserve(stats.component_1_sample.size()); + for (const auto& s : stats.component_1_sample) { + outStats->component_1_sample.push_back(static_cast(s)); + } + outStats->component_2_sample.reserve(stats.component_2_sample.size()); + for (const auto& s : stats.component_2_sample) { + outStats->component_2_sample.push_back(static_cast(s)); + } + outStats->component_3_sample.reserve(stats.component_3_sample.size()); + for (const auto& s : stats.component_3_sample) { + outStats->component_3_sample.push_back(static_cast(s)); + } + } + return statusTFromBinderStatus(status); } status_t SurfaceComposerClient::isWideColorDisplay(const sp& display, @@ -2347,39 +2908,55 @@ status_t SurfaceComposerClient::isWideColorDisplay(const sp& display, binder::Status status = ComposerServiceAIDL::getComposerService()->isWideColorDisplay(display, outIsWideColorDisplay); - return status.transactionError(); + return statusTFromBinderStatus(status); } status_t SurfaceComposerClient::addRegionSamplingListener( const Rect& samplingArea, const sp& stopLayerHandle, const sp& listener) { - return ComposerService::getComposerService()->addRegionSamplingListener(samplingArea, - stopLayerHandle, - listener); + gui::ARect rect; + rect.left = samplingArea.left; + rect.top = samplingArea.top; + rect.right = samplingArea.right; + rect.bottom = samplingArea.bottom; + binder::Status status = + ComposerServiceAIDL::getComposerService()->addRegionSamplingListener(rect, + stopLayerHandle, + listener); + return statusTFromBinderStatus(status); } status_t SurfaceComposerClient::removeRegionSamplingListener( const sp& listener) { - return ComposerService::getComposerService()->removeRegionSamplingListener(listener); + binder::Status status = + ComposerServiceAIDL::getComposerService()->removeRegionSamplingListener(listener); + return statusTFromBinderStatus(status); } status_t SurfaceComposerClient::addFpsListener(int32_t taskId, const sp& listener) { - return ComposerService::getComposerService()->addFpsListener(taskId, listener); + binder::Status status = + ComposerServiceAIDL::getComposerService()->addFpsListener(taskId, listener); + return statusTFromBinderStatus(status); } status_t SurfaceComposerClient::removeFpsListener(const sp& listener) { - return ComposerService::getComposerService()->removeFpsListener(listener); + binder::Status status = ComposerServiceAIDL::getComposerService()->removeFpsListener(listener); + return statusTFromBinderStatus(status); } status_t SurfaceComposerClient::addTunnelModeEnabledListener( const sp& listener) { - return ComposerService::getComposerService()->addTunnelModeEnabledListener(listener); + binder::Status status = + ComposerServiceAIDL::getComposerService()->addTunnelModeEnabledListener(listener); + return statusTFromBinderStatus(status); } status_t SurfaceComposerClient::removeTunnelModeEnabledListener( const sp& listener) { - return ComposerService::getComposerService()->removeTunnelModeEnabledListener(listener); + binder::Status status = + ComposerServiceAIDL::getComposerService()->removeTunnelModeEnabledListener(listener); + return statusTFromBinderStatus(status); } bool SurfaceComposerClient::getDisplayBrightnessSupport(const sp& displayToken) { @@ -2395,7 +2972,7 @@ status_t SurfaceComposerClient::setDisplayBrightness(const sp& displayT binder::Status status = ComposerServiceAIDL::getComposerService()->setDisplayBrightness(displayToken, brightness); - return status.transactionError(); + return statusTFromBinderStatus(status); } status_t SurfaceComposerClient::addHdrLayerInfoListener( @@ -2403,7 +2980,7 @@ status_t SurfaceComposerClient::addHdrLayerInfoListener( binder::Status status = ComposerServiceAIDL::getComposerService()->addHdrLayerInfoListener(displayToken, listener); - return status.transactionError(); + return statusTFromBinderStatus(status); } status_t SurfaceComposerClient::removeHdrLayerInfoListener( @@ -2411,45 +2988,79 @@ status_t SurfaceComposerClient::removeHdrLayerInfoListener( binder::Status status = ComposerServiceAIDL::getComposerService()->removeHdrLayerInfoListener(displayToken, listener); - return status.transactionError(); + return statusTFromBinderStatus(status); } status_t SurfaceComposerClient::notifyPowerBoost(int32_t boostId) { binder::Status status = ComposerServiceAIDL::getComposerService()->notifyPowerBoost(boostId); - return status.transactionError(); + return statusTFromBinderStatus(status); } status_t SurfaceComposerClient::setGlobalShadowSettings(const half4& ambientColor, const half4& spotColor, float lightPosY, float lightPosZ, float lightRadius) { - return ComposerService::getComposerService()->setGlobalShadowSettings(ambientColor, spotColor, - lightPosY, lightPosZ, - lightRadius); + gui::Color ambientColorG, spotColorG; + ambientColorG.r = ambientColor.r; + ambientColorG.g = ambientColor.g; + ambientColorG.b = ambientColor.b; + ambientColorG.a = ambientColor.a; + spotColorG.r = spotColor.r; + spotColorG.g = spotColor.g; + spotColorG.b = spotColor.b; + spotColorG.a = spotColor.a; + binder::Status status = + ComposerServiceAIDL::getComposerService()->setGlobalShadowSettings(ambientColorG, + spotColorG, + lightPosY, lightPosZ, + lightRadius); + return statusTFromBinderStatus(status); } std::optional SurfaceComposerClient::getDisplayDecorationSupport( const sp& displayToken) { + std::optional gsupport; + binder::Status status = + ComposerServiceAIDL::getComposerService()->getDisplayDecorationSupport(displayToken, + &gsupport); std::optional support; - ComposerService::getComposerService()->getDisplayDecorationSupport(displayToken, &support); + if (status.isOk() && gsupport.has_value()) { + support.emplace(DisplayDecorationSupport{ + .format = + static_cast( + gsupport->format), + .alphaInterpretation = + static_cast( + gsupport->alphaInterpretation) + }); + } return support; } -int SurfaceComposerClient::getGPUContextPriority() { - return ComposerService::getComposerService()->getGPUContextPriority(); +int SurfaceComposerClient::getGpuContextPriority() { + int priority; + binder::Status status = + ComposerServiceAIDL::getComposerService()->getGpuContextPriority(&priority); + if (!status.isOk()) { + status_t err = statusTFromBinderStatus(status); + ALOGE("getGpuContextPriority failed to read data: %s (%d)", strerror(-err), err); + return 0; + } + return priority; } status_t SurfaceComposerClient::addWindowInfosListener( const sp& windowInfosListener, std::pair, std::vector>* outInitialInfo) { return WindowInfosListenerReporter::getInstance() - ->addWindowInfosListener(windowInfosListener, ComposerService::getComposerService(), + ->addWindowInfosListener(windowInfosListener, ComposerServiceAIDL::getComposerService(), outInitialInfo); } status_t SurfaceComposerClient::removeWindowInfosListener( const sp& windowInfosListener) { return WindowInfosListenerReporter::getInstance() - ->removeWindowInfosListener(windowInfosListener, ComposerService::getComposerService()); + ->removeWindowInfosListener(windowInfosListener, + ComposerServiceAIDL::getComposerService()); } // ---------------------------------------------------------------------------- @@ -2460,7 +3071,7 @@ status_t ScreenshotClient::captureDisplay(const DisplayCaptureArgs& captureArgs, if (s == nullptr) return NO_INIT; binder::Status status = s->captureDisplay(captureArgs, captureListener); - return status.transactionError(); + return statusTFromBinderStatus(status); } status_t ScreenshotClient::captureDisplay(DisplayId displayId, @@ -2469,7 +3080,7 @@ status_t ScreenshotClient::captureDisplay(DisplayId displayId, if (s == nullptr) return NO_INIT; binder::Status status = s->captureDisplayById(displayId.value, captureListener); - return status.transactionError(); + return statusTFromBinderStatus(status); } status_t ScreenshotClient::captureLayers(const LayerCaptureArgs& captureArgs, @@ -2478,7 +3089,7 @@ status_t ScreenshotClient::captureLayers(const LayerCaptureArgs& captureArgs, if (s == nullptr) return NO_INIT; binder::Status status = s->captureLayers(captureArgs, captureListener); - return status.transactionError(); + return statusTFromBinderStatus(status); } // --------------------------------------------------------------------------------- @@ -2501,6 +3112,7 @@ void ReleaseCallbackThread::threadMain() { while (true) { { std::unique_lock lock(mMutex); + base::ScopedLockAssertion assumeLocked(mMutex); callbackInfos = std::move(mCallbackInfos); mCallbackInfos = {}; } @@ -2513,6 +3125,7 @@ void ReleaseCallbackThread::threadMain() { { std::unique_lock lock(mMutex); + base::ScopedLockAssertion assumeLocked(mMutex); if (mCallbackInfos.size() == 0) { mReleaseCallbackPending.wait(lock); } diff --git a/libs/gui/SurfaceControl.cpp b/libs/gui/SurfaceControl.cpp index 654fb336fe9d03527a6a2e0872dcb8ac17876bde..c5f9c38ca3b276b73e634137a54c65d7976b8f5f 100644 --- a/libs/gui/SurfaceControl.cpp +++ b/libs/gui/SurfaceControl.cpp @@ -26,6 +26,7 @@ #include #include #include +#include #include #include @@ -34,8 +35,9 @@ #include #include -#include #include +#include +#include #include #include #include @@ -49,13 +51,12 @@ namespace android { // ============================================================================ SurfaceControl::SurfaceControl(const sp& client, const sp& handle, - const sp& gbp, int32_t layerId, - uint32_t w, uint32_t h, PixelFormat format, uint32_t transform, - uint32_t flags) + int32_t layerId, const std::string& name, uint32_t w, uint32_t h, + PixelFormat format, uint32_t transform, uint32_t flags) : mClient(client), mHandle(handle), - mGraphicBufferProducer(gbp), mLayerId(layerId), + mName(name), mTransformHint(transform), mWidth(w), mHeight(h), @@ -65,9 +66,9 @@ SurfaceControl::SurfaceControl(const sp& client, const sp SurfaceControl::SurfaceControl(const sp& other) { mClient = other->mClient; mHandle = other->mHandle; - mGraphicBufferProducer = other->mGraphicBufferProducer; mTransformHint = other->mTransformHint; mLayerId = other->mLayerId; + mName = other->mName; mWidth = other->mWidth; mHeight = other->mHeight; mFormat = other->mFormat; @@ -165,11 +166,11 @@ sp SurfaceControl::createSurface() void SurfaceControl::updateDefaultBufferSize(uint32_t width, uint32_t height) { Mutex::Autolock _l(mLock); - mWidth = width; mHeight = height; + mWidth = width; + mHeight = height; if (mBbq) { mBbq->update(mBbqChild, width, height, mFormat); } - } sp SurfaceControl::getLayerStateHandle() const @@ -188,6 +189,28 @@ int32_t SurfaceControl::getLayerId() const { return mLayerId; } +const std::string& SurfaceControl::getName() const { + return mName; +} + +std::shared_ptr SurfaceControl::getChoreographer() { + if (mChoreographer) { + return mChoreographer; + } + sp looper = Looper::getForThread(); + if (!looper.get()) { + ALOGE("%s: No looper prepared for thread", __func__); + return nullptr; + } + mChoreographer = std::make_shared(looper, getHandle()); + status_t result = mChoreographer->initialize(); + if (result != OK) { + ALOGE("Failed to initialize choreographer"); + mChoreographer = nullptr; + } + return mChoreographer; +} + sp SurfaceControl::getIGraphicBufferProducer() { getSurface(); @@ -215,6 +238,7 @@ status_t SurfaceControl::writeToParcel(Parcel& parcel) { SAFE_PARCEL(parcel.writeStrongBinder, ISurfaceComposerClient::asBinder(mClient->getClient())); SAFE_PARCEL(parcel.writeStrongBinder, mHandle); SAFE_PARCEL(parcel.writeInt32, mLayerId); + SAFE_PARCEL(parcel.writeUtf8AsUtf16, mName); SAFE_PARCEL(parcel.writeUint32, mTransformHint); SAFE_PARCEL(parcel.writeUint32, mWidth); SAFE_PARCEL(parcel.writeUint32, mHeight); @@ -228,6 +252,7 @@ status_t SurfaceControl::readFromParcel(const Parcel& parcel, sp client; sp handle; int32_t layerId; + std::string layerName; uint32_t transformHint; uint32_t width; uint32_t height; @@ -236,18 +261,17 @@ status_t SurfaceControl::readFromParcel(const Parcel& parcel, SAFE_PARCEL(parcel.readStrongBinder, &client); SAFE_PARCEL(parcel.readStrongBinder, &handle); SAFE_PARCEL(parcel.readInt32, &layerId); + SAFE_PARCEL(parcel.readUtf8FromUtf16, &layerName); SAFE_PARCEL(parcel.readUint32, &transformHint); SAFE_PARCEL(parcel.readUint32, &width); SAFE_PARCEL(parcel.readUint32, &height); SAFE_PARCEL(parcel.readUint32, &format); // We aren't the original owner of the surface. - *outSurfaceControl = - new SurfaceControl(new SurfaceComposerClient( - interface_cast(client)), - handle.get(), nullptr, layerId, - width, height, format, - transformHint); + *outSurfaceControl = new SurfaceControl(new SurfaceComposerClient( + interface_cast(client)), + handle.get(), layerId, layerName, width, height, format, + transformHint); return NO_ERROR; } diff --git a/libs/gui/SyncFeatures.cpp b/libs/gui/SyncFeatures.cpp index 1a8fc1a00af0c12e64536ea52bc1d233a3fd0954..2d863c2585971e0679c2dbee192e2caf6ec05493 100644 --- a/libs/gui/SyncFeatures.cpp +++ b/libs/gui/SyncFeatures.cpp @@ -36,8 +36,12 @@ SyncFeatures::SyncFeatures() : Singleton(), mHasFenceSync(false), mHasWaitSync(false) { EGLDisplay dpy = eglGetDisplay(EGL_DEFAULT_DISPLAY); - // This can only be called after EGL has been initialized; otherwise the - // check below will abort. + // eglQueryString can only be called after EGL has been initialized; + // otherwise the check below will abort. If RenderEngine is using SkiaVk, + // EGL will not have been initialized. There's no problem with initializing + // it again here (it is ref counted), and then terminating it later. + EGLBoolean initialized = eglInitialize(dpy, nullptr, nullptr); + LOG_ALWAYS_FATAL_IF(!initialized, "eglInitialize failed"); const char* exts = eglQueryString(dpy, EGL_EXTENSIONS); LOG_ALWAYS_FATAL_IF(exts == nullptr, "eglQueryString failed"); if (strstr(exts, "EGL_ANDROID_native_fence_sync")) { @@ -63,6 +67,8 @@ SyncFeatures::SyncFeatures() : Singleton(), mString.append(" EGL_KHR_wait_sync"); } mString.append("]"); + // Terminate EGL to match the eglInitialize above + eglTerminate(dpy); } bool SyncFeatures::useNativeFenceSync() const { diff --git a/libs/gui/TEST_MAPPING b/libs/gui/TEST_MAPPING index 1c435304a8ab5644477ddd60b8092c5a1f9cf71a..941503548d7888b5bcb08fe2e3b8bb4ab13a8be4 100644 --- a/libs/gui/TEST_MAPPING +++ b/libs/gui/TEST_MAPPING @@ -3,5 +3,11 @@ { "path": "frameworks/native/libs/nativewindow" } + ], + "postsubmit": [ + { + // TODO(257123981): move this to presubmit after dealing with existing breakages. + "name": "libgui_test" + } ] } diff --git a/libs/gui/TransactionTracing.cpp b/libs/gui/TransactionTracing.cpp deleted file mode 100644 index eedc3df00916fccd7bf9b09d1179e598e6f60ef3..0000000000000000000000000000000000000000 --- a/libs/gui/TransactionTracing.cpp +++ /dev/null @@ -1,53 +0,0 @@ -/* - * Copyright 2020 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#include "gui/TransactionTracing.h" -#include "gui/ISurfaceComposer.h" - -#include - -namespace android { - -sp TransactionTraceListener::sInstance = nullptr; -std::mutex TransactionTraceListener::sMutex; - -TransactionTraceListener::TransactionTraceListener() {} - -sp TransactionTraceListener::getInstance() { - const std::lock_guard lock(sMutex); - - if (sInstance == nullptr) { - sInstance = new TransactionTraceListener; - - sp sf(ComposerService::getComposerService()); - sf->addTransactionTraceListener(sInstance); - } - - return sInstance; -} - -binder::Status TransactionTraceListener::onToggled(bool enabled) { - ALOGD("TransactionTraceListener: onToggled listener called"); - mTracingEnabled = enabled; - - return binder::Status::ok(); -} - -bool TransactionTraceListener::isTracingEnabled() { - return mTracingEnabled; -} - -} // namespace android \ No newline at end of file diff --git a/libs/gui/VsyncEventData.cpp b/libs/gui/VsyncEventData.cpp index 23f0921e99f261f9682decd2a81b020c28b81691..8e00c2fe325a5d4b6ceaccc96af25f177ab5b001 100644 --- a/libs/gui/VsyncEventData.cpp +++ b/libs/gui/VsyncEventData.cpp @@ -23,6 +23,9 @@ namespace android::gui { +static_assert(VsyncEventData::kFrameTimelinesCapacity == 7, + "Must update value in DisplayEventReceiver.java#FRAME_TIMELINES_CAPACITY (and here)"); + int64_t VsyncEventData::preferredVsyncId() const { return frameTimelines[preferredFrameTimelineIndex].vsyncId; } @@ -43,11 +46,15 @@ status_t ParcelableVsyncEventData::readFromParcel(const Parcel* parcel) { SAFE_PARCEL(parcel->readInt64, &vsync.frameInterval); - uint64_t uintPreferredFrameTimelineIndex; - SAFE_PARCEL(parcel->readUint64, &uintPreferredFrameTimelineIndex); + uint32_t uintPreferredFrameTimelineIndex; + SAFE_PARCEL(parcel->readUint32, &uintPreferredFrameTimelineIndex); vsync.preferredFrameTimelineIndex = static_cast(uintPreferredFrameTimelineIndex); - for (int i = 0; i < VsyncEventData::kFrameTimelinesLength; i++) { + uint32_t uintFrameTimelinesLength; + SAFE_PARCEL(parcel->readUint32, &uintFrameTimelinesLength); + vsync.frameTimelinesLength = static_cast(uintFrameTimelinesLength); + + for (size_t i = 0; i < vsync.frameTimelinesLength; i++) { SAFE_PARCEL(parcel->readInt64, &vsync.frameTimelines[i].vsyncId); SAFE_PARCEL(parcel->readInt64, &vsync.frameTimelines[i].deadlineTimestamp); SAFE_PARCEL(parcel->readInt64, &vsync.frameTimelines[i].expectedPresentationTime); @@ -57,8 +64,9 @@ status_t ParcelableVsyncEventData::readFromParcel(const Parcel* parcel) { } status_t ParcelableVsyncEventData::writeToParcel(Parcel* parcel) const { SAFE_PARCEL(parcel->writeInt64, vsync.frameInterval); - SAFE_PARCEL(parcel->writeUint64, vsync.preferredFrameTimelineIndex); - for (int i = 0; i < VsyncEventData::kFrameTimelinesLength; i++) { + SAFE_PARCEL(parcel->writeUint32, vsync.preferredFrameTimelineIndex); + SAFE_PARCEL(parcel->writeUint32, vsync.frameTimelinesLength); + for (size_t i = 0; i < vsync.frameTimelinesLength; i++) { SAFE_PARCEL(parcel->writeInt64, vsync.frameTimelines[i].vsyncId); SAFE_PARCEL(parcel->writeInt64, vsync.frameTimelines[i].deadlineTimestamp); SAFE_PARCEL(parcel->writeInt64, vsync.frameTimelines[i].expectedPresentationTime); diff --git a/libs/gui/WindowInfo.cpp b/libs/gui/WindowInfo.cpp index 4e966d139389bc77ea54e8aa9985f0582bffa630..6df9ff166474b0ce9a4b59a6a3844aa2b09d55a2 100644 --- a/libs/gui/WindowInfo.cpp +++ b/libs/gui/WindowInfo.cpp @@ -76,7 +76,7 @@ bool WindowInfo::operator==(const WindowInfo& info) const { info.inputConfig == inputConfig && info.displayId == displayId && info.replaceTouchableRegionWithCrop == replaceTouchableRegionWithCrop && info.applicationInfo == applicationInfo && info.layoutParamsType == layoutParamsType && - info.layoutParamsFlags == layoutParamsFlags && info.isClone == isClone; + info.layoutParamsFlags == layoutParamsFlags; } status_t WindowInfo::writeToParcel(android::Parcel* parcel) const { @@ -124,8 +124,8 @@ status_t WindowInfo::writeToParcel(android::Parcel* parcel) const { parcel->write(touchableRegion) ?: parcel->writeBool(replaceTouchableRegionWithCrop) ?: parcel->writeStrongBinder(touchableRegionCropHandle.promote()) ?: - parcel->writeStrongBinder(windowToken) ?: - parcel->writeBool(isClone); + parcel->writeStrongBinder(windowToken); + parcel->writeStrongBinder(focusTransferTarget); // clang-format on return status; } @@ -177,7 +177,8 @@ status_t WindowInfo::readFromParcel(const android::Parcel* parcel) { parcel->readBool(&replaceTouchableRegionWithCrop) ?: parcel->readNullableStrongBinder(&touchableRegionCropHandleSp) ?: parcel->readNullableStrongBinder(&windowToken) ?: - parcel->readBool(&isClone); + parcel->readNullableStrongBinder(&focusTransferTarget); + // clang-format on if (status != OK) { diff --git a/libs/gui/WindowInfosListenerReporter.cpp b/libs/gui/WindowInfosListenerReporter.cpp index cfc7dbc4630a1fe6b9836d8509075ab86cc458f4..0929b8e1202a16e40d60fc2eb2eff4a25ea22350 100644 --- a/libs/gui/WindowInfosListenerReporter.cpp +++ b/libs/gui/WindowInfosListenerReporter.cpp @@ -14,15 +14,17 @@ * limitations under the License. */ -#include +#include +#include #include +#include "gui/WindowInfosUpdate.h" namespace android { using gui::DisplayInfo; -using gui::IWindowInfosReportedListener; using gui::WindowInfo; using gui::WindowInfosListener; +using gui::aidl_utils::statusTFromBinderStatus; sp WindowInfosListenerReporter::getInstance() { static sp sInstance = new WindowInfosListenerReporter; @@ -31,13 +33,19 @@ sp WindowInfosListenerReporter::getInstance() { status_t WindowInfosListenerReporter::addWindowInfosListener( const sp& windowInfosListener, - const sp& surfaceComposer, + const sp& surfaceComposer, std::pair, std::vector>* outInitialInfo) { status_t status = OK; { std::scoped_lock lock(mListenersMutex); if (mWindowInfosListeners.empty()) { - status = surfaceComposer->addWindowInfosListener(this); + gui::WindowInfosListenerInfo listenerInfo; + binder::Status s = surfaceComposer->addWindowInfosListener(this, &listenerInfo); + status = statusTFromBinderStatus(s); + if (status == OK) { + mWindowInfosPublisher = std::move(listenerInfo.windowInfosPublisher); + mListenerId = listenerInfo.listenerId; + } } if (status == OK) { @@ -55,12 +63,17 @@ status_t WindowInfosListenerReporter::addWindowInfosListener( status_t WindowInfosListenerReporter::removeWindowInfosListener( const sp& windowInfosListener, - const sp& surfaceComposer) { + const sp& surfaceComposer) { status_t status = OK; { std::scoped_lock lock(mListenersMutex); + if (mWindowInfosListeners.find(windowInfosListener) == mWindowInfosListeners.end()) { + return status; + } + if (mWindowInfosListeners.size() == 1) { - status = surfaceComposer->removeWindowInfosListener(this); + binder::Status s = surfaceComposer->removeWindowInfosListener(this); + status = statusTFromBinderStatus(s); // Clear the last stored state since we're disabling updates and don't want to hold // stale values mLastWindowInfos.clear(); @@ -76,9 +89,9 @@ status_t WindowInfosListenerReporter::removeWindowInfosListener( } binder::Status WindowInfosListenerReporter::onWindowInfosChanged( - const std::vector& windowInfos, const std::vector& displayInfos, - const sp& windowInfosReportedListener) { - std::unordered_set, SpHash> windowInfosListeners; + const gui::WindowInfosUpdate& update) { + std::unordered_set, gui::SpHash> + windowInfosListeners; { std::scoped_lock lock(mListenersMutex); @@ -86,25 +99,26 @@ binder::Status WindowInfosListenerReporter::onWindowInfosChanged( windowInfosListeners.insert(listener); } - mLastWindowInfos = windowInfos; - mLastDisplayInfos = displayInfos; + mLastWindowInfos = update.windowInfos; + mLastDisplayInfos = update.displayInfos; } for (auto listener : windowInfosListeners) { - listener->onWindowInfosChanged(windowInfos, displayInfos); + listener->onWindowInfosChanged(update); } - if (windowInfosReportedListener) { - windowInfosReportedListener->onWindowInfosReported(); - } + mWindowInfosPublisher->ackWindowInfosReceived(update.vsyncId, mListenerId); return binder::Status::ok(); } -void WindowInfosListenerReporter::reconnect(const sp& composerService) { +void WindowInfosListenerReporter::reconnect(const sp& composerService) { std::scoped_lock lock(mListenersMutex); if (!mWindowInfosListeners.empty()) { - composerService->addWindowInfosListener(this); + gui::WindowInfosListenerInfo listenerInfo; + composerService->addWindowInfosListener(this, &listenerInfo); + mWindowInfosPublisher = std::move(listenerInfo.windowInfosPublisher); + mListenerId = listenerInfo.listenerId; } } diff --git a/libs/gui/WindowInfosUpdate.cpp b/libs/gui/WindowInfosUpdate.cpp new file mode 100644 index 0000000000000000000000000000000000000000..38ae5ef102bcea0b8eb6e9a597d194eab56c6a42 --- /dev/null +++ b/libs/gui/WindowInfosUpdate.cpp @@ -0,0 +1,72 @@ +/* + * 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::gui { + +status_t WindowInfosUpdate::readFromParcel(const android::Parcel* parcel) { + if (parcel == nullptr) { + ALOGE("%s: Null parcel", __func__); + return BAD_VALUE; + } + + uint32_t size; + + SAFE_PARCEL(parcel->readUint32, &size); + windowInfos.reserve(size); + for (uint32_t i = 0; i < size; i++) { + windowInfos.push_back({}); + SAFE_PARCEL(windowInfos.back().readFromParcel, parcel); + } + + SAFE_PARCEL(parcel->readUint32, &size); + displayInfos.reserve(size); + for (uint32_t i = 0; i < size; i++) { + displayInfos.push_back({}); + SAFE_PARCEL(displayInfos.back().readFromParcel, parcel); + } + + SAFE_PARCEL(parcel->readInt64, &vsyncId); + SAFE_PARCEL(parcel->readInt64, ×tamp); + + return OK; +} + +status_t WindowInfosUpdate::writeToParcel(android::Parcel* parcel) const { + if (parcel == nullptr) { + ALOGE("%s: Null parcel", __func__); + return BAD_VALUE; + } + + SAFE_PARCEL(parcel->writeUint32, static_cast(windowInfos.size())); + for (auto& windowInfo : windowInfos) { + SAFE_PARCEL(windowInfo.writeToParcel, parcel); + } + + SAFE_PARCEL(parcel->writeUint32, static_cast(displayInfos.size())); + for (auto& displayInfo : displayInfos) { + SAFE_PARCEL(displayInfo.writeToParcel, parcel); + } + + SAFE_PARCEL(parcel->writeInt64, vsyncId); + SAFE_PARCEL(parcel->writeInt64, timestamp); + + return OK; +} + +} // namespace android::gui diff --git a/libs/gui/aidl/android/gui/Rect.aidl b/libs/gui/aidl/android/gui/ARect.aidl similarity index 98% rename from libs/gui/aidl/android/gui/Rect.aidl rename to libs/gui/aidl/android/gui/ARect.aidl index 1b1376139229f7d4ee4e83082ffefeb2890b5551..5785907a9cabf2c62b68b9b74f635d9a9e4b2528 100644 --- a/libs/gui/aidl/android/gui/Rect.aidl +++ b/libs/gui/aidl/android/gui/ARect.aidl @@ -20,7 +20,7 @@ package android.gui; // TODO(b/221473398): // use hardware/interfaces/graphics/common/aidl/android/hardware/graphics/common/Rect.aidl /** @hide */ -parcelable Rect { +parcelable ARect { /// Minimum X coordinate of the rectangle. int left; diff --git a/libs/binder/aidl/android/content/pm/PackageChangeEvent.aidl b/libs/gui/aidl/android/gui/CachingHint.aidl similarity index 59% rename from libs/binder/aidl/android/content/pm/PackageChangeEvent.aidl rename to libs/gui/aidl/android/gui/CachingHint.aidl index e30e9072fc2891f74ad27d93cbf824973c736ef8..b35c79547fa076963edb777de24a346437cbca6e 100644 --- a/libs/binder/aidl/android/content/pm/PackageChangeEvent.aidl +++ b/libs/gui/aidl/android/gui/CachingHint.aidl @@ -1,5 +1,5 @@ /* - * Copyright (C) 2020 The Android Open Source Project + * 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. @@ -14,19 +14,17 @@ * limitations under the License. */ -package android.content.pm; +package android.gui; -/** - * This event is designed for notification to native code listener about - * any changes on a package including update, deletion and etc. - * +/* + * Hint for configuring caching behavior for a layer * @hide */ -parcelable PackageChangeEvent { - @utf8InCpp String packageName; - long version; - long lastUpdateTimeMillis; - boolean newInstalled; - boolean dataRemoved; - boolean isDeleted; +@Backing(type="int") +enum CachingHint { + // Caching is disabled. A layer may explicitly disable caching for + // improving image quality for some scenes. + Disabled = 0, + // Caching is enabled. A layer is cacheable by default. + Enabled = 1 } diff --git a/libs/gui/aidl/android/gui/Color.aidl b/libs/gui/aidl/android/gui/Color.aidl new file mode 100644 index 0000000000000000000000000000000000000000..12af06656283ac5735cff5ebc2bc7fb76cc8a52e --- /dev/null +++ b/libs/gui/aidl/android/gui/Color.aidl @@ -0,0 +1,25 @@ +/* + * 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. + */ + +package android.gui; + +/** @hide */ +parcelable Color { + float r; + float g; + float b; + float a; +} diff --git a/libs/gui/aidl/android/gui/CompositionPreference.aidl b/libs/gui/aidl/android/gui/CompositionPreference.aidl new file mode 100644 index 0000000000000000000000000000000000000000..b615824a7db4cab5e0df9b222aa4287957177828 --- /dev/null +++ b/libs/gui/aidl/android/gui/CompositionPreference.aidl @@ -0,0 +1,25 @@ +/* + * 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. + */ + +package android.gui; + +/** @hide */ +parcelable CompositionPreference { + int /*ui::Dataspace*/ defaultDataspace; + int /*ui::PixelFormat*/ defaultPixelFormat; + int /*ui::Dataspace*/ wideColorGamutDataspace; + int /*ui::PixelFormat*/ wideColorGamutPixelFormat; +} diff --git a/libs/gui/aidl/android/gui/ContentSamplingAttributes.aidl b/libs/gui/aidl/android/gui/ContentSamplingAttributes.aidl new file mode 100644 index 0000000000000000000000000000000000000000..5d913b1da67828b8ef789645a32e0d8118de8f5f --- /dev/null +++ b/libs/gui/aidl/android/gui/ContentSamplingAttributes.aidl @@ -0,0 +1,24 @@ +/* + * 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. + */ + +package android.gui; + +/** @hide */ +parcelable ContentSamplingAttributes { + int /*ui::PixelFormat*/ format; + int /*ui::Dataspace*/ dataspace; + byte componentMask; +} diff --git a/libs/gui/aidl/android/gui/CreateSurfaceResult.aidl b/libs/gui/aidl/android/gui/CreateSurfaceResult.aidl new file mode 100644 index 0000000000000000000000000000000000000000..eea12dc75d4b2dfc787d54f08917dd59c928a27f --- /dev/null +++ b/libs/gui/aidl/android/gui/CreateSurfaceResult.aidl @@ -0,0 +1,25 @@ +/* + * 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. + */ + +package android.gui; + +/** @hide */ +parcelable CreateSurfaceResult { + IBinder handle; + int layerId; + String layerName; + int transformHint; +} diff --git a/libs/gui/aidl/android/gui/DeviceProductInfo.aidl b/libs/gui/aidl/android/gui/DeviceProductInfo.aidl new file mode 100644 index 0000000000000000000000000000000000000000..98404cf0fd4001e47160b153d24ea8f90a60ae3a --- /dev/null +++ b/libs/gui/aidl/android/gui/DeviceProductInfo.aidl @@ -0,0 +1,58 @@ +/* + * 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. + */ + +package android.gui; + +// Product-specific information about the display or the directly connected device on the +// display chain. For example, if the display is transitively connected, this field may contain +// product information about the intermediate device. + +/** @hide */ +parcelable DeviceProductInfo { + parcelable ModelYear { + int year; + } + + parcelable ManufactureYear { + ModelYear modelYear; + } + + parcelable ManufactureWeekAndYear { + ManufactureYear manufactureYear; + + // 1-base week number. Week numbering may not be consistent between manufacturers. + int week; + } + + union ManufactureOrModelDate { + ModelYear modelYear; + ManufactureYear manufactureYear; + ManufactureWeekAndYear manufactureWeekAndYear; + } + + // Display name. + @utf8InCpp String name; + + // NULL-terminated Manufacturer plug and play ID. + byte[] manufacturerPnpId; + + // Manufacturer product ID. + @utf8InCpp String productId; + + ManufactureOrModelDate manufactureOrModelDate; + + byte[] relativeAddress; +} diff --git a/libs/gui/aidl/android/gui/DisplayConnectionType.aidl b/libs/gui/aidl/android/gui/DisplayConnectionType.aidl new file mode 100644 index 0000000000000000000000000000000000000000..72c4ede7acd541ceaa3f606306154c33595e4076 --- /dev/null +++ b/libs/gui/aidl/android/gui/DisplayConnectionType.aidl @@ -0,0 +1,24 @@ +/* + * 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. + */ + +package android.gui; + +/** @hide */ +@Backing(type="int") +enum DisplayConnectionType { + Internal = 0, + External = 1 +} diff --git a/libs/input/android/os/BlockUntrustedTouchesMode.aidl b/libs/gui/aidl/android/gui/DisplayDecorationSupport.aidl similarity index 61% rename from libs/input/android/os/BlockUntrustedTouchesMode.aidl rename to libs/gui/aidl/android/gui/DisplayDecorationSupport.aidl index 9504e993f8a8e9efa48b29923f9ddd0c06676d5b..023049657be1f166907c9bf16a948bf5fffd485e 100644 --- a/libs/input/android/os/BlockUntrustedTouchesMode.aidl +++ b/libs/gui/aidl/android/gui/DisplayDecorationSupport.aidl @@ -1,5 +1,5 @@ /** - * Copyright (c) 2020, The Android Open Source Project + * 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. @@ -14,22 +14,12 @@ * limitations under the License. */ -package android.os; +package android.gui; - -/** - * Block untrusted touches feature mode. - * - * @hide - */ -@Backing(type="int") -enum BlockUntrustedTouchesMode { - /** Feature is off. */ - DISABLED, - - /** Untrusted touches are flagged but not blocked. */ - PERMISSIVE, - - /** Untrusted touches are blocked. */ - BLOCK +// TODO(b/222607970): +// remove this aidl and use android.hardware.graphics.common.DisplayDecorationSupport +/** @hide */ +parcelable DisplayDecorationSupport { + int format; + int alphaInterpretation; } diff --git a/libs/gui/aidl/android/gui/DisplayMode.aidl b/libs/gui/aidl/android/gui/DisplayMode.aidl new file mode 100644 index 0000000000000000000000000000000000000000..ce30426cb57a565b5fdd6f69e6c9ec054dfee4ca --- /dev/null +++ b/libs/gui/aidl/android/gui/DisplayMode.aidl @@ -0,0 +1,37 @@ +/* + * 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. + */ + +package android.gui; + +import android.gui.Size; + +// Mode supported by physical display. +// Make sure to sync with libui DisplayMode.h + +/** @hide */ +parcelable DisplayMode { + int id; + Size resolution; + float xDpi = 0.0f; + float yDpi = 0.0f; + int[] supportedHdrTypes; + + float refreshRate = 0.0f; + long appVsyncOffset = 0; + long sfVsyncOffset = 0; + long presentationDeadline = 0; + int group = -1; +} diff --git a/libs/gui/aidl/android/gui/DisplayModeSpecs.aidl b/libs/gui/aidl/android/gui/DisplayModeSpecs.aidl new file mode 100644 index 0000000000000000000000000000000000000000..af138c7539389bdb0d467c482a1a4626011f12d1 --- /dev/null +++ b/libs/gui/aidl/android/gui/DisplayModeSpecs.aidl @@ -0,0 +1,75 @@ +/* + * 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. + */ + +package android.gui; + +/** @hide */ +parcelable DisplayModeSpecs { + /** + * Defines the refresh rates ranges that should be used by SF. + */ + parcelable RefreshRateRanges { + /** + * Defines a range of refresh rates. + */ + parcelable RefreshRateRange { + float min; + float max; + } + + /** + * The range of refresh rates that the display should run at. + */ + RefreshRateRange physical; + + /** + * The range of refresh rates that apps should render at. + */ + RefreshRateRange render; + } + + /** + * Base mode ID. This is what system defaults to for all other settings, or + * if the refresh rate range is not available. + */ + int defaultMode; + + /** + * If true this will allow switching between modes in different display configuration + * groups. This way the user may see visual interruptions when the display mode changes. + */ + + boolean allowGroupSwitching; + + /** + * The primary physical and render refresh rate ranges represent DisplayManager's general + * guidance on the display modes SurfaceFlinger will consider when switching refresh + * rates and scheduling the frame rate. Unless SurfaceFlinger has a specific reason to do + * otherwise, it will stay within this range. + */ + RefreshRateRanges primaryRanges; + + /** + * The app request physical and render refresh rate ranges allow SurfaceFlinger to consider + * more display modes when switching refresh rates. Although SurfaceFlinger will + * generally stay within the primary range, specific considerations, such as layer frame + * rate settings specified via the setFrameRate() API, may cause SurfaceFlinger to go + * outside the primary range. SurfaceFlinger never goes outside the app request range. + * The app request range will be greater than or equal to the primary refresh rate range, + * never smaller. + */ + RefreshRateRanges appRequestRanges; +} diff --git a/libs/binder/aidl/android/content/pm/IPackageChangeObserver.aidl b/libs/gui/aidl/android/gui/DisplayPrimaries.aidl similarity index 64% rename from libs/binder/aidl/android/content/pm/IPackageChangeObserver.aidl rename to libs/gui/aidl/android/gui/DisplayPrimaries.aidl index 6929a6cb493a96a1a5b82e8fb9edbab8f2324589..dbf668c62993fef9e9a66f158209fa497822cf7a 100644 --- a/libs/binder/aidl/android/content/pm/IPackageChangeObserver.aidl +++ b/libs/gui/aidl/android/gui/DisplayPrimaries.aidl @@ -1,5 +1,5 @@ /* - * Copyright (C) 2020 The Android Open Source Project + * 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. @@ -14,15 +14,20 @@ * limitations under the License. */ -package android.content.pm; +package android.gui; -import android.content.pm.PackageChangeEvent; +// copied from libui ConfigStoreTypes.h -/** - * This is a non-blocking notification when a package has changed. - * - * @hide - */ -oneway interface IPackageChangeObserver { - void onPackageChanged(in PackageChangeEvent event); +/** @hide */ +parcelable DisplayPrimaries { + parcelable CieXyz { + float X; + float Y; + float Z; + } + + CieXyz red; + CieXyz green; + CieXyz blue; + CieXyz white; } diff --git a/libs/gui/aidl/android/gui/DisplayedFrameStats.aidl b/libs/gui/aidl/android/gui/DisplayedFrameStats.aidl new file mode 100644 index 0000000000000000000000000000000000000000..f4b6dadc49a73631b7b34caded6a9b87f59cf4b0 --- /dev/null +++ b/libs/gui/aidl/android/gui/DisplayedFrameStats.aidl @@ -0,0 +1,40 @@ +/* + * 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. + */ + +package android.gui; + +/** @hide */ +parcelable DisplayedFrameStats { + /* The number of frames represented by this sample. */ + long numFrames = 0; + + /* A histogram counting how many times a pixel of a given value was displayed onscreen for + * FORMAT_COMPONENT_0. The buckets of the histogram are evenly weighted, the number of buckets + * is device specific. eg, for RGBA_8888, if sampleComponent0 is {10, 6, 4, 1} this means that + * 10 red pixels were displayed onscreen in range 0x00->0x3F, 6 red pixels + * were displayed onscreen in range 0x40->0x7F, etc. + */ + long[] component_0_sample; + + /* The same sample definition as sampleComponent0, but for FORMAT_COMPONENT_1. */ + long[] component_1_sample; + + /* The same sample definition as sampleComponent0, but for FORMAT_COMPONENT_2. */ + long[] component_2_sample; + + /* The same sample definition as sampleComponent0, but for FORMAT_COMPONENT_3. */ + long[] component_3_sample; +} diff --git a/libs/gui/aidl/android/gui/DynamicDisplayInfo.aidl b/libs/gui/aidl/android/gui/DynamicDisplayInfo.aidl new file mode 100644 index 0000000000000000000000000000000000000000..3114929e868ce939bdedf8ecc70ee7895c7b50e8 --- /dev/null +++ b/libs/gui/aidl/android/gui/DynamicDisplayInfo.aidl @@ -0,0 +1,46 @@ +/* + * 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. + */ + +package android.gui; + +import android.gui.DisplayMode; +import android.gui.HdrCapabilities; + +// Information about a physical display which may change on hotplug reconnect. +// Make sure to sync with libui DynamicDisplayInfo.h + +/** @hide */ +parcelable DynamicDisplayInfo { + List supportedDisplayModes; + + int activeDisplayModeId; + float renderFrameRate; + + int[] supportedColorModes; + int activeColorMode; + HdrCapabilities hdrCapabilities; + + // True if the display reports support for HDMI 2.1 Auto Low Latency Mode. + // For more information, see the HDMI 2.1 specification. + boolean autoLowLatencyModeSupported; + + // True if the display reports support for Game Content Type. + // For more information, see the HDMI 1.4 specification. + boolean gameContentTypeSupported; + + // The boot display mode preferred by the implementation. + int preferredBootDisplayMode; +} diff --git a/libs/gui/aidl/android/gui/FrameEvent.aidl b/libs/gui/aidl/android/gui/FrameEvent.aidl new file mode 100644 index 0000000000000000000000000000000000000000..aaabdb5b54765bb79e10fad8f4ea34db23b53485 --- /dev/null +++ b/libs/gui/aidl/android/gui/FrameEvent.aidl @@ -0,0 +1,35 @@ +/* + * 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. + */ + +package android.gui; + +// Identifiers for all the events that may be recorded or reported. + +/** @hide */ +@Backing(type="int") +enum FrameEvent { + POSTED = 0, + REQUESTED_PRESENT = 1, + LATCH = 2, + ACQUIRE = 3, + FIRST_REFRESH_START = 4, + LAST_REFRESH_START = 5, + GPU_COMPOSITION_DONE = 6, + DISPLAY_PRESENT = 7, + DEQUEUE_READY = 8, + RELEASE = 9, + EVENT_COUNT = 10 // Not an actual event. +} diff --git a/libs/gui/aidl/android/gui/FrameStats.aidl b/libs/gui/aidl/android/gui/FrameStats.aidl new file mode 100644 index 0000000000000000000000000000000000000000..a145e74b112263132372e966bc2f7ce41abcc298 --- /dev/null +++ b/libs/gui/aidl/android/gui/FrameStats.aidl @@ -0,0 +1,47 @@ +/* + * 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. + */ + +package android.gui; + +// Make sure to sync with libui FrameStats.h + +/** @hide */ +parcelable FrameStats { + /* + * Approximate refresh time, in nanoseconds. + */ + long refreshPeriodNano; + + /* + * The times in nanoseconds for when the frame contents were posted by the producer (e.g. + * the application). They are either explicitly set or defaulted to the time when + * Surface::queueBuffer() was called. + */ + long[] desiredPresentTimesNano; + + /* + * The times in milliseconds for when the frame contents were presented on the screen. + */ + long[] actualPresentTimesNano; + + /* + * The times in nanoseconds for when the frame contents were ready to be presented. Note that + * a frame can be posted and still it contents being rendered asynchronously in GL. In such a + * case these are the times when the frame contents were completely rendered (i.e. their fences + * signaled). + */ + long[] frameReadyTimesNano; +} diff --git a/libs/gui/include/gui/FrameTimelineInfo.h b/libs/gui/aidl/android/gui/FrameTimelineInfo.aidl similarity index 70% rename from libs/gui/include/gui/FrameTimelineInfo.h rename to libs/gui/aidl/android/gui/FrameTimelineInfo.aidl index 255ce568d21d9aa13248f23d99b3d3e39f1c02b2..6a86c6a5cd6ba49a3b83bda2668ebe480ae16b21 100644 --- a/libs/gui/include/gui/FrameTimelineInfo.h +++ b/libs/gui/aidl/android/gui/FrameTimelineInfo.aidl @@ -1,5 +1,5 @@ /* - * Copyright 2021 The Android Open Source Project + * 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. @@ -14,36 +14,27 @@ * limitations under the License. */ -#pragma once +package android.gui; -#include - -#include - -namespace android { - -struct FrameTimelineInfo { +/** @hide */ +parcelable FrameTimelineInfo { // Needs to be in sync with android.graphics.FrameInfo.INVALID_VSYNC_ID in java - static constexpr int64_t INVALID_VSYNC_ID = -1; + const long INVALID_VSYNC_ID = -1; // The vsync id that was used to start the transaction - int64_t vsyncId = INVALID_VSYNC_ID; + long vsyncId = INVALID_VSYNC_ID; // The id of the input event that caused this buffer // Default is android::os::IInputConstants::INVALID_INPUT_EVENT_ID = 0 // We copy the value of the input event ID instead of including the header, because libgui // header libraries containing FrameTimelineInfo must be available to vendors, but libinput is // not directly vendor available. - int32_t inputEventId = 0; + int inputEventId = 0; // The current time in nanoseconds the application started to render the frame. - int64_t startTimeNanos = 0; - - status_t write(Parcel& output) const; - status_t read(const Parcel& input); - - void merge(const FrameTimelineInfo& other); - void clear(); -}; + long startTimeNanos = 0; -} // namespace android + // Whether this vsyncId should be used to heuristically select the display refresh rate + // TODO(b/281695725): Clean this up once TextureView use setFrameRate API + boolean useForRefreshRateSelection = false; +} diff --git a/libs/gui/aidl/android/gui/HdrCapabilities.aidl b/libs/gui/aidl/android/gui/HdrCapabilities.aidl new file mode 100644 index 0000000000000000000000000000000000000000..9d06da9f27a2f4d7048b73551be271f41c0e3753 --- /dev/null +++ b/libs/gui/aidl/android/gui/HdrCapabilities.aidl @@ -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. + */ + +package android.gui; + +// Make sure to sync with libui HdrCapabilities.h + +/** @hide */ +parcelable HdrCapabilities { + int[] supportedHdrTypes; + float maxLuminance; + float maxAverageLuminance; + float minLuminance; +} diff --git a/libs/gui/aidl/android/gui/HdrConversionCapability.aidl b/libs/gui/aidl/android/gui/HdrConversionCapability.aidl new file mode 100644 index 0000000000000000000000000000000000000000..1bcfd38eff35a5ac3a38a462841c028886289ec3 --- /dev/null +++ b/libs/gui/aidl/android/gui/HdrConversionCapability.aidl @@ -0,0 +1,25 @@ +/* + * 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. + */ + +package android.gui; + +// TODO(b/265277221): use android.hardware.graphics.common.HdrConversionCapability.aidl +/** @hide */ +parcelable HdrConversionCapability { + int sourceType; + int outputType; + boolean addsLatency; +} \ No newline at end of file diff --git a/libs/gui/aidl/android/gui/HdrConversionStrategy.aidl b/libs/gui/aidl/android/gui/HdrConversionStrategy.aidl new file mode 100644 index 0000000000000000000000000000000000000000..1be74b46e733caa8ecbe1c3e894eb7ed1a8f8d5a --- /dev/null +++ b/libs/gui/aidl/android/gui/HdrConversionStrategy.aidl @@ -0,0 +1,25 @@ +/* + * 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. + */ + +package android.gui; + +// TODO(b/265277221): use android.hardware.graphics.common.HdrConversionStrategy.aidl +/** @hide */ +union HdrConversionStrategy { + boolean passthrough = true; + int[] autoAllowedHdrTypes; + int forceHdrConversion; +} diff --git a/libs/gui/aidl/android/gui/IHdrConversionConstants.aidl b/libs/gui/aidl/android/gui/IHdrConversionConstants.aidl new file mode 100644 index 0000000000000000000000000000000000000000..7697f2995bc3c672f8275186d783c1e846c65c26 --- /dev/null +++ b/libs/gui/aidl/android/gui/IHdrConversionConstants.aidl @@ -0,0 +1,30 @@ +/* + * 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. + */ + +package android.gui; + +/** @hide */ +interface IHdrConversionConstants +{ + /** HDR Conversion Mode when there is no conversion being done */ + const int HdrConversionModePassthrough = 1; + + /** HDR Conversion Mode when HDR conversion is decided by the system or implementation */ + const int HdrConversionModeAuto = 2; + + /** HDR Conversion Mode when the output HDR types is selected by the user or framework */ + const int HdrConversionModeForce = 3; +} \ No newline at end of file diff --git a/libs/gui/aidl/android/gui/IHdrLayerInfoListener.aidl b/libs/gui/aidl/android/gui/IHdrLayerInfoListener.aidl index fc809c4c88201b7853d33536c692487098801da9..e8c36ee001e0033fe05acc41e2454fee96f9a595 100644 --- a/libs/gui/aidl/android/gui/IHdrLayerInfoListener.aidl +++ b/libs/gui/aidl/android/gui/IHdrLayerInfoListener.aidl @@ -19,7 +19,9 @@ package android.gui; /** @hide */ oneway interface IHdrLayerInfoListener { // Callback with the total number of HDR layers, the dimensions of the largest layer, - // and a placeholder flags + // a placeholder flags, and the max desired HDR/SDR ratio. The max desired HDR/SDR + // ratio may be positive infinity to indicate an unbounded ratio. // TODO (b/182312559): Define the flags (likely need an indicator that a UDFPS layer is present) - void onHdrLayerInfoChanged(int numberOfHdrLayers, int maxW, int maxH, int flags); + void onHdrLayerInfoChanged(int numberOfHdrLayers, int maxW, int maxH, + int flags, float maxDesiredHdrSdrRatio); } \ No newline at end of file diff --git a/libs/gui/aidl/android/gui/ISurfaceComposer.aidl b/libs/gui/aidl/android/gui/ISurfaceComposer.aidl index b31b37bada3eceff6c9afbd78066c77fd33eef86..539a1c140e47c799f66dea2a0651135628107729 100644 --- a/libs/gui/aidl/android/gui/ISurfaceComposer.aidl +++ b/libs/gui/aidl/android/gui/ISurfaceComposer.aidl @@ -16,39 +16,117 @@ package android.gui; -import android.gui.DisplayCaptureArgs; +import android.gui.Color; +import android.gui.CompositionPreference; +import android.gui.ContentSamplingAttributes; import android.gui.DisplayBrightness; +import android.gui.DisplayCaptureArgs; +import android.gui.DisplayDecorationSupport; +import android.gui.DisplayedFrameStats; +import android.gui.DisplayModeSpecs; +import android.gui.DisplayPrimaries; import android.gui.DisplayState; import android.gui.DisplayStatInfo; +import android.gui.DynamicDisplayInfo; +import android.gui.FrameEvent; +import android.gui.FrameStats; +import android.gui.HdrConversionCapability; +import android.gui.HdrConversionStrategy; +import android.gui.IDisplayEventConnection; +import android.gui.IFpsListener; import android.gui.IHdrLayerInfoListener; -import android.gui.LayerCaptureArgs; +import android.gui.IRegionSamplingListener; import android.gui.IScreenCaptureListener; +import android.gui.ISurfaceComposerClient; +import android.gui.ITunnelModeEnabledListener; +import android.gui.IWindowInfosListener; +import android.gui.IWindowInfosPublisher; +import android.gui.LayerCaptureArgs; +import android.gui.LayerDebugInfo; +import android.gui.OverlayProperties; +import android.gui.PullAtomData; +import android.gui.ARect; +import android.gui.StaticDisplayInfo; +import android.gui.WindowInfosListenerInfo; /** @hide */ interface ISurfaceComposer { - /* create a virtual display + enum VsyncSource { + eVsyncSourceApp = 0, + eVsyncSourceSurfaceFlinger = 1 + } + + enum EventRegistration { + modeChanged = 1 << 0, + frameRateOverride = 1 << 1, + } + + /** + * Signal that we're done booting. + * Requires ACCESS_SURFACE_FLINGER permission + */ + void bootFinished(); + + /** + * Create a display event connection + * + * layerHandle + * Optional binder handle representing a Layer in SF to associate the new + * DisplayEventConnection with. This handle can be found inside a surface control after + * surface creation, see ISurfaceComposerClient::createSurface. Set to null if no layer + * association should be made. + */ + @nullable IDisplayEventConnection createDisplayEventConnection(VsyncSource vsyncSource, + EventRegistration eventRegistration, @nullable IBinder layerHandle); + + /** + * Create a connection with SurfaceFlinger. + */ + @nullable ISurfaceComposerClient createConnection(); + + /** + * Create a virtual display + * + * displayName + * The name of the virtual display + * secure + * Whether this virtual display is secure + * 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 + * with physical displays running concurrently. If 0 is specified, the + * virtual display is refreshed at the physical display refresh rate. + * * requires ACCESS_SURFACE_FLINGER permission. */ - @nullable IBinder createDisplay(@utf8InCpp String displayName, boolean secure); + @nullable IBinder createDisplay(@utf8InCpp String displayName, boolean secure, + float requestedRefreshRate); - /* destroy a virtual display + /** + * Destroy a virtual display * requires ACCESS_SURFACE_FLINGER permission. */ void destroyDisplay(IBinder display); - /* get stable IDs for connected physical displays. + /** + * Get stable IDs for connected physical displays. */ long[] getPhysicalDisplayIds(); - long getPrimaryPhysicalDisplayId(); - - /* get token for a physical display given its stable ID obtained via getPhysicalDisplayIds or a - * DisplayEventReceiver hotplug event. + /** + * Get token for a physical display given its stable ID obtained via getPhysicalDisplayIds or + * a DisplayEventReceiver hotplug event. */ @nullable IBinder getPhysicalDisplayToken(long displayId); - /* set display power mode. depending on the mode, it can either trigger + /** + * Returns the frame timestamps supported by SurfaceFlinger. + */ + FrameEvent[] getSupportedFrameTimestamps(); + + /** + * Set display power mode. depending on the mode, it can either trigger * screen on, off or low power mode and wait for it to complete. * requires ACCESS_SURFACE_FLINGER permission. */ @@ -60,11 +138,32 @@ interface ISurfaceComposer { * video frames */ DisplayStatInfo getDisplayStats(@nullable IBinder display); - /** + /** * Get transactional state of given display. */ DisplayState getDisplayState(IBinder display); + /** + * Gets immutable information about given physical display. + */ + StaticDisplayInfo getStaticDisplayInfo(long displayId); + + /** + * Gets dynamic information about given physical display. + */ + DynamicDisplayInfo getDynamicDisplayInfoFromId(long displayId); + + DynamicDisplayInfo getDynamicDisplayInfoFromToken(IBinder display); + + DisplayPrimaries getDisplayNativePrimaries(IBinder display); + + void setActiveColorMode(IBinder display, int colorMode); + + /** + * Sets the user-preferred display mode that a device should boot in. + */ + void setBootDisplayMode(IBinder display, int displayModeId); + /** * Clears the user-preferred display mode. The device should now boot in system preferred * display mode. @@ -84,6 +183,28 @@ interface ISurfaceComposer { // TODO(b/213909104) : Add unit tests to verify surface flinger boot time APIs boolean getBootDisplayModeSupport(); + /** + * Gets the HDR conversion capabilities of the device. The conversion capability defines whether + * conversion from sourceType to outputType is possible (with or without latency). + * + * Requires the ACCESS_SURFACE_FLINGER permission. + */ + List getHdrConversionCapabilities(); + + /** + * Sets the HDR conversion strategy of the device. + * Returns the preferred HDR output type of the device, in case when HdrConversionStrategy has + * autoAllowedHdrTypes set. Returns Hdr::INVALID in other cases. + * + * Requires the ACCESS_SURFACE_FLINGER permission. + */ + int setHdrConversionStrategy(in HdrConversionStrategy hdrConversionStrategy); + + /** + * Gets whether HDR output conversion operations are supported on the device. + */ + boolean getHdrOutputConversionSupport(); + /** * Switches Auto Low Latency Mode on/off on the connected display, if it is * available. This should only be called if the display supports Auto Low @@ -110,7 +231,13 @@ interface ISurfaceComposer { * match the size of the output buffer. */ void captureDisplay(in DisplayCaptureArgs args, IScreenCaptureListener listener); + + /** + * Capture the specified screen. This requires the READ_FRAME_BUFFER + * permission. + */ void captureDisplayById(long displayId, IScreenCaptureListener listener); + /** * Capture a subtree of the layer hierarchy, potentially ignoring the root node. * This requires READ_FRAME_BUFFER permission. This function will fail if there @@ -118,13 +245,143 @@ interface ISurfaceComposer { */ void captureLayers(in LayerCaptureArgs args, IScreenCaptureListener listener); - /* + /** + * Clears the frame statistics for animations. + * + * Requires the ACCESS_SURFACE_FLINGER permission. + */ + void clearAnimationFrameStats(); + + /** + * Gets the frame statistics for animations. + * + * Requires the ACCESS_SURFACE_FLINGER permission. + */ + FrameStats getAnimationFrameStats(); + + /** + * Overrides the supported HDR modes for the given display device. + * + * Requires the ACCESS_SURFACE_FLINGER permission. + */ + void overrideHdrTypes(IBinder display, in int[] hdrTypes); + + /** + * Pulls surfaceflinger atoms global stats and layer stats to pipe to statsd. + * + * Requires the calling uid be from system server. + */ + PullAtomData onPullAtom(int atomId); + + /** + * Gets the list of active layers in Z order for debugging purposes + * + * Requires the ACCESS_SURFACE_FLINGER permission. + */ + List getLayerDebugInfo(); + + boolean getColorManagement(); + + /** + * Gets the composition preference of the default data space and default pixel format, + * as well as the wide color gamut data space and wide color gamut pixel format. + * If the wide color gamut data space is V0_SRGB, then it implies that the platform + * has no wide color gamut support. + * + */ + CompositionPreference getCompositionPreference(); + + /** + * Requires the ACCESS_SURFACE_FLINGER permission. + */ + ContentSamplingAttributes getDisplayedContentSamplingAttributes(IBinder display); + + /** + * Turns on the color sampling engine on the display. + * + * Requires the ACCESS_SURFACE_FLINGER permission. + */ + void setDisplayContentSamplingEnabled(IBinder display, boolean enable, byte componentMask, long maxFrames); + + /** + * Returns statistics on the color profile of the last frame displayed for a given display + * + * Requires the ACCESS_SURFACE_FLINGER permission. + */ + DisplayedFrameStats getDisplayedContentSample(IBinder display, long maxFrames, long timestamp); + + /** + * Gets whether SurfaceFlinger can support protected content in GPU composition. + */ + boolean getProtectedContentSupport(); + + /** * Queries whether the given display is a wide color display. * Requires the ACCESS_SURFACE_FLINGER permission. */ boolean isWideColorDisplay(IBinder token); - /* + /** + * Registers a listener to stream median luma updates from SurfaceFlinger. + * + * The sampling area is bounded by both samplingArea and the given stopLayerHandle + * (i.e., only layers behind the stop layer will be captured and sampled). + * + * Multiple listeners may be provided so long as they have independent listeners. + * If multiple listeners are provided, the effective sampling region for each listener will + * be bounded by whichever stop layer has a lower Z value. + * + * Requires the same permissions as captureLayers and captureScreen. + */ + void addRegionSamplingListener(in ARect samplingArea, @nullable IBinder stopLayerHandle, IRegionSamplingListener listener); + + /** + * Removes a listener that was streaming median luma updates from SurfaceFlinger. + */ + void removeRegionSamplingListener(IRegionSamplingListener listener); + + /** + * Registers a listener that streams fps updates from SurfaceFlinger. + * + * The listener will stream fps updates for the layer tree rooted at the layer denoted by the + * task ID, i.e., the layer must have the task ID as part of its layer metadata with key + * METADATA_TASK_ID. If there is no such layer, then no fps is expected to be reported. + * + * Multiple listeners may be supported. + * + * Requires the READ_FRAME_BUFFER permission. + */ + void addFpsListener(int taskId, IFpsListener listener); + + /** + * Removes a listener that was streaming fps updates from SurfaceFlinger. + */ + void removeFpsListener(IFpsListener listener); + + /** + * Registers a listener to receive tunnel mode enabled updates from SurfaceFlinger. + * + * Requires ACCESS_SURFACE_FLINGER permission. + */ + void addTunnelModeEnabledListener(ITunnelModeEnabledListener listener); + + /** + * Removes a listener that was receiving tunnel mode enabled updates from SurfaceFlinger. + * + * Requires ACCESS_SURFACE_FLINGER permission. + */ + void removeTunnelModeEnabledListener(ITunnelModeEnabledListener listener); + + /** + * Sets the refresh rate boundaries for the display. + * + * @see DisplayModeSpecs.aidl for details. + */ + void setDesiredDisplayModeSpecs(IBinder displayToken, in DisplayModeSpecs specs); + + DisplayModeSpecs getDesiredDisplayModeSpecs(IBinder displayToken); + + /** * Gets whether brightness operations are supported on a display. * * displayToken @@ -138,7 +395,7 @@ interface ISurfaceComposer { */ boolean getDisplayBrightnessSupport(IBinder displayToken); - /* + /** * Sets the brightness of a display. * * displayToken @@ -153,7 +410,7 @@ interface ISurfaceComposer { */ void setDisplayBrightness(IBinder displayToken, in DisplayBrightness brightness); - /* + /** * Adds a listener that receives HDR layer information. This is used in combination * with setDisplayBrightness to adjust the display brightness depending on factors such * as whether or not HDR is in use. @@ -162,7 +419,7 @@ interface ISurfaceComposer { */ void addHdrLayerInfoListener(IBinder displayToken, IHdrLayerInfoListener listener); - /* + /** * Removes a listener that was added with addHdrLayerInfoListener. * * Returns NO_ERROR upon success, NAME_NOT_FOUND if the display is invalid, and BAD_VALUE if @@ -171,7 +428,7 @@ interface ISurfaceComposer { */ void removeHdrLayerInfoListener(IBinder displayToken, IHdrLayerInfoListener listener); - /* + /** * Sends a power boost to the composer. This function is asynchronous. * * boostId @@ -179,5 +436,75 @@ interface ISurfaceComposer { * * Returns NO_ERROR upon success. */ - void notifyPowerBoost(int boostId); + oneway void notifyPowerBoost(int boostId); + + /* + * Sets the global configuration for all the shadows drawn by SurfaceFlinger. Shadow follows + * material design guidelines. + * + * ambientColor + * Color to the ambient shadow. The alpha is premultiplied. + * + * spotColor + * Color to the spot shadow. The alpha is premultiplied. The position of the spot shadow + * depends on the light position. + * + * lightPosY/lightPosZ + * Position of the light used to cast the spot shadow. The X value is always the display + * width / 2. + * + * lightRadius + * Radius of the light casting the shadow. + */ + oneway void setGlobalShadowSettings(in Color ambientColor, in Color spotColor, float lightPosY, float lightPosZ, float lightRadius); + + /** + * Gets whether a display supports DISPLAY_DECORATION layers. + * + * displayToken + * The token of the display. + * outSupport + * An output parameter for whether/how the display supports + * DISPLAY_DECORATION layers. + * + * Returns NO_ERROR upon success. Otherwise, + * NAME_NOT_FOUND if the display is invalid, or + * BAD_VALUE if the output parameter is invalid. + */ + @nullable DisplayDecorationSupport getDisplayDecorationSupport(IBinder displayToken); + + /** + * Set the override frame rate for a specified uid by GameManagerService. + * Passing the frame rate and uid to SurfaceFlinger to update the override mapping + * in the scheduler. + */ + void setOverrideFrameRate(int uid, float frameRate); + + /** + * Gets priority of the RenderEngine in SurfaceFlinger. + */ + int getGpuContextPriority(); + + /** + * Gets the number of buffers SurfaceFlinger would need acquire. This number + * would be propagated to the client via MIN_UNDEQUEUED_BUFFERS so that the + * client could allocate enough buffers to match SF expectations of the + * pipeline depth. SurfaceFlinger will make sure that it will give the app at + * least the time configured as the 'appDuration' before trying to latch + * the buffer. + * + * The total buffers needed for a given configuration is basically the + * numbers of vsyncs a single buffer is used across the stack. For the default + * configuration a buffer is held ~1 vsync by the app, ~1 vsync by SurfaceFlinger + * and 1 vsync by the display. The extra buffers are calculated as the + * number of additional buffers on top of the 2 buffers already present + * in MIN_UNDEQUEUED_BUFFERS. + */ + int getMaxAcquiredBufferCount(); + + WindowInfosListenerInfo addWindowInfosListener(IWindowInfosListener windowInfosListener); + + void removeWindowInfosListener(IWindowInfosListener windowInfosListener); + + OverlayProperties getOverlaySupport(); } diff --git a/libs/gui/aidl/android/gui/ISurfaceComposerClient.aidl b/libs/gui/aidl/android/gui/ISurfaceComposerClient.aidl new file mode 100644 index 0000000000000000000000000000000000000000..68781ce953493369d0e9046a1f810f3e24bebb6e --- /dev/null +++ b/libs/gui/aidl/android/gui/ISurfaceComposerClient.aidl @@ -0,0 +1,63 @@ +/* + * 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. + */ + +package android.gui; + +import android.gui.CreateSurfaceResult; +import android.gui.FrameStats; +import android.gui.LayerMetadata; + +/** @hide */ +interface ISurfaceComposerClient { + + // flags for createSurface() + // (keep in sync with SurfaceControl.java) + const int eHidden = 0x00000004; + const int eDestroyBackbuffer = 0x00000020; + const int eSkipScreenshot = 0x00000040; + const int eSecure = 0x00000080; + const int eNonPremultiplied = 0x00000100; + const int eOpaque = 0x00000400; + const int eProtectedByApp = 0x00000800; + const int eProtectedByDRM = 0x00001000; + const int eCursorWindow = 0x00002000; + const int eNoColorFill = 0x00004000; + + const int eFXSurfaceBufferQueue = 0x00000000; + const int eFXSurfaceEffect = 0x00020000; + const int eFXSurfaceBufferState = 0x00040000; + const int eFXSurfaceContainer = 0x00080000; + const int eFXSurfaceMask = 0x000F0000; + + /** + * Requires ACCESS_SURFACE_FLINGER permission + */ + CreateSurfaceResult createSurface(@utf8InCpp String name, int flags, @nullable IBinder parent, in LayerMetadata metadata); + + /** + * Requires ACCESS_SURFACE_FLINGER permission + */ + void clearLayerFrameStats(IBinder handle); + + /** + * Requires ACCESS_SURFACE_FLINGER permission + */ + FrameStats getLayerFrameStats(IBinder handle); + + CreateSurfaceResult mirrorSurface(IBinder mirrorFromHandle); + + CreateSurfaceResult mirrorDisplay(long displayId); +} diff --git a/libs/gui/aidl/android/gui/ITransactionTraceListener.aidl b/libs/gui/aidl/android/gui/ITransactionTraceListener.aidl deleted file mode 100644 index 5cd12fdc2b2fd1a7a7d84d5e7d267e44dd335fec..0000000000000000000000000000000000000000 --- a/libs/gui/aidl/android/gui/ITransactionTraceListener.aidl +++ /dev/null @@ -1,6 +0,0 @@ -package android.gui; - -/** @hide */ -interface ITransactionTraceListener { - void onToggled(boolean enabled); -} \ No newline at end of file diff --git a/libs/gui/aidl/android/gui/LayerDebugInfo.aidl b/libs/gui/aidl/android/gui/LayerDebugInfo.aidl new file mode 100644 index 0000000000000000000000000000000000000000..faca980f3cee93ce4fc2223a40a61677a15ecb4c --- /dev/null +++ b/libs/gui/aidl/android/gui/LayerDebugInfo.aidl @@ -0,0 +1,19 @@ +/* + * 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. + */ + +package android.gui; + +parcelable LayerDebugInfo cpp_header "gui/LayerDebugInfo.h"; diff --git a/libs/gui/aidl/android/gui/LayerMetadata.aidl b/libs/gui/aidl/android/gui/LayerMetadata.aidl new file mode 100644 index 0000000000000000000000000000000000000000..1368ac512fe56885d943fad1292e60205f3ac0f6 --- /dev/null +++ b/libs/gui/aidl/android/gui/LayerMetadata.aidl @@ -0,0 +1,19 @@ +/* + * 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. + */ + +package android.gui; + +parcelable LayerMetadata cpp_header "gui/LayerMetadata.h"; diff --git a/libs/gui/aidl/android/gui/OverlayProperties.aidl b/libs/gui/aidl/android/gui/OverlayProperties.aidl new file mode 100644 index 0000000000000000000000000000000000000000..5fb1a83c6565487da4af21d18f90660d4c929f03 --- /dev/null +++ b/libs/gui/aidl/android/gui/OverlayProperties.aidl @@ -0,0 +1,30 @@ +/* + * 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. + */ + +package android.gui; + +/** @hide */ +parcelable OverlayProperties { + parcelable SupportedBufferCombinations { + int[] pixelFormats; + int[] standards; + int[] transfers; + int[] ranges; + } + SupportedBufferCombinations[] combinations; + + boolean supportMixedColorSpaces; +} diff --git a/libs/gui/aidl/android/gui/PullAtomData.aidl b/libs/gui/aidl/android/gui/PullAtomData.aidl new file mode 100644 index 0000000000000000000000000000000000000000..c307cef70ed1d1280866b62163e6421b6fc51f16 --- /dev/null +++ b/libs/gui/aidl/android/gui/PullAtomData.aidl @@ -0,0 +1,23 @@ +/* + * 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. + */ + +package android.gui; + +/** @hide */ +parcelable PullAtomData { + byte[] data; + boolean success; +} diff --git a/libs/gui/aidl/android/gui/StaticDisplayInfo.aidl b/libs/gui/aidl/android/gui/StaticDisplayInfo.aidl new file mode 100644 index 0000000000000000000000000000000000000000..0ccda56ef5d45a63aa81892cbb69ca2451b9ef7f --- /dev/null +++ b/libs/gui/aidl/android/gui/StaticDisplayInfo.aidl @@ -0,0 +1,30 @@ +/* + * 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. + */ + +package android.gui; + +import android.gui.DisplayConnectionType; +import android.gui.DeviceProductInfo; +import android.gui.Rotation; + +/** @hide */ +parcelable StaticDisplayInfo { + DisplayConnectionType connectionType = DisplayConnectionType.Internal; + float density; + boolean secure; + @nullable DeviceProductInfo deviceProductInfo; + Rotation installOrientation = Rotation.Rotation0; +} diff --git a/libs/gui/aidl/android/gui/TrustedPresentationThresholds.aidl b/libs/gui/aidl/android/gui/TrustedPresentationThresholds.aidl new file mode 100644 index 0000000000000000000000000000000000000000..1eea5b44a445e3a275c2f1d701e1f5c43c8bc529 --- /dev/null +++ b/libs/gui/aidl/android/gui/TrustedPresentationThresholds.aidl @@ -0,0 +1,24 @@ +/* + * 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. + */ + +package android.gui; + +parcelable TrustedPresentationThresholds { + float minAlpha = -1.0f; + float minFractionRendered = -1.0f; + + int stabilityRequirementMs = 0; +} diff --git a/libs/gui/aidl/android/gui/WindowInfosListenerInfo.aidl b/libs/gui/aidl/android/gui/WindowInfosListenerInfo.aidl new file mode 100644 index 0000000000000000000000000000000000000000..0ca13b768a76a285cd9c9a8b4501be951aa9d5e6 --- /dev/null +++ b/libs/gui/aidl/android/gui/WindowInfosListenerInfo.aidl @@ -0,0 +1,25 @@ +/** + * Copyright (c) 2023, The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.gui; + +import android.gui.IWindowInfosPublisher; + +/** @hide */ +parcelable WindowInfosListenerInfo { + long listenerId; + IWindowInfosPublisher windowInfosPublisher; +} \ No newline at end of file diff --git a/libs/gui/android/gui/FocusRequest.aidl b/libs/gui/android/gui/FocusRequest.aidl index b13c60049c6b588b96566fe3e576c54b161c5b1b..62d1b68147435e080c786f0a2f50e8ab407e1ad1 100644 --- a/libs/gui/android/gui/FocusRequest.aidl +++ b/libs/gui/android/gui/FocusRequest.aidl @@ -23,15 +23,6 @@ parcelable FocusRequest { */ @nullable IBinder token; @utf8InCpp String windowName; - /** - * The token that the caller expects currently to be focused. If the - * specified token does not match the currently focused window, this request will be dropped. - * If the specified focused token matches the currently focused window, the call will succeed. - * Set this to "null" if this call should succeed no matter what the currently focused token - * is. - */ - @nullable IBinder focusedToken; - @utf8InCpp String focusedWindowName; /** * SYSTEM_TIME_MONOTONIC timestamp in nanos set by the client (wm) when requesting the focus * change. This determines which request gets precedence if there is a focus change request diff --git a/libs/gui/android/gui/IWindowInfosListener.aidl b/libs/gui/android/gui/IWindowInfosListener.aidl index a5b2762318384a22934672f8b170024aa153cc8a..07cb5ed0e66827e9d3b90fa159c7462b76e677e1 100644 --- a/libs/gui/android/gui/IWindowInfosListener.aidl +++ b/libs/gui/android/gui/IWindowInfosListener.aidl @@ -16,12 +16,9 @@ package android.gui; -import android.gui.DisplayInfo; -import android.gui.IWindowInfosReportedListener; -import android.gui.WindowInfo; +import android.gui.WindowInfosUpdate; /** @hide */ -oneway interface IWindowInfosListener -{ - void onWindowInfosChanged(in WindowInfo[] windowInfos, in DisplayInfo[] displayInfos, in @nullable IWindowInfosReportedListener windowInfosReportedListener); +oneway interface IWindowInfosListener { + void onWindowInfosChanged(in WindowInfosUpdate update); } diff --git a/services/inputflinger/tests/IInputFlingerQuery.aidl b/libs/gui/android/gui/IWindowInfosPublisher.aidl similarity index 66% rename from services/inputflinger/tests/IInputFlingerQuery.aidl rename to libs/gui/android/gui/IWindowInfosPublisher.aidl index 5aeb21f6b481c1c0a0527ca43fec9736b868f430..5a9c32845ef680ad2a45795a96a4ebff70bfc0f4 100644 --- a/services/inputflinger/tests/IInputFlingerQuery.aidl +++ b/libs/gui/android/gui/IWindowInfosPublisher.aidl @@ -1,5 +1,5 @@ /** - * Copyright (c) 2020, The Android Open Source Project + * 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. @@ -14,14 +14,10 @@ * limitations under the License. */ -import android.InputChannel; -import android.gui.FocusRequest; -import android.gui.WindowInfo; +package android.gui; /** @hide */ -interface IInputFlingerQuery +oneway interface IWindowInfosPublisher { - /* Test interfaces */ - void getInputChannels(out InputChannel[] channels); - void resetInputManager(); + void ackWindowInfosReceived(long vsyncId, long listenerId); } diff --git a/libs/gui/android/gui/WindowInfosUpdate.aidl b/libs/gui/android/gui/WindowInfosUpdate.aidl new file mode 100644 index 0000000000000000000000000000000000000000..0c6109da8f00a7e588c56b5caa8bae85729c0233 --- /dev/null +++ b/libs/gui/android/gui/WindowInfosUpdate.aidl @@ -0,0 +1,22 @@ +/* +** 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. +*/ + +package android.gui; + +import android.gui.DisplayInfo; +import android.gui.WindowInfo; + +parcelable WindowInfosUpdate cpp_header "gui/WindowInfosUpdate.h"; diff --git a/libs/gui/fuzzer/Android.bp b/libs/gui/fuzzer/Android.bp new file mode 100644 index 0000000000000000000000000000000000000000..82e1b5ae4d4d2bba4dd9cc005098cc517a5c67d6 --- /dev/null +++ b/libs/gui/fuzzer/Android.bp @@ -0,0 +1,136 @@ +/* + * 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. + */ +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_defaults { + name: "libgui_fuzzer_defaults", + static_libs: [ + "android.hidl.token@1.0-utils", + "libbinder_random_parcel", + "libgui_aidl_static", + "libgui_window_info_static", + "libpdx", + "libgmock", + "libgui_mocks", + "libgmock_ndk", + "libgmock_main", + "libgtest_ndk_c++", + "libgmock_main_ndk", + "librenderengine_mocks", + "perfetto_trace_protos", + "libcompositionengine_mocks", + "perfetto_trace_protos", + ], + shared_libs: [ + "android.hardware.configstore@1.0", + "android.hardware.configstore-utils", + "android.hardware.graphics.bufferqueue@1.0", + "android.hardware.graphics.bufferqueue@2.0", + "android.hardware.power-V4-cpp", + "android.hidl.token@1.0", + "libSurfaceFlingerProp", + "libgui", + "libbase", + "liblog", + "libEGL", + "libGLESv2", + "libbinder", + "libcutils", + "libhidlbase", + "libinput", + "libui", + "libutils", + "libnativewindow", + "libvndksupport", + ], + header_libs: [ + "libdvr_headers", + "libui_fuzzableDataspaces_headers", + ], + fuzz_config: { + cc: [ + "android-media-fuzzing-reports@google.com", + ], + componentid: 155276, + }, +} + +cc_fuzz { + name: "libgui_surfaceComposer_fuzzer", + srcs: [ + "libgui_surfaceComposer_fuzzer.cpp", + ], + defaults: [ + "libgui_fuzzer_defaults", + ], +} + +cc_fuzz { + name: "libgui_surfaceComposerClient_fuzzer", + srcs: [ + "libgui_surfaceComposerClient_fuzzer.cpp", + ], + defaults: [ + "libgui_fuzzer_defaults", + ], +} + +cc_fuzz { + name: "libgui_parcelable_fuzzer", + srcs: [ + "libgui_parcelable_fuzzer.cpp", + ], + defaults: [ + "libgui_fuzzer_defaults", + ], +} + +cc_fuzz { + name: "libgui_bufferQueue_fuzzer", + srcs: [ + "libgui_bufferQueue_fuzzer.cpp", + ], + defaults: [ + "libgui_fuzzer_defaults", + ], +} + +cc_fuzz { + name: "libgui_consumer_fuzzer", + srcs: [ + "libgui_consumer_fuzzer.cpp", + ], + defaults: [ + "libgui_fuzzer_defaults", + ], +} + +cc_fuzz { + name: "libgui_displayEvent_fuzzer", + srcs: [ + "libgui_displayEvent_fuzzer.cpp", + ], + defaults: [ + "libgui_fuzzer_defaults", + ], +} diff --git a/libs/gui/fuzzer/README.md b/libs/gui/fuzzer/README.md new file mode 100644 index 0000000000000000000000000000000000000000..96e27c989f15b2742ab26321981abc6ffb39c26e --- /dev/null +++ b/libs/gui/fuzzer/README.md @@ -0,0 +1,219 @@ +# Fuzzers for Libgui + +## Table of contents ++ [libgui_surfaceComposer_fuzzer](#SurfaceComposer) ++ [libgui_surfaceComposerClient_fuzzer](#SurfaceComposerClient) ++ [libgui_parcelable_fuzzer](#Libgui_Parcelable) ++ [libgui_bufferQueue_fuzzer](#BufferQueue) ++ [libgui_consumer_fuzzer](#Libgui_Consumer) ++ [libgui_displayEvent_fuzzer](#LibGui_DisplayEvent) + +# Fuzzer for SurfaceComposer + +SurfaceComposer supports the following parameters: +1. SurfaceWidth (parameter name:`width`) +2. SurfaceHeight (parameter name:`height`) +3. TransactionStateFlags (parameter name:`flags`) +4. TransformHint (parameter name:`outTransformHint`) +5. SurfacePixelFormat (parameter name:`format`) +6. LayerId (parameter name:`outLayerId`) +7. SurfaceComposerTags (parameter name:`surfaceTag`) +8. PowerBoostID (parameter name:`boostId`) +9. VsyncSource (parameter name:`vsyncSource`) +10. EventRegistrationFlags (parameter name:`eventRegistration`) +11. FrameRateCompatibility (parameter name:`frameRateCompatibility`) +12. ChangeFrameRateStrategy (parameter name:`changeFrameRateStrategy`) +13. HdrTypes (parameter name:`hdrTypes`) + +| Parameter| Valid Values| Configured Value| +|------------- |-------------| ----- | +|`surfaceTag` | 0.`BnSurfaceComposer::BOOT_FINISHED`, 1.`BnSurfaceComposer::CREATE_CONNECTION`, 2.`BnSurfaceComposer::GET_STATIC_DISPLAY_INFO`, 3.`BnSurfaceComposer::CREATE_DISPLAY_EVENT_CONNECTION`, 4.`BnSurfaceComposer::CREATE_DISPLAY`, 5.`BnSurfaceComposer::DESTROY_DISPLAY`, 6.`BnSurfaceComposer::GET_PHYSICAL_DISPLAY_TOKEN`, 7.`BnSurfaceComposer::SET_TRANSACTION_STATE`, 8.`BnSurfaceComposer::AUTHENTICATE_SURFACE`, 9.`BnSurfaceComposer::GET_SUPPORTED_FRAME_TIMESTAMPS`, 10.`BnSurfaceComposer::GET_DISPLAY_STATE`, 11.`BnSurfaceComposer::CAPTURE_DISPLAY`, 12.`BnSurfaceComposer::CAPTURE_LAYERS`, 13.`BnSurfaceComposer::CLEAR_ANIMATION_FRAME_STATS`, 14.`BnSurfaceComposer::GET_ANIMATION_FRAME_STATS`, 15.`BnSurfaceComposer::SET_POWER_MODE`, 16.`BnSurfaceComposer::GET_DISPLAY_STATS`, 17.`BnSurfaceComposer::SET_ACTIVE_COLOR_MODE`, 18.`BnSurfaceComposer::ENABLE_VSYNC_INJECTIONS`, 19.`BnSurfaceComposer::INJECT_VSYNC`, 20.`BnSurfaceComposer::GET_LAYER_DEBUG_INFO`, 21.`BnSurfaceComposer::GET_COMPOSITION_PREFERENCE`, 22.`BnSurfaceComposer::GET_COLOR_MANAGEMENT`, 23.`BnSurfaceComposer::GET_DISPLAYED_CONTENT_SAMPLING_ATTRIBUTES`, 24.`BnSurfaceComposer::SET_DISPLAY_CONTENT_SAMPLING_ENABLED`, 25.`BnSurfaceComposer::GET_DISPLAYED_CONTENT_SAMPLE`, 26.`BnSurfaceComposer::GET_PROTECTED_CONTENT_SUPPORT`, 27.`BnSurfaceComposer::IS_WIDE_COLOR_DISPLAY`, 28.`BnSurfaceComposer::GET_DISPLAY_NATIVE_PRIMARIES`, 29.`BnSurfaceComposer::GET_PHYSICAL_DISPLAY_IDS`, 30.`BnSurfaceComposer::ADD_REGION_SAMPLING_LISTENER`, 31.`BnSurfaceComposer::REMOVE_REGION_SAMPLING_LISTENER`, 32.`BnSurfaceComposer::SET_DESIRED_DISPLAY_MODE_SPECS`, 33.`BnSurfaceComposer::GET_DESIRED_DISPLAY_MODE_SPECS`, 34.`BnSurfaceComposer::GET_DISPLAY_BRIGHTNESS_SUPPORT`, 35.`BnSurfaceComposer::SET_DISPLAY_BRIGHTNESS`, 36.`BnSurfaceComposer::CAPTURE_DISPLAY_BY_ID`, 37.`BnSurfaceComposer::NOTIFY_POWER_BOOST`, 38.`BnSurfaceComposer::SET_GLOBAL_SHADOW_SETTINGS`, 39.`BnSurfaceComposer::SET_AUTO_LOW_LATENCY_MODE`, 40.`BnSurfaceComposer::SET_GAME_CONTENT_TYPE`, 41.`BnSurfaceComposer::SET_FRAME_RATE`, 42.`BnSurfaceComposer::ACQUIRE_FRAME_RATE_FLEXIBILITY_TOKEN`, 43.`BnSurfaceComposer::SET_FRAME_TIMELINE_INFO`, 44.`BnSurfaceComposer::ADD_TRANSACTION_TRACE_LISTENER`, 45.`BnSurfaceComposer::GET_GPU_CONTEXT_PRIORITY`, 46.`BnSurfaceComposer::GET_MAX_ACQUIRED_BUFFER_COUNT`, 47.`BnSurfaceComposer::GET_DYNAMIC_DISPLAY_INFO`, 48.`BnSurfaceComposer::ADD_FPS_LISTENER`, 49.`BnSurfaceComposer::REMOVE_FPS_LISTENER`, 50.`BnSurfaceComposer::OVERRIDE_HDR_TYPES`, 51.`BnSurfaceComposer::ADD_HDR_LAYER_INFO_LISTENER`, 52.`BnSurfaceComposer::REMOVE_HDR_LAYER_INFO_LISTENER`, 53.`BnSurfaceComposer::ON_PULL_ATOM`, 54.`BnSurfaceComposer::ADD_TUNNEL_MODE_ENABLED_LISTENER`, 55.`BnSurfaceComposer::REMOVE_TUNNEL_MODE_ENABLED_LISTENER` | Value obtained from FuzzedDataProvider| +|`boostId`| 0.`hardware::power::Boost::INTERACTION`, 1.`hardware::power::Boost::DISPLAY_UPDATE_IMMINENT`, 2.`hardware::power::Boost::ML_ACC`, 3.`hardware::power::Boost::AUDIO_LAUNCH`, 4.`hardware::power::Boost::CAMERA_LAUNCH`, 5.`hardware::power::Boost::CAMERA_SHOT` |Value obtained from FuzzedDataProvider| +|`vsyncSource`| 0.`ISurfaceComposer::eVsyncSourceApp`, 1.`ISurfaceComposer::eVsyncSourceSurfaceFlinger`, |Value obtained from FuzzedDataProvider| +|`eventRegistration`| 0.`ISurfaceComposer::EventRegistration::modeChanged`, 1.`ISurfaceComposer::EventRegistration::frameRateOverride` |Value obtained from FuzzedDataProvider| +|`frameRateCompatibility`| 0.`ANATIVEWINDOW_FRAME_RATE_COMPATIBILITY_DEFAULT`, 1.`ANATIVEWINDOW_FRAME_RATE_COMPATIBILITY_FIXED_SOURCE` |Value obtained from FuzzedDataProvider| +|`changeFrameRateStrategy`| 0.`ANATIVEWINDOW_CHANGE_FRAME_RATE_ONLY_IF_SEAMLESS`, 1.`ANATIVEWINDOW_CHANGE_FRAME_RATE_ALWAYS` |Value obtained from FuzzedDataProvider| +|`hdrTypes`| 0.`ui::Hdr::DOLBY_VISION`, 1.`ui::Hdr::HDR10`, 2.`ui::Hdr::HLG`, 3.`ui::Hdr::HDR10_PLUS` |Value obtained from FuzzedDataProvider| + +#### Steps to run +1. Build the fuzzer +``` + $ mm -j$(nproc) libgui_surfaceComposer_fuzzer +``` +2. Run on device +``` + $ adb sync data + $ adb shell /data/fuzz/arm64/libgui_surfaceComposer_fuzzer/libgui_surfaceComposer_fuzzer +``` + +# Fuzzer for SurfaceComposerClient + +SurfaceComposerClient supports the following data sources: +1. SurfaceWidth (parameter name:`width`) +2. SurfaceHeight (parameter name:`height`) +3. TransactionStateFlags (parameter name:`flags`) +4. TransformHint (parameter name:`outTransformHint`) +5. SurfacePixelFormat (parameter name:`format`) +6. LayerId (parameter name:`outLayerId`) +7. SurfaceComposerClientTags (parameter name:`surfaceTag`) +8. DefaultMode (parameter name:`defaultMode`) +9. PrimaryRefreshRateMin (parameter name:`primaryRefreshRateMin`) +10. PrimaryRefreshRateMax (parameter name:`primaryRefreshRateMax`) +11. AppRefreshRateMin (parameter name:`appRefreshRateMin`) +12. AppRefreshRateMax (parameter name:`appRefreshRateMax`) +13. DisplayPowerMode (parameter name:`mode`) +14. CacheId (parameter name:`cacheId`) +15. DisplayBrightness (parameter name:`brightness`) +16. PowerBoostID (parameter name:`boostId`) +17. AtomId (parameter name:`atomId`) +18. ComponentMask (parameter name:`componentMask`) +19. MaxFrames (parameter name:`maxFrames`) +20. TaskId (parameter name:`taskId`) +21. Alpha (parameter name:`aplha`) +22. CornerRadius (parameter name:`cornerRadius`) +23. BackgroundBlurRadius (parameter name:`backgroundBlurRadius`) +24. Half3Color (parameter name:`color`) +25. LayerStack (parameter name:`layerStack`) +26. Dataspace (parameter name:`dataspace`) +27. Api (parameter name:`api`) +28. Priority (parameter name:`priority`) +29. TouchableRegionPointX (parameter name:`pointX`) +30. TouchableRegionPointY (parameter name:`pointY`) +31. ColorMode (parameter name:`colorMode`) +32. WindowInfoFlags (parameter name:`flags`) +33. WindowInfoTransformOrientation (parameter name:`transform`) + +| Parameter| Valid Values| Configured Value| +|------------- |-------------| ----- | +|`surfaceTag`| 0.`Tag::CREATE_SURFACE`, 1.`Tag::CREATE_WITH_SURFACE_PARENT`, 2.`Tag::CLEAR_LAYER_FRAME_STATS`, 3.`Tag::GET_LAYER_FRAME_STATS`, 4.`Tag::MIRROR_SURFACE`, 5.`Tag::LAST` |Value obtained from FuzzedDataProvider| +|`mode`| 0.`gui::TouchOcclusionMode::BLOCK_UNTRUSTED`, 1.`gui::TouchOcclusionMode::USE_OPACITY`, 2.`gui::TouchOcclusionMode::ALLOW` |Value obtained from FuzzedDataProvider| +|`boostId`| 0.`hardware::power::Boost::INTERACTION`, 1.`hardware::power::Boost::DISPLAY_UPDATE_IMMINENT`, 2.`hardware::power::Boost::ML_ACC`, 3.`hardware::power::Boost::AUDIO_LAUNCH`, 4.`hardware::power::Boost::CAMERA_LAUNCH`, 5.`hardware::power::Boost::CAMERA_SHOT` |Value obtained from FuzzedDataProvider| +|`colorMode`|0.`ui::ColorMode::NATIVE`, 1.`ui::ColorMode::STANDARD_BT601_625`, 2.`ui::ColorMode::STANDARD_BT601_625_UNADJUSTED`, 3.`ui::ColorMode::STANDARD_BT601_525`, 4.`ui::ColorMode::STANDARD_BT601_525_UNADJUSTED`, 5.`ui::ColorMode::STANDARD_BT709`, 6.`ui::ColorMode::DCI_P3`, 7.`ui::ColorMode::SRGB`, 8.`ui::ColorMode::ADOBE_RGB`, 9.`ui::ColorMode::DISPLAY_P3`, 10.`ui::ColorMode::BT2020`, 11.`ui::ColorMode::BT2100_PQ`, 12.`ui::ColorMode::BT2100_HLG`, 13.`ui::ColorMode::DISPLAY_BT2020` |Value obtained from FuzzedDataProvider| +|`flags`|0 .`gui::WindowInfo::Flag::ALLOW_LOCK_WHILE_SCREEN_ON`, 1.`gui::WindowInfo::Flag::DIM_BEHIND`, 2.`gui::WindowInfo::Flag::BLUR_BEHIND`, 3.`gui::WindowInfo::Flag::NOT_FOCUSABLE`, 4.`gui::WindowInfo::Flag::NOT_TOUCHABLE`, 5.`gui::WindowInfo::Flag::NOT_TOUCH_MODAL`, 6.`gui::WindowInfo::Flag::TOUCHABLE_WHEN_WAKING`, 7.`gui::WindowInfo::Flag::KEEP_SCREEN_ON`, 8.`gui::WindowInfo::Flag::LAYOUT_IN_SCREEN`, 9.`gui::WindowInfo::Flag::LAYOUT_NO_LIMITS`, 10.`gui::WindowInfo::Flag::FULLSCREEN`, 11.`gui::WindowInfo::Flag::FORCE_NOT_FULLSCREEN`, 12.`gui::WindowInfo::Flag::DITHER`, 13.`gui::WindowInfo::Flag::SECURE`, 14.`gui::WindowInfo::Flag::SCALED`, 15.`gui::WindowInfo::Flag::IGNORE_CHEEK_PRESSES`, 16.`gui::WindowInfo::Flag::LAYOUT_INSET_DECOR`, 17.`gui::WindowInfo::Flag::ALT_FOCUSABLE_IM`, 18.`gui::WindowInfo::Flag::WATCH_OUTSIDE_TOUCH`, 19.`gui::WindowInfo::Flag::SHOW_WHEN_LOCKED`, 20.`gui::WindowInfo::Flag::SHOW_WALLPAPER`, 21.`gui::WindowInfo::Flag::TURN_SCREEN_ON`, 22.`gui::WindowInfo::Flag::DISMISS_KEYGUARD`, 23.`gui::WindowInfo::Flag::SPLIT_TOUCH`, 24.`gui::WindowInfo::Flag::HARDWARE_ACCELERATED`, 25.`gui::WindowInfo::Flag::LAYOUT_IN_OVERSCAN`, 26.`gui::WindowInfo::Flag::TRANSLUCENT_STATUS`, 27.`gui::WindowInfo::Flag::TRANSLUCENT_NAVIGATION`, 28.`gui::WindowInfo::Flag::LOCAL_FOCUS_MODE`, 29.`gui::WindowInfo::Flag::SLIPPERY`, 30.`gui::WindowInfo::Flag::LAYOUT_ATTACHED_IN_DECOR`, 31.`gui::WindowInfo::Flag::DRAWS_SYSTEM_BAR_BACKGROUNDS`, |Value obtained from FuzzedDataProvider| +|`dataspace`| 0.`ui::Dataspace::UNKNOWN`, 1.`ui::Dataspace::ARBITRARY`, 2.`ui::Dataspace::STANDARD_SHIFT`, 3.`ui::Dataspace::STANDARD_MASK`, 4.`ui::Dataspace::STANDARD_UNSPECIFIED`, 5.`ui::Dataspace::STANDARD_BT709`, 6.`ui::Dataspace::STANDARD_BT601_625`, 7.`ui::Dataspace::STANDARD_BT601_625_UNADJUSTED`, 8.`ui::Dataspace::STANDARD_BT601_525`, 9.`ui::Dataspace::STANDARD_BT601_525_UNADJUSTED`, 10.`ui::Dataspace::STANDARD_BT2020`, 11.`ui::Dataspace::STANDARD_BT2020_CONSTANT_LUMINANCE`, 12.`ui::Dataspace::STANDARD_BT470M`, 13.`ui::Dataspace::STANDARD_FILM`, 14.`ui::Dataspace::STANDARD_DCI_P3`, 15.`ui::Dataspace::STANDARD_ADOBE_RGB`, 16.`ui::Dataspace::TRANSFER_SHIFT`, 17.`ui::Dataspace::TRANSFER_MASK`, 18.`ui::Dataspace::TRANSFER_UNSPECIFIED`, 19.`ui::Dataspace::TRANSFER_LINEAR`, 20.`ui::Dataspace::TRANSFER_SRGB`, 21.`ui::Dataspace::TRANSFER_SMPTE_170M`, 22.`ui::Dataspace::TRANSFER_GAMMA2_2`, 23.`ui::Dataspace::TRANSFER_GAMMA2_6`, 24.`ui::Dataspace::TRANSFER_GAMMA2_8`, 25.`ui::Dataspace::TRANSFER_ST2084`, 26.`ui::Dataspace::TRANSFER_HLG`, 27.`ui::Dataspace::RANGE_SHIFT`, 28.`ui::Dataspace::RANGE_MASK`, 29.`ui::Dataspace::RANGE_UNSPECIFIED`, 30.`ui::Dataspace::RANGE_FULL`, 31.`ui::Dataspace::RANGE_LIMITED`, 32.`ui::Dataspace::RANGE_EXTENDED`, 33.`ui::Dataspace::SRGB_LINEAR`, 34.`ui::Dataspace::V0_SRGB_LINEAR`, 35.`ui::Dataspace::V0_SCRGB_LINEAR`, 36.`ui::Dataspace::SRGB`, 37.`ui::Dataspace::V0_SRGB`, 38.`ui::Dataspace::V0_SCRGB`, 39.`ui::Dataspace::JFIF`, 40.`ui::Dataspace::V0_JFIF`, 41.`ui::Dataspace::BT601_625`, 42.`ui::Dataspace::V0_BT601_625`, 43.`ui::Dataspace::BT601_525`, 44.`ui::Dataspace::V0_BT601_525`, 45.`ui::Dataspace::BT709`, 46.`ui::Dataspace::V0_BT709`, 47.`ui::Dataspace::DCI_P3_LINEAR`, 48.`ui::Dataspace::DCI_P3`, 49.`ui::Dataspace::DISPLAY_P3_LINEAR`, 50.`ui::Dataspace::DISPLAY_P3`, 51.`ui::Dataspace::ADOBE_RGB`, 52.`ui::Dataspace::BT2020_LINEAR`, 53.`ui::Dataspace::BT2020`, 54.`ui::Dataspace::BT2020_PQ`, 55.`ui::Dataspace::DEPTH`, 56.`ui::Dataspace::SENSOR`, 57.`ui::Dataspace::BT2020_ITU`, 58.`ui::Dataspace::BT2020_ITU_PQ`, 59.`ui::Dataspace::BT2020_ITU_HLG`, 60.`ui::Dataspace::BT2020_HLG`, 61.`ui::Dataspace::DISPLAY_BT2020`, 62.`ui::Dataspace::DYNAMIC_DEPTH`, 63.`ui::Dataspace::JPEG_APP_SEGMENTS`, 64.`ui::Dataspace::HEIF`, |Value obtained from FuzzedDataProvider| +|`transform`| 0.`ui::Transform::ROT_0`, 1.`ui::Transform::FLIP_H`, 2.`ui::Transform::FLIP_V`, 3.`ui::Transform::ROT_90`, 4.`ui::Transform::ROT_180`, 5.`ui::Transform::ROT_270` |Value obtained from FuzzedDataProvider| + +#### Steps to run +1. Build the fuzzer +``` + $ mm -j$(nproc) libgui_surfaceComposerClient_fuzzer +``` +2. To run on device +``` + $ adb sync data + $ adb shell /data/fuzz/arm64/libgui_surfaceComposerClient_fuzzer/libgui_surfaceComposerClient_fuzzer +``` + +# Fuzzer for Libgui_Parcelable + +Libgui_Parcelable supports the following parameters: +1. LayerMetadataKey (parameter name:`key`) +2. Dataspace (parameter name:`mDataspace`) + +| Parameter| Valid Values| Configured Value| +|------------- |-------------| ----- | +|`key`| 0.`view::LayerMetadataKey::METADATA_OWNER_UID`, 1.`view::LayerMetadataKey::METADATA_WINDOW_TYPE`, 2.`view::LayerMetadataKey::METADATA_TASK_ID`, 3.`view::LayerMetadataKey::METADATA_MOUSE_CURSOR`, 4.`view::LayerMetadataKey::METADATA_ACCESSIBILITY_ID`, 5.`view::LayerMetadataKey::METADATA_OWNER_PID`, 6.`view::LayerMetadataKey::METADATA_DEQUEUE_TIME`, 7.`view::LayerMetadataKey::METADATA_GAME_MODE`, |Value obtained from FuzzedDataProvider| +|`mDataSpace`| 0.`ui::Dataspace::UNKNOWN`, 1.`ui::Dataspace::ARBITRARY`, 2.`ui::Dataspace::STANDARD_SHIFT`, 3.`ui::Dataspace::STANDARD_MASK`, 4.`ui::Dataspace::STANDARD_UNSPECIFIED`, 5.`ui::Dataspace::STANDARD_BT709`, 6.`ui::Dataspace::STANDARD_BT601_625`, 7.`ui::Dataspace::STANDARD_BT601_625_UNADJUSTED`, 8.`ui::Dataspace::STANDARD_BT601_525`, 9.`ui::Dataspace::STANDARD_BT601_525_UNADJUSTED`, 10.`ui::Dataspace::STANDARD_BT2020`, 11.`ui::Dataspace::STANDARD_BT2020_CONSTANT_LUMINANCE`, 12.`ui::Dataspace::STANDARD_BT470M`, 13.`ui::Dataspace::STANDARD_FILM`, 14.`ui::Dataspace::STANDARD_DCI_P3`, 15.`ui::Dataspace::STANDARD_ADOBE_RGB`, 16.`ui::Dataspace::TRANSFER_SHIFT`, 17.`ui::Dataspace::TRANSFER_MASK`, 18.`ui::Dataspace::TRANSFER_UNSPECIFIED`, 19.`ui::Dataspace::TRANSFER_LINEAR`, 20.`ui::Dataspace::TRANSFER_SRGB`, 21.`ui::Dataspace::TRANSFER_SMPTE_170M`, 22.`ui::Dataspace::TRANSFER_GAMMA2_2`, 23.`ui::Dataspace::TRANSFER_GAMMA2_6`, 24.`ui::Dataspace::TRANSFER_GAMMA2_8`, 25.`ui::Dataspace::TRANSFER_ST2084`, 26.`ui::Dataspace::TRANSFER_HLG`, 27.`ui::Dataspace::RANGE_SHIFT`, 28.`ui::Dataspace::RANGE_MASK`, 29.`ui::Dataspace::RANGE_UNSPECIFIED`, 30.`ui::Dataspace::RANGE_FULL`, 31.`ui::Dataspace::RANGE_LIMITED`, 32.`ui::Dataspace::RANGE_EXTENDED`, 33.`ui::Dataspace::SRGB_LINEAR`, 34.`ui::Dataspace::V0_SRGB_LINEAR`, 35.`ui::Dataspace::V0_SCRGB_LINEAR`, 36.`ui::Dataspace::SRGB`, 37.`ui::Dataspace::V0_SRGB`, 38.`ui::Dataspace::V0_SCRGB`, 39.`ui::Dataspace::JFIF`, 40.`ui::Dataspace::V0_JFIF`, 41.`ui::Dataspace::BT601_625`, 42.`ui::Dataspace::V0_BT601_625`, 43.`ui::Dataspace::BT601_525`, 44.`ui::Dataspace::V0_BT601_525`, 45.`ui::Dataspace::BT709`, 46.`ui::Dataspace::V0_BT709`, 47.`ui::Dataspace::DCI_P3_LINEAR`, 48.`ui::Dataspace::DCI_P3`, 49.`ui::Dataspace::DISPLAY_P3_LINEAR`, 50.`ui::Dataspace::DISPLAY_P3`, 51.`ui::Dataspace::ADOBE_RGB`, 52.`ui::Dataspace::BT2020_LINEAR`, 53.`ui::Dataspace::BT2020`, 54.`ui::Dataspace::BT2020_PQ`, 55.`ui::Dataspace::DEPTH`, 56.`ui::Dataspace::SENSOR`, 57.`ui::Dataspace::BT2020_ITU`, 58.`ui::Dataspace::BT2020_ITU_PQ`, 59.`ui::Dataspace::BT2020_ITU_HLG`, 60.`ui::Dataspace::BT2020_HLG`, 61.`ui::Dataspace::DISPLAY_BT2020`, 62.`ui::Dataspace::DYNAMIC_DEPTH`, 63.`ui::Dataspace::JPEG_APP_SEGMENTS`, 64.`ui::Dataspace::HEIF`, |Value obtained from FuzzedDataProvider| + +#### Steps to run +1. Build the fuzzer +``` + $ mm -j$(nproc) libgui_fuzzer +``` +2. Run on device +``` + $ adb sync data + $ adb shell /data/fuzz/arm64/libgui_fuzzer/libgui_fuzzer +``` + +# Fuzzer for BufferQueue + +BufferQueue supports the following parameters: +1. SurfaceWidth (parameter name:`width`) +2. SurfaceHeight (parameter name:`height`) +3. TransactionStateFlags (parameter name:`flags`) +4. TransformHint (parameter name:`outTransformHint`) +5. SurfacePixelFormat (parameter name:`format`) +6. LayerId (parameter name:`layerId`) +7. BufferId (parameter name:`bufferId`) +8. FrameNumber (parameter name:`frameNumber`) +9. FrameRate (parameter name:`frameRate`) +10. Compatability (parameter name:`compatability`) +11. LatchTime (parameter name:`latchTime`) +12. AcquireTime (parameter name:`acquireTime`) +13. RefreshTime (parameter name:`refreshTime`) +14. DequeueTime (parameter name:`dequeueTime`) +15. Slot (parameter name:`slot`) +16. MaxBuffers (parameter name:`maxBuffers`) +17. GenerationNumber (parameter name:`generationNumber`) +18. Api (parameter name:`api`) +19. Usage (parameter name:`usage`) +20. MaxFrameNumber (parameter name:`maxFrameNumber`) +21. BufferCount (parameter name:`bufferCount`) +22. MaxAcquredBufferCount (parameter name:`maxAcquredBufferCount`) +23. Status (parameter name:`status`) +24. ApiConnection (parameter name:`apiConnection`) +25. Dataspace (parameter name:`dataspace`) + +| Parameter| Valid Values| Configured Value| +|------------- |-------------| ----- | +|`status`| 0.`OK`, 1.`NO_MEMORY`, 2.`NO_INIT`, 3.`BAD_VALUE`, 4.`DEAD_OBJECT`, 5.`INVALID_OPERATION`, 6.`TIMED_OUT`, 7.`WOULD_BLOCK`, 8.`UNKNOWN_ERROR`, 9.`ALREADY_EXISTS`, |Value obtained from FuzzedDataProvider| +|`apiConnection`| 0.`BufferQueueCore::CURRENTLY_CONNECTED_API`, 1.`BufferQueueCore::NO_CONNECTED_API`, 2.`NATIVE_WINDOW_API_EGL`, 3.`NATIVE_WINDOW_API_CPU`, 4.`NATIVE_WINDOW_API_MEDIA`, 5.`NATIVE_WINDOW_API_CAMERA`, |Value obtained from FuzzedDataProvider| +|`dataspace`| 0.`ui::Dataspace::UNKNOWN`, 1.`ui::Dataspace::ARBITRARY`, 2.`ui::Dataspace::STANDARD_SHIFT`, 3.`ui::Dataspace::STANDARD_MASK`, 4.`ui::Dataspace::STANDARD_UNSPECIFIED`, 5.`ui::Dataspace::STANDARD_BT709`, 6.`ui::Dataspace::STANDARD_BT601_625`, 7.`ui::Dataspace::STANDARD_BT601_625_UNADJUSTED`, 8.`ui::Dataspace::STANDARD_BT601_525`, 9.`ui::Dataspace::STANDARD_BT601_525_UNADJUSTED`, 10.`ui::Dataspace::STANDARD_BT2020`, 11.`ui::Dataspace::STANDARD_BT2020_CONSTANT_LUMINANCE`, 12.`ui::Dataspace::STANDARD_BT470M`, 13.`ui::Dataspace::STANDARD_FILM`, 14.`ui::Dataspace::STANDARD_DCI_P3`, 15.`ui::Dataspace::STANDARD_ADOBE_RGB`, 16.`ui::Dataspace::TRANSFER_SHIFT`, 17.`ui::Dataspace::TRANSFER_MASK`, 18.`ui::Dataspace::TRANSFER_UNSPECIFIED`, 19.`ui::Dataspace::TRANSFER_LINEAR`, 20.`ui::Dataspace::TRANSFER_SRGB`, 21.`ui::Dataspace::TRANSFER_SMPTE_170M`, 22.`ui::Dataspace::TRANSFER_GAMMA2_2`, 23.`ui::Dataspace::TRANSFER_GAMMA2_6`, 24.`ui::Dataspace::TRANSFER_GAMMA2_8`, 25.`ui::Dataspace::TRANSFER_ST2084`, 26.`ui::Dataspace::TRANSFER_HLG`, 27.`ui::Dataspace::RANGE_SHIFT`, 28.`ui::Dataspace::RANGE_MASK`, 29.`ui::Dataspace::RANGE_UNSPECIFIED`, 30.`ui::Dataspace::RANGE_FULL`, 31.`ui::Dataspace::RANGE_LIMITED`, 32.`ui::Dataspace::RANGE_EXTENDED`, 33.`ui::Dataspace::SRGB_LINEAR`, 34.`ui::Dataspace::V0_SRGB_LINEAR`, 35.`ui::Dataspace::V0_SCRGB_LINEAR`, 36.`ui::Dataspace::SRGB`, 37.`ui::Dataspace::V0_SRGB`, 38.`ui::Dataspace::V0_SCRGB`, 39.`ui::Dataspace::JFIF`, 40.`ui::Dataspace::V0_JFIF`, 41.`ui::Dataspace::BT601_625`, 42.`ui::Dataspace::V0_BT601_625`, 43.`ui::Dataspace::BT601_525`, 44.`ui::Dataspace::V0_BT601_525`, 45.`ui::Dataspace::BT709`, 46.`ui::Dataspace::V0_BT709`, 47.`ui::Dataspace::DCI_P3_LINEAR`, 48.`ui::Dataspace::DCI_P3`, 49.`ui::Dataspace::DISPLAY_P3_LINEAR`, 50.`ui::Dataspace::DISPLAY_P3`, 51.`ui::Dataspace::ADOBE_RGB`, 52.`ui::Dataspace::BT2020_LINEAR`, 53.`ui::Dataspace::BT2020`, 54.`ui::Dataspace::BT2020_PQ`, 55.`ui::Dataspace::DEPTH`, 56.`ui::Dataspace::SENSOR`, 57.`ui::Dataspace::BT2020_ITU`, 58.`ui::Dataspace::BT2020_ITU_PQ`, 59.`ui::Dataspace::BT2020_ITU_HLG`, 60.`ui::Dataspace::BT2020_HLG`, 61.`ui::Dataspace::DISPLAY_BT2020`, 62.`ui::Dataspace::DYNAMIC_DEPTH`, 63.`ui::Dataspace::JPEG_APP_SEGMENTS`, 64.`ui::Dataspace::HEIF`, |Value obtained from FuzzedDataProvider| + +#### Steps to run +1. Build the fuzzer +``` + $ mm -j$(nproc) libgui_bufferQueue_fuzzer +``` +2. To run on device +``` + $ adb sync data + $ adb shell /data/fuzz/arm64/libgui_bufferQueue_fuzzer/libgui_bufferQueue_fuzzer +``` + +# Fuzzer for Libgui_Consumer + +Libgui_Consumer supports the following parameters: +1. GraphicWidth (parameter name:`graphicWidth`) +2. GraphicHeight (parameter name:`graphicHeight`) +4. TransformHint (parameter name:`outTransformHint`) +5. GraphicPixelFormat (parameter name:`format`) +6. Usage (parameter name:`usage`) + +#### Steps to run +1. Build the fuzzer +``` + $ mm -j$(nproc) libgui_consumer_fuzzer +``` +2. Run on device +``` + $ adb sync data + $ adb shell /data/fuzz/arm64/libgui_consumer_fuzzer/libgui_consumer_fuzzer +``` + +# Fuzzer for LibGui_DisplayEvent + +LibGui_DisplayEvent supports the following parameters: +1. DisplayEventType (parameter name:`type`) +2. Events (parameter name:`events`) +3. VsyncSource (parameter name:`vsyncSource`) +4. EventRegistrationFlags (parameter name:`flags`) + +| Parameter| Valid Values| Configured Value| +|------------- |-------------| ----- | +|`vsyncSource`| 0.`ISurfaceComposer::eVsyncSourceApp`, 1.`ISurfaceComposer::eVsyncSourceSurfaceFlinger`, |Value obtained from FuzzedDataProvider| +|`flags`| 0.`ISurfaceComposer::EventRegistration::modeChanged`, 1.`ISurfaceComposer::EventRegistration::frameRateOverride`, |Value obtained from FuzzedDataProvider| +|`type`| 0.`DisplayEventReceiver::DISPLAY_EVENT_NULL`, 1.`DisplayEventReceiver::DISPLAY_EVENT_VSYNC`, 2.`DisplayEventReceiver::DISPLAY_EVENT_HOTPLUG`, 3.`DisplayEventReceiver::DISPLAY_EVENT_MODE_CHANGE`, 4.`DisplayEventReceiver::DISPLAY_EVENT_FRAME_RATE_OVERRIDE`, 5.`DisplayEventReceiver::DISPLAY_EVENT_FRAME_RATE_OVERRIDE_FLUSH`, |Value obtained from FuzzedDataProvider| +|`events`| 0.`Looper::EVENT_INPUT`, 1.`Looper::EVENT_OUTPUT`, 2.`Looper::EVENT_ERROR`, 3.`Looper::EVENT_HANGUP`, 4.`Looper::EVENT_INVALID`, |Value obtained from FuzzedDataProvider| + +#### Steps to run +1. Build the fuzzer +``` + $ mm -j$(nproc) libgui_displayEvent_fuzzer +``` +2. Run on device +``` + $ adb sync data + $ adb shell /data/fuzz/arm64/libgui_displayEvent_fuzzer/libgui_displayEvent_fuzzer +``` diff --git a/libs/gui/fuzzer/libgui_bufferQueue_fuzzer.cpp b/libs/gui/fuzzer/libgui_bufferQueue_fuzzer.cpp new file mode 100644 index 0000000000000000000000000000000000000000..17f4c630cee3fbf9761f24c01927d6f6ae23b5b5 --- /dev/null +++ b/libs/gui/fuzzer/libgui_bufferQueue_fuzzer.cpp @@ -0,0 +1,392 @@ +/* + * 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 + +using namespace android; +using namespace hardware::graphics::bufferqueue; +using namespace V1_0::utils; +using namespace V2_0::utils; + +constexpr int32_t kMaxBytes = 256; + +constexpr int32_t kError[] = { + OK, NO_MEMORY, NO_INIT, BAD_VALUE, DEAD_OBJECT, INVALID_OPERATION, + TIMED_OUT, WOULD_BLOCK, UNKNOWN_ERROR, ALREADY_EXISTS, +}; + +constexpr int32_t kAPIConnection[] = { + BufferQueueCore::CURRENTLY_CONNECTED_API, + BufferQueueCore::NO_CONNECTED_API, + NATIVE_WINDOW_API_EGL, + NATIVE_WINDOW_API_CPU, + NATIVE_WINDOW_API_MEDIA, + NATIVE_WINDOW_API_CAMERA, +}; + +class BufferQueueFuzzer { +public: + BufferQueueFuzzer(const uint8_t* data, size_t size) : mFdp(data, size){}; + void process(); + +private: + void invokeTypes(); + void invokeH2BGraphicBufferV1(); + void invokeH2BGraphicBufferV2(); + void invokeBufferQueueConsumer(); + void invokeBufferQueueProducer(); + void invokeBlastBufferQueue(); + void invokeQuery(sp); + void invokeQuery(sp); + void invokeQuery(sp); + void invokeAcquireBuffer(sp); + void invokeOccupancyTracker(sp); + sp makeSurfaceControl(); + sp makeBLASTBufferQueue(sp); + + FuzzedDataProvider mFdp; +}; + +class ManageResourceHandle { +public: + ManageResourceHandle(FuzzedDataProvider* fdp) { + mNativeHandle = native_handle_create(0 /*numFds*/, 1 /*numInts*/); + mShouldOwn = fdp->ConsumeBool(); + mStream = NativeHandle::create(mNativeHandle, mShouldOwn); + } + ~ManageResourceHandle() { + if (!mShouldOwn) { + native_handle_close(mNativeHandle); + native_handle_delete(mNativeHandle); + } + } + sp getStream() { return mStream; } + +private: + bool mShouldOwn; + sp mStream; + native_handle_t* mNativeHandle; +}; + +sp BufferQueueFuzzer::makeSurfaceControl() { + sp handle; + const sp testClient(new FakeBnSurfaceComposerClient()); + sp client = new SurfaceComposerClient(testClient); + sp producer; + uint32_t layerId = mFdp.ConsumeIntegral(); + std::string layerName = base::StringPrintf("#%d", layerId); + return sp::make(client, handle, layerId, layerName, + mFdp.ConsumeIntegral(), + mFdp.ConsumeIntegral(), + mFdp.ConsumeIntegral(), + mFdp.ConsumeIntegral(), + mFdp.ConsumeIntegral()); +} + +sp BufferQueueFuzzer::makeBLASTBufferQueue(sp surface) { + return sp::make(mFdp.ConsumeRandomLengthString(kMaxBytes), surface, + mFdp.ConsumeIntegral(), + mFdp.ConsumeIntegral(), + mFdp.ConsumeIntegral()); +} + +void BufferQueueFuzzer::invokeBlastBufferQueue() { + sp surface = makeSurfaceControl(); + sp queue = makeBLASTBufferQueue(surface); + + BufferItem item; + queue->onFrameAvailable(item); + queue->onFrameReplaced(item); + uint64_t bufferId = mFdp.ConsumeIntegral(); + queue->onFrameDequeued(bufferId); + queue->onFrameCancelled(bufferId); + + SurfaceComposerClient::Transaction next; + uint64_t frameNumber = mFdp.ConsumeIntegral(); + queue->mergeWithNextTransaction(&next, frameNumber); + queue->applyPendingTransactions(frameNumber); + + queue->update(surface, mFdp.ConsumeIntegral(), mFdp.ConsumeIntegral(), + mFdp.ConsumeIntegral()); + queue->setFrameRate(mFdp.ConsumeFloatingPoint(), mFdp.ConsumeIntegral(), + mFdp.ConsumeBool() /*shouldBeSeamless*/); + FrameTimelineInfo info; + queue->setFrameTimelineInfo(mFdp.ConsumeIntegral(), info); + + ManageResourceHandle handle(&mFdp); + queue->setSidebandStream(handle.getStream()); + + queue->getLastTransformHint(); + queue->getLastAcquiredFrameNum(); + + CompositorTiming compTiming; + sp previousFence = new Fence(memfd_create("pfd", MFD_ALLOW_SEALING)); + sp gpuFence = new Fence(memfd_create("gfd", MFD_ALLOW_SEALING)); + FrameEventHistoryStats frameStats(frameNumber, gpuFence, compTiming, + mFdp.ConsumeIntegral(), + mFdp.ConsumeIntegral()); + std::vector stats; + sp presentFence = new Fence(memfd_create("fd", MFD_ALLOW_SEALING)); + SurfaceControlStats controlStats(surface, mFdp.ConsumeIntegral(), + mFdp.ConsumeIntegral(), presentFence, previousFence, + mFdp.ConsumeIntegral(), frameStats, + mFdp.ConsumeIntegral()); + stats.push_back(controlStats); +} + +void BufferQueueFuzzer::invokeQuery(sp producer) { + int32_t value; + producer->query(mFdp.ConsumeIntegral(), &value); +} + +void BufferQueueFuzzer::invokeQuery(sp producer) { + int32_t value; + producer->query(mFdp.ConsumeIntegral(), &value); +} + +void BufferQueueFuzzer::invokeQuery(sp producer) { + int32_t value; + producer->query(mFdp.ConsumeIntegral(), &value); +} + +void BufferQueueFuzzer::invokeBufferQueueProducer() { + sp core(new BufferQueueCore()); + sp producer(new BufferQueueProducer(core)); + const sp listener; + android::IGraphicBufferProducer::QueueBufferOutput output; + uint32_t api = mFdp.ConsumeIntegral(); + producer->connect(listener, api, mFdp.ConsumeBool() /*producerControlledByApp*/, &output); + + sp buffer; + int32_t slot = mFdp.ConsumeIntegral(); + uint32_t maxBuffers = mFdp.ConsumeIntegral(); + producer->requestBuffer(slot, &buffer); + producer->setMaxDequeuedBufferCount(maxBuffers); + producer->setAsyncMode(mFdp.ConsumeBool() /*async*/); + + android::IGraphicBufferProducer::QueueBufferInput input; + producer->attachBuffer(&slot, buffer); + producer->queueBuffer(slot, input, &output); + + int32_t format = mFdp.ConsumeIntegral(); + uint32_t width = mFdp.ConsumeIntegral(); + uint32_t height = mFdp.ConsumeIntegral(); + uint64_t usage = mFdp.ConsumeIntegral(); + uint64_t outBufferAge; + FrameEventHistoryDelta outTimestamps; + sp fence; + producer->dequeueBuffer(&slot, &fence, width, height, format, usage, &outBufferAge, + &outTimestamps); + producer->detachBuffer(slot); + producer->detachNextBuffer(&buffer, &fence); + producer->cancelBuffer(slot, fence); + + invokeQuery(producer); + + ManageResourceHandle handle(&mFdp); + producer->setSidebandStream(handle.getStream()); + + producer->allocateBuffers(width, height, format, usage); + producer->allowAllocation(mFdp.ConsumeBool() /*allow*/); + producer->setSharedBufferMode(mFdp.ConsumeBool() /*sharedBufferMode*/); + producer->setAutoRefresh(mFdp.ConsumeBool() /*autoRefresh*/); + producer->setLegacyBufferDrop(mFdp.ConsumeBool() /*drop*/); + producer->setAutoPrerotation(mFdp.ConsumeBool() /*autoPrerotation*/); + + producer->setGenerationNumber(mFdp.ConsumeIntegral()); + producer->setDequeueTimeout(mFdp.ConsumeIntegral()); + producer->disconnect(api); +} + +void BufferQueueFuzzer::invokeAcquireBuffer(sp consumer) { + BufferItem item; + consumer->acquireBuffer(&item, mFdp.ConsumeIntegral(), + mFdp.ConsumeIntegral()); +} + +void BufferQueueFuzzer::invokeOccupancyTracker(sp consumer) { + String8 outResult; + String8 prefix((mFdp.ConsumeRandomLengthString(kMaxBytes)).c_str()); + consumer->dumpState(prefix, &outResult); + + std::vector outHistory; + consumer->getOccupancyHistory(mFdp.ConsumeBool() /*forceFlush*/, &outHistory); +} + +void BufferQueueFuzzer::invokeBufferQueueConsumer() { + sp core(new BufferQueueCore()); + sp consumer(new BufferQueueConsumer(core)); + sp listener; + consumer->consumerConnect(listener, mFdp.ConsumeBool() /*controlledByApp*/); + invokeAcquireBuffer(consumer); + + int32_t slot = mFdp.ConsumeIntegral(); + sp buffer = + new GraphicBuffer(mFdp.ConsumeIntegral(), mFdp.ConsumeIntegral(), + mFdp.ConsumeIntegral(), mFdp.ConsumeIntegral(), + mFdp.ConsumeIntegral()); + consumer->attachBuffer(&slot, buffer); + consumer->detachBuffer(slot); + + consumer->setDefaultBufferSize(mFdp.ConsumeIntegral(), + mFdp.ConsumeIntegral()); + consumer->setMaxBufferCount(mFdp.ConsumeIntegral()); + consumer->setMaxAcquiredBufferCount(mFdp.ConsumeIntegral()); + + String8 name((mFdp.ConsumeRandomLengthString(kMaxBytes)).c_str()); + consumer->setConsumerName(name); + consumer->setDefaultBufferFormat(mFdp.ConsumeIntegral()); + android_dataspace dataspace = + static_cast(mFdp.PickValueInArray(kDataspaces)); + consumer->setDefaultBufferDataSpace(dataspace); + + consumer->setTransformHint(mFdp.ConsumeIntegral()); + consumer->setConsumerUsageBits(mFdp.ConsumeIntegral()); + consumer->setConsumerIsProtected(mFdp.ConsumeBool() /*isProtected*/); + invokeOccupancyTracker(consumer); + + sp releaseFence = new Fence(memfd_create("fd", MFD_ALLOW_SEALING)); + consumer->releaseBuffer(mFdp.ConsumeIntegral(), mFdp.ConsumeIntegral(), + EGL_NO_DISPLAY, EGL_NO_SYNC_KHR, releaseFence); + consumer->consumerDisconnect(); +} + +void BufferQueueFuzzer::invokeTypes() { + HStatus hStatus; + int32_t status = mFdp.PickValueInArray(kError); + bool bufferNeedsReallocation = mFdp.ConsumeBool(); + bool releaseAllBuffers = mFdp.ConsumeBool(); + b2h(status, &hStatus, &bufferNeedsReallocation, &releaseAllBuffers); + h2b(hStatus, &status); + + HConnectionType type; + int32_t apiConnection = mFdp.PickValueInArray(kAPIConnection); + b2h(apiConnection, &type); + h2b(type, &apiConnection); +} + +void BufferQueueFuzzer::invokeH2BGraphicBufferV1() { + sp producer( + new V1_0::utils::H2BGraphicBufferProducer(new FakeGraphicBufferProducerV1())); + const sp listener; + android::IGraphicBufferProducer::QueueBufferOutput output; + uint32_t api = mFdp.ConsumeIntegral(); + producer->connect(listener, api, mFdp.ConsumeBool() /*producerControlledByApp*/, &output); + + sp buffer; + int32_t slot = mFdp.ConsumeIntegral(); + producer->requestBuffer(slot, &buffer); + producer->setMaxDequeuedBufferCount(mFdp.ConsumeIntegral()); + producer->setAsyncMode(mFdp.ConsumeBool()); + + android::IGraphicBufferProducer::QueueBufferInput input; + input.fence = new Fence(memfd_create("ffd", MFD_ALLOW_SEALING)); + producer->attachBuffer(&slot, buffer); + producer->queueBuffer(slot, input, &output); + + int32_t format = mFdp.ConsumeIntegral(); + uint32_t width = mFdp.ConsumeIntegral(); + uint32_t height = mFdp.ConsumeIntegral(); + uint64_t usage = mFdp.ConsumeIntegral(); + uint64_t outBufferAge; + FrameEventHistoryDelta outTimestamps; + sp fence; + producer->dequeueBuffer(&slot, &fence, width, height, format, usage, &outBufferAge, + &outTimestamps); + producer->detachBuffer(slot); + producer->cancelBuffer(slot, fence); + + invokeQuery(producer); + + ManageResourceHandle handle(&mFdp); + producer->setSidebandStream(handle.getStream()); + + producer->allocateBuffers(width, height, format, usage); + producer->allowAllocation(mFdp.ConsumeBool() /*allow*/); + producer->setSharedBufferMode(mFdp.ConsumeBool() /*sharedBufferMode*/); + producer->setAutoRefresh(mFdp.ConsumeBool() /*autoRefresh*/); + + producer->setGenerationNumber(mFdp.ConsumeIntegral()); + producer->setDequeueTimeout(mFdp.ConsumeIntegral()); + producer->disconnect(api); +} + +void BufferQueueFuzzer::invokeH2BGraphicBufferV2() { + sp producer( + new V2_0::utils::H2BGraphicBufferProducer(new FakeGraphicBufferProducerV2())); + const sp listener; + android::IGraphicBufferProducer::QueueBufferOutput output; + uint32_t api = mFdp.ConsumeIntegral(); + producer->connect(listener, api, mFdp.ConsumeBool() /*producerControlledByApp*/, &output); + + sp buffer; + int32_t slot = mFdp.ConsumeIntegral(); + producer->requestBuffer(slot, &buffer); + producer->setMaxDequeuedBufferCount(mFdp.ConsumeIntegral()); + producer->setAsyncMode(mFdp.ConsumeBool()); + + android::IGraphicBufferProducer::QueueBufferInput input; + input.fence = new Fence(memfd_create("ffd", MFD_ALLOW_SEALING)); + producer->attachBuffer(&slot, buffer); + producer->queueBuffer(slot, input, &output); + + int32_t format = mFdp.ConsumeIntegral(); + uint32_t width = mFdp.ConsumeIntegral(); + uint32_t height = mFdp.ConsumeIntegral(); + uint64_t usage = mFdp.ConsumeIntegral(); + uint64_t outBufferAge; + FrameEventHistoryDelta outTimestamps; + sp fence; + producer->dequeueBuffer(&slot, &fence, width, height, format, usage, &outBufferAge, + &outTimestamps); + producer->detachBuffer(slot); + producer->cancelBuffer(slot, fence); + + invokeQuery(producer); + + ManageResourceHandle handle(&mFdp); + producer->setSidebandStream(handle.getStream()); + + producer->allocateBuffers(width, height, format, usage); + producer->allowAllocation(mFdp.ConsumeBool() /*allow*/); + producer->setSharedBufferMode(mFdp.ConsumeBool() /*sharedBufferMode*/); + producer->setAutoRefresh(mFdp.ConsumeBool() /*autoRefresh*/); + + producer->setGenerationNumber(mFdp.ConsumeIntegral()); + producer->setDequeueTimeout(mFdp.ConsumeIntegral()); + producer->disconnect(api); +} + +void BufferQueueFuzzer::process() { + invokeBlastBufferQueue(); + invokeH2BGraphicBufferV1(); + invokeH2BGraphicBufferV2(); + invokeTypes(); + invokeBufferQueueConsumer(); + invokeBufferQueueProducer(); +} + +extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) { + BufferQueueFuzzer bufferQueueFuzzer(data, size); + bufferQueueFuzzer.process(); + return 0; +} diff --git a/libs/gui/fuzzer/libgui_consumer_fuzzer.cpp b/libs/gui/fuzzer/libgui_consumer_fuzzer.cpp new file mode 100644 index 0000000000000000000000000000000000000000..24a046d3a951bb6ffd5e90ea0e06973dc55f4e37 --- /dev/null +++ b/libs/gui/fuzzer/libgui_consumer_fuzzer.cpp @@ -0,0 +1,82 @@ +/* + * 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 android; + +constexpr int32_t kMinBuffer = 0; +constexpr int32_t kMaxBuffer = 100000; + +class ConsumerFuzzer { +public: + ConsumerFuzzer(const uint8_t* data, size_t size) : mFdp(data, size){}; + void process(); + +private: + FuzzedDataProvider mFdp; +}; + +void ConsumerFuzzer::process() { + sp core(new BufferQueueCore()); + sp consumer(new BufferQueueConsumer(core)); + + uint64_t maxBuffers = mFdp.ConsumeIntegralInRange(kMinBuffer, kMaxBuffer); + sp cpu( + new CpuConsumer(consumer, maxBuffers, mFdp.ConsumeBool() /*controlledByApp*/)); + CpuConsumer::LockedBuffer lockBuffer; + cpu->lockNextBuffer(&lockBuffer); + cpu->unlockBuffer(lockBuffer); + cpu->abandon(); + + uint32_t tex = mFdp.ConsumeIntegral(); + sp glComsumer(new GLConsumer(consumer, tex, GLConsumer::TEXTURE_EXTERNAL, + mFdp.ConsumeBool() /*useFenceSync*/, + mFdp.ConsumeBool() /*isControlledByApp*/)); + sp releaseFence = new Fence(memfd_create("rfd", MFD_ALLOW_SEALING)); + glComsumer->setReleaseFence(releaseFence); + glComsumer->updateTexImage(); + glComsumer->releaseTexImage(); + + sp buffer = + new GraphicBuffer(mFdp.ConsumeIntegral(), mFdp.ConsumeIntegral(), + mFdp.ConsumeIntegral(), mFdp.ConsumeIntegral(), + mFdp.ConsumeIntegral()); + float mtx[16]; + glComsumer->getTransformMatrix(mtx); + glComsumer->computeTransformMatrix(mtx, buffer, getRect(&mFdp), + mFdp.ConsumeIntegral(), + mFdp.ConsumeBool() /*filtering*/); + glComsumer->scaleDownCrop(getRect(&mFdp), mFdp.ConsumeIntegral(), + mFdp.ConsumeIntegral()); + + glComsumer->setDefaultBufferSize(mFdp.ConsumeIntegral(), + mFdp.ConsumeIntegral()); + glComsumer->setFilteringEnabled(mFdp.ConsumeBool() /*enabled*/); + + glComsumer->setConsumerUsageBits(mFdp.ConsumeIntegral()); + glComsumer->attachToContext(tex); + glComsumer->abandon(); +} + +extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) { + ConsumerFuzzer consumerFuzzer(data, size); + consumerFuzzer.process(); + return 0; +} diff --git a/libs/gui/fuzzer/libgui_displayEvent_fuzzer.cpp b/libs/gui/fuzzer/libgui_displayEvent_fuzzer.cpp new file mode 100644 index 0000000000000000000000000000000000000000..6e4f074825030c77ec0e004639075df5e773005b --- /dev/null +++ b/libs/gui/fuzzer/libgui_displayEvent_fuzzer.cpp @@ -0,0 +1,104 @@ +/* + * 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 + +using namespace android; + +constexpr gui::ISurfaceComposer::VsyncSource kVsyncSource[] = { + gui::ISurfaceComposer::VsyncSource::eVsyncSourceApp, + gui::ISurfaceComposer::VsyncSource::eVsyncSourceSurfaceFlinger, +}; + +constexpr gui::ISurfaceComposer::EventRegistration kEventRegistration[] = { + gui::ISurfaceComposer::EventRegistration::modeChanged, + gui::ISurfaceComposer::EventRegistration::frameRateOverride, +}; + +constexpr uint32_t kDisplayEvent[] = { + DisplayEventReceiver::DISPLAY_EVENT_NULL, + DisplayEventReceiver::DISPLAY_EVENT_VSYNC, + DisplayEventReceiver::DISPLAY_EVENT_HOTPLUG, + DisplayEventReceiver::DISPLAY_EVENT_MODE_CHANGE, + DisplayEventReceiver::DISPLAY_EVENT_FRAME_RATE_OVERRIDE, + DisplayEventReceiver::DISPLAY_EVENT_FRAME_RATE_OVERRIDE_FLUSH, +}; + +constexpr int32_t kEvents[] = { + Looper::EVENT_INPUT, Looper::EVENT_OUTPUT, Looper::EVENT_ERROR, + Looper::EVENT_HANGUP, Looper::EVENT_INVALID, +}; + +DisplayEventReceiver::Event buildDisplayEvent(FuzzedDataProvider* fdp, uint32_t type, + DisplayEventReceiver::Event event) { + switch (type) { + case DisplayEventReceiver::DISPLAY_EVENT_VSYNC: { + event.vsync.count = fdp->ConsumeIntegral(); + event.vsync.vsyncData.frameInterval = fdp->ConsumeIntegral(); + event.vsync.vsyncData.preferredFrameTimelineIndex = fdp->ConsumeIntegral(); + for (size_t idx = 0; idx < gui::VsyncEventData::kFrameTimelinesCapacity; ++idx) { + event.vsync.vsyncData.frameTimelines[idx].vsyncId = fdp->ConsumeIntegral(); + event.vsync.vsyncData.frameTimelines[idx].deadlineTimestamp = + fdp->ConsumeIntegral(); + event.vsync.vsyncData.frameTimelines[idx].expectedPresentationTime = + fdp->ConsumeIntegral(); + } + break; + + } + case DisplayEventReceiver::DISPLAY_EVENT_HOTPLUG: { + event.hotplug = DisplayEventReceiver::Event::Hotplug{fdp->ConsumeBool() /*connected*/}; + break; + } + case DisplayEventReceiver::DISPLAY_EVENT_MODE_CHANGE: { + event.modeChange = + DisplayEventReceiver::Event::ModeChange{fdp->ConsumeIntegral(), + fdp->ConsumeIntegral()}; + break; + } + case DisplayEventReceiver::DISPLAY_EVENT_FRAME_RATE_OVERRIDE: + case DisplayEventReceiver::DISPLAY_EVENT_FRAME_RATE_OVERRIDE_FLUSH: { + event.frameRateOverride = + DisplayEventReceiver::Event::FrameRateOverride{fdp->ConsumeIntegral(), + fdp->ConsumeFloatingPoint< + float>()}; + break; + } + } + return event; +} + +extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) { + FuzzedDataProvider fdp(data, size); + sp looper; + sp dispatcher( + new FakeDisplayEventDispatcher(looper, fdp.PickValueInArray(kVsyncSource), + fdp.PickValueInArray(kEventRegistration))); + + dispatcher->initialize(); + DisplayEventReceiver::Event event; + uint32_t type = fdp.PickValueInArray(kDisplayEvent); + PhysicalDisplayId displayId; + event.header = + DisplayEventReceiver::Event::Header{type, displayId, fdp.ConsumeIntegral()}; + event = buildDisplayEvent(&fdp, type, event); + + dispatcher->injectEvent(event); + dispatcher->handleEvent(0, fdp.PickValueInArray(kEvents), nullptr); + return 0; +} diff --git a/libs/gui/fuzzer/libgui_fuzzer_utils.h b/libs/gui/fuzzer/libgui_fuzzer_utils.h new file mode 100644 index 0000000000000000000000000000000000000000..4c7d0562af106ee441f82debfa7682f616f29818 --- /dev/null +++ b/libs/gui/fuzzer/libgui_fuzzer_utils.h @@ -0,0 +1,315 @@ +/* + * 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. + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace android { + +constexpr uint32_t kOrientation[] = { + ui::Transform::ROT_0, ui::Transform::FLIP_H, ui::Transform::FLIP_V, + ui::Transform::ROT_90, ui::Transform::ROT_180, ui::Transform::ROT_270, +}; + +Rect getRect(FuzzedDataProvider* fdp) { + const int32_t left = fdp->ConsumeIntegral(); + const int32_t top = fdp->ConsumeIntegral(); + const int32_t right = fdp->ConsumeIntegral(); + const int32_t bottom = fdp->ConsumeIntegral(); + return Rect(left, top, right, bottom); +} + +gui::DisplayBrightness getBrightness(FuzzedDataProvider* fdp) { + static constexpr float kMinBrightness = 0; + static constexpr float kMaxBrightness = 1; + gui::DisplayBrightness brightness; + brightness.sdrWhitePoint = + fdp->ConsumeFloatingPointInRange(kMinBrightness, kMaxBrightness); + brightness.sdrWhitePointNits = + fdp->ConsumeFloatingPointInRange(kMinBrightness, kMaxBrightness); + brightness.displayBrightness = + fdp->ConsumeFloatingPointInRange(kMinBrightness, kMaxBrightness); + brightness.displayBrightnessNits = + fdp->ConsumeFloatingPointInRange(kMinBrightness, kMaxBrightness); + return brightness; +} + +class FakeBnSurfaceComposer : public gui::BnSurfaceComposer { +public: + MOCK_METHOD(binder::Status, bootFinished, (), (override)); + MOCK_METHOD(binder::Status, createDisplayEventConnection, + (gui::ISurfaceComposer::VsyncSource, gui::ISurfaceComposer::EventRegistration, + const sp& /*layerHandle*/, sp*), + (override)); + MOCK_METHOD(binder::Status, createConnection, (sp*), (override)); + MOCK_METHOD(binder::Status, createDisplay, (const std::string&, bool, float, sp*), + (override)); + MOCK_METHOD(binder::Status, destroyDisplay, (const sp&), (override)); + MOCK_METHOD(binder::Status, getPhysicalDisplayIds, (std::vector*), (override)); + MOCK_METHOD(binder::Status, getPhysicalDisplayToken, (int64_t, sp*), (override)); + MOCK_METHOD(binder::Status, setPowerMode, (const sp&, int), (override)); + MOCK_METHOD(binder::Status, getSupportedFrameTimestamps, (std::vector*), + (override)); + MOCK_METHOD(binder::Status, getDisplayStats, (const sp&, gui::DisplayStatInfo*), + (override)); + MOCK_METHOD(binder::Status, getDisplayState, (const sp&, gui::DisplayState*), + (override)); + MOCK_METHOD(binder::Status, getStaticDisplayInfo, (int64_t, gui::StaticDisplayInfo*), + (override)); + MOCK_METHOD(binder::Status, getDynamicDisplayInfoFromId, (int64_t, gui::DynamicDisplayInfo*), + (override)); + MOCK_METHOD(binder::Status, getDynamicDisplayInfoFromToken, + (const sp&, gui::DynamicDisplayInfo*), (override)); + MOCK_METHOD(binder::Status, getDisplayNativePrimaries, + (const sp&, gui::DisplayPrimaries*), (override)); + MOCK_METHOD(binder::Status, setActiveColorMode, (const sp&, int), (override)); + MOCK_METHOD(binder::Status, setBootDisplayMode, (const sp&, int), (override)); + MOCK_METHOD(binder::Status, clearBootDisplayMode, (const sp&), (override)); + MOCK_METHOD(binder::Status, getBootDisplayModeSupport, (bool*), (override)); + MOCK_METHOD(binder::Status, getHdrConversionCapabilities, + (std::vector*), (override)); + MOCK_METHOD(binder::Status, setHdrConversionStrategy, + (const gui::HdrConversionStrategy&, int32_t*), (override)); + MOCK_METHOD(binder::Status, getHdrOutputConversionSupport, (bool*), (override)); + MOCK_METHOD(binder::Status, setAutoLowLatencyMode, (const sp&, bool), (override)); + MOCK_METHOD(binder::Status, setGameContentType, (const sp&, bool), (override)); + MOCK_METHOD(binder::Status, captureDisplay, + (const DisplayCaptureArgs&, const sp&), (override)); + MOCK_METHOD(binder::Status, captureDisplayById, (int64_t, const sp&), + (override)); + MOCK_METHOD(binder::Status, captureLayers, + (const LayerCaptureArgs&, const sp&), (override)); + MOCK_METHOD(binder::Status, clearAnimationFrameStats, (), (override)); + MOCK_METHOD(binder::Status, getAnimationFrameStats, (gui::FrameStats*), (override)); + MOCK_METHOD(binder::Status, overrideHdrTypes, (const sp&, const std::vector&), + (override)); + MOCK_METHOD(binder::Status, onPullAtom, (int32_t, gui::PullAtomData*), (override)); + MOCK_METHOD(binder::Status, getLayerDebugInfo, (std::vector*), (override)); + MOCK_METHOD(binder::Status, getColorManagement, (bool*), (override)); + MOCK_METHOD(binder::Status, getCompositionPreference, (gui::CompositionPreference*), + (override)); + MOCK_METHOD(binder::Status, getDisplayedContentSamplingAttributes, + (const sp&, gui::ContentSamplingAttributes*), (override)); + MOCK_METHOD(binder::Status, setDisplayContentSamplingEnabled, + (const sp&, bool, int8_t, int64_t), (override)); + MOCK_METHOD(binder::Status, getDisplayedContentSample, + (const sp&, int64_t, int64_t, gui::DisplayedFrameStats*), (override)); + MOCK_METHOD(binder::Status, getProtectedContentSupport, (bool*), (override)); + MOCK_METHOD(binder::Status, isWideColorDisplay, (const sp&, bool*), (override)); + MOCK_METHOD(binder::Status, addRegionSamplingListener, + (const gui::ARect&, const sp&, const sp&), + (override)); + MOCK_METHOD(binder::Status, removeRegionSamplingListener, + (const sp&), (override)); + MOCK_METHOD(binder::Status, addFpsListener, (int32_t, const sp&), + (override)); + MOCK_METHOD(binder::Status, removeFpsListener, (const sp&), (override)); + MOCK_METHOD(binder::Status, addTunnelModeEnabledListener, + (const sp&), (override)); + MOCK_METHOD(binder::Status, removeTunnelModeEnabledListener, + (const sp&), (override)); + MOCK_METHOD(binder::Status, setDesiredDisplayModeSpecs, + (const sp&, const gui::DisplayModeSpecs&), (override)); + MOCK_METHOD(binder::Status, getDesiredDisplayModeSpecs, + (const sp&, gui::DisplayModeSpecs*), (override)); + MOCK_METHOD(binder::Status, getDisplayBrightnessSupport, (const sp&, bool*), + (override)); + MOCK_METHOD(binder::Status, setDisplayBrightness, + (const sp&, const gui::DisplayBrightness&), (override)); + MOCK_METHOD(binder::Status, addHdrLayerInfoListener, + (const sp&, const sp&), (override)); + MOCK_METHOD(binder::Status, removeHdrLayerInfoListener, + (const sp&, const sp&), (override)); + MOCK_METHOD(binder::Status, notifyPowerBoost, (int), (override)); + MOCK_METHOD(binder::Status, setGlobalShadowSettings, + (const gui::Color&, const gui::Color&, float, float, float), (override)); + MOCK_METHOD(binder::Status, getDisplayDecorationSupport, + (const sp&, std::optional*), (override)); + MOCK_METHOD(binder::Status, setOverrideFrameRate, (int32_t, float), (override)); + MOCK_METHOD(binder::Status, getGpuContextPriority, (int32_t*), (override)); + MOCK_METHOD(binder::Status, getMaxAcquiredBufferCount, (int32_t*), (override)); + MOCK_METHOD(binder::Status, addWindowInfosListener, + (const sp&, gui::WindowInfosListenerInfo*), (override)); + MOCK_METHOD(binder::Status, removeWindowInfosListener, (const sp&), + (override)); + MOCK_METHOD(binder::Status, getOverlaySupport, (gui::OverlayProperties*), (override)); +}; + +class FakeBnSurfaceComposerClient : public gui::BnSurfaceComposerClient { +public: + MOCK_METHOD(binder::Status, createSurface, + (const std::string& name, int32_t flags, const sp& parent, + const gui::LayerMetadata& metadata, gui::CreateSurfaceResult* outResult), + (override)); + + MOCK_METHOD(binder::Status, clearLayerFrameStats, (const sp& handle), (override)); + + MOCK_METHOD(binder::Status, getLayerFrameStats, + (const sp& handle, gui::FrameStats* outStats), (override)); + + MOCK_METHOD(binder::Status, mirrorSurface, + (const sp& mirrorFromHandle, gui::CreateSurfaceResult* outResult), + (override)); + + MOCK_METHOD(binder::Status, mirrorDisplay, + (int64_t displayId, gui::CreateSurfaceResult* outResult), (override)); +}; + +class FakeDisplayEventDispatcher : public DisplayEventDispatcher { +public: + FakeDisplayEventDispatcher(const sp& looper, + gui::ISurfaceComposer::VsyncSource vsyncSource, + gui::ISurfaceComposer::EventRegistration eventRegistration) + : DisplayEventDispatcher(looper, vsyncSource, eventRegistration){}; + + MOCK_METHOD4(dispatchVsync, void(nsecs_t, PhysicalDisplayId, uint32_t, VsyncEventData)); + MOCK_METHOD3(dispatchHotplug, void(nsecs_t, PhysicalDisplayId, bool)); + MOCK_METHOD4(dispatchModeChanged, void(nsecs_t, PhysicalDisplayId, int32_t, nsecs_t)); + MOCK_METHOD2(dispatchNullEvent, void(nsecs_t, PhysicalDisplayId)); + MOCK_METHOD3(dispatchFrameRateOverrides, + void(nsecs_t, PhysicalDisplayId, std::vector)); +}; + +} // namespace android + +namespace android::hardware { + +namespace graphics::bufferqueue::V1_0::utils { + +class FakeGraphicBufferProducerV1 : public HGraphicBufferProducer { +public: + FakeGraphicBufferProducerV1() { + ON_CALL(*this, setMaxDequeuedBufferCount).WillByDefault([]() { return 0; }); + ON_CALL(*this, setAsyncMode).WillByDefault([]() { return 0; }); + ON_CALL(*this, detachBuffer).WillByDefault([]() { return 0; }); + ON_CALL(*this, cancelBuffer).WillByDefault([]() { return 0; }); + ON_CALL(*this, disconnect).WillByDefault([]() { return 0; }); + ON_CALL(*this, setSidebandStream).WillByDefault([]() { return 0; }); + ON_CALL(*this, allowAllocation).WillByDefault([]() { return 0; }); + ON_CALL(*this, setGenerationNumber).WillByDefault([]() { return 0; }); + ON_CALL(*this, setSharedBufferMode).WillByDefault([]() { return 0; }); + ON_CALL(*this, setAutoRefresh).WillByDefault([]() { return 0; }); + ON_CALL(*this, setDequeueTimeout).WillByDefault([]() { return 0; }); + ON_CALL(*this, setLegacyBufferDrop).WillByDefault([]() { return 0; }); + }; + MOCK_METHOD2(requestBuffer, Return(int, requestBuffer_cb)); + MOCK_METHOD1(setMaxDequeuedBufferCount, Return(int32_t)); + MOCK_METHOD1(setAsyncMode, Return(bool)); + MOCK_METHOD6(dequeueBuffer, + Return(uint32_t, uint32_t, graphics::common::V1_0::PixelFormat, uint32_t, + bool, dequeueBuffer_cb)); + MOCK_METHOD1(detachBuffer, Return(int)); + MOCK_METHOD1(detachNextBuffer, Return(detachNextBuffer_cb)); + MOCK_METHOD2(attachBuffer, Return(const media::V1_0::AnwBuffer&, attachBuffer_cb)); + MOCK_METHOD3( + queueBuffer, + Return( + int, + const graphics::bufferqueue::V1_0::IGraphicBufferProducer::QueueBufferInput&, + queueBuffer_cb)); + MOCK_METHOD2(cancelBuffer, Return(int, const hidl_handle&)); + MOCK_METHOD2(query, Return(int32_t, query_cb)); + MOCK_METHOD4(connect, + Return(const sp&, int32_t, + bool, connect_cb)); + MOCK_METHOD2(disconnect, + Return( + int, graphics::bufferqueue::V1_0::IGraphicBufferProducer::DisconnectMode)); + MOCK_METHOD1(setSidebandStream, Return(const hidl_handle&)); + MOCK_METHOD4(allocateBuffers, + Return(uint32_t, uint32_t, graphics::common::V1_0::PixelFormat, uint32_t)); + MOCK_METHOD1(allowAllocation, Return(bool)); + MOCK_METHOD1(setGenerationNumber, Return(uint32_t)); + MOCK_METHOD1(getConsumerName, Return(getConsumerName_cb)); + MOCK_METHOD1(setSharedBufferMode, Return(bool)); + MOCK_METHOD1(setAutoRefresh, Return(bool)); + MOCK_METHOD1(setDequeueTimeout, Return(nsecs_t)); + MOCK_METHOD1(setLegacyBufferDrop, Return(bool)); + MOCK_METHOD1(getLastQueuedBuffer, Return(getLastQueuedBuffer_cb)); + MOCK_METHOD1(getFrameTimestamps, Return(getFrameTimestamps_cb)); + MOCK_METHOD1(getUniqueId, Return(getUniqueId_cb)); +}; + +}; // namespace graphics::bufferqueue::V1_0::utils + +namespace graphics::bufferqueue::V2_0::utils { + +class FakeGraphicBufferProducerV2 : public HGraphicBufferProducer { +public: + FakeGraphicBufferProducerV2() { + ON_CALL(*this, setMaxDequeuedBufferCount).WillByDefault([]() { return Status::OK; }); + ON_CALL(*this, setAsyncMode).WillByDefault([]() { return Status::OK; }); + ON_CALL(*this, detachBuffer).WillByDefault([]() { return Status::OK; }); + ON_CALL(*this, cancelBuffer).WillByDefault([]() { return Status::OK; }); + ON_CALL(*this, disconnect).WillByDefault([]() { return Status::OK; }); + ON_CALL(*this, allocateBuffers).WillByDefault([]() { return Status::OK; }); + ON_CALL(*this, allowAllocation).WillByDefault([]() { return Status::OK; }); + ON_CALL(*this, setGenerationNumber).WillByDefault([]() { return Status::OK; }); + ON_CALL(*this, setDequeueTimeout).WillByDefault([]() { return Status::OK; }); + ON_CALL(*this, getUniqueId).WillByDefault([]() { return 0; }); + }; + MOCK_METHOD2(requestBuffer, Return(int, requestBuffer_cb)); + MOCK_METHOD1(setMaxDequeuedBufferCount, Return(int)); + MOCK_METHOD1(setAsyncMode, Return(bool)); + MOCK_METHOD2( + dequeueBuffer, + Return( + const graphics::bufferqueue::V2_0::IGraphicBufferProducer::DequeueBufferInput&, + dequeueBuffer_cb)); + MOCK_METHOD1(detachBuffer, Return(int)); + MOCK_METHOD1(detachNextBuffer, Return(detachNextBuffer_cb)); + MOCK_METHOD3(attachBuffer, + Return(const graphics::common::V1_2::HardwareBuffer&, uint32_t, + attachBuffer_cb)); + MOCK_METHOD3( + queueBuffer, + Return( + int, + const graphics::bufferqueue::V2_0::IGraphicBufferProducer::QueueBufferInput&, + queueBuffer_cb)); + MOCK_METHOD2(cancelBuffer, + Return(int, const hidl_handle&)); + MOCK_METHOD2(query, Return(int32_t, query_cb)); + MOCK_METHOD4(connect, + Return(const sp&, + graphics::bufferqueue::V2_0::ConnectionType, bool, connect_cb)); + MOCK_METHOD1(disconnect, + Return( + graphics::bufferqueue::V2_0::ConnectionType)); + MOCK_METHOD4(allocateBuffers, + Return(uint32_t, uint32_t, uint32_t, + uint64_t)); + MOCK_METHOD1(allowAllocation, Return(bool)); + MOCK_METHOD1(setGenerationNumber, Return(uint32_t)); + MOCK_METHOD1(getConsumerName, Return(getConsumerName_cb)); + MOCK_METHOD1(setDequeueTimeout, Return(int64_t)); + MOCK_METHOD0(getUniqueId, Return()); +}; + +}; // namespace graphics::bufferqueue::V2_0::utils +}; // namespace android::hardware diff --git a/libs/gui/fuzzer/libgui_parcelable_fuzzer.cpp b/libs/gui/fuzzer/libgui_parcelable_fuzzer.cpp new file mode 100644 index 0000000000000000000000000000000000000000..9f0f6cac19faebefc90d3c2e7e18167974ca4b32 --- /dev/null +++ b/libs/gui/fuzzer/libgui_parcelable_fuzzer.cpp @@ -0,0 +1,175 @@ +/* + * 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 "android/view/LayerMetadataKey.h" + +using namespace android; + +constexpr int32_t kMaxBytes = 256; +constexpr int32_t kMatrixSize = 4; +constexpr int32_t kLayerMetadataKeyCount = 8; + +constexpr uint32_t kMetadataKey[] = { + (uint32_t)view::LayerMetadataKey::METADATA_OWNER_UID, + (uint32_t)view::LayerMetadataKey::METADATA_WINDOW_TYPE, + (uint32_t)view::LayerMetadataKey::METADATA_TASK_ID, + (uint32_t)view::LayerMetadataKey::METADATA_MOUSE_CURSOR, + (uint32_t)view::LayerMetadataKey::METADATA_ACCESSIBILITY_ID, + (uint32_t)view::LayerMetadataKey::METADATA_OWNER_PID, + (uint32_t)view::LayerMetadataKey::METADATA_DEQUEUE_TIME, + (uint32_t)view::LayerMetadataKey::METADATA_GAME_MODE, +}; + +class ParcelableFuzzer { +public: + ParcelableFuzzer(const uint8_t* data, size_t size) : mFdp(data, size){}; + void process(); + +private: + void invokeStreamSplitter(); + void invokeOccupancyTracker(); + void invokeLayerDebugInfo(); + void invokeLayerMetadata(); + void invokeViewSurface(); + + FuzzedDataProvider mFdp; +}; + +void ParcelableFuzzer::invokeViewSurface() { + view::Surface surface; + surface.name = String16((mFdp.ConsumeRandomLengthString(kMaxBytes)).c_str()); + Parcel parcel; + surface.writeToParcel(&parcel); + parcel.setDataPosition(0); + surface.readFromParcel(&parcel); + bool nameAlreadyWritten = mFdp.ConsumeBool(); + surface.writeToParcel(&parcel, nameAlreadyWritten); + parcel.setDataPosition(0); + surface.readFromParcel(&parcel, mFdp.ConsumeBool()); +} + +void ParcelableFuzzer::invokeLayerMetadata() { + std::unordered_map> map; + for (size_t idx = 0; idx < kLayerMetadataKeyCount; ++idx) { + std::vector data; + for (size_t idx1 = 0; idx1 < mFdp.ConsumeIntegral(); ++idx1) { + data.push_back(mFdp.ConsumeIntegral()); + } + map[kMetadataKey[idx]] = data; + } + LayerMetadata metadata(map); + uint32_t key = mFdp.PickValueInArray(kMetadataKey); + metadata.setInt32(key, mFdp.ConsumeIntegral()); + metadata.itemToString(key, (mFdp.ConsumeRandomLengthString(kMaxBytes)).c_str()); + + Parcel parcel; + metadata.writeToParcel(&parcel); + parcel.setDataPosition(0); + metadata.readFromParcel(&parcel); +} + +void ParcelableFuzzer::invokeLayerDebugInfo() { + gui::LayerDebugInfo info; + info.mName = mFdp.ConsumeRandomLengthString(kMaxBytes); + info.mParentName = mFdp.ConsumeRandomLengthString(kMaxBytes); + info.mType = mFdp.ConsumeRandomLengthString(kMaxBytes); + info.mLayerStack = mFdp.ConsumeIntegral(); + info.mX = mFdp.ConsumeFloatingPoint(); + info.mY = mFdp.ConsumeFloatingPoint(); + info.mZ = mFdp.ConsumeIntegral(); + info.mWidth = mFdp.ConsumeIntegral(); + info.mHeight = mFdp.ConsumeIntegral(); + info.mActiveBufferWidth = mFdp.ConsumeIntegral(); + info.mActiveBufferHeight = mFdp.ConsumeIntegral(); + info.mActiveBufferStride = mFdp.ConsumeIntegral(); + info.mActiveBufferFormat = mFdp.ConsumeIntegral(); + info.mNumQueuedFrames = mFdp.ConsumeIntegral(); + + info.mFlags = mFdp.ConsumeIntegral(); + info.mPixelFormat = mFdp.ConsumeIntegral(); + info.mTransparentRegion = Region(getRect(&mFdp)); + info.mVisibleRegion = Region(getRect(&mFdp)); + info.mSurfaceDamageRegion = Region(getRect(&mFdp)); + info.mCrop = getRect(&mFdp); + info.mDataSpace = static_cast(mFdp.PickValueInArray(kDataspaces)); + info.mColor = half4(mFdp.ConsumeFloatingPoint(), mFdp.ConsumeFloatingPoint(), + mFdp.ConsumeFloatingPoint(), mFdp.ConsumeFloatingPoint()); + for (size_t idx = 0; idx < kMatrixSize; ++idx) { + info.mMatrix[idx / 2][idx % 2] = mFdp.ConsumeFloatingPoint(); + } + info.mIsOpaque = mFdp.ConsumeBool(); + info.mContentDirty = mFdp.ConsumeBool(); + info.mStretchEffect.width = mFdp.ConsumeFloatingPoint(); + info.mStretchEffect.height = mFdp.ConsumeFloatingPoint(); + info.mStretchEffect.vectorX = mFdp.ConsumeFloatingPoint(); + info.mStretchEffect.vectorY = mFdp.ConsumeFloatingPoint(); + info.mStretchEffect.maxAmountX = mFdp.ConsumeFloatingPoint(); + info.mStretchEffect.maxAmountY = mFdp.ConsumeFloatingPoint(); + info.mStretchEffect.mappedChildBounds = + FloatRect(mFdp.ConsumeFloatingPoint(), mFdp.ConsumeFloatingPoint(), + mFdp.ConsumeFloatingPoint(), mFdp.ConsumeFloatingPoint()); + + Parcel parcel; + info.writeToParcel(&parcel); + parcel.setDataPosition(0); + info.readFromParcel(&parcel); +} + +void ParcelableFuzzer::invokeOccupancyTracker() { + nsecs_t totalTime = mFdp.ConsumeIntegral(); + size_t numFrames = mFdp.ConsumeIntegral(); + float occupancyAverage = mFdp.ConsumeFloatingPoint(); + OccupancyTracker::Segment segment(totalTime, numFrames, occupancyAverage, + mFdp.ConsumeBool() /*usedThirdBuffer*/); + Parcel parcel; + segment.writeToParcel(&parcel); + parcel.setDataPosition(0); + segment.readFromParcel(&parcel); +} + +void ParcelableFuzzer::invokeStreamSplitter() { + sp producer; + sp consumer; + BufferQueue::createBufferQueue(&producer, &consumer); + sp splitter; + StreamSplitter::createSplitter(consumer, &splitter); + splitter->addOutput(producer); + std::string name = mFdp.ConsumeRandomLengthString(kMaxBytes); + splitter->setName(String8(name.c_str())); +} + +void ParcelableFuzzer::process() { + invokeStreamSplitter(); + invokeOccupancyTracker(); + invokeLayerDebugInfo(); + invokeLayerMetadata(); + invokeViewSurface(); +} + +extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) { + ParcelableFuzzer libGuiFuzzer(data, size); + libGuiFuzzer.process(); + return 0; +} diff --git a/libs/gui/fuzzer/libgui_surfaceComposerClient_fuzzer.cpp b/libs/gui/fuzzer/libgui_surfaceComposerClient_fuzzer.cpp new file mode 100644 index 0000000000000000000000000000000000000000..57720dd5131bfce10505c0252088f8cae45ef0f7 --- /dev/null +++ b/libs/gui/fuzzer/libgui_surfaceComposerClient_fuzzer.cpp @@ -0,0 +1,327 @@ +/* + * 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 "android-base/stringprintf.h" + +using namespace android; + +constexpr int32_t kRandomStringMaxBytes = 256; + +constexpr ui::ColorMode kColormodes[] = {ui::ColorMode::NATIVE, + ui::ColorMode::STANDARD_BT601_625, + ui::ColorMode::STANDARD_BT601_625_UNADJUSTED, + ui::ColorMode::STANDARD_BT601_525, + ui::ColorMode::STANDARD_BT601_525_UNADJUSTED, + ui::ColorMode::STANDARD_BT709, + ui::ColorMode::DCI_P3, + ui::ColorMode::SRGB, + ui::ColorMode::ADOBE_RGB, + ui::ColorMode::DISPLAY_P3, + ui::ColorMode::BT2020, + ui::ColorMode::BT2100_PQ, + ui::ColorMode::BT2100_HLG, + ui::ColorMode::DISPLAY_BT2020}; + +constexpr hardware::power::Boost kBoost[] = { + hardware::power::Boost::INTERACTION, hardware::power::Boost::DISPLAY_UPDATE_IMMINENT, + hardware::power::Boost::ML_ACC, hardware::power::Boost::AUDIO_LAUNCH, + hardware::power::Boost::CAMERA_LAUNCH, hardware::power::Boost::CAMERA_SHOT, +}; + +constexpr gui::TouchOcclusionMode kMode[] = { + gui::TouchOcclusionMode::BLOCK_UNTRUSTED, + gui::TouchOcclusionMode::USE_OPACITY, + gui::TouchOcclusionMode::ALLOW, +}; + +constexpr gui::WindowInfo::Flag kFlags[] = { + gui::WindowInfo::Flag::ALLOW_LOCK_WHILE_SCREEN_ON, + gui::WindowInfo::Flag::DIM_BEHIND, + gui::WindowInfo::Flag::BLUR_BEHIND, + gui::WindowInfo::Flag::NOT_FOCUSABLE, + gui::WindowInfo::Flag::NOT_TOUCHABLE, + gui::WindowInfo::Flag::NOT_TOUCH_MODAL, + gui::WindowInfo::Flag::TOUCHABLE_WHEN_WAKING, + gui::WindowInfo::Flag::KEEP_SCREEN_ON, + gui::WindowInfo::Flag::LAYOUT_IN_SCREEN, + gui::WindowInfo::Flag::LAYOUT_NO_LIMITS, + gui::WindowInfo::Flag::FULLSCREEN, + gui::WindowInfo::Flag::FORCE_NOT_FULLSCREEN, + gui::WindowInfo::Flag::DITHER, + gui::WindowInfo::Flag::SECURE, + gui::WindowInfo::Flag::SCALED, + gui::WindowInfo::Flag::IGNORE_CHEEK_PRESSES, + gui::WindowInfo::Flag::LAYOUT_INSET_DECOR, + gui::WindowInfo::Flag::ALT_FOCUSABLE_IM, + gui::WindowInfo::Flag::WATCH_OUTSIDE_TOUCH, + gui::WindowInfo::Flag::SHOW_WHEN_LOCKED, + gui::WindowInfo::Flag::SHOW_WALLPAPER, + gui::WindowInfo::Flag::TURN_SCREEN_ON, + gui::WindowInfo::Flag::DISMISS_KEYGUARD, + gui::WindowInfo::Flag::SPLIT_TOUCH, + gui::WindowInfo::Flag::HARDWARE_ACCELERATED, + gui::WindowInfo::Flag::LAYOUT_IN_OVERSCAN, + gui::WindowInfo::Flag::TRANSLUCENT_STATUS, + gui::WindowInfo::Flag::TRANSLUCENT_NAVIGATION, + gui::WindowInfo::Flag::LOCAL_FOCUS_MODE, + gui::WindowInfo::Flag::SLIPPERY, + gui::WindowInfo::Flag::LAYOUT_ATTACHED_IN_DECOR, + gui::WindowInfo::Flag::DRAWS_SYSTEM_BAR_BACKGROUNDS, +}; + +constexpr gui::WindowInfo::Type kType[] = { + gui::WindowInfo::Type::UNKNOWN, + gui::WindowInfo::Type::FIRST_APPLICATION_WINDOW, + gui::WindowInfo::Type::BASE_APPLICATION, + gui::WindowInfo::Type::APPLICATION, + gui::WindowInfo::Type::APPLICATION_STARTING, + gui::WindowInfo::Type::LAST_APPLICATION_WINDOW, + gui::WindowInfo::Type::FIRST_SUB_WINDOW, + gui::WindowInfo::Type::APPLICATION_PANEL, + gui::WindowInfo::Type::APPLICATION_MEDIA, + gui::WindowInfo::Type::APPLICATION_SUB_PANEL, + gui::WindowInfo::Type::APPLICATION_ATTACHED_DIALOG, + gui::WindowInfo::Type::APPLICATION_MEDIA_OVERLAY, +}; + +constexpr gui::WindowInfo::InputConfig kFeatures[] = { + gui::WindowInfo::InputConfig::NO_INPUT_CHANNEL, + gui::WindowInfo::InputConfig::DISABLE_USER_ACTIVITY, + gui::WindowInfo::InputConfig::DROP_INPUT, + gui::WindowInfo::InputConfig::DROP_INPUT_IF_OBSCURED, + gui::WindowInfo::InputConfig::SPY, + gui::WindowInfo::InputConfig::INTERCEPTS_STYLUS, +}; + +class SurfaceComposerClientFuzzer { +public: + SurfaceComposerClientFuzzer(const uint8_t* data, size_t size) : mFdp(data, size){}; + void process(); + +private: + void invokeSurfaceComposerClient(); + void invokeSurfaceComposerClientBinder(); + void invokeSurfaceComposerTransaction(); + void getWindowInfo(gui::WindowInfo*); + sp makeSurfaceControl(); + BlurRegion getBlurRegion(); + void fuzzOnPullAtom(); + gui::DisplayModeSpecs getDisplayModeSpecs(); + + FuzzedDataProvider mFdp; +}; + +gui::DisplayModeSpecs SurfaceComposerClientFuzzer::getDisplayModeSpecs() { + const auto getRefreshRateRange = [&] { + gui::DisplayModeSpecs::RefreshRateRanges::RefreshRateRange range; + range.min = mFdp.ConsumeFloatingPoint(); + range.max = mFdp.ConsumeFloatingPoint(); + return range; + }; + + const auto getRefreshRateRanges = [&] { + gui::DisplayModeSpecs::RefreshRateRanges ranges; + ranges.physical = getRefreshRateRange(); + ranges.render = getRefreshRateRange(); + return ranges; + }; + + String8 displayName((mFdp.ConsumeRandomLengthString(kRandomStringMaxBytes)).c_str()); + sp displayToken = + SurfaceComposerClient::createDisplay(displayName, mFdp.ConsumeBool() /*secure*/); + gui::DisplayModeSpecs specs; + specs.defaultMode = mFdp.ConsumeIntegral(); + specs.allowGroupSwitching = mFdp.ConsumeBool(); + specs.primaryRanges = getRefreshRateRanges(); + specs.appRequestRanges = getRefreshRateRanges(); + return specs; +} + +BlurRegion SurfaceComposerClientFuzzer::getBlurRegion() { + int32_t left = mFdp.ConsumeIntegral(); + int32_t right = mFdp.ConsumeIntegral(); + int32_t top = mFdp.ConsumeIntegral(); + int32_t bottom = mFdp.ConsumeIntegral(); + uint32_t blurRadius = mFdp.ConsumeIntegral(); + float alpha = mFdp.ConsumeFloatingPoint(); + float cornerRadiusTL = mFdp.ConsumeFloatingPoint(); + float cornerRadiusTR = mFdp.ConsumeFloatingPoint(); + float cornerRadiusBL = mFdp.ConsumeFloatingPoint(); + float cornerRadiusBR = mFdp.ConsumeFloatingPoint(); + return BlurRegion{blurRadius, cornerRadiusTL, cornerRadiusTR, cornerRadiusBL, + cornerRadiusBR, alpha, left, top, + right, bottom}; +} + +void SurfaceComposerClientFuzzer::getWindowInfo(gui::WindowInfo* windowInfo) { + windowInfo->id = mFdp.ConsumeIntegral(); + windowInfo->name = mFdp.ConsumeRandomLengthString(kRandomStringMaxBytes); + windowInfo->layoutParamsFlags = mFdp.PickValueInArray(kFlags); + windowInfo->layoutParamsType = mFdp.PickValueInArray(kType); + windowInfo->frameLeft = mFdp.ConsumeIntegral(); + windowInfo->frameTop = mFdp.ConsumeIntegral(); + windowInfo->frameRight = mFdp.ConsumeIntegral(); + windowInfo->frameBottom = mFdp.ConsumeIntegral(); + windowInfo->surfaceInset = mFdp.ConsumeIntegral(); + windowInfo->alpha = mFdp.ConsumeFloatingPointInRange(0, 1); + ui::Transform transform(mFdp.PickValueInArray(kOrientation)); + windowInfo->transform = transform; + windowInfo->touchableRegion = Region(getRect(&mFdp)); + windowInfo->replaceTouchableRegionWithCrop = mFdp.ConsumeBool(); + windowInfo->touchOcclusionMode = mFdp.PickValueInArray(kMode); + windowInfo->ownerPid = mFdp.ConsumeIntegral(); + windowInfo->ownerUid = mFdp.ConsumeIntegral(); + windowInfo->packageName = mFdp.ConsumeRandomLengthString(kRandomStringMaxBytes); + windowInfo->inputConfig = mFdp.PickValueInArray(kFeatures); +} + +sp SurfaceComposerClientFuzzer::makeSurfaceControl() { + sp handle; + const sp testClient(new FakeBnSurfaceComposerClient()); + sp client = new SurfaceComposerClient(testClient); + sp producer; + uint32_t width = mFdp.ConsumeIntegral(); + uint32_t height = mFdp.ConsumeIntegral(); + uint32_t transformHint = mFdp.ConsumeIntegral(); + uint32_t flags = mFdp.ConsumeIntegral(); + int32_t format = mFdp.ConsumeIntegral(); + int32_t layerId = mFdp.ConsumeIntegral(); + std::string layerName = base::StringPrintf("#%d", layerId); + return new SurfaceControl(client, handle, layerId, layerName, width, height, format, + transformHint, flags); +} + +void SurfaceComposerClientFuzzer::invokeSurfaceComposerTransaction() { + sp surface = makeSurfaceControl(); + + SurfaceComposerClient::Transaction transaction; + int32_t layer = mFdp.ConsumeIntegral(); + transaction.setLayer(surface, layer); + + sp relativeSurface = makeSurfaceControl(); + transaction.setRelativeLayer(surface, relativeSurface, layer); + + Region transparentRegion(getRect(&mFdp)); + transaction.setTransparentRegionHint(surface, transparentRegion); + transaction.setAlpha(surface, mFdp.ConsumeFloatingPoint()); + + transaction.setCornerRadius(surface, mFdp.ConsumeFloatingPoint()); + transaction.setBackgroundBlurRadius(surface, mFdp.ConsumeFloatingPoint()); + std::vector regions; + uint32_t vectorSize = mFdp.ConsumeIntegralInRange(0, 100); + regions.resize(vectorSize); + for (size_t idx = 0; idx < vectorSize; ++idx) { + regions.push_back(getBlurRegion()); + } + transaction.setBlurRegions(surface, regions); + + transaction.setLayerStack(surface, {mFdp.ConsumeIntegral()}); + half3 color = {mFdp.ConsumeIntegral(), mFdp.ConsumeIntegral(), + mFdp.ConsumeIntegral()}; + transaction.setColor(surface, color); + transaction.setBackgroundColor(surface, color, mFdp.ConsumeFloatingPoint(), + mFdp.PickValueInArray(kDataspaces)); + + transaction.setApi(surface, mFdp.ConsumeIntegral()); + transaction.setFrameRateSelectionPriority(surface, mFdp.ConsumeIntegral()); + transaction.setColorSpaceAgnostic(surface, mFdp.ConsumeBool() /*agnostic*/); + + gui::WindowInfo windowInfo; + getWindowInfo(&windowInfo); + transaction.setInputWindowInfo(surface, windowInfo); + Parcel windowParcel; + windowInfo.writeToParcel(&windowParcel); + windowParcel.setDataPosition(0); + windowInfo.readFromParcel(&windowParcel); + + windowInfo.addTouchableRegion(getRect(&mFdp)); + int32_t pointX = mFdp.ConsumeIntegral(); + int32_t pointY = mFdp.ConsumeIntegral(); + windowInfo.touchableRegionContainsPoint(pointX, pointY); + windowInfo.frameContainsPoint(pointX, pointY); + + Parcel transactionParcel; + transaction.writeToParcel(&transactionParcel); + transactionParcel.setDataPosition(0); + transaction.readFromParcel(&transactionParcel); + SurfaceComposerClient::Transaction::createFromParcel(&transactionParcel); +} + +void SurfaceComposerClientFuzzer::fuzzOnPullAtom() { + std::string outData; + bool success; + SurfaceComposerClient::onPullAtom(mFdp.ConsumeIntegral(), &outData, &success); +} + +void SurfaceComposerClientFuzzer::invokeSurfaceComposerClient() { + String8 displayName((mFdp.ConsumeRandomLengthString(kRandomStringMaxBytes)).c_str()); + sp displayToken = + SurfaceComposerClient::createDisplay(displayName, mFdp.ConsumeBool() /*secure*/); + SurfaceComposerClient::setDesiredDisplayModeSpecs(displayToken, getDisplayModeSpecs()); + + ui::ColorMode colorMode = mFdp.PickValueInArray(kColormodes); + SurfaceComposerClient::setActiveColorMode(displayToken, colorMode); + SurfaceComposerClient::setAutoLowLatencyMode(displayToken, mFdp.ConsumeBool() /*on*/); + SurfaceComposerClient::setGameContentType(displayToken, mFdp.ConsumeBool() /*on*/); + SurfaceComposerClient::setDisplayPowerMode(displayToken, mFdp.ConsumeIntegral()); + SurfaceComposerClient::doUncacheBufferTransaction(mFdp.ConsumeIntegral()); + + SurfaceComposerClient::setDisplayBrightness(displayToken, getBrightness(&mFdp)); + hardware::power::Boost boostId = mFdp.PickValueInArray(kBoost); + SurfaceComposerClient::notifyPowerBoost((int32_t)boostId); + + String8 surfaceName((mFdp.ConsumeRandomLengthString(kRandomStringMaxBytes)).c_str()); + sp handle(new BBinder()); + sp producer; + sp surfaceParent( + new Surface(producer, mFdp.ConsumeBool() /*controlledByApp*/, handle)); + + fuzzOnPullAtom(); + SurfaceComposerClient::setDisplayContentSamplingEnabled(displayToken, + mFdp.ConsumeBool() /*enable*/, + mFdp.ConsumeIntegral(), + mFdp.ConsumeIntegral()); + + sp stopLayerHandle; + sp listener = sp::make(); + sp sampleListener = + new gui::IRegionSamplingListenerDelegator(listener); + SurfaceComposerClient::addRegionSamplingListener(getRect(&mFdp), stopLayerHandle, + sampleListener); + sp fpsListener; + SurfaceComposerClient::addFpsListener(mFdp.ConsumeIntegral(), fpsListener); +} + +void SurfaceComposerClientFuzzer::invokeSurfaceComposerClientBinder() { + sp client(new FakeBnSurfaceComposerClient()); + fuzzService(client.get(), std::move(mFdp)); +} + +void SurfaceComposerClientFuzzer::process() { + invokeSurfaceComposerClient(); + invokeSurfaceComposerTransaction(); + invokeSurfaceComposerClientBinder(); +} + +extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) { + SurfaceComposerClientFuzzer surfaceComposerClientFuzzer(data, size); + surfaceComposerClientFuzzer.process(); + return 0; +} diff --git a/libs/gui/fuzzer/libgui_surfaceComposer_fuzzer.cpp b/libs/gui/fuzzer/libgui_surfaceComposer_fuzzer.cpp new file mode 100644 index 0000000000000000000000000000000000000000..6d5427bc9ebdbcea549bf3faf346054051ddd855 --- /dev/null +++ b/libs/gui/fuzzer/libgui_surfaceComposer_fuzzer.cpp @@ -0,0 +1,41 @@ +/* + * 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. + */ + +#include +#include +#include + +using namespace android; + +class SurfaceComposerFuzzer { +public: + SurfaceComposerFuzzer(const uint8_t* data, size_t size) : mFdp(data, size){}; + void process(); + +private: + FuzzedDataProvider mFdp; +}; + +void SurfaceComposerFuzzer::process() { + sp composer(new FakeBnSurfaceComposer()); + fuzzService(composer.get(), std::move(mFdp)); +} + +extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) { + SurfaceComposerFuzzer surfaceComposerFuzzer(data, size); + surfaceComposerFuzzer.process(); + return 0; +} diff --git a/libs/gui/include/gui/AidlStatusUtil.h b/libs/gui/include/gui/AidlStatusUtil.h new file mode 100644 index 0000000000000000000000000000000000000000..55be27bf3547d59f7d7b5cf9dc0a0210b472db74 --- /dev/null +++ b/libs/gui/include/gui/AidlStatusUtil.h @@ -0,0 +1,114 @@ +/* + * 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. + */ + +#pragma once + +#include + +// Extracted from frameworks/av/media/libaudioclient/include/media/AidlConversionUtil.h +namespace android::gui::aidl_utils { + +/** + * Return the equivalent Android status_t from a binder exception code. + * + * Generally one should use statusTFromBinderStatus() instead. + * + * Exception codes can be generated from a remote Java service exception, translate + * them for use on the Native side. + * + * Note: for EX_TRANSACTION_FAILED and EX_SERVICE_SPECIFIC a more detailed error code + * can be found from transactionError() or serviceSpecificErrorCode(). + */ +static inline status_t statusTFromExceptionCode(int32_t exceptionCode) { + using namespace ::android::binder; + switch (exceptionCode) { + case Status::EX_NONE: + return OK; + case Status::EX_SECURITY: // Java SecurityException, rethrows locally in Java + return PERMISSION_DENIED; + case Status::EX_BAD_PARCELABLE: // Java BadParcelableException, rethrows in Java + case Status::EX_ILLEGAL_ARGUMENT: // Java IllegalArgumentException, rethrows in Java + case Status::EX_NULL_POINTER: // Java NullPointerException, rethrows in Java + return BAD_VALUE; + case Status::EX_ILLEGAL_STATE: // Java IllegalStateException, rethrows in Java + case Status::EX_UNSUPPORTED_OPERATION: // Java UnsupportedOperationException, rethrows + return INVALID_OPERATION; + case Status::EX_HAS_REPLY_HEADER: // Native strictmode violation + case Status::EX_PARCELABLE: // Java bootclass loader (not standard exception), rethrows + case Status::EX_NETWORK_MAIN_THREAD: // Java NetworkOnMainThreadException, rethrows + case Status::EX_TRANSACTION_FAILED: // Native - see error code + case Status::EX_SERVICE_SPECIFIC: // Java ServiceSpecificException, + // rethrows in Java with integer error code + return UNKNOWN_ERROR; + } + return UNKNOWN_ERROR; +} + +/** + * Return the equivalent Android status_t from a binder status. + * + * Used to handle errors from a AIDL method declaration + * + * [oneway] void method(type0 param0, ...) + * + * or the following (where return_type is not a status_t) + * + * return_type method(type0 param0, ...) + */ +static inline status_t statusTFromBinderStatus(const ::android::binder::Status &status) { + return status.isOk() ? OK // check OK, + : status.serviceSpecificErrorCode() // service-side error, not standard Java exception + // (fromServiceSpecificError) + ?: status.transactionError() // a native binder transaction error (fromStatusT) + ?: statusTFromExceptionCode(status.exceptionCode()); // a service-side error with a + // standard Java exception (fromExceptionCode) +} + +/** + * Return a binder::Status from native service status. + * + * This is used for methods not returning an explicit status_t, + * where Java callers expect an exception, not an integer return value. + */ +static inline ::android::binder::Status binderStatusFromStatusT( + status_t status, const char *optionalMessage = nullptr) { + const char *const emptyIfNull = optionalMessage == nullptr ? "" : optionalMessage; + // From binder::Status instructions: + // Prefer a generic exception code when possible, then a service specific + // code, and finally a status_t for low level failures or legacy support. + // Exception codes and service specific errors map to nicer exceptions for + // Java clients. + + using namespace ::android::binder; + switch (status) { + case OK: + return Status::ok(); + case PERMISSION_DENIED: // throw SecurityException on Java side + return Status::fromExceptionCode(Status::EX_SECURITY, emptyIfNull); + case BAD_VALUE: // throw IllegalArgumentException on Java side + return Status::fromExceptionCode(Status::EX_ILLEGAL_ARGUMENT, emptyIfNull); + case INVALID_OPERATION: // throw IllegalStateException on Java side + return Status::fromExceptionCode(Status::EX_ILLEGAL_STATE, emptyIfNull); + } + + // A service specific error will not show on status.transactionError() so + // be sure to use statusTFromBinderStatus() for reliable error handling. + + // throw a ServiceSpecificException. + return Status::fromServiceSpecificError(status, emptyIfNull); +} + +} // namespace android::gui::aidl_utils diff --git a/libs/gui/include/gui/BLASTBufferQueue.h b/libs/gui/include/gui/BLASTBufferQueue.h index 40ffea68094bb80e9559af6cbe1fa4296c29d1e3..a49a85984f9e4af3ff9b6601296e12180374b35e 100644 --- a/libs/gui/include/gui/BLASTBufferQueue.h +++ b/libs/gui/include/gui/BLASTBufferQueue.h @@ -44,25 +44,25 @@ public: mCurrentlyConnected(false), mPreviouslyConnected(false) {} - void onDisconnect() override; + void onDisconnect() override EXCLUDES(mMutex); void addAndGetFrameTimestamps(const NewFrameEventsEntry* newTimestamps, - FrameEventHistoryDelta* outDelta) override REQUIRES(mMutex); + FrameEventHistoryDelta* outDelta) override EXCLUDES(mMutex); void updateFrameTimestamps(uint64_t frameNumber, nsecs_t refreshStartTime, const sp& gpuCompositionDoneFence, const sp& presentFence, const sp& prevReleaseFence, CompositorTiming compositorTiming, nsecs_t latchTime, - nsecs_t dequeueReadyTime) REQUIRES(mMutex); - void getConnectionEvents(uint64_t frameNumber, bool* needsDisconnect); + nsecs_t dequeueReadyTime) EXCLUDES(mMutex); + void getConnectionEvents(uint64_t frameNumber, bool* needsDisconnect) EXCLUDES(mMutex); void resizeFrameEventHistory(size_t newSize); protected: - void onSidebandStreamChanged() override REQUIRES(mMutex); + void onSidebandStreamChanged() override EXCLUDES(mMutex); private: const wp mBLASTBufferQueue; - uint64_t mCurrentFrameNumber = 0; + uint64_t mCurrentFrameNumber GUARDED_BY(mMutex) = 0; Mutex mMutex; ConsumerFrameEventHistory mFrameEventHistory GUARDED_BY(mMutex); @@ -71,9 +71,7 @@ private: bool mPreviouslyConnected GUARDED_BY(mMutex); }; -class BLASTBufferQueue - : public ConsumerBase::FrameAvailableListener, public BufferItemConsumer::BufferFreedListener -{ +class BLASTBufferQueue : public ConsumerBase::FrameAvailableListener { public: BLASTBufferQueue(const std::string& name, bool updateDestinationFrame = true); BLASTBufferQueue(const std::string& name, const sp& surface, int width, @@ -85,7 +83,6 @@ public: sp getSurface(bool includeSurfaceControlHandle); bool isSameSurfaceControl(const sp& surfaceControl) const; - void onBufferFreed(const wp&/* graphicBuffer*/) override { /* TODO */ } void onFrameReplaced(const BufferItem& item) override; void onFrameAvailable(const BufferItem& item) override; void onFrameDequeued(const uint64_t) override; @@ -99,10 +96,11 @@ public: std::optional currentMaxAcquiredBufferCount); void releaseBufferCallbackLocked(const ReleaseCallbackId& id, const sp& releaseFence, std::optional currentMaxAcquiredBufferCount, - bool fakeRelease); - void syncNextTransaction(std::function callback, + bool fakeRelease) REQUIRES(mMutex); + bool syncNextTransaction(std::function callback, bool acquireSingleBuffer = true); void stopContinuousSyncTransaction(); + void clearSyncTransaction(); void mergeWithNextTransaction(SurfaceComposerClient::Transaction* t, uint64_t frameNumber); void applyPendingTransactions(uint64_t frameNumber); @@ -117,15 +115,12 @@ public: uint32_t getLastTransformHint() const; uint64_t getLastAcquiredFrameNum(); - void abandon(); /** - * Set a callback to be invoked when we are hung. The boolean parameter - * indicates whether the hang is due to an unfired fence. - * TODO: The boolean is always true atm, unfired fence is - * the only case we detect. + * Set a callback to be invoked when we are hung. The string parameter + * indicates the reason for the hang. */ - void setTransactionHangCallback(std::function callback); + void setTransactionHangCallback(std::function callback); virtual ~BLASTBufferQueue(); @@ -161,7 +156,7 @@ private: // mNumAcquired (buffers that queued to SF) mPendingRelease.size() (buffers that are held by // blast). This counter is read by android studio profiler. std::string mQueuedBufferTrace; - sp mSurfaceControl; + sp mSurfaceControl GUARDED_BY(mMutex); mutable std::mutex mMutex; std::condition_variable mCallbackCV; @@ -173,6 +168,11 @@ private: int32_t mNumFrameAvailable GUARDED_BY(mMutex) = 0; int32_t mNumAcquired GUARDED_BY(mMutex) = 0; + // A value used to identify if a producer has been changed for the same SurfaceControl. + // This is needed to know when the frame number has been reset to make sure we don't + // latch stale buffers and that we don't wait on barriers from an old producer. + uint32_t mProducerId = 0; + // Keep a reference to the submitted buffers so we can release when surfaceflinger drops the // buffer or the buffer has been presented and a new buffer is ready to be presented. std::unordered_map mSubmitted @@ -249,7 +249,7 @@ private: // Queues up transactions using this token in SurfaceFlinger. This prevents queued up // transactions from other parts of the client from blocking this transaction. - const sp mApplyToken GUARDED_BY(mMutex) = new BBinder(); + const sp mApplyToken GUARDED_BY(mMutex) = sp::make(); // Guards access to mDequeueTimestamps since we cannot hold to mMutex in onFrameDequeued or // we will deadlock. @@ -263,7 +263,7 @@ private: // callback for them. std::queue> mSurfaceControlsWithPendingCallback GUARDED_BY(mMutex); - uint32_t mCurrentMaxAcquiredBufferCount; + uint32_t mCurrentMaxAcquiredBufferCount GUARDED_BY(mMutex); // Flag to determine if syncTransaction should only acquire a single buffer and then clear or // continue to acquire buffers until explicitly cleared @@ -287,10 +287,10 @@ private: // need to set this flag, notably only in the case where we are transitioning from a previous // transaction applied by us (one way, may not yet have reached server) and an upcoming // transaction that will be applied by some sync consumer. - bool mAppliedLastTransaction = false; - uint64_t mLastAppliedFrameNumber = 0; + bool mAppliedLastTransaction GUARDED_BY(mMutex) = false; + uint64_t mLastAppliedFrameNumber GUARDED_BY(mMutex) = 0; - std::function mTransactionHangCallback; + std::function mTransactionHangCallback; std::unordered_set mSyncedFrameNumbers GUARDED_BY(mMutex); }; diff --git a/libs/gui/include/gui/Choreographer.h b/libs/gui/include/gui/Choreographer.h new file mode 100644 index 0000000000000000000000000000000000000000..1df9b11432d62dff174c387cde436d2d4907b66c --- /dev/null +++ b/libs/gui/include/gui/Choreographer.h @@ -0,0 +1,140 @@ +/* + * 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 +#include +#include + +#include +#include +#include + +namespace android { +using gui::VsyncEventData; + +struct FrameCallback { + AChoreographer_frameCallback callback; + AChoreographer_frameCallback64 callback64; + AChoreographer_vsyncCallback vsyncCallback; + void* data; + nsecs_t dueTime; + + inline bool operator<(const FrameCallback& rhs) const { + // Note that this is intentionally flipped because we want callbacks due sooner to be at + // the head of the queue + return dueTime > rhs.dueTime; + } +}; + +struct RefreshRateCallback { + AChoreographer_refreshRateCallback callback; + void* data; + bool firstCallbackFired = false; +}; + +class Choreographer; + +/** + * Implementation of AChoreographerFrameCallbackData. + */ +struct ChoreographerFrameCallbackDataImpl { + int64_t frameTimeNanos{0}; + + VsyncEventData vsyncEventData; + + const Choreographer* choreographer; +}; + +class Choreographer : public DisplayEventDispatcher, public MessageHandler { +public: + struct Context { + std::mutex lock; + std::vector ptrs GUARDED_BY(lock); + std::map startTimes GUARDED_BY(lock); + bool registeredToDisplayManager GUARDED_BY(lock) = false; + + std::atomic mLastKnownVsync = -1; + }; + static Context gChoreographers; + + explicit Choreographer(const sp& looper, const sp& layerHandle = nullptr) + EXCLUDES(gChoreographers.lock); + void postFrameCallbackDelayed(AChoreographer_frameCallback cb, + AChoreographer_frameCallback64 cb64, + AChoreographer_vsyncCallback vsyncCallback, void* data, + nsecs_t delay); + void registerRefreshRateCallback(AChoreographer_refreshRateCallback cb, void* data) + EXCLUDES(gChoreographers.lock); + void unregisterRefreshRateCallback(AChoreographer_refreshRateCallback cb, void* data); + // Drains the queue of pending vsync periods and dispatches refresh rate + // updates to callbacks. + // The assumption is that this method is only called on a single + // processing thread, either by looper or by AChoreographer_handleEvents + void handleRefreshRateUpdates(); + void scheduleLatestConfigRequest(); + + enum { + MSG_SCHEDULE_CALLBACKS = 0, + MSG_SCHEDULE_VSYNC = 1, + MSG_HANDLE_REFRESH_RATE_UPDATES = 2, + }; + virtual void handleMessage(const Message& message) override; + + static void initJVM(JNIEnv* env); + static Choreographer* getForThread(); + static void signalRefreshRateCallbacks(nsecs_t vsyncPeriod) EXCLUDES(gChoreographers.lock); + static int64_t getStartTimeNanosForVsyncId(AVsyncId vsyncId) EXCLUDES(gChoreographers.lock); + virtual ~Choreographer() override EXCLUDES(gChoreographers.lock); + int64_t getFrameInterval() const; + bool inCallback() const; + +private: + Choreographer(const Choreographer&) = delete; + + void dispatchVsync(nsecs_t timestamp, PhysicalDisplayId displayId, uint32_t count, + VsyncEventData vsyncEventData) override; + void dispatchHotplug(nsecs_t timestamp, PhysicalDisplayId displayId, bool connected) override; + void dispatchModeChanged(nsecs_t timestamp, PhysicalDisplayId displayId, int32_t modeId, + nsecs_t vsyncPeriod) override; + void dispatchNullEvent(nsecs_t, PhysicalDisplayId) override; + void dispatchFrameRateOverrides(nsecs_t timestamp, PhysicalDisplayId displayId, + std::vector overrides) override; + + void scheduleCallbacks(); + + ChoreographerFrameCallbackDataImpl createFrameCallbackData(nsecs_t timestamp) const; + void registerStartTime() const; + + std::mutex mLock; + // Protected by mLock + std::priority_queue mFrameCallbacks; + std::vector mRefreshRateCallbacks; + + nsecs_t mLatestVsyncPeriod = -1; + VsyncEventData mLastVsyncEventData; + bool mInCallback = false; + + const sp mLooper; + const std::thread::id mThreadId; + + // Approximation of num_threads_using_choreographer * num_frames_of_history with leeway. + static constexpr size_t kMaxStartTimes = 250; +}; + +} // namespace android \ No newline at end of file diff --git a/libs/gui/include/gui/CompositorTiming.h b/libs/gui/include/gui/CompositorTiming.h new file mode 100644 index 0000000000000000000000000000000000000000..cb8ca7a15cddf9347cc2c5d562a4473dfe38e447 --- /dev/null +++ b/libs/gui/include/gui/CompositorTiming.h @@ -0,0 +1,43 @@ +/* + * 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::gui { + +// Expected timing of the next composited frame, based on the timing of the latest frames. +struct CompositorTiming { + static constexpr nsecs_t kDefaultVsyncPeriod = 16'666'667; + + CompositorTiming() = default; + CompositorTiming(nsecs_t vsyncDeadline, nsecs_t vsyncPeriod, nsecs_t vsyncPhase, + nsecs_t presentLatency); + + // Time point when compositing is expected to start. + nsecs_t deadline = 0; + + // Duration between consecutive frames. In other words, the VSYNC period. + nsecs_t interval = kDefaultVsyncPeriod; + + // Duration between composite start and present. For missed frames, the extra latency is rounded + // to a multiple of the VSYNC period, such that the remainder (presentLatency % interval) always + // evaluates to the VSYNC phase offset. + nsecs_t presentLatency = kDefaultVsyncPeriod; +}; + +} // namespace android::gui diff --git a/libs/gui/include/gui/DisplayCaptureArgs.h b/libs/gui/include/gui/DisplayCaptureArgs.h index ec884cfa8c0078448302ca7d057397f48f50e3ec..2676e0a338205ecf50a103e78a494a97b0b6e393 100644 --- a/libs/gui/include/gui/DisplayCaptureArgs.h +++ b/libs/gui/include/gui/DisplayCaptureArgs.h @@ -22,8 +22,11 @@ #include #include #include +#include #include #include +#include +#include namespace android::gui { @@ -38,7 +41,7 @@ struct CaptureArgs : public Parcelable { bool captureSecureLayers{false}; int32_t uid{UNSET_UID}; // Force capture to be in a color space. If the value is ui::Dataspace::UNKNOWN, the captured - // result will be in the display's colorspace. + // result will be in a colorspace appropriate for capturing the display contents // The display may use non-RGB dataspace (ex. displayP3) that could cause pixel data could be // different from SRGB (byte per color), and failed when checking colors in tests. // NOTE: In normal cases, we want the screen to be captured in display's colorspace. @@ -54,6 +57,17 @@ struct CaptureArgs : public Parcelable { bool grayscale = false; + std::unordered_set, SpHash> excludeHandles; + + // Hint that the caller will use the screenshot animation as part of a transition animation. + // The canonical example would be screen rotation - in such a case any color shift in the + // screenshot is a detractor so composition in the display's colorspace is required. + // Otherwise, the system may choose a colorspace that is more appropriate for use-cases + // such as file encoding or for blending HDR content into an ap's UI, where the display's + // exact colorspace is not an appropriate intermediate result. + // Note that if the caller is requesting a specific dataspace, this hint does nothing. + bool hintForSeamlessTransition = false; + virtual status_t writeToParcel(Parcel* output) const; virtual status_t readFromParcel(const Parcel* input); }; diff --git a/libs/gui/include/gui/DisplayEventDispatcher.h b/libs/gui/include/gui/DisplayEventDispatcher.h index a3425395bfe8411b6835f6264656e562687bda14..140efa6d970e4ba5d7b95d96298af6177336b891 100644 --- a/libs/gui/include/gui/DisplayEventDispatcher.h +++ b/libs/gui/include/gui/DisplayEventDispatcher.h @@ -23,10 +23,11 @@ using FrameRateOverride = DisplayEventReceiver::Event::FrameRateOverride; class DisplayEventDispatcher : public LooperCallback { public: - explicit DisplayEventDispatcher( - const sp& looper, - ISurfaceComposer::VsyncSource vsyncSource = ISurfaceComposer::eVsyncSourceApp, - ISurfaceComposer::EventRegistrationFlags eventRegistration = {}); + explicit DisplayEventDispatcher(const sp& looper, + gui::ISurfaceComposer::VsyncSource vsyncSource = + gui::ISurfaceComposer::VsyncSource::eVsyncSourceApp, + EventRegistrationFlags eventRegistration = {}, + const sp& layerHandle = nullptr); status_t initialize(); void dispose(); diff --git a/libs/gui/include/gui/DisplayEventReceiver.h b/libs/gui/include/gui/DisplayEventReceiver.h index cf7a4e5522e7e9b58f48f3fd7c7d20671ceb79fb..7fd6c35c5e247375d5029e6fe0c97301e696da49 100644 --- a/libs/gui/include/gui/DisplayEventReceiver.h +++ b/libs/gui/include/gui/DisplayEventReceiver.h @@ -20,20 +20,26 @@ #include #include +#include + #include #include #include +#include #include -#include #include +#include + // ---------------------------------------------------------------------------- namespace android { // ---------------------------------------------------------------------------- +using EventRegistrationFlags = ftl::Flags; + using gui::IDisplayEventConnection; using gui::ParcelableVsyncEventData; using gui::VsyncEventData; @@ -111,9 +117,10 @@ public: * To receive ModeChanged and/or FrameRateOverrides events specify this in * the constructor. Other events start being delivered immediately. */ - explicit DisplayEventReceiver( - ISurfaceComposer::VsyncSource vsyncSource = ISurfaceComposer::eVsyncSourceApp, - ISurfaceComposer::EventRegistrationFlags eventRegistration = {}); + explicit DisplayEventReceiver(gui::ISurfaceComposer::VsyncSource vsyncSource = + gui::ISurfaceComposer::VsyncSource::eVsyncSourceApp, + EventRegistrationFlags eventRegistration = {}, + const sp& layerHandle = nullptr); /* * ~DisplayEventReceiver severs the connection with SurfaceFlinger, new events diff --git a/libs/gui/include/gui/DisplayInfo.h b/libs/gui/include/gui/DisplayInfo.h index 74f33a2a8702b743bc9e98acf44973184d02aed1..42b62c755cbd6df842534c103fc8bef38a379fd6 100644 --- a/libs/gui/include/gui/DisplayInfo.h +++ b/libs/gui/include/gui/DisplayInfo.h @@ -41,6 +41,8 @@ struct DisplayInfo : public Parcelable { status_t writeToParcel(android::Parcel*) const override; status_t readFromParcel(const android::Parcel*) override; + + void dump(std::string& result, const char* prefix = "") const; }; } // namespace android::gui \ No newline at end of file diff --git a/libs/gui/include/gui/FenceMonitor.h b/libs/gui/include/gui/FenceMonitor.h new file mode 100644 index 0000000000000000000000000000000000000000..62ceddee5f981e0f642d0cd38de2377dc195ed60 --- /dev/null +++ b/libs/gui/include/gui/FenceMonitor.h @@ -0,0 +1,44 @@ +/* + * 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. + */ + +#pragma once + +#include +#include +#include + +#include + +namespace android::gui { + +class FenceMonitor { +public: + explicit FenceMonitor(const char* name); + void queueFence(const sp& fence); + +private: + void loop(); + void threadLoop(); + + const char* mName; + uint32_t mFencesQueued; + uint32_t mFencesSignaled; + std::deque> mQueue; + std::condition_variable mCondition; + std::mutex mMutex; +}; + +} // namespace android::gui \ No newline at end of file diff --git a/libs/gui/include/gui/FrameTimestamps.h b/libs/gui/include/gui/FrameTimestamps.h index 968aa2b25740bc90b92cd8f586fdb36492fd78de..3d1be4d2ebc20918209b1cc3d38a10708ea5d39b 100644 --- a/libs/gui/include/gui/FrameTimestamps.h +++ b/libs/gui/include/gui/FrameTimestamps.h @@ -17,6 +17,9 @@ #ifndef ANDROID_GUI_FRAMETIMESTAMPS_H #define ANDROID_GUI_FRAMETIMESTAMPS_H +#include + +#include #include #include #include @@ -31,22 +34,8 @@ namespace android { struct FrameEvents; class FrameEventHistoryDelta; - -// Identifiers for all the events that may be recorded or reported. -enum class FrameEvent { - POSTED, - REQUESTED_PRESENT, - LATCH, - ACQUIRE, - FIRST_REFRESH_START, - LAST_REFRESH_START, - GPU_COMPOSITION_DONE, - DISPLAY_PRESENT, - DEQUEUE_READY, - RELEASE, - EVENT_COUNT, // Not an actual event. -}; - +using gui::CompositorTiming; +using gui::FrameEvent; // A collection of timestamps corresponding to a single frame. struct FrameEvents { @@ -96,12 +85,6 @@ struct FrameEvents { std::shared_ptr releaseFence{FenceTime::NO_FENCE}; }; -struct CompositorTiming { - nsecs_t deadline{0}; - nsecs_t interval{16666667}; - nsecs_t presentLatency{16666667}; -}; - // A short history of frames that are synchronized between the consumer and // producer via deltas. class FrameEventHistory { diff --git a/libs/gui/include/gui/GLConsumer.h b/libs/gui/include/gui/GLConsumer.h index 2f538ffb867734a7f2ebed7fc2b4c885d13439a7..ba268ab17a39b01c1d8b774f0cf60abb97a1847c 100644 --- a/libs/gui/include/gui/GLConsumer.h +++ b/libs/gui/include/gui/GLConsumer.h @@ -138,6 +138,10 @@ public: const sp& buf, const Rect& cropRect, uint32_t transform, bool filtering); + static void computeTransformMatrix(float outTransform[16], float bufferWidth, + float bufferHeight, PixelFormat pixelFormat, + const Rect& cropRect, uint32_t transform, bool filtering); + // Scale the crop down horizontally or vertically such that it has the // same aspect ratio as the buffer does. static Rect scaleDownCrop(const Rect& crop, uint32_t bufferWidth, diff --git a/libs/gui/include/gui/ISurfaceComposer.h b/libs/gui/include/gui/ISurfaceComposer.h index a610e940be59835bf0cc4d5b5b33383d234aa947..3ff6735926a45746643424b6ae76fe5736018774 100644 --- a/libs/gui/include/gui/ISurfaceComposer.h +++ b/libs/gui/include/gui/ISurfaceComposer.h @@ -16,19 +16,19 @@ #pragma once +#include #include +#include #include #include #include #include #include -#include #include #include +#include #include #include -#include -#include #include #include #include @@ -57,17 +57,14 @@ namespace android { struct client_cache_t; -struct ComposerState; +class ComposerState; struct DisplayStatInfo; struct DisplayState; struct InputWindowCommands; -class LayerDebugInfo; class HdrCapabilities; -class IGraphicBufferProducer; -class ISurfaceComposerClient; class Rect; -enum class FrameEvent; +using gui::FrameTimelineInfo; using gui::IDisplayEventConnection; using gui::IRegionSamplingListener; using gui::IScreenCaptureListener; @@ -77,6 +74,7 @@ namespace gui { struct DisplayCaptureArgs; struct LayerCaptureArgs; +class LayerDebugInfo; } // namespace gui @@ -85,7 +83,6 @@ namespace ui { struct DisplayMode; struct DisplayState; struct DynamicDisplayInfo; -struct StaticDisplayInfo; } // namespace ui @@ -97,11 +94,8 @@ class ISurfaceComposer: public IInterface { public: DECLARE_META_INTERFACE(SurfaceComposer) - static constexpr size_t MAX_LAYERS = 4096; - // flags for setTransactionState() enum { - eSynchronous = 0x01, eAnimation = 0x02, // Explicit indication that this transaction and others to follow will likely result in a @@ -110,328 +104,20 @@ public: // (sf vsync offset - debug.sf.early_phase_offset_ns). SurfaceFlinger will continue to be // in the early configuration until it receives eEarlyWakeupEnd. These flags are // expected to be used by WindowManager only and are guarded by - // android.permission.ACCESS_SURFACE_FLINGER + // android.permission.WAKEUP_SURFACE_FLINGER eEarlyWakeupStart = 0x08, eEarlyWakeupEnd = 0x10, eOneWay = 0x20 }; - enum VsyncSource { - eVsyncSourceApp = 0, - eVsyncSourceSurfaceFlinger = 1 - }; - - enum class EventRegistration { - modeChanged = 1 << 0, - frameRateOverride = 1 << 1, - }; - - using EventRegistrationFlags = ftl::Flags; - - /* - * Create a connection with SurfaceFlinger. - */ - virtual sp createConnection() = 0; - - /* return an IDisplayEventConnection */ - virtual sp createDisplayEventConnection( - VsyncSource vsyncSource = eVsyncSourceApp, - EventRegistrationFlags eventRegistration = {}) = 0; - /* open/close transactions. requires ACCESS_SURFACE_FLINGER permission */ virtual status_t setTransactionState( - const FrameTimelineInfo& frameTimelineInfo, const Vector& state, + const FrameTimelineInfo& frameTimelineInfo, Vector& state, const Vector& displays, uint32_t flags, const sp& applyToken, - const InputWindowCommands& inputWindowCommands, int64_t desiredPresentTime, - bool isAutoTimestamp, const client_cache_t& uncacheBuffer, bool hasListenerCallbacks, - const std::vector& listenerCallbacks, uint64_t transactionId) = 0; - - /* signal that we're done booting. - * Requires ACCESS_SURFACE_FLINGER permission - */ - virtual void bootFinished() = 0; - - /* verify that an IGraphicBufferProducer was created by SurfaceFlinger. - */ - virtual bool authenticateSurfaceTexture( - const sp& surface) const = 0; - - /* Returns the frame timestamps supported by SurfaceFlinger. - */ - virtual status_t getSupportedFrameTimestamps( - std::vector* outSupported) const = 0; - - /** - * Gets immutable information about given physical display. - */ - virtual status_t getStaticDisplayInfo(const sp& display, ui::StaticDisplayInfo*) = 0; - - /** - * Gets dynamic information about given physical display. - */ - virtual status_t getDynamicDisplayInfo(const sp& display, ui::DynamicDisplayInfo*) = 0; - - virtual status_t getDisplayNativePrimaries(const sp& display, - ui::DisplayPrimaries& primaries) = 0; - virtual status_t setActiveColorMode(const sp& display, - ui::ColorMode colorMode) = 0; - - /** - * Sets the user-preferred display mode that a device should boot in. - */ - virtual status_t setBootDisplayMode(const sp& display, ui::DisplayModeId) = 0; - - /* Clears the frame statistics for animations. - * - * Requires the ACCESS_SURFACE_FLINGER permission. - */ - virtual status_t clearAnimationFrameStats() = 0; - - /* Gets the frame statistics for animations. - * - * Requires the ACCESS_SURFACE_FLINGER permission. - */ - virtual status_t getAnimationFrameStats(FrameStats* outStats) const = 0; - - /* Overrides the supported HDR modes for the given display device. - * - * Requires the ACCESS_SURFACE_FLINGER permission. - */ - virtual status_t overrideHdrTypes(const sp& display, - const std::vector& hdrTypes) = 0; - - /* Pulls surfaceflinger atoms global stats and layer stats to pipe to statsd. - * - * Requires the calling uid be from system server. - */ - virtual status_t onPullAtom(const int32_t atomId, std::string* outData, bool* success) = 0; - - virtual status_t enableVSyncInjections(bool enable) = 0; - - virtual status_t injectVSync(nsecs_t when) = 0; - - /* Gets the list of active layers in Z order for debugging purposes - * - * Requires the ACCESS_SURFACE_FLINGER permission. - */ - virtual status_t getLayerDebugInfo(std::vector* outLayers) = 0; - - virtual status_t getColorManagement(bool* outGetColorManagement) const = 0; - - /* Gets the composition preference of the default data space and default pixel format, - * as well as the wide color gamut data space and wide color gamut pixel format. - * If the wide color gamut data space is V0_SRGB, then it implies that the platform - * has no wide color gamut support. - * - * Requires the ACCESS_SURFACE_FLINGER permission. - */ - virtual status_t getCompositionPreference(ui::Dataspace* defaultDataspace, - ui::PixelFormat* defaultPixelFormat, - ui::Dataspace* wideColorGamutDataspace, - ui::PixelFormat* wideColorGamutPixelFormat) const = 0; - /* - * Requires the ACCESS_SURFACE_FLINGER permission. - */ - virtual status_t getDisplayedContentSamplingAttributes(const sp& display, - ui::PixelFormat* outFormat, - ui::Dataspace* outDataspace, - uint8_t* outComponentMask) const = 0; - - /* Turns on the color sampling engine on the display. - * - * Requires the ACCESS_SURFACE_FLINGER permission. - */ - virtual status_t setDisplayContentSamplingEnabled(const sp& display, bool enable, - uint8_t componentMask, - uint64_t maxFrames) = 0; - - /* Returns statistics on the color profile of the last frame displayed for a given display - * - * Requires the ACCESS_SURFACE_FLINGER permission. - */ - virtual status_t getDisplayedContentSample(const sp& display, uint64_t maxFrames, - uint64_t timestamp, - DisplayedFrameStats* outStats) const = 0; - - /* - * Gets whether SurfaceFlinger can support protected content in GPU composition. - * Requires the ACCESS_SURFACE_FLINGER permission. - */ - virtual status_t getProtectedContentSupport(bool* outSupported) const = 0; - - /* Registers a listener to stream median luma updates from SurfaceFlinger. - * - * The sampling area is bounded by both samplingArea and the given stopLayerHandle - * (i.e., only layers behind the stop layer will be captured and sampled). - * - * Multiple listeners may be provided so long as they have independent listeners. - * If multiple listeners are provided, the effective sampling region for each listener will - * be bounded by whichever stop layer has a lower Z value. - * - * Requires the same permissions as captureLayers and captureScreen. - */ - virtual status_t addRegionSamplingListener(const Rect& samplingArea, - const sp& stopLayerHandle, - const sp& listener) = 0; - - /* - * Removes a listener that was streaming median luma updates from SurfaceFlinger. - */ - virtual status_t removeRegionSamplingListener(const sp& listener) = 0; - - /* Registers a listener that streams fps updates from SurfaceFlinger. - * - * The listener will stream fps updates for the layer tree rooted at the layer denoted by the - * task ID, i.e., the layer must have the task ID as part of its layer metadata with key - * METADATA_TASK_ID. If there is no such layer, then no fps is expected to be reported. - * - * Multiple listeners may be supported. - * - * Requires the READ_FRAME_BUFFER permission. - */ - virtual status_t addFpsListener(int32_t taskId, const sp& listener) = 0; - /* - * Removes a listener that was streaming fps updates from SurfaceFlinger. - */ - virtual status_t removeFpsListener(const sp& listener) = 0; - - /* Registers a listener to receive tunnel mode enabled updates from SurfaceFlinger. - * - * Requires ACCESS_SURFACE_FLINGER permission. - */ - virtual status_t addTunnelModeEnabledListener( - const sp& listener) = 0; - - /* - * Removes a listener that was receiving tunnel mode enabled updates from SurfaceFlinger. - * - * Requires ACCESS_SURFACE_FLINGER permission. - */ - virtual status_t removeTunnelModeEnabledListener( - const sp& listener) = 0; - - /* Sets the refresh rate boundaries for the display. - * - * The primary refresh rate range represents display manager's general guidance on the display - * modes we'll consider when switching refresh rates. Unless we get an explicit signal from an - * app, we should stay within this range. - * - * The app request refresh rate range allows us to consider more display modes when switching - * refresh rates. Although we should generally stay within the primary range, specific - * considerations, such as layer frame rate settings specified via the setFrameRate() api, may - * cause us to go outside the primary range. We never go outside the app request range. The app - * request range will be greater than or equal to the primary refresh rate range, never smaller. - * - * defaultMode is used to narrow the list of display modes SurfaceFlinger will consider - * switching between. Only modes with a mode group and resolution matching defaultMode - * will be considered for switching. The defaultMode corresponds to an ID of mode in the list - * of supported modes returned from getDynamicDisplayInfo(). - */ - virtual status_t setDesiredDisplayModeSpecs( - const sp& displayToken, ui::DisplayModeId defaultMode, - bool allowGroupSwitching, float primaryRefreshRateMin, float primaryRefreshRateMax, - float appRequestRefreshRateMin, float appRequestRefreshRateMax) = 0; - - virtual status_t getDesiredDisplayModeSpecs(const sp& displayToken, - ui::DisplayModeId* outDefaultMode, - bool* outAllowGroupSwitching, - float* outPrimaryRefreshRateMin, - float* outPrimaryRefreshRateMax, - float* outAppRequestRefreshRateMin, - float* outAppRequestRefreshRateMax) = 0; - - /* - * Sets the global configuration for all the shadows drawn by SurfaceFlinger. Shadow follows - * material design guidelines. - * - * ambientColor - * Color to the ambient shadow. The alpha is premultiplied. - * - * spotColor - * Color to the spot shadow. The alpha is premultiplied. The position of the spot shadow - * depends on the light position. - * - * lightPosY/lightPosZ - * Position of the light used to cast the spot shadow. The X value is always the display - * width / 2. - * - * lightRadius - * Radius of the light casting the shadow. - */ - virtual status_t setGlobalShadowSettings(const half4& ambientColor, const half4& spotColor, - float lightPosY, float lightPosZ, - float lightRadius) = 0; - - /* - * Gets whether a display supports DISPLAY_DECORATION layers. - * - * displayToken - * The token of the display. - * outSupport - * An output parameter for whether/how the display supports - * DISPLAY_DECORATION layers. - * - * Returns NO_ERROR upon success. Otherwise, - * NAME_NOT_FOUND if the display is invalid, or - * BAD_VALUE if the output parameter is invalid. - */ - virtual status_t getDisplayDecorationSupport( - const sp& displayToken, - std::optional* - outSupport) const = 0; - - /* - * Sets the intended frame rate for a surface. See ANativeWindow_setFrameRate() for more info. - */ - virtual status_t setFrameRate(const sp& surface, float frameRate, - int8_t compatibility, int8_t changeFrameRateStrategy) = 0; - - /* - * Set the override frame rate for a specified uid by GameManagerService. - * Passing the frame rate and uid to SurfaceFlinger to update the override mapping - * in the scheduler. - */ - virtual status_t setOverrideFrameRate(uid_t uid, float frameRate) = 0; - - /* - * Sets the frame timeline vsync info received from choreographer that corresponds to next - * buffer submitted on that surface. - */ - virtual status_t setFrameTimelineInfo(const sp& surface, - const FrameTimelineInfo& frameTimelineInfo) = 0; - - /* - * Adds a TransactionTraceListener to listen for transaction tracing state updates. - */ - virtual status_t addTransactionTraceListener( - const sp& listener) = 0; - - /** - * Gets priority of the RenderEngine in SurfaceFlinger. - */ - virtual int getGPUContextPriority() = 0; - - /** - * Gets the number of buffers SurfaceFlinger would need acquire. This number - * would be propagated to the client via MIN_UNDEQUEUED_BUFFERS so that the - * client could allocate enough buffers to match SF expectations of the - * pipeline depth. SurfaceFlinger will make sure that it will give the app at - * least the time configured as the 'appDuration' before trying to latch - * the buffer. - * - * The total buffers needed for a given configuration is basically the - * numbers of vsyncs a single buffer is used across the stack. For the default - * configuration a buffer is held ~1 vsync by the app, ~1 vsync by SurfaceFlinger - * and 1 vsync by the display. The extra buffers are calculated as the - * number of additional buffers on top of the 2 buffers already present - * in MIN_UNDEQUEUED_BUFFERS. - */ - virtual status_t getMaxAcquiredBufferCount(int* buffers) const = 0; - - virtual status_t addWindowInfosListener( - const sp& windowInfosListener) const = 0; - virtual status_t removeWindowInfosListener( - const sp& windowInfosListener) const = 0; + InputWindowCommands inputWindowCommands, int64_t desiredPresentTime, + bool isAutoTimestamp, const std::vector& uncacheBuffer, + bool hasListenerCallbacks, const std::vector& listenerCallbacks, + uint64_t transactionId, const std::vector& mergedTransactionIds) = 0; }; // ---------------------------------------------------------------------------- @@ -442,77 +128,77 @@ public: // Note: BOOT_FINISHED must remain this value, it is called from // Java by ActivityManagerService. BOOT_FINISHED = IBinder::FIRST_CALL_TRANSACTION, - CREATE_CONNECTION, - GET_STATIC_DISPLAY_INFO, - CREATE_DISPLAY_EVENT_CONNECTION, - CREATE_DISPLAY, // Deprecated. Autogenerated by .aidl now. - DESTROY_DISPLAY, // Deprecated. Autogenerated by .aidl now. - GET_PHYSICAL_DISPLAY_TOKEN, // Deprecated. Autogenerated by .aidl now. + 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. + GET_PHYSICAL_DISPLAY_TOKEN, // Deprecated. Autogenerated by .aidl now. SET_TRANSACTION_STATE, - AUTHENTICATE_SURFACE, - GET_SUPPORTED_FRAME_TIMESTAMPS, - GET_DISPLAY_MODES, // Deprecated. Use GET_DYNAMIC_DISPLAY_INFO instead. - GET_ACTIVE_DISPLAY_MODE, // Deprecated. Use GET_DYNAMIC_DISPLAY_INFO instead. + AUTHENTICATE_SURFACE, // Deprecated. Autogenerated by .aidl now. + GET_SUPPORTED_FRAME_TIMESTAMPS, // Deprecated. Autogenerated by .aidl now. + GET_DISPLAY_MODES, // Deprecated. Use GET_DYNAMIC_DISPLAY_INFO instead. + GET_ACTIVE_DISPLAY_MODE, // Deprecated. Use GET_DYNAMIC_DISPLAY_INFO instead. GET_DISPLAY_STATE, - CAPTURE_DISPLAY, // Deprecated. Autogenerated by .aidl now. - CAPTURE_LAYERS, // Deprecated. Autogenerated by .aidl now. - CLEAR_ANIMATION_FRAME_STATS, - GET_ANIMATION_FRAME_STATS, - SET_POWER_MODE, // Deprecated. Autogenerated by .aidl now. + CAPTURE_DISPLAY, // Deprecated. Autogenerated by .aidl now. + CAPTURE_LAYERS, // Deprecated. Autogenerated by .aidl now. + CLEAR_ANIMATION_FRAME_STATS, // Deprecated. Autogenerated by .aidl now. + GET_ANIMATION_FRAME_STATS, // Deprecated. Autogenerated by .aidl now. + SET_POWER_MODE, // Deprecated. Autogenerated by .aidl now. GET_DISPLAY_STATS, - GET_HDR_CAPABILITIES, // Deprecated. Use GET_DYNAMIC_DISPLAY_INFO instead. - GET_DISPLAY_COLOR_MODES, // Deprecated. Use GET_DYNAMIC_DISPLAY_INFO instead. - GET_ACTIVE_COLOR_MODE, // Deprecated. Use GET_DYNAMIC_DISPLAY_INFO instead. - SET_ACTIVE_COLOR_MODE, - ENABLE_VSYNC_INJECTIONS, - INJECT_VSYNC, - GET_LAYER_DEBUG_INFO, - GET_COMPOSITION_PREFERENCE, - GET_COLOR_MANAGEMENT, - GET_DISPLAYED_CONTENT_SAMPLING_ATTRIBUTES, - SET_DISPLAY_CONTENT_SAMPLING_ENABLED, + GET_HDR_CAPABILITIES, // Deprecated. Use GET_DYNAMIC_DISPLAY_INFO instead. + GET_DISPLAY_COLOR_MODES, // Deprecated. Use GET_DYNAMIC_DISPLAY_INFO instead. + GET_ACTIVE_COLOR_MODE, // Deprecated. Use GET_DYNAMIC_DISPLAY_INFO instead. + SET_ACTIVE_COLOR_MODE, // Deprecated. Autogenerated by .aidl now. + ENABLE_VSYNC_INJECTIONS, // Deprecated. Autogenerated by .aidl now. + INJECT_VSYNC, // Deprecated. Autogenerated by .aidl now. + GET_LAYER_DEBUG_INFO, // Deprecated. Autogenerated by .aidl now. + GET_COMPOSITION_PREFERENCE, // Deprecated. Autogenerated by .aidl now. + GET_COLOR_MANAGEMENT, // Deprecated. Autogenerated by .aidl now. + GET_DISPLAYED_CONTENT_SAMPLING_ATTRIBUTES, // Deprecated. Autogenerated by .aidl now. + SET_DISPLAY_CONTENT_SAMPLING_ENABLED, // Deprecated. Autogenerated by .aidl now. GET_DISPLAYED_CONTENT_SAMPLE, - GET_PROTECTED_CONTENT_SUPPORT, - IS_WIDE_COLOR_DISPLAY, // Deprecated. Autogenerated by .aidl now. - GET_DISPLAY_NATIVE_PRIMARIES, - GET_PHYSICAL_DISPLAY_IDS, // Deprecated. Autogenerated by .aidl now. - ADD_REGION_SAMPLING_LISTENER, - REMOVE_REGION_SAMPLING_LISTENER, - SET_DESIRED_DISPLAY_MODE_SPECS, - GET_DESIRED_DISPLAY_MODE_SPECS, - GET_DISPLAY_BRIGHTNESS_SUPPORT, // Deprecated. Autogenerated by .aidl now. - SET_DISPLAY_BRIGHTNESS, // Deprecated. Autogenerated by .aidl now. - CAPTURE_DISPLAY_BY_ID, // Deprecated. Autogenerated by .aidl now. - NOTIFY_POWER_BOOST, // Deprecated. Autogenerated by .aidl now. + GET_PROTECTED_CONTENT_SUPPORT, // Deprecated. Autogenerated by .aidl now. + IS_WIDE_COLOR_DISPLAY, // Deprecated. Autogenerated by .aidl now. + GET_DISPLAY_NATIVE_PRIMARIES, // Deprecated. Autogenerated by .aidl now. + GET_PHYSICAL_DISPLAY_IDS, // Deprecated. Autogenerated by .aidl now. + ADD_REGION_SAMPLING_LISTENER, // Deprecated. Autogenerated by .aidl now. + REMOVE_REGION_SAMPLING_LISTENER, // Deprecated. Autogenerated by .aidl now. + SET_DESIRED_DISPLAY_MODE_SPECS, // Deprecated. Autogenerated by .aidl now. + GET_DESIRED_DISPLAY_MODE_SPECS, // Deprecated. Autogenerated by .aidl now. + GET_DISPLAY_BRIGHTNESS_SUPPORT, // Deprecated. Autogenerated by .aidl now. + SET_DISPLAY_BRIGHTNESS, // Deprecated. Autogenerated by .aidl now. + CAPTURE_DISPLAY_BY_ID, // Deprecated. Autogenerated by .aidl now. + NOTIFY_POWER_BOOST, // Deprecated. Autogenerated by .aidl now. SET_GLOBAL_SHADOW_SETTINGS, GET_AUTO_LOW_LATENCY_MODE_SUPPORT, // Deprecated. Use GET_DYNAMIC_DISPLAY_INFO instead. SET_AUTO_LOW_LATENCY_MODE, // Deprecated. Autogenerated by .aidl now. GET_GAME_CONTENT_TYPE_SUPPORT, // Deprecated. Use GET_DYNAMIC_DISPLAY_INFO instead. SET_GAME_CONTENT_TYPE, // Deprecated. Use GET_DYNAMIC_DISPLAY_INFO instead. - SET_FRAME_RATE, + SET_FRAME_RATE, // Deprecated. Autogenerated by .aidl now. // Deprecated. Use DisplayManager.setShouldAlwaysRespectAppRequestedMode(true); ACQUIRE_FRAME_RATE_FLEXIBILITY_TOKEN, - SET_FRAME_TIMELINE_INFO, - ADD_TRANSACTION_TRACE_LISTENER, + SET_FRAME_TIMELINE_INFO, // Deprecated. Autogenerated by .aidl now. + ADD_TRANSACTION_TRACE_LISTENER, // Deprecated. Autogenerated by .aidl now. GET_GPU_CONTEXT_PRIORITY, GET_MAX_ACQUIRED_BUFFER_COUNT, - GET_DYNAMIC_DISPLAY_INFO, - ADD_FPS_LISTENER, - REMOVE_FPS_LISTENER, - OVERRIDE_HDR_TYPES, - ADD_HDR_LAYER_INFO_LISTENER, // Deprecated. Autogenerated by .aidl now. - REMOVE_HDR_LAYER_INFO_LISTENER, // Deprecated. Autogenerated by .aidl now. - ON_PULL_ATOM, - ADD_TUNNEL_MODE_ENABLED_LISTENER, - REMOVE_TUNNEL_MODE_ENABLED_LISTENER, - ADD_WINDOW_INFOS_LISTENER, - REMOVE_WINDOW_INFOS_LISTENER, - GET_PRIMARY_PHYSICAL_DISPLAY_ID, // Deprecated. Autogenerated by .aidl now. + GET_DYNAMIC_DISPLAY_INFO, // Deprecated. Autogenerated by .aidl now. + ADD_FPS_LISTENER, // Deprecated. Autogenerated by .aidl now. + REMOVE_FPS_LISTENER, // Deprecated. Autogenerated by .aidl now. + OVERRIDE_HDR_TYPES, // Deprecated. Autogenerated by .aidl now. + ADD_HDR_LAYER_INFO_LISTENER, // Deprecated. Autogenerated by .aidl now. + REMOVE_HDR_LAYER_INFO_LISTENER, // Deprecated. Autogenerated by .aidl now. + ON_PULL_ATOM, // Deprecated. Autogenerated by .aidl now. + ADD_TUNNEL_MODE_ENABLED_LISTENER, // Deprecated. Autogenerated by .aidl now. + REMOVE_TUNNEL_MODE_ENABLED_LISTENER, // Deprecated. Autogenerated by .aidl now. + ADD_WINDOW_INFOS_LISTENER, // Deprecated. Autogenerated by .aidl now. + REMOVE_WINDOW_INFOS_LISTENER, // Deprecated. Autogenerated by .aidl now. + GET_PRIMARY_PHYSICAL_DISPLAY_ID, // Deprecated. Autogenerated by .aidl now. GET_DISPLAY_DECORATION_SUPPORT, GET_BOOT_DISPLAY_MODE_SUPPORT, // Deprecated. Autogenerated by .aidl now. - SET_BOOT_DISPLAY_MODE, - CLEAR_BOOT_DISPLAY_MODE, // Deprecated. Autogenerated by .aidl now. - SET_OVERRIDE_FRAME_RATE, + SET_BOOT_DISPLAY_MODE, // Deprecated. Autogenerated by .aidl now. + CLEAR_BOOT_DISPLAY_MODE, // Deprecated. Autogenerated by .aidl now. + SET_OVERRIDE_FRAME_RATE, // Deprecated. Autogenerated by .aidl now. // Always append new enum to the end. }; diff --git a/libs/gui/include/gui/ISurfaceComposerClient.h b/libs/gui/include/gui/ISurfaceComposerClient.h deleted file mode 100644 index 9e9e191480b18a79e08134f877ced9ed50a74059..0000000000000000000000000000000000000000 --- a/libs/gui/include/gui/ISurfaceComposerClient.h +++ /dev/null @@ -1,97 +0,0 @@ -/* - * Copyright (C) 2007 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 { - -class FrameStats; -class IGraphicBufferProducer; - -class ISurfaceComposerClient : public IInterface { -public: - DECLARE_META_INTERFACE(SurfaceComposerClient) - - // flags for createSurface() - enum { // (keep in sync with SurfaceControl.java) - eHidden = 0x00000004, - eDestroyBackbuffer = 0x00000020, - eSkipScreenshot = 0x00000040, - eSecure = 0x00000080, - eNonPremultiplied = 0x00000100, - eOpaque = 0x00000400, - eProtectedByApp = 0x00000800, - eProtectedByDRM = 0x00001000, - eCursorWindow = 0x00002000, - eNoColorFill = 0x00004000, - - eFXSurfaceBufferQueue = 0x00000000, - eFXSurfaceEffect = 0x00020000, - eFXSurfaceBufferState = 0x00040000, - eFXSurfaceContainer = 0x00080000, - eFXSurfaceMask = 0x000F0000, - }; - - // TODO(b/172002646): Clean up the Surface Creation Arguments - /* - * Requires ACCESS_SURFACE_FLINGER permission - */ - virtual status_t createSurface(const String8& name, uint32_t w, uint32_t h, PixelFormat format, - uint32_t flags, const sp& parent, - LayerMetadata metadata, sp* handle, - sp* gbp, int32_t* outLayerId, - uint32_t* outTransformHint) = 0; - - /* - * Requires ACCESS_SURFACE_FLINGER permission - */ - virtual status_t createWithSurfaceParent(const String8& name, uint32_t w, uint32_t h, - PixelFormat format, uint32_t flags, - const sp& parent, - LayerMetadata metadata, sp* handle, - sp* gbp, int32_t* outLayerId, - uint32_t* outTransformHint) = 0; - - /* - * Requires ACCESS_SURFACE_FLINGER permission - */ - virtual status_t clearLayerFrameStats(const sp& handle) const = 0; - - /* - * Requires ACCESS_SURFACE_FLINGER permission - */ - virtual status_t getLayerFrameStats(const sp& handle, FrameStats* outStats) const = 0; - - virtual status_t mirrorSurface(const sp& mirrorFromHandle, sp* outHandle, - int32_t* outLayerId) = 0; -}; - -class BnSurfaceComposerClient : public SafeBnInterface { -public: - BnSurfaceComposerClient() - : SafeBnInterface("BnSurfaceComposerClient") {} - - status_t onTransact(uint32_t code, const Parcel& data, Parcel* reply, uint32_t flags) override; -}; - -} // namespace android diff --git a/libs/gui/include/gui/ITransactionCompletedListener.h b/libs/gui/include/gui/ITransactionCompletedListener.h index cc136bb40a04bab08ae6a99f46f0c100622d0480..39bcb4a56c6ff561532a37d3f5de21d651cd149b 100644 --- a/libs/gui/include/gui/ITransactionCompletedListener.h +++ b/libs/gui/include/gui/ITransactionCompletedListener.h @@ -40,10 +40,15 @@ class ListenerCallbacks; class CallbackId : public Parcelable { public: int64_t id; - enum class Type : int32_t { ON_COMPLETE, ON_COMMIT } type; + enum class Type : int32_t { + ON_COMPLETE = 0, + ON_COMMIT = 1, + /*reserved for serialization = 2*/ + } type; + bool includeJankData; // Only respected for ON_COMPLETE callbacks. CallbackId() {} - CallbackId(int64_t id, Type type) : id(id), type(type) {} + CallbackId(int64_t id, Type type) : id(id), type(type), includeJankData(false) {} status_t writeToParcel(Parcel* output) const override; status_t readFromParcel(const Parcel* input) override; @@ -132,7 +137,7 @@ public: SurfaceStats() = default; SurfaceStats(const sp& sc, std::variant> acquireTimeOrFence, - const sp& prevReleaseFence, uint32_t hint, + const sp& prevReleaseFence, std::optional hint, uint32_t currentMaxAcquiredBuffersCount, FrameEventHistoryStats frameEventStats, std::vector jankData, ReleaseCallbackId previousReleaseCallbackId) : surfaceControl(sc), @@ -147,7 +152,7 @@ public: sp surfaceControl; std::variant> acquireTimeOrFence = -1; sp previousReleaseFence; - uint32_t transformHint = 0; + std::optional transformHint = 0; uint32_t currentMaxAcquiredBufferCount = 0; FrameEventHistoryStats eventStats; std::vector jankData; @@ -194,7 +199,10 @@ public: virtual void onReleaseBuffer(ReleaseCallbackId callbackId, sp releaseFence, uint32_t currentMaxAcquiredBufferCount) = 0; - virtual void onTransactionQueueStalled() = 0; + + virtual void onTransactionQueueStalled(const String8& name) = 0; + + virtual void onTrustedPresentationChanged(int id, bool inTrustedPresentationState) = 0; }; class BnTransactionCompletedListener : public SafeBnInterface { diff --git a/libs/gui/include/gui/JankInfo.h b/libs/gui/include/gui/JankInfo.h index ce9716f1fe523c8f846940f77bd67021093630c9..1dddeba61623437011613254a3ee7f31bb15fc0d 100644 --- a/libs/gui/include/gui/JankInfo.h +++ b/libs/gui/include/gui/JankInfo.h @@ -24,9 +24,9 @@ enum JankType { None = 0x0, // Jank that occurs in the layers below SurfaceFlinger DisplayHAL = 0x1, - // SF took too long on the CPU + // SF took too long on the CPU; deadline missed during HWC SurfaceFlingerCpuDeadlineMissed = 0x2, - // SF took too long on the GPU + // SF took too long on the GPU; deadline missed during GPU composition SurfaceFlingerGpuDeadlineMissed = 0x4, // Either App or GPU took too long on the frame AppDeadlineMissed = 0x8, diff --git a/libs/gui/include/gui/LayerCaptureArgs.h b/libs/gui/include/gui/LayerCaptureArgs.h index 05ff9d5b7b5665cc16867b2a14f1f1752c7072c1..fae2bcc78725752dbff2f52a065be854aa5d5235 100644 --- a/libs/gui/include/gui/LayerCaptureArgs.h +++ b/libs/gui/include/gui/LayerCaptureArgs.h @@ -20,14 +20,11 @@ #include #include -#include -#include namespace android::gui { struct LayerCaptureArgs : CaptureArgs { sp layerHandle; - std::unordered_set, SpHash> excludeHandles; bool childrenOnly{false}; status_t writeToParcel(Parcel* output) const override; diff --git a/libs/gui/include/gui/LayerDebugInfo.h b/libs/gui/include/gui/LayerDebugInfo.h index af834d78dfb549591c570d6bea9931bdf05d36f3..dbb80e583cf4a4d13d516e447282ce4ad1a67198 100644 --- a/libs/gui/include/gui/LayerDebugInfo.h +++ b/libs/gui/include/gui/LayerDebugInfo.h @@ -25,7 +25,7 @@ #include #include -namespace android { +namespace android::gui { /* Class for transporting debug info from SurfaceFlinger to authorized * recipients. The class is intended to be a data container. There are @@ -52,7 +52,7 @@ public: uint32_t mZ = 0 ; int32_t mWidth = -1; int32_t mHeight = -1; - Rect mCrop = Rect::INVALID_RECT; + android::Rect mCrop = android::Rect::INVALID_RECT; half4 mColor = half4(1.0_hf, 1.0_hf, 1.0_hf, 0.0_hf); uint32_t mFlags = 0; PixelFormat mPixelFormat = PIXEL_FORMAT_NONE; @@ -71,4 +71,4 @@ public: std::string to_string(const LayerDebugInfo& info); -} // namespace android +} // namespace android::gui diff --git a/libs/gui/include/gui/LayerMetadata.h b/libs/gui/include/gui/LayerMetadata.h index 27f4d379e9e998b98ea5411bbb12e1062404b5e2..9cf62bc7d6b2fb8e574c4c33867bd772d08b90cd 100644 --- a/libs/gui/include/gui/LayerMetadata.h +++ b/libs/gui/include/gui/LayerMetadata.h @@ -20,7 +20,7 @@ #include -namespace android { +namespace android::gui { enum { METADATA_OWNER_UID = 1, @@ -30,7 +30,8 @@ enum { METADATA_ACCESSIBILITY_ID = 5, METADATA_OWNER_PID = 6, METADATA_DEQUEUE_TIME = 7, - METADATA_GAME_MODE = 8 + METADATA_GAME_MODE = 8, + METADATA_CALLING_UID = 9, }; struct LayerMetadata : public Parcelable { @@ -65,8 +66,18 @@ enum class GameMode : int32_t { Standard = 1, Performance = 2, Battery = 3, + Custom = 4, - ftl_last = Battery + ftl_last = Custom }; -} // namespace android +} // namespace android::gui + +using android::gui::METADATA_ACCESSIBILITY_ID; +using android::gui::METADATA_DEQUEUE_TIME; +using android::gui::METADATA_GAME_MODE; +using android::gui::METADATA_MOUSE_CURSOR; +using android::gui::METADATA_OWNER_PID; +using android::gui::METADATA_OWNER_UID; +using android::gui::METADATA_TASK_ID; +using android::gui::METADATA_WINDOW_TYPE; diff --git a/libs/gui/include/gui/LayerState.h b/libs/gui/include/gui/LayerState.h index 0071d48227890ebd086bca3be29d881274bca120..a6f503ef559ef0439dad31774c49b0f3f0e25e1d 100644 --- a/libs/gui/include/gui/LayerState.h +++ b/libs/gui/include/gui/LayerState.h @@ -21,6 +21,8 @@ #include #include +#include +#include #include #include #include @@ -51,7 +53,11 @@ namespace android { class Parcel; -class ISurfaceComposerClient; + +using gui::ISurfaceComposerClient; +using gui::LayerMetadata; + +using gui::TrustedPresentationThresholds; struct client_cache_t { wp token = nullptr; @@ -62,6 +68,19 @@ struct client_cache_t { bool isValid() const { return token != nullptr; } }; +class TrustedPresentationListener : public Parcelable { +public: + sp callbackInterface; + int callbackId = -1; + + void invoke(bool presentedWithinThresholds) { + callbackInterface->onTrustedPresentationChanged(callbackId, presentedWithinThresholds); + } + + status_t writeToParcel(Parcel* parcel) const; + status_t readFromParcel(const Parcel* parcel); +}; + class BufferData : public Parcelable { public: virtual ~BufferData() = default; @@ -92,6 +111,7 @@ public: uint64_t frameNumber = 0; bool hasBarrier = false; uint64_t barrierFrameNumber = 0; + uint32_t producerId = 0; // Listens to when the buffer is safe to be released. This is used for blast // layers only. The callback includes a release fence as well as the graphic @@ -130,7 +150,7 @@ struct layer_state_t { eLayerOpaque = 0x02, // SURFACE_OPAQUE eLayerSkipScreenshot = 0x40, // SKIP_SCREENSHOT eLayerSecure = 0x80, // SECURE - // Queue up BufferStateLayer buffers instead of dropping the oldest buffer when this flag is + // Queue up layer buffers instead of dropping the oldest buffer when this flag is // set. This blocks the client until all the buffers have been presented. If the buffers // have presentation timestamps, then we may drop buffers. eEnableBackpressure = 0x100, // ENABLE_BACKPRESSURE @@ -140,30 +160,33 @@ struct layer_state_t { // This is needed to maintain compatibility for SurfaceView scaling behavior. // See SurfaceView scaling behavior for more details. eIgnoreDestinationFrame = 0x400, + eLayerIsRefreshRateIndicator = 0x800, // REFRESH_RATE_INDICATOR }; enum { ePositionChanged = 0x00000001, eLayerChanged = 0x00000002, - eSizeChanged = 0x00000004, + eTrustedPresentationInfoChanged = 0x00000004, eAlphaChanged = 0x00000008, eMatrixChanged = 0x00000010, eTransparentRegionChanged = 0x00000020, eFlagsChanged = 0x00000040, eLayerStackChanged = 0x00000080, + eFlushJankData = 0x00000100, + eCachingHintChanged = 0x00000200, eDimmingEnabledChanged = 0x00000400, eShadowRadiusChanged = 0x00000800, - /* unused 0x00001000, */ + eRenderBorderChanged = 0x00001000, eBufferCropChanged = 0x00002000, eRelativeLayerChanged = 0x00004000, eReparent = 0x00008000, eColorChanged = 0x00010000, - eDestroySurface = 0x00020000, - eTransformChanged = 0x00040000, + /* unused = 0x00020000, */ + eBufferTransformChanged = 0x00040000, eTransformToDisplayInverseChanged = 0x00080000, eCropChanged = 0x00100000, eBufferChanged = 0x00200000, - /* unused 0x00400000, */ + eDefaultFrameRateCompatibilityChanged = 0x00400000, eDataspaceChanged = 0x00800000, eHdrMetadataChanged = 0x01000000, eSurfaceDamageRegionChanged = 0x02000000, @@ -188,7 +211,9 @@ struct layer_state_t { eAutoRefreshChanged = 0x1000'00000000, eStretchChanged = 0x2000'00000000, eTrustedOverlayChanged = 0x4000'00000000, - eDropInputModeChanged = 0x8000'00000000 + eDropInputModeChanged = 0x8000'00000000, + eExtendedRangeBrightnessChanged = 0x10000'00000000, + }; layer_state_t(); @@ -196,7 +221,63 @@ struct layer_state_t { void merge(const layer_state_t& other); status_t write(Parcel& output) const; status_t read(const Parcel& input); + // Compares two layer_state_t structs and returns a set of change flags describing all the + // states that are different. + uint64_t diff(const layer_state_t& other) const; bool hasBufferChanges() const; + + // Layer hierarchy updates. + static constexpr uint64_t HIERARCHY_CHANGES = layer_state_t::eLayerChanged | + layer_state_t::eRelativeLayerChanged | layer_state_t::eReparent | + layer_state_t::eLayerStackChanged; + + // Geometry updates. + static constexpr uint64_t GEOMETRY_CHANGES = layer_state_t::eBufferCropChanged | + layer_state_t::eBufferTransformChanged | layer_state_t::eCornerRadiusChanged | + layer_state_t::eCropChanged | layer_state_t::eDestinationFrameChanged | + layer_state_t::eMatrixChanged | layer_state_t::ePositionChanged | + layer_state_t::eTransformToDisplayInverseChanged | + layer_state_t::eTransparentRegionChanged; + + // Buffer and related updates. + static constexpr uint64_t BUFFER_CHANGES = layer_state_t::eApiChanged | + layer_state_t::eBufferChanged | layer_state_t::eBufferCropChanged | + layer_state_t::eBufferTransformChanged | layer_state_t::eDataspaceChanged | + layer_state_t::eSidebandStreamChanged | layer_state_t::eSurfaceDamageRegionChanged | + layer_state_t::eTransformToDisplayInverseChanged | + layer_state_t::eTransparentRegionChanged | + layer_state_t::eExtendedRangeBrightnessChanged; + + // Content updates. + static constexpr uint64_t CONTENT_CHANGES = layer_state_t::BUFFER_CHANGES | + layer_state_t::eAlphaChanged | layer_state_t::eAutoRefreshChanged | + layer_state_t::eBackgroundBlurRadiusChanged | layer_state_t::eBackgroundColorChanged | + 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; + + // Changes which invalidates the layer's visible region in CE. + static constexpr uint64_t CONTENT_DIRTY = layer_state_t::CONTENT_CHANGES | + layer_state_t::GEOMETRY_CHANGES | layer_state_t::HIERARCHY_CHANGES; + + // Changes affecting child states. + static constexpr uint64_t AFFECTS_CHILDREN = layer_state_t::GEOMETRY_CHANGES | + layer_state_t::HIERARCHY_CHANGES | layer_state_t::eAlphaChanged | + layer_state_t::eColorTransformChanged | layer_state_t::eCornerRadiusChanged | + layer_state_t::eFlagsChanged | layer_state_t::eTrustedOverlayChanged | + layer_state_t::eFrameRateChanged | layer_state_t::eFixedTransformHintChanged; + + // Changes affecting data sent to input. + static constexpr uint64_t INPUT_CHANGES = layer_state_t::GEOMETRY_CHANGES | + layer_state_t::HIERARCHY_CHANGES | layer_state_t::eInputInfoChanged | + layer_state_t::eDropInputModeChanged | layer_state_t::eTrustedOverlayChanged; + + // 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; + bool hasValidBuffer() const; void sanitize(int32_t permissions); @@ -207,6 +288,11 @@ struct layer_state_t { float dsdy{0}; status_t write(Parcel& output) const; status_t read(const Parcel& input); + inline bool operator==(const matrix22_t& other) const { + return std::tie(dsdx, dtdx, dtdy, dsdy) == + std::tie(other.dsdx, other.dtdx, other.dtdy, other.dsdy); + } + inline bool operator!=(const matrix22_t& other) const { return !(*this == other); } }; sp surface; int32_t layerId; @@ -214,28 +300,23 @@ struct layer_state_t { float x; float y; int32_t z; - uint32_t w; - uint32_t h; ui::LayerStack layerStack = ui::DEFAULT_LAYER_STACK; - float alpha; uint32_t flags; uint32_t mask; uint8_t reserved; matrix22_t matrix; float cornerRadius; uint32_t backgroundBlurRadius; - sp reparentSurfaceControl; sp relativeLayerSurfaceControl; sp parentSurfaceControlForChild; - half3 color; + half4 color; // non POD must be last. see write/read Region transparentRegion; - - uint32_t transform; + uint32_t bufferTransform; bool transformToDisplayInverse; Rect crop; std::shared_ptr bufferData = nullptr; @@ -247,13 +328,13 @@ struct layer_state_t { mat4 colorTransform; std::vector blurRegions; - sp windowInfoHandle = new gui::WindowInfoHandle(); + sp windowInfoHandle = sp::make(); LayerMetadata metadata; // The following refer to the alpha, and dataspace, respectively of // the background color layer - float bgColorAlpha; + half4 bgColor; ui::Dataspace bgColorDataspace; // A color space agnostic layer means the color of this layer can be @@ -273,6 +354,9 @@ struct layer_state_t { int8_t frameRateCompatibility; int8_t changeFrameRateStrategy; + // Default frame rate compatibility used to set the layer refresh rate votetype. + int8_t defaultFrameRateCompatibility; + // Set by window manager indicating the layer and all its children are // in a different orientation than the display. The hint suggests that // the graphic producers should receive a transform hint as if the @@ -291,6 +375,11 @@ 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; @@ -301,9 +390,17 @@ struct layer_state_t { gui::DropInputMode dropInputMode; bool dimmingEnabled; + float currentHdrSdrRatio = 1.f; + float desiredHdrSdrRatio = 1.f; + + gui::CachingHint cachingHint = gui::CachingHint::Enabled; + + TrustedPresentationThresholds trustedPresentationThresholds; + TrustedPresentationListener trustedPresentationListener; }; -struct ComposerState { +class ComposerState { +public: layer_state_t state; status_t write(Parcel& output) const; status_t read(const Parcel& input); @@ -353,7 +450,9 @@ struct DisplayState { struct InputWindowCommands { std::vector focusRequests; - bool syncInputWindows{false}; + std::unordered_set, + SpHash> + windowInfosReportedListeners; // Merges the passed in commands and returns true if there were any changes. bool merge(const InputWindowCommands& other); diff --git a/libs/gui/include/gui/LayerStatePermissions.h b/libs/gui/include/gui/LayerStatePermissions.h new file mode 100644 index 0000000000000000000000000000000000000000..a90f30c6213e3ee04c7be95af420338d346c369b --- /dev/null +++ b/libs/gui/include/gui/LayerStatePermissions.h @@ -0,0 +1,29 @@ +/* + * 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. + */ + +#include +#include +#include + +namespace android { +class LayerStatePermissions { +public: + static uint32_t getTransactionPermissions(int pid, int uid); + +private: + static std::unordered_map mPermissionMap; +}; +} // namespace android \ No newline at end of file diff --git a/libs/gui/include/gui/ScreenCaptureResults.h b/libs/gui/include/gui/ScreenCaptureResults.h index 724c11c8812ae29256a6482afd0d249452f7d757..6e17791a29f138f20a194a5458eca2d3de3352d8 100644 --- a/libs/gui/include/gui/ScreenCaptureResults.h +++ b/libs/gui/include/gui/ScreenCaptureResults.h @@ -19,6 +19,7 @@ #include #include #include +#include #include namespace android::gui { @@ -31,11 +32,10 @@ public: status_t readFromParcel(const android::Parcel* parcel) override; sp buffer; - sp fence = Fence::NO_FENCE; + FenceResult fenceResult = Fence::NO_FENCE; bool capturedSecureLayers{false}; bool capturedHdrLayers{false}; ui::Dataspace capturedDataspace{ui::Dataspace::V0_SRGB}; - status_t result = OK; }; } // namespace android::gui diff --git a/libs/gui/include/gui/Surface.h b/libs/gui/include/gui/Surface.h index 4a552b66431f8e88ab684f810a19fe984329753d..39a59e42aaa9b995cfde3d82700f9367e8a33620 100644 --- a/libs/gui/include/gui/Surface.h +++ b/libs/gui/include/gui/Surface.h @@ -17,8 +17,8 @@ #ifndef ANDROID_GUI_SURFACE_H #define ANDROID_GUI_SURFACE_H +#include #include -#include #include #include #include @@ -41,6 +41,8 @@ class ISurfaceComposer; class ISurfaceComposer; +using gui::FrameTimelineInfo; + /* This is the same as ProducerListener except that onBuffersDiscarded is * called with a vector of graphic buffers instead of buffer slots. */ @@ -203,8 +205,8 @@ public: nsecs_t* outDisplayPresentTime, nsecs_t* outDequeueReadyTime, nsecs_t* outReleaseTime); - status_t getWideColorSupport(bool* supported); - status_t getHdrSupport(bool* supported); + status_t getWideColorSupport(bool* supported) __attribute__((__deprecated__)); + status_t getHdrSupport(bool* supported) __attribute__((__deprecated__)); status_t getUniqueId(uint64_t* outId) const; status_t getConsumerUsage(uint64_t* outUsage) const; @@ -301,6 +303,10 @@ private: int dispatchGetLastQueuedBuffer2(va_list args); int dispatchSetFrameTimelineInfo(va_list args); + std::mutex mNameMutex; + std::string mName; + const char* getDebugName(); + protected: virtual int dequeueBuffer(ANativeWindowBuffer** buffer, int* fenceFd); virtual int cancelBuffer(ANativeWindowBuffer* buffer, int fenceFd); diff --git a/libs/gui/include/gui/SurfaceComposerClient.h b/libs/gui/include/gui/SurfaceComposerClient.h index 9033e17c539db911dbdfc15e5db3020354a6d90b..fb57f63dad535fd70283f5135ef0bd67e67efd70 100644 --- a/libs/gui/include/gui/SurfaceComposerClient.h +++ b/libs/gui/include/gui/SurfaceComposerClient.h @@ -38,6 +38,9 @@ #include #include #include +#include + +#include #include #include @@ -52,20 +55,22 @@ namespace android { class HdrCapabilities; -class ISurfaceComposerClient; class IGraphicBufferProducer; class ITunnelModeEnabledListener; class Region; +class TransactionCompletedListener; using gui::DisplayCaptureArgs; using gui::IRegionSamplingListener; +using gui::ISurfaceComposerClient; using gui::LayerCaptureArgs; +using gui::LayerMetadata; struct SurfaceControlStats { SurfaceControlStats(const sp& sc, nsecs_t latchTime, std::variant> acquireTimeOrFence, const sp& presentFence, const sp& prevReleaseFence, - uint32_t hint, FrameEventHistoryStats eventStats, + std::optional hint, FrameEventHistoryStats eventStats, uint32_t currentMaxAcquiredBufferCount) : surfaceControl(sc), latchTime(latchTime), @@ -81,7 +86,7 @@ struct SurfaceControlStats { std::variant> acquireTimeOrFence = -1; sp presentFence; sp previousReleaseFence; - uint32_t transformHint = 0; + std::optional transformHint = 0; FrameEventHistoryStats frameEventStats; uint32_t currentMaxAcquiredBufferCount = 0; }; @@ -102,6 +107,8 @@ using SurfaceStatsCallback = const sp& /*presentFence*/, const SurfaceStats& /*stats*/)>; +using TrustedPresentationCallback = std::function; + // --------------------------------------------------------------------------- class ReleaseCallbackThread { @@ -141,32 +148,28 @@ public: status_t linkToComposerDeath(const sp& recipient, void* cookie = nullptr, uint32_t flags = 0); + // Notify the SurfaceComposerClient that the boot procedure has completed + static status_t bootFinished(); + // Get transactional state of given display. static status_t getDisplayState(const sp& display, ui::DisplayState*); // Get immutable information about given physical display. - static status_t getStaticDisplayInfo(const sp& display, ui::StaticDisplayInfo*); + static status_t getStaticDisplayInfo(int64_t, ui::StaticDisplayInfo*); - // Get dynamic information about given physical display. - static status_t getDynamicDisplayInfo(const sp& display, ui::DynamicDisplayInfo*); + // Get dynamic information about given physical display from display id + static status_t getDynamicDisplayInfoFromId(int64_t, ui::DynamicDisplayInfo*); // Shorthand for the active display mode from getDynamicDisplayInfo(). // TODO(b/180391891): Update clients to use getDynamicDisplayInfo and remove this function. static status_t getActiveDisplayMode(const sp& display, ui::DisplayMode*); // Sets the refresh rate boundaries for the display. - static status_t setDesiredDisplayModeSpecs( - const sp& displayToken, ui::DisplayModeId defaultMode, - bool allowGroupSwitching, float primaryRefreshRateMin, float primaryRefreshRateMax, - float appRequestRefreshRateMin, float appRequestRefreshRateMax); + static status_t setDesiredDisplayModeSpecs(const sp& displayToken, + const gui::DisplayModeSpecs&); // Gets the refresh rate boundaries for the display. static status_t getDesiredDisplayModeSpecs(const sp& displayToken, - ui::DisplayModeId* outDefaultMode, - bool* outAllowGroupSwitching, - float* outPrimaryRefreshRateMin, - float* outPrimaryRefreshRateMax, - float* outAppRequestRefreshRateMin, - float* outAppRequestRefreshRateMax); + gui::DisplayModeSpecs*); // Get the coordinates of the display's native color primaries static status_t getDisplayNativePrimaries(const sp& display, @@ -178,11 +181,24 @@ public: // Gets if boot display mode operations are supported on a device static status_t getBootDisplayModeSupport(bool* support); + + // Gets the overlay properties of the device + static status_t getOverlaySupport(gui::OverlayProperties* outProperties); + // Sets the user-preferred display mode that a device should boot in static status_t setBootDisplayMode(const sp& display, ui::DisplayModeId); // Clears the user-preferred display mode static status_t clearBootDisplayMode(const sp& display); + // Gets the HDR conversion capabilities of the device + static status_t getHdrConversionCapabilities(std::vector*); + // Sets the HDR conversion strategy for the device. in case when HdrConversionStrategy has + // autoAllowedHdrTypes set. Returns Hdr::INVALID in other cases. + static status_t setHdrConversionStrategy(gui::HdrConversionStrategy hdrConversionStrategy, + ui::Hdr* outPreferredHdrOutputType); + // Returns whether HDR conversion is supported by the device. + static status_t getHdrOutputConversionSupport(bool* isSupported); + // Sets the frame rate of a particular app (uid). This is currently called // by GameManager. static status_t setOverrideFrameRate(uid_t uid, float frameRate); @@ -218,7 +234,7 @@ public: /** * Gets the context priority of surface flinger's render engine. */ - static int getGPUContextPriority(); + static int getGpuContextPriority(); /** * Uncaches a buffer in ISurfaceComposer. It must be uncached via a transaction so that it is @@ -314,7 +330,7 @@ public: uint32_t w, // width in pixel uint32_t h, // height in pixel PixelFormat format, // pixel-format desired - uint32_t flags = 0, // usage flags + int32_t flags = 0, // usage flags const sp& parentHandle = nullptr, // parentHandle LayerMetadata metadata = LayerMetadata(), // metadata uint32_t* outTransformHint = nullptr); @@ -324,21 +340,11 @@ public: uint32_t h, // height in pixel PixelFormat format, // pixel-format desired sp* outSurface, - uint32_t flags = 0, // usage flags + int32_t flags = 0, // usage flags const sp& parentHandle = nullptr, // parentHandle LayerMetadata metadata = LayerMetadata(), // metadata uint32_t* outTransformHint = nullptr); - //! Create a surface - sp createWithSurfaceParent(const String8& name, // name of the surface - uint32_t w, // width in pixel - uint32_t h, // height in pixel - PixelFormat format, // pixel-format desired - uint32_t flags = 0, // usage flags - Surface* parent = nullptr, // parent - LayerMetadata metadata = LayerMetadata(), // metadata - uint32_t* outTransformHint = nullptr); - // Creates a mirrored hierarchy for the mirrorFromSurface. This returns a SurfaceControl // which is a parent of the root of the mirrored hierarchy. // @@ -350,24 +356,20 @@ public: // B B' sp mirrorSurface(SurfaceControl* mirrorFromSurface); + sp mirrorDisplay(DisplayId displayId); + //! Create a virtual display - static sp createDisplay(const String8& displayName, bool secure); + static sp createDisplay(const String8& displayName, bool secure, + float requestedRefereshRate = 0); //! Destroy a virtual display static void destroyDisplay(const sp& display); //! Get stable IDs for connected physical displays static std::vector getPhysicalDisplayIds(); - static status_t getPrimaryPhysicalDisplayId(PhysicalDisplayId*); - static std::optional getInternalDisplayId(); //! Get token for a physical display given its stable ID static sp getPhysicalDisplayToken(PhysicalDisplayId displayId); - static sp getInternalDisplayToken(); - - static status_t enableVSyncInjections(bool enable); - - static status_t injectVSync(nsecs_t when); struct SCHash { std::size_t operator()(const sp& sc) const { @@ -396,26 +398,43 @@ public: std::unordered_set, SCHash> surfaceControls; }; + struct PresentationCallbackRAII : public RefBase { + sp mTcl; + int mId; + PresentationCallbackRAII(TransactionCompletedListener* tcl, int id); + virtual ~PresentationCallbackRAII(); + }; + class Transaction : public Parcelable { private: + static sp sApplyToken; void releaseBufferIfOverwriting(const layer_state_t& state); + static void mergeFrameTimelineInfo(FrameTimelineInfo& t, const FrameTimelineInfo& other); + static void clearFrameTimelineInfo(FrameTimelineInfo& t); protected: std::unordered_map, ComposerState, IBinderHash> mComposerStates; SortedVector mDisplayStates; std::unordered_map, CallbackInfo, TCLHash> mListenerCallbacks; + std::vector mUncacheBuffers; + + // We keep track of the last MAX_MERGE_HISTORY_LENGTH merged transaction ids. + // Ordered most recently merged to least recently merged. + static const size_t MAX_MERGE_HISTORY_LENGTH = 10u; + std::vector mMergedTransactionIds; uint64_t mId; - uint32_t mForceSynchronous = 0; uint32_t mTransactionNestCount = 0; bool mAnimation = false; bool mEarlyWakeupStart = false; bool mEarlyWakeupEnd = false; - // Indicates that the Transaction contains a buffer that should be cached - bool mContainsBuffer = false; + // Indicates that the Transaction may contain buffers that should be cached. The reason this + // is only a guess is that buffers can be removed before cache is called. This is only a + // hint that at some point a buffer was added to this transaction before apply was called. + bool mMayContainBuffer = false; // mDesiredPresentTime is the time in nanoseconds that the client would like the transaction // to be presented. When it is not possible to present at exactly that time, it will be @@ -468,16 +487,17 @@ public: // The id is updated every time the transaction is applied. uint64_t getId(); + std::vector getMergedTransactionIds(); + status_t apply(bool synchronous = false, bool oneWay = false); // Merge another transaction in to this one, clearing other // as if it had been applied. Transaction& merge(Transaction&& other); Transaction& show(const sp& sc); Transaction& hide(const sp& sc); - Transaction& setPosition(const sp& sc, - float x, float y); - Transaction& setSize(const sp& sc, - uint32_t w, uint32_t h); + Transaction& setPosition(const sp& sc, float x, float y); + // b/243180033 remove once functions are not called from vendor code + Transaction& setSize(const sp&, uint32_t, uint32_t) { return *this; } Transaction& setLayer(const sp& sc, int32_t z); @@ -527,7 +547,8 @@ public: Transaction& setBuffer(const sp& sc, const sp& buffer, const std::optional>& fence = std::nullopt, const std::optional& frameNumber = std::nullopt, - ReleaseBufferCallback callback = nullptr); + uint32_t producerId = 0, ReleaseBufferCallback callback = nullptr); + Transaction& unsetBuffer(const sp& sc); std::shared_ptr getAndClearBuffer(const sp& sc); /** @@ -551,6 +572,9 @@ public: Transaction& setBufferHasBarrier(const sp& sc, uint64_t barrierFrameNumber); Transaction& setDataspace(const sp& sc, ui::Dataspace dataspace); + Transaction& setExtendedRangeBrightness(const sp& sc, + float currentBufferRatio, float desiredRatio); + Transaction& setCachingHint(const sp& sc, gui::CachingHint cachingHint); Transaction& setHdrMetadata(const sp& sc, const HdrMetadata& hdrMetadata); Transaction& setSurfaceDamageRegion(const sp& sc, const Region& surfaceDamageRegion); @@ -572,12 +596,67 @@ public: Transaction& addTransactionCommittedCallback( TransactionCompletedCallbackTakesContext callback, void* callbackContext); + /** + * Set a callback to receive feedback about the presentation of a layer. + * When the layer is presented according to the passed in Thresholds, + * it is said to "enter the state", and receives the callback with true. + * When the conditions fall out of thresholds, it is then said to leave the + * state. + * + * There are a few simple thresholds: + * minAlpha: Lower bound on computed alpha + * minFractionRendered: Lower bounds on fraction of pixels that + * were rendered. + * stabilityThresholdMs: A time that alpha and fraction rendered + * must remain within bounds before we can "enter the state" + * + * The fraction of pixels rendered is a computation based on scale, crop + * and occlusion. The calculation may be somewhat counterintuitive, so we + * can work through an example. Imagine we have a layer with a 100x100 buffer + * which is occluded by (10x100) pixels on the left, and cropped by (100x10) pixels + * on the top. Furthermore imagine this layer is scaled by 0.9 in both dimensions. + * (c=crop,o=occluded,b=both,x=none + * b c c c + * o x x x + * o x x x + * o x x x + * + * We first start by computing fr=xscale*yscale=0.9*0.9=0.81, indicating + * that "81%" of the pixels were rendered. This corresponds to what was 100 + * pixels being displayed in 81 pixels. This is somewhat of an abuse of + * language, as the information of merged pixels isn't totally lost, but + * we err on the conservative side. + * + * We then repeat a similar process for the crop and covered regions and + * accumulate the results: fr = fr * (fractionNotCropped) * (fractionNotCovered) + * So for this example we would get 0.9*0.9*0.9*0.9=0.65... + * + * Notice that this is not completely accurate, as we have double counted + * the region marked as b. However we only wanted a "lower bound" and so it + * is ok to err in this direction. Selection of the threshold will ultimately + * be somewhat arbitrary, and so there are some somewhat arbitrary decisions in + * this API as well. + * + * The caller must keep "PresentationCallbackRAII" alive, or the callback + * in SurfaceComposerClient will be unregistered. + */ + Transaction& setTrustedPresentationCallback(const sp& sc, + TrustedPresentationCallback callback, + const TrustedPresentationThresholds& thresholds, + void* context, + sp& outCallbackOwner); + + // Clear local memory in SCC + Transaction& clearTrustedPresentationCallback(const sp& sc); + // ONLY FOR BLAST ADAPTER Transaction& notifyProducerDisconnect(const sp& sc); Transaction& setInputWindowInfo(const sp& sc, const gui::WindowInfo& info); Transaction& setFocusedWindow(const gui::FocusRequest& request); - Transaction& syncInputWindows(); + + Transaction& addWindowInfosReportedListener( + sp windowInfosReportedListener); // Set a color transform matrix on the given layer on the built-in display. Transaction& setColorTransform(const sp& sc, const mat3& matrix, @@ -590,6 +669,9 @@ public: Transaction& setFrameRate(const sp& sc, float frameRate, int8_t compatibility, int8_t changeFrameRateStrategy); + Transaction& setDefaultFrameRateCompatibility(const sp& sc, + int8_t compatibility); + // Set by window manager indicating the layer and all its children are // in a different orientation than the display. The hint suggests that // the graphic producers should receive a transform hint as if the @@ -636,6 +718,9 @@ 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); @@ -666,7 +751,12 @@ public: * * TODO (b/213644870): Remove all permissioned things from Transaction */ - void sanitize(); + void sanitize(int pid, int uid); + + static sp getDefaultApplyToken(); + static void setDefaultApplyToken(sp applyToken); + + static status_t sendSurfaceFlushJankDataTransaction(const sp& sc); }; status_t clearLayerFrameStats(const sp& token) const; @@ -714,6 +804,12 @@ protected: ReleaseCallbackThread mReleaseCallbackThread; private: + // Get dynamic information about given physical display from token + static status_t getDynamicDisplayInfoFromToken(const sp& display, + ui::DynamicDisplayInfo*); + + static void getDynamicDisplayInfoInternal(gui::DynamicDisplayInfo& ginfo, + ui::DynamicDisplayInfo*& outInfo); virtual void onFirstRef(); mutable Mutex mLock; @@ -779,7 +875,10 @@ protected: // This is protected by mSurfaceStatsListenerMutex, but GUARDED_BY isn't supported for // std::recursive_mutex std::multimap mSurfaceStatsListeners; - std::unordered_map> mQueueStallListeners; + std::unordered_map> mQueueStallListeners; + + std::unordered_map> + mTrustedPresentationCallbacks; public: static sp getInstance(); @@ -792,14 +891,22 @@ public: const std::unordered_set, SurfaceComposerClient::SCHash>& surfaceControls, CallbackId::Type callbackType); + CallbackId addCallbackFunctionLocked( + const TransactionCompletedCallback& callbackFunction, + const std::unordered_set, SurfaceComposerClient::SCHash>& + surfaceControls, + CallbackId::Type callbackType) REQUIRES(mMutex); - void addSurfaceControlToCallbacks( - const sp& surfaceControl, - const std::unordered_set& callbackIds); + void addSurfaceControlToCallbacks(SurfaceComposerClient::CallbackInfo& callbackInfo, + const sp& surfaceControl); - void addQueueStallListener(std::function stallListener, void* id); + void addQueueStallListener(std::function stallListener, void* id); void removeQueueStallListener(void *id); + sp addTrustedPresentationCallback( + TrustedPresentationCallback tpc, int id, void* context); + void clearTrustedPresentationCallback(int id); + /* * Adds a jank listener to be informed about SurfaceFlinger's jank classification for a specific * surface. Jank classifications arrive as part of the transaction callbacks about previous @@ -828,10 +935,12 @@ public: // For Testing Only static void setInstance(const sp&); - void onTransactionQueueStalled() override; + void onTransactionQueueStalled(const String8& reason) override; + + void onTrustedPresentationChanged(int id, bool presentedWithinThresholds) override; private: - ReleaseBufferCallback popReleaseBufferCallbackLocked(const ReleaseCallbackId&); + ReleaseBufferCallback popReleaseBufferCallbackLocked(const ReleaseCallbackId&) REQUIRES(mMutex); static sp sInstance; }; diff --git a/libs/gui/include/gui/SurfaceControl.h b/libs/gui/include/gui/SurfaceControl.h index b72cf8390e4ddbfeccc15c5ecced997f7d22bb82..344b957ba712b8f55ef0e537b74a87a0b5404b6f 100644 --- a/libs/gui/include/gui/SurfaceControl.h +++ b/libs/gui/include/gui/SurfaceControl.h @@ -24,17 +24,19 @@ #include #include +#include + #include #include #include -#include #include namespace android { // --------------------------------------------------------------------------- +class Choreographer; class IGraphicBufferProducer; class Surface; class SurfaceComposerClient; @@ -77,6 +79,10 @@ public: sp getHandle() const; sp getLayerStateHandle() const; int32_t getLayerId() const; + const std::string& getName() const; + + // TODO(b/267195698): Consider renaming. + std::shared_ptr getChoreographer(); sp getIGraphicBufferProducer(); @@ -93,9 +99,9 @@ public: explicit SurfaceControl(const sp& other); SurfaceControl(const sp& client, const sp& handle, - const sp& gbp, int32_t layerId, - uint32_t width = 0, uint32_t height = 0, PixelFormat format = 0, - uint32_t transformHint = 0, uint32_t flags = 0); + int32_t layerId, const std::string& layerName, uint32_t width = 0, + uint32_t height = 0, PixelFormat format = 0, uint32_t transformHint = 0, + uint32_t flags = 0); sp getParentingLayer(); @@ -115,19 +121,20 @@ private: status_t validate() const; sp mClient; - sp mHandle; - sp mGraphicBufferProducer; + sp mHandle; mutable Mutex mLock; mutable sp mSurfaceData; mutable sp mBbq; mutable sp mBbqChild; int32_t mLayerId = 0; + std::string mName; uint32_t mTransformHint = 0; uint32_t mWidth = 0; uint32_t mHeight = 0; PixelFormat mFormat = PIXEL_FORMAT_NONE; uint32_t mCreateFlags = 0; uint64_t mFallbackFrameNumber = 100; + std::shared_ptr mChoreographer; }; }; // namespace android diff --git a/libs/gui/include/gui/SyncScreenCaptureListener.h b/libs/gui/include/gui/SyncScreenCaptureListener.h index 0784fbc0581242d4be5c2702e8e7fb91c958fbe3..bcf565a4949395cee47115b95a3c1319034b517d 100644 --- a/libs/gui/include/gui/SyncScreenCaptureListener.h +++ b/libs/gui/include/gui/SyncScreenCaptureListener.h @@ -34,7 +34,9 @@ public: ScreenCaptureResults waitForResults() { std::future resultsFuture = resultsPromise.get_future(); const auto screenCaptureResults = resultsFuture.get(); - screenCaptureResults.fence->waitForever(""); + if (screenCaptureResults.fenceResult.ok()) { + screenCaptureResults.fenceResult.value()->waitForever(""); + } return screenCaptureResults; } @@ -42,4 +44,4 @@ private: std::promise resultsPromise; }; -} // namespace android \ No newline at end of file +} // namespace android diff --git a/libs/gui/include/gui/TraceUtils.h b/libs/gui/include/gui/TraceUtils.h index 00096158e7220e2891c99ece1176c24e30b40969..441b833b5dd70837f752ee290ce3c9f95e51a684 100644 --- a/libs/gui/include/gui/TraceUtils.h +++ b/libs/gui/include/gui/TraceUtils.h @@ -21,13 +21,20 @@ #include #include -#define ATRACE_FORMAT(fmt, ...) \ - TraceUtils::TraceEnder __traceEnder = \ - (TraceUtils::atraceFormatBegin(fmt, ##__VA_ARGS__), TraceUtils::TraceEnder()) +#define ATRACE_FORMAT(fmt, ...) \ + TraceUtils::TraceEnder traceEnder = \ + (CC_UNLIKELY(ATRACE_ENABLED()) && \ + (TraceUtils::atraceFormatBegin(fmt, ##__VA_ARGS__), true), \ + TraceUtils::TraceEnder()) -#define ATRACE_FORMAT_BEGIN(fmt, ...) TraceUtils::atraceFormatBegin(fmt, ##__VA_ARGS__) +#define ATRACE_FORMAT_INSTANT(fmt, ...) \ + (CC_UNLIKELY(ATRACE_ENABLED()) && (TraceUtils::instantFormat(fmt, ##__VA_ARGS__), true)) -#define ATRACE_FORMAT_INSTANT(fmt, ...) TraceUtils::intantFormat(fmt, ##__VA_ARGS__) +#define ALOGE_AND_TRACE(fmt, ...) \ + do { \ + ALOGE(fmt, ##__VA_ARGS__); \ + ATRACE_FORMAT_INSTANT(fmt, ##__VA_ARGS__); \ + } while (false) namespace android { @@ -39,8 +46,6 @@ public: }; static void atraceFormatBegin(const char* fmt, ...) { - if (CC_LIKELY(!ATRACE_ENABLED())) return; - const int BUFFER_SIZE = 256; va_list ap; char buf[BUFFER_SIZE]; @@ -52,9 +57,7 @@ public: ATRACE_BEGIN(buf); } - static void intantFormat(const char* fmt, ...) { - if (CC_LIKELY(!ATRACE_ENABLED())) return; - + static void instantFormat(const char* fmt, ...) { const int BUFFER_SIZE = 256; va_list ap; char buf[BUFFER_SIZE]; @@ -65,7 +68,6 @@ public: ATRACE_INSTANT(buf); } +}; -}; // class TraceUtils - -} /* namespace android */ +} // namespace android diff --git a/libs/gui/include/gui/VsyncEventData.h b/libs/gui/include/gui/VsyncEventData.h index 8e99539fe933afe4a9bfba14b9ca942aaf8c2b22..b40a84099c0067d54c9e7d7860cdfd9183dc1116 100644 --- a/libs/gui/include/gui/VsyncEventData.h +++ b/libs/gui/include/gui/VsyncEventData.h @@ -16,7 +16,7 @@ #pragma once -#include +#include #include @@ -24,8 +24,8 @@ namespace android::gui { // Plain Old Data (POD) vsync data structure. For example, it can be easily used in the // DisplayEventReceiver::Event union. struct VsyncEventData { - // Max amount of frame timelines is arbitrarily set to be reasonable. - static constexpr int64_t kFrameTimelinesLength = 7; + // Max capacity of frame timelines is arbitrarily set to be reasonable. + static constexpr int64_t kFrameTimelinesCapacity = 7; // The current frame interval in ns when this frame was scheduled. int64_t frameInterval; @@ -33,6 +33,9 @@ struct VsyncEventData { // Index into the frameTimelines that represents the platform's preferred frame timeline. uint32_t preferredFrameTimelineIndex; + // Size of frame timelines provided by the platform; max is kFrameTimelinesCapacity. + uint32_t frameTimelinesLength; + struct alignas(8) FrameTimeline { // The Vsync Id corresponsing to this vsync event. This will be used to // populate ISurfaceComposer::setFrameTimelineVsync and @@ -45,7 +48,7 @@ struct VsyncEventData { // The anticipated Vsync presentation time in nanos. int64_t expectedPresentationTime; - } frameTimelines[kFrameTimelinesLength]; // Sorted possible frame timelines. + } frameTimelines[kFrameTimelinesCapacity]; // Sorted possible frame timelines. // Gets the preferred frame timeline's vsync ID. int64_t preferredVsyncId() const; diff --git a/libs/gui/include/gui/WindowInfo.h b/libs/gui/include/gui/WindowInfo.h index 169f7f022b6df7d48a0ea4926e0f567faa6081f6..70b2ee8e32702276e24f552263fe6320bf6728ee 100644 --- a/libs/gui/include/gui/WindowInfo.h +++ b/libs/gui/include/gui/WindowInfo.h @@ -171,6 +171,8 @@ struct WindowInfo : public Parcelable { static_cast(os::InputConfig::SPY), INTERCEPTS_STYLUS = static_cast(os::InputConfig::INTERCEPTS_STYLUS), + CLONE = + static_cast(os::InputConfig::CLONE), // clang-format on }; @@ -234,9 +236,12 @@ struct WindowInfo : public Parcelable { Type layoutParamsType = Type::UNKNOWN; ftl::Flags layoutParamsFlags; - void setInputConfig(ftl::Flags config, bool value); + // The input token for the window to which focus should be transferred when this input window + // can be successfully focused. If null, this input window will not transfer its focus to + // any other window. + sp focusTransferTarget; - bool isClone = false; + void setInputConfig(ftl::Flags config, bool value); void addTouchableRegion(const Rect& region); @@ -272,6 +277,7 @@ public: WindowInfoHandle(const WindowInfo& other); inline const WindowInfo* getInfo() const { return &mInfo; } + inline WindowInfo* editInfo() { return &mInfo; } sp getToken() const; diff --git a/libs/gui/include/gui/WindowInfosListener.h b/libs/gui/include/gui/WindowInfosListener.h index a18a498c5eada8bf45aec48a24c97c859b12e1a0..02c8eb5ef3f8ec88fb7fe781414c2a98f6bf0eed 100644 --- a/libs/gui/include/gui/WindowInfosListener.h +++ b/libs/gui/include/gui/WindowInfosListener.h @@ -16,15 +16,13 @@ #pragma once -#include -#include +#include #include namespace android::gui { class WindowInfosListener : public virtual RefBase { public: - virtual void onWindowInfosChanged(const std::vector&, - const std::vector&) = 0; + virtual void onWindowInfosChanged(const WindowInfosUpdate& update) = 0; }; -} // namespace android::gui \ No newline at end of file +} // namespace android::gui diff --git a/libs/gui/include/gui/WindowInfosListenerReporter.h b/libs/gui/include/gui/WindowInfosListenerReporter.h index 3b4aed442e27bdcf95b2ae62793206a8d06de7a4..684e21ad96db332caa87cea064485ec7963b84a8 100644 --- a/libs/gui/include/gui/WindowInfosListenerReporter.h +++ b/libs/gui/include/gui/WindowInfosListenerReporter.h @@ -17,36 +17,37 @@ #pragma once #include -#include +#include +#include #include -#include #include #include +#include #include namespace android { -class ISurfaceComposer; class WindowInfosListenerReporter : public gui::BnWindowInfosListener { public: static sp getInstance(); - binder::Status onWindowInfosChanged(const std::vector&, - const std::vector&, - const sp&) override; - + binder::Status onWindowInfosChanged(const gui::WindowInfosUpdate& update) override; status_t addWindowInfosListener( - const sp& windowInfosListener, const sp&, + const sp& windowInfosListener, + const sp&, std::pair, std::vector>* outInitialInfo); status_t removeWindowInfosListener(const sp& windowInfosListener, - const sp& surfaceComposer); - void reconnect(const sp&); + const sp& surfaceComposer); + void reconnect(const sp&); private: std::mutex mListenersMutex; - std::unordered_set, SpHash> + std::unordered_set, gui::SpHash> mWindowInfosListeners GUARDED_BY(mListenersMutex); std::vector mLastWindowInfos GUARDED_BY(mListenersMutex); std::vector mLastDisplayInfos GUARDED_BY(mListenersMutex); + + sp mWindowInfosPublisher; + int64_t mListenerId; }; } // namespace android diff --git a/libs/gui/include/gui/WindowInfosUpdate.h b/libs/gui/include/gui/WindowInfosUpdate.h new file mode 100644 index 0000000000000000000000000000000000000000..2ca59fb497e02f8145276813dcb89166a6331e5e --- /dev/null +++ b/libs/gui/include/gui/WindowInfosUpdate.h @@ -0,0 +1,44 @@ +/* + * 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. + */ + +#pragma once + +#include +#include +#include + +namespace android::gui { + +struct WindowInfosUpdate : public Parcelable { + WindowInfosUpdate() {} + + WindowInfosUpdate(std::vector windowInfos, std::vector displayInfos, + int64_t vsyncId, int64_t timestamp) + : windowInfos(std::move(windowInfos)), + displayInfos(std::move(displayInfos)), + vsyncId(vsyncId), + timestamp(timestamp) {} + + std::vector windowInfos; + std::vector displayInfos; + int64_t vsyncId; + int64_t timestamp; + + status_t writeToParcel(android::Parcel*) const override; + status_t readFromParcel(const android::Parcel*) override; +}; + +} // namespace android::gui diff --git a/libs/gui/include/gui/fake/BufferData.h b/libs/gui/include/gui/fake/BufferData.h new file mode 100644 index 0000000000000000000000000000000000000000..725d11c313d13f60599f47eb6d3ea29ade75202b --- /dev/null +++ b/libs/gui/include/gui/fake/BufferData.h @@ -0,0 +1,51 @@ +/* + * 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::fake { + +// Class which exposes buffer properties from BufferData without holding on to an actual buffer +class BufferData : public android::BufferData { +public: + BufferData(uint64_t bufferId, uint32_t width, uint32_t height, int32_t pixelFormat, + uint64_t outUsage) + : mBufferId(bufferId), + mWidth(width), + mHeight(height), + mPixelFormat(pixelFormat), + mOutUsage(outUsage) {} + bool hasBuffer() const override { return mBufferId != 0; } + bool hasSameBuffer(const android::BufferData& other) const override { + return getId() == other.getId() && frameNumber == other.frameNumber; + } + uint32_t getWidth() const override { return mWidth; } + uint32_t getHeight() const override { return mHeight; } + uint64_t getId() const override { return mBufferId; } + PixelFormat getPixelFormat() const override { return mPixelFormat; } + uint64_t getUsage() const override { return mOutUsage; } + +private: + uint64_t mBufferId; + uint32_t mWidth; + uint32_t mHeight; + int32_t mPixelFormat; + uint64_t mOutUsage; +}; + +} // namespace android::fake diff --git a/libs/gui/include/gui/test/CallbackUtils.h b/libs/gui/include/gui/test/CallbackUtils.h index 08785b49c1372cb2e604e1f082cae861dfc0ee0a..1c900e9da50327016f6f3cf22bd240e878a4c598 100644 --- a/libs/gui/include/gui/test/CallbackUtils.h +++ b/libs/gui/include/gui/test/CallbackUtils.h @@ -51,6 +51,7 @@ public: enum Buffer { NOT_ACQUIRED = 0, ACQUIRED, + ACQUIRED_NULL, }; enum PreviousBuffer { @@ -133,17 +134,28 @@ private: : mBufferResult(bufferResult), mPreviousBufferResult(previousBufferResult) {} void verifySurfaceControlStats(const SurfaceControlStats& surfaceControlStats, - nsecs_t latchTime) const { + nsecs_t /* latchTime */) const { const auto& [surfaceControl, latch, acquireTimeOrFence, presentFence, previousReleaseFence, transformHint, frameEvents, ignore] = - surfaceControlStats; - - ASSERT_TRUE(std::holds_alternative(acquireTimeOrFence)); - ASSERT_EQ(std::get(acquireTimeOrFence) > 0, - mBufferResult == ExpectedResult::Buffer::ACQUIRED) - << "bad acquire time"; - ASSERT_LE(std::get(acquireTimeOrFence), latchTime) - << "acquire time should be <= latch time"; + surfaceControlStats; + + nsecs_t acquireTime = -1; + if (std::holds_alternative(acquireTimeOrFence)) { + acquireTime = std::get(acquireTimeOrFence); + } else { + auto fence = std::get>(acquireTimeOrFence); + if (fence) { + ASSERT_EQ(fence->wait(3000), NO_ERROR); + acquireTime = fence->getSignalTime(); + } + } + + if (mBufferResult == ExpectedResult::Buffer::ACQUIRED) { + ASSERT_GT(acquireTime, 0) << "acquire time should be valid"; + } else { + ASSERT_LE(acquireTime, 0) << "acquire time should not be valid"; + } + ASSERT_EQ(acquireTime > 0, mBufferResult == ExpectedResult::Buffer::ACQUIRED); if (mPreviousBufferResult == ExpectedResult::PreviousBuffer::RELEASED) { ASSERT_NE(previousReleaseFence, nullptr) diff --git a/libs/gui/include/private/gui/ComposerServiceAIDL.h b/libs/gui/include/private/gui/ComposerServiceAIDL.h index 9a96976c0f1013dc23bed567ebdf877dd81dc1c5..6352a5851c76887933f2e98fe5f009e0d02ebfe5 100644 --- a/libs/gui/include/private/gui/ComposerServiceAIDL.h +++ b/libs/gui/include/private/gui/ComposerServiceAIDL.h @@ -20,6 +20,7 @@ #include #include +#include #include #include @@ -50,28 +51,6 @@ public: // Get a connection to the Composer Service. This will block until // a connection is established. Returns null if permission is denied. static sp getComposerService(); - - // the following two methods are moved from ISurfaceComposer.h - // TODO(b/74619554): Remove this stopgap once the framework is display-agnostic. - std::optional getInternalDisplayId() const { - std::vector displayIds; - binder::Status status = mComposerService->getPhysicalDisplayIds(&displayIds); - return (!status.isOk() || displayIds.empty()) - ? std::nullopt - : DisplayId::fromValue( - static_cast(displayIds.front())); - } - - // TODO(b/74619554): Remove this stopgap once the framework is display-agnostic. - sp getInternalDisplayToken() const { - const auto displayId = getInternalDisplayId(); - if (!displayId) return nullptr; - sp display; - binder::Status status = - mComposerService->getPhysicalDisplayToken(static_cast(displayId->value), - &display); - return status.isOk() ? display : nullptr; - } }; // --------------------------------------------------------------------------- diff --git a/libs/gui/tests/Android.bp b/libs/gui/tests/Android.bp index 0702e0f3d542cb02dab396449c2d639604a16b32..cd35d2fe3c56c338367a125b5065cbc5b221fd6c 100644 --- a/libs/gui/tests/Android.bp +++ b/libs/gui/tests/Android.bp @@ -24,6 +24,7 @@ cc_test { "BLASTBufferQueue_test.cpp", "BufferItemConsumer_test.cpp", "BufferQueue_test.cpp", + "CompositorTiming_test.cpp", "CpuConsumer_test.cpp", "EndToEndNativeInputTest.cpp", "DisplayInfo_test.cpp", diff --git a/libs/gui/tests/BLASTBufferQueue_test.cpp b/libs/gui/tests/BLASTBufferQueue_test.cpp index b993289e6a98b0d47370dd2ea04c7806e9219d88..a3ad6807c560144d79ba38b0bee2a74cc1799e72 100644 --- a/libs/gui/tests/BLASTBufferQueue_test.cpp +++ b/libs/gui/tests/BLASTBufferQueue_test.cpp @@ -19,6 +19,7 @@ #include #include +#include #include #include #include @@ -31,6 +32,7 @@ #include #include #include +#include #include #include #include @@ -115,15 +117,17 @@ public: mBlastBufferQueueAdapter->syncNextTransaction(callback, acquireSingleBuffer); } - void syncNextTransaction(std::function callback, + bool syncNextTransaction(std::function callback, bool acquireSingleBuffer = true) { - mBlastBufferQueueAdapter->syncNextTransaction(callback, acquireSingleBuffer); + return mBlastBufferQueueAdapter->syncNextTransaction(callback, acquireSingleBuffer); } void stopContinuousSyncTransaction() { mBlastBufferQueueAdapter->stopContinuousSyncTransaction(); } + void clearSyncTransaction() { mBlastBufferQueueAdapter->clearSyncTransaction(); } + int getWidth() { return mBlastBufferQueueAdapter->mSize.width; } int getHeight() { return mBlastBufferQueueAdapter->mSize.height; } @@ -175,30 +179,35 @@ protected: BLASTBufferQueueTest() { const ::testing::TestInfo* const testInfo = ::testing::UnitTest::GetInstance()->current_test_info(); - ALOGV("Begin test: %s.%s", testInfo->test_case_name(), testInfo->name()); + ALOGD("Begin test: %s.%s", testInfo->test_case_name(), testInfo->name()); } ~BLASTBufferQueueTest() { const ::testing::TestInfo* const testInfo = ::testing::UnitTest::GetInstance()->current_test_info(); - ALOGV("End test: %s.%s", testInfo->test_case_name(), testInfo->name()); + ALOGD("End test: %s.%s", testInfo->test_case_name(), testInfo->name()); } void SetUp() { mComposer = ComposerService::getComposerService(); mClient = new SurfaceComposerClient(); - mDisplayToken = mClient->getInternalDisplayToken(); + const auto ids = SurfaceComposerClient::getPhysicalDisplayIds(); + ASSERT_FALSE(ids.empty()); + // display 0 is picked as this test is not much display depedent + mDisplayToken = SurfaceComposerClient::getPhysicalDisplayToken(ids.front()); ASSERT_NE(nullptr, mDisplayToken.get()); Transaction t; t.setDisplayLayerStack(mDisplayToken, ui::DEFAULT_LAYER_STACK); t.apply(); t.clear(); - ui::DisplayMode mode; - ASSERT_EQ(NO_ERROR, SurfaceComposerClient::getActiveDisplayMode(mDisplayToken, &mode)); - const ui::Size& resolution = mode.resolution; + ui::DisplayState displayState; + ASSERT_EQ(NO_ERROR, SurfaceComposerClient::getDisplayState(mDisplayToken, &displayState)); + const ui::Size& resolution = displayState.layerStackSpaceRect; mDisplayWidth = resolution.getWidth(); mDisplayHeight = resolution.getHeight(); + ALOGD("Display: %dx%d orientation:%d", mDisplayWidth, mDisplayHeight, + displayState.orientation); mSurfaceControl = mClient->createSurface(String8("TestSurface"), mDisplayWidth, mDisplayHeight, PIXEL_FORMAT_RGBA_8888, @@ -305,11 +314,12 @@ protected: const sp captureListener = new SyncScreenCaptureListener(); binder::Status status = sf->captureDisplay(captureArgs, captureListener); - if (status.transactionError() != NO_ERROR) { - return status.transactionError(); + status_t err = gui::aidl_utils::statusTFromBinderStatus(status); + if (err != NO_ERROR) { + return err; } captureResults = captureListener->waitForResults(); - return captureResults.result; + return fenceStatus(captureResults.fenceResult); } void queueBuffer(sp igbp, uint8_t r, uint8_t g, uint8_t b, @@ -1103,7 +1113,11 @@ TEST_F(BLASTBufferQueueTest, SyncNextTransactionOverwrite) { ASSERT_NE(nullptr, adapter.getTransactionReadyCallback()); auto callback2 = [](Transaction*) {}; - adapter.syncNextTransaction(callback2); + ASSERT_FALSE(adapter.syncNextTransaction(callback2)); + + sp igbProducer; + setUpProducer(adapter, igbProducer); + queueBuffer(igbProducer, 0, 255, 0, 0); std::unique_lock lock(mutex); if (!receivedCallback) { @@ -1115,6 +1129,37 @@ TEST_F(BLASTBufferQueueTest, SyncNextTransactionOverwrite) { ASSERT_TRUE(receivedCallback); } +TEST_F(BLASTBufferQueueTest, ClearSyncTransaction) { + std::mutex mutex; + std::condition_variable callbackReceivedCv; + bool receivedCallback = false; + + BLASTBufferQueueHelper adapter(mSurfaceControl, mDisplayWidth, mDisplayHeight); + ASSERT_EQ(nullptr, adapter.getTransactionReadyCallback()); + auto callback = [&](Transaction*) { + std::unique_lock lock(mutex); + receivedCallback = true; + callbackReceivedCv.notify_one(); + }; + adapter.syncNextTransaction(callback); + ASSERT_NE(nullptr, adapter.getTransactionReadyCallback()); + + adapter.clearSyncTransaction(); + + sp igbProducer; + setUpProducer(adapter, igbProducer); + queueBuffer(igbProducer, 0, 255, 0, 0); + + std::unique_lock lock(mutex); + if (!receivedCallback) { + ASSERT_EQ(callbackReceivedCv.wait_for(lock, std::chrono::seconds(3)), + std::cv_status::timeout) + << "did not receive callback"; + } + + ASSERT_FALSE(receivedCallback); +} + TEST_F(BLASTBufferQueueTest, SyncNextTransactionDropBuffer) { uint8_t r = 255; uint8_t g = 0; @@ -1146,6 +1191,7 @@ TEST_F(BLASTBufferQueueTest, SyncNextTransactionDropBuffer) { ASSERT_EQ(NO_ERROR, captureDisplay(mCaptureArgs, mCaptureResults)); ASSERT_NO_FATAL_FAILURE( checkScreenCapture(r, g, b, {0, 0, (int32_t)mDisplayWidth, (int32_t)mDisplayHeight})); + sync.apply(); } // This test will currently fail because the old surfacecontrol will steal the last presented buffer diff --git a/libs/gui/tests/BufferItemConsumer_test.cpp b/libs/gui/tests/BufferItemConsumer_test.cpp index fc6551c8e6ba16e2f6079fa064056dcc6488c0c2..6880678050ddb7520815b53564b193d2580d21fb 100644 --- a/libs/gui/tests/BufferItemConsumer_test.cpp +++ b/libs/gui/tests/BufferItemConsumer_test.cpp @@ -68,7 +68,7 @@ class BufferItemConsumerTest : public ::testing::Test { void HandleBufferFreed() { std::lock_guard lock(mMutex); mFreedBufferCount++; - ALOGV("HandleBufferFreed, mFreedBufferCount=%d", mFreedBufferCount); + ALOGD("HandleBufferFreed, mFreedBufferCount=%d", mFreedBufferCount); } void DequeueBuffer(int* outSlot) { @@ -80,7 +80,7 @@ class BufferItemConsumerTest : public ::testing::Test { nullptr, nullptr); ASSERT_GE(ret, 0); - ALOGV("dequeueBuffer: slot=%d", slot); + ALOGD("dequeueBuffer: slot=%d", slot); if (ret & IGraphicBufferProducer::BUFFER_NEEDS_REALLOCATION) { ret = mProducer->requestBuffer(slot, &mBuffers[slot]); ASSERT_EQ(NO_ERROR, ret); @@ -89,7 +89,7 @@ class BufferItemConsumerTest : public ::testing::Test { } void QueueBuffer(int slot) { - ALOGV("enqueueBuffer: slot=%d", slot); + ALOGD("enqueueBuffer: slot=%d", slot); IGraphicBufferProducer::QueueBufferInput bufferInput( 0ULL, true, HAL_DATASPACE_UNKNOWN, Rect::INVALID_RECT, NATIVE_WINDOW_SCALING_MODE_FREEZE, 0, Fence::NO_FENCE); @@ -104,12 +104,12 @@ class BufferItemConsumerTest : public ::testing::Test { status_t ret = mBIC->acquireBuffer(&buffer, 0, false); ASSERT_EQ(NO_ERROR, ret); - ALOGV("acquireBuffer: slot=%d", buffer.mSlot); + ALOGD("acquireBuffer: slot=%d", buffer.mSlot); *outSlot = buffer.mSlot; } void ReleaseBuffer(int slot) { - ALOGV("releaseBuffer: slot=%d", slot); + ALOGD("releaseBuffer: slot=%d", slot); BufferItem buffer; buffer.mSlot = slot; buffer.mGraphicBuffer = mBuffers[slot]; diff --git a/libs/gui/tests/BufferQueue_test.cpp b/libs/gui/tests/BufferQueue_test.cpp index d1208ee5ae8ceb0d3697e69566cb7498afc62fe7..2f1fd3e78f6ee9b0f438182a4c4bdbac9efc456a 100644 --- a/libs/gui/tests/BufferQueue_test.cpp +++ b/libs/gui/tests/BufferQueue_test.cpp @@ -49,14 +49,14 @@ protected: BufferQueueTest() { const ::testing::TestInfo* const testInfo = ::testing::UnitTest::GetInstance()->current_test_info(); - ALOGV("Begin test: %s.%s", testInfo->test_case_name(), + ALOGD("Begin test: %s.%s", testInfo->test_case_name(), testInfo->name()); } ~BufferQueueTest() { const ::testing::TestInfo* const testInfo = ::testing::UnitTest::GetInstance()->current_test_info(); - ALOGV("End test: %s.%s", testInfo->test_case_name(), + ALOGD("End test: %s.%s", testInfo->test_case_name(), testInfo->name()); } diff --git a/libs/gui/tests/CompositorTiming_test.cpp b/libs/gui/tests/CompositorTiming_test.cpp new file mode 100644 index 0000000000000000000000000000000000000000..d8bb21d582bb78854c7c42d33895f83b1fe875af --- /dev/null +++ b/libs/gui/tests/CompositorTiming_test.cpp @@ -0,0 +1,61 @@ +/* + * 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 + +namespace android::test { +namespace { + +constexpr nsecs_t kMillisecond = 1'000'000; +constexpr nsecs_t kVsyncPeriod = 8'333'333; +constexpr nsecs_t kVsyncPhase = -2'166'667; +constexpr nsecs_t kIdealLatency = -kVsyncPhase; + +} // namespace + +TEST(CompositorTimingTest, InvalidVsyncPeriod) { + const nsecs_t vsyncDeadline = systemTime(); + constexpr nsecs_t kInvalidVsyncPeriod = -1; + + const gui::CompositorTiming timing(vsyncDeadline, kInvalidVsyncPeriod, kVsyncPhase, + kIdealLatency); + + EXPECT_EQ(timing.deadline, 0); + EXPECT_EQ(timing.interval, gui::CompositorTiming::kDefaultVsyncPeriod); + EXPECT_EQ(timing.presentLatency, gui::CompositorTiming::kDefaultVsyncPeriod); +} + +TEST(CompositorTimingTest, PresentLatencySnapping) { + for (nsecs_t presentDelay = 0, compositeTime = systemTime(); presentDelay < 10 * kVsyncPeriod; + presentDelay += kMillisecond, compositeTime += kVsyncPeriod) { + const nsecs_t presentLatency = kIdealLatency + presentDelay; + const nsecs_t vsyncDeadline = compositeTime + presentLatency + kVsyncPeriod; + + const gui::CompositorTiming timing(vsyncDeadline, kVsyncPeriod, kVsyncPhase, + presentLatency); + + EXPECT_EQ(timing.deadline, compositeTime + presentDelay + kVsyncPeriod); + EXPECT_EQ(timing.interval, kVsyncPeriod); + + // The presentDelay should be rounded to a multiple of the VSYNC period, such that the + // remainder (presentLatency % interval) always evaluates to the VSYNC phase offset. + EXPECT_GE(timing.presentLatency, kIdealLatency); + EXPECT_EQ(timing.presentLatency % timing.interval, kIdealLatency); + } +} + +} // namespace android::test diff --git a/libs/gui/tests/CpuConsumer_test.cpp b/libs/gui/tests/CpuConsumer_test.cpp index 00e32d912476c5b484c10371bc41fbaa5215b14c..0a14afac555ecdb5ac722bb51537cb22144bca64 100644 --- a/libs/gui/tests/CpuConsumer_test.cpp +++ b/libs/gui/tests/CpuConsumer_test.cpp @@ -62,7 +62,7 @@ protected: const ::testing::TestInfo* const test_info = ::testing::UnitTest::GetInstance()->current_test_info(); CpuConsumerTestParams params = GetParam(); - ALOGV("** Starting test %s (%d x %d, %d, 0x%x)", + ALOGD("** Starting test %s (%d x %d, %d, 0x%x)", test_info->name(), params.width, params.height, params.maxLockedBuffers, params.format); @@ -582,7 +582,7 @@ TEST_P(CpuConsumerTest, FromCpuManyInQueue) { uint32_t stride[numInQueue]; for (int i = 0; i < numInQueue; i++) { - ALOGV("Producing frame %d", i); + ALOGD("Producing frame %d", i); ASSERT_NO_FATAL_FAILURE(produceOneFrame(mANW, params, time[i], &stride[i])); } @@ -590,7 +590,7 @@ TEST_P(CpuConsumerTest, FromCpuManyInQueue) { // Consume for (int i = 0; i < numInQueue; i++) { - ALOGV("Consuming frame %d", i); + ALOGD("Consuming frame %d", i); CpuConsumer::LockedBuffer b; err = mCC->lockNextBuffer(&b); ASSERT_NO_ERROR(err, "getNextBuffer error: "); @@ -624,7 +624,7 @@ TEST_P(CpuConsumerTest, FromCpuLockMax) { uint32_t stride; for (int i = 0; i < params.maxLockedBuffers + 1; i++) { - ALOGV("Producing frame %d", i); + ALOGD("Producing frame %d", i); ASSERT_NO_FATAL_FAILURE(produceOneFrame(mANW, params, time, &stride)); } @@ -633,7 +633,7 @@ TEST_P(CpuConsumerTest, FromCpuLockMax) { std::vector b(params.maxLockedBuffers); for (int i = 0; i < params.maxLockedBuffers; i++) { - ALOGV("Locking frame %d", i); + ALOGD("Locking frame %d", i); err = mCC->lockNextBuffer(&b[i]); ASSERT_NO_ERROR(err, "getNextBuffer error: "); @@ -647,16 +647,16 @@ TEST_P(CpuConsumerTest, FromCpuLockMax) { checkAnyBuffer(b[i], GetParam().format); } - ALOGV("Locking frame %d (too many)", params.maxLockedBuffers); + ALOGD("Locking frame %d (too many)", params.maxLockedBuffers); CpuConsumer::LockedBuffer bTooMuch; err = mCC->lockNextBuffer(&bTooMuch); ASSERT_TRUE(err == NOT_ENOUGH_DATA) << "Allowing too many locks"; - ALOGV("Unlocking frame 0"); + ALOGD("Unlocking frame 0"); err = mCC->unlockBuffer(b[0]); ASSERT_NO_ERROR(err, "Could not unlock buffer 0: "); - ALOGV("Locking frame %d (should work now)", params.maxLockedBuffers); + ALOGD("Locking frame %d (should work now)", params.maxLockedBuffers); err = mCC->lockNextBuffer(&bTooMuch); ASSERT_NO_ERROR(err, "Did not allow new lock after unlock"); @@ -669,11 +669,11 @@ TEST_P(CpuConsumerTest, FromCpuLockMax) { checkAnyBuffer(bTooMuch, GetParam().format); - ALOGV("Unlocking extra buffer"); + ALOGD("Unlocking extra buffer"); err = mCC->unlockBuffer(bTooMuch); ASSERT_NO_ERROR(err, "Could not unlock extra buffer: "); - ALOGV("Locking frame %d (no more available)", params.maxLockedBuffers + 1); + ALOGD("Locking frame %d (no more available)", params.maxLockedBuffers + 1); err = mCC->lockNextBuffer(&b[0]); ASSERT_EQ(BAD_VALUE, err) << "Not out of buffers somehow"; diff --git a/libs/gui/tests/DisplayEventStructLayout_test.cpp b/libs/gui/tests/DisplayEventStructLayout_test.cpp index da88463d63b290656dcd981ba18f1fbe976f1043..3949d70aac41175f621e0596d1a3554fed046852 100644 --- a/libs/gui/tests/DisplayEventStructLayout_test.cpp +++ b/libs/gui/tests/DisplayEventStructLayout_test.cpp @@ -35,6 +35,7 @@ TEST(DisplayEventStructLayoutTest, TestEventAlignment) { CHECK_OFFSET(DisplayEventReceiver::Event::VSync, count, 0); CHECK_OFFSET(DisplayEventReceiver::Event::VSync, vsyncData.frameInterval, 8); CHECK_OFFSET(DisplayEventReceiver::Event::VSync, vsyncData.preferredFrameTimelineIndex, 16); + CHECK_OFFSET(DisplayEventReceiver::Event::VSync, vsyncData.frameTimelinesLength, 20); CHECK_OFFSET(DisplayEventReceiver::Event::VSync, vsyncData.frameTimelines, 24); CHECK_OFFSET(DisplayEventReceiver::Event::VSync, vsyncData.frameTimelines[0].vsyncId, 24); CHECK_OFFSET(DisplayEventReceiver::Event::VSync, vsyncData.frameTimelines[0].deadlineTimestamp, @@ -44,16 +45,16 @@ TEST(DisplayEventStructLayoutTest, TestEventAlignment) { // Also test the offsets of the last frame timeline. A loop is not used because the non-const // index cannot be used in static_assert. const int lastFrameTimelineOffset = /* Start of array */ 24 + - (VsyncEventData::kFrameTimelinesLength - 1) * /* Size of FrameTimeline */ 24; + (VsyncEventData::kFrameTimelinesCapacity - 1) * /* Size of FrameTimeline */ 24; CHECK_OFFSET(DisplayEventReceiver::Event::VSync, - vsyncData.frameTimelines[VsyncEventData::kFrameTimelinesLength - 1].vsyncId, + vsyncData.frameTimelines[VsyncEventData::kFrameTimelinesCapacity - 1].vsyncId, lastFrameTimelineOffset); CHECK_OFFSET(DisplayEventReceiver::Event::VSync, - vsyncData.frameTimelines[VsyncEventData::kFrameTimelinesLength - 1] + vsyncData.frameTimelines[VsyncEventData::kFrameTimelinesCapacity - 1] .deadlineTimestamp, lastFrameTimelineOffset + 8); CHECK_OFFSET(DisplayEventReceiver::Event::VSync, - vsyncData.frameTimelines[VsyncEventData::kFrameTimelinesLength - 1] + vsyncData.frameTimelines[VsyncEventData::kFrameTimelinesCapacity - 1] .expectedPresentationTime, lastFrameTimelineOffset + 16); diff --git a/libs/gui/tests/DisplayedContentSampling_test.cpp b/libs/gui/tests/DisplayedContentSampling_test.cpp index b647aaba8fe3337aa38c4d33579c37be7a0b9581..0a2750a4dd421b07c9480d966a351542b38416a2 100644 --- a/libs/gui/tests/DisplayedContentSampling_test.cpp +++ b/libs/gui/tests/DisplayedContentSampling_test.cpp @@ -32,7 +32,10 @@ protected: void SetUp() { mComposerClient = new SurfaceComposerClient; ASSERT_EQ(OK, mComposerClient->initCheck()); - mDisplayToken = mComposerClient->getInternalDisplayToken(); + const auto ids = SurfaceComposerClient::getPhysicalDisplayIds(); + ASSERT_FALSE(ids.empty()); + // display 0 is picked for now, can extend to support all displays if needed + mDisplayToken = SurfaceComposerClient::getPhysicalDisplayToken(ids.front()); ASSERT_TRUE(mDisplayToken); } diff --git a/libs/gui/tests/EndToEndNativeInputTest.cpp b/libs/gui/tests/EndToEndNativeInputTest.cpp index 2637f59b5e76115d687901a3bbb67bf9a4c462cd..4ec7a06cb8270aeabe649e96851ce1df7e75cba6 100644 --- a/libs/gui/tests/EndToEndNativeInputTest.cpp +++ b/libs/gui/tests/EndToEndNativeInputTest.cpp @@ -164,7 +164,7 @@ public: void assertFocusChange(bool hasFocus) { InputEvent *ev = consumeEvent(); ASSERT_NE(ev, nullptr); - ASSERT_EQ(AINPUT_EVENT_TYPE_FOCUS, ev->getType()); + ASSERT_EQ(InputEventType::FOCUS, ev->getType()); FocusEvent *focusEvent = static_cast(ev); EXPECT_EQ(hasFocus, focusEvent->getHasFocus()); } @@ -172,7 +172,7 @@ public: void expectTap(int x, int y) { InputEvent* ev = consumeEvent(); ASSERT_NE(ev, nullptr); - ASSERT_EQ(AINPUT_EVENT_TYPE_MOTION, ev->getType()); + ASSERT_EQ(InputEventType::MOTION, ev->getType()); MotionEvent* mev = static_cast(ev); EXPECT_EQ(AMOTION_EVENT_ACTION_DOWN, mev->getAction()); EXPECT_EQ(x, mev->getX(0)); @@ -181,7 +181,7 @@ public: ev = consumeEvent(); ASSERT_NE(ev, nullptr); - ASSERT_EQ(AINPUT_EVENT_TYPE_MOTION, ev->getType()); + ASSERT_EQ(InputEventType::MOTION, ev->getType()); mev = static_cast(ev); EXPECT_EQ(AMOTION_EVENT_ACTION_UP, mev->getAction()); EXPECT_EQ(0, mev->getFlags() & VERIFIED_MOTION_EVENT_FLAGS); @@ -190,7 +190,7 @@ public: void expectTapWithFlag(int x, int y, int32_t flags) { InputEvent *ev = consumeEvent(); ASSERT_NE(ev, nullptr); - ASSERT_EQ(AINPUT_EVENT_TYPE_MOTION, ev->getType()); + ASSERT_EQ(InputEventType::MOTION, ev->getType()); MotionEvent *mev = static_cast(ev); EXPECT_EQ(AMOTION_EVENT_ACTION_DOWN, mev->getAction()); EXPECT_EQ(x, mev->getX(0)); @@ -199,7 +199,7 @@ public: ev = consumeEvent(); ASSERT_NE(ev, nullptr); - ASSERT_EQ(AINPUT_EVENT_TYPE_MOTION, ev->getType()); + ASSERT_EQ(InputEventType::MOTION, ev->getType()); mev = static_cast(ev); EXPECT_EQ(AMOTION_EVENT_ACTION_UP, mev->getAction()); EXPECT_EQ(flags, mev->getFlags() & flags); @@ -208,7 +208,7 @@ public: void expectTapInDisplayCoordinates(int displayX, int displayY) { InputEvent *ev = consumeEvent(); ASSERT_NE(ev, nullptr); - ASSERT_EQ(AINPUT_EVENT_TYPE_MOTION, ev->getType()); + ASSERT_EQ(InputEventType::MOTION, ev->getType()); MotionEvent *mev = static_cast(ev); EXPECT_EQ(AMOTION_EVENT_ACTION_DOWN, mev->getAction()); const PointerCoords &coords = *mev->getRawPointerCoords(0 /*pointerIndex*/); @@ -218,7 +218,7 @@ public: ev = consumeEvent(); ASSERT_NE(ev, nullptr); - ASSERT_EQ(AINPUT_EVENT_TYPE_MOTION, ev->getType()); + ASSERT_EQ(InputEventType::MOTION, ev->getType()); mev = static_cast(ev); EXPECT_EQ(AMOTION_EVENT_ACTION_UP, mev->getAction()); EXPECT_EQ(0, mev->getFlags() & VERIFIED_MOTION_EVENT_FLAGS); @@ -227,7 +227,7 @@ public: void expectKey(uint32_t keycode) { InputEvent *ev = consumeEvent(); ASSERT_NE(ev, nullptr); - ASSERT_EQ(AINPUT_EVENT_TYPE_KEY, ev->getType()); + ASSERT_EQ(InputEventType::KEY, ev->getType()); KeyEvent *keyEvent = static_cast(ev); EXPECT_EQ(AMOTION_EVENT_ACTION_DOWN, keyEvent->getAction()); EXPECT_EQ(keycode, keyEvent->getKeyCode()); @@ -235,7 +235,7 @@ public: ev = consumeEvent(); ASSERT_NE(ev, nullptr); - ASSERT_EQ(AINPUT_EVENT_TYPE_KEY, ev->getType()); + ASSERT_EQ(InputEventType::KEY, ev->getType()); keyEvent = static_cast(ev); EXPECT_EQ(AMOTION_EVENT_ACTION_UP, keyEvent->getAction()); EXPECT_EQ(keycode, keyEvent->getKeyCode()); @@ -272,8 +272,6 @@ public: FocusRequest request; request.token = mInputInfo.token; request.windowName = mInputInfo.name; - request.focusedToken = nullptr; - request.focusedWindowName = ""; request.timestamp = systemTime(SYSTEM_TIME_MONOTONIC); request.displayId = displayId; t.setFocusedWindow(request); @@ -360,8 +358,10 @@ public: void SetUp() { mComposerClient = new SurfaceComposerClient; ASSERT_EQ(NO_ERROR, mComposerClient->initCheck()); - - const auto display = mComposerClient->getInternalDisplayToken(); + const auto ids = SurfaceComposerClient::getPhysicalDisplayIds(); + ASSERT_FALSE(ids.empty()); + // display 0 is picked for now, can extend to support all displays if needed + const auto display = SurfaceComposerClient::getPhysicalDisplayToken(ids.front()); ASSERT_NE(display, nullptr); ui::DisplayMode mode; @@ -510,6 +510,22 @@ TEST_F(InputSurfacesTest, input_respects_surface_insets) { bgSurface->expectTap(1, 1); } +TEST_F(InputSurfacesTest, input_respects_surface_insets_with_replaceTouchableRegionWithCrop) { + std::unique_ptr bgSurface = makeSurface(100, 100); + std::unique_ptr fgSurface = makeSurface(100, 100); + bgSurface->showAt(100, 100); + + fgSurface->mInputInfo.surfaceInset = 5; + fgSurface->mInputInfo.replaceTouchableRegionWithCrop = true; + fgSurface->showAt(100, 100); + + injectTap(106, 106); + fgSurface->expectTap(1, 1); + + injectTap(101, 101); + bgSurface->expectTap(1, 1); +} + // Ensure a surface whose insets are cropped, handles the touch offset correctly. ref:b/120413463 TEST_F(InputSurfacesTest, input_respects_cropped_surface_insets) { std::unique_ptr parentSurface = makeSurface(100, 100); @@ -612,7 +628,7 @@ TEST_F(InputSurfacesTest, input_respects_scaled_touchable_region_overflow) { // Expect no crash for overflow. injectTap(12, 24); - fgSurface->expectTap(6, 12); + bgSurface->expectTap(12, 24); } // Ensure we ignore transparent region when getting screen bounds when positioning input frame. @@ -1219,32 +1235,6 @@ TEST_F(MultiDisplayTests, virtual_display_receives_input) { surface->expectKey(AKEYCODE_V); } -/** - * When multiple DisplayDevices are mapped to the same layerStack, use the configuration for the - * display that can receive input. - */ -TEST_F(MultiDisplayTests, many_to_one_display_mapping) { - ui::LayerStack layerStack = ui::LayerStack::fromValue(42); - createDisplay(1000, 1000, false /*isSecure*/, layerStack, false /*receivesInput*/, - 100 /*offsetX*/, 100 /*offsetY*/); - createDisplay(1000, 1000, false /*isSecure*/, layerStack, true /*receivesInput*/, - 200 /*offsetX*/, 200 /*offsetY*/); - createDisplay(1000, 1000, false /*isSecure*/, layerStack, false /*receivesInput*/, - 300 /*offsetX*/, 300 /*offsetY*/); - std::unique_ptr surface = makeSurface(100, 100); - surface->doTransaction([&](auto &t, auto &sc) { t.setLayerStack(sc, layerStack); }); - surface->showAt(10, 10); - - // Input injection happens in logical display coordinates. - injectTapOnDisplay(11, 11, layerStack.id); - // Expect that the display transform for the display that receives input was used. - surface->expectTapInDisplayCoordinates(211, 211); - - surface->requestFocus(layerStack.id); - surface->assertFocusChange(true); - injectKeyOnDisplay(AKEYCODE_V, layerStack.id); -} - TEST_F(MultiDisplayTests, drop_input_for_secure_layer_on_nonsecure_display) { ui::LayerStack layerStack = ui::LayerStack::fromValue(42); createDisplay(1000, 1000, false /*isSecure*/, layerStack); diff --git a/libs/gui/tests/GLTest.cpp b/libs/gui/tests/GLTest.cpp index 449cbf9f7570d959a3046a516f2407b8545f6648..e5f4aaa9996d860227a210d8e9aafd7258091c45 100644 --- a/libs/gui/tests/GLTest.cpp +++ b/libs/gui/tests/GLTest.cpp @@ -31,7 +31,7 @@ static int abs(int value) { void GLTest::SetUp() { const ::testing::TestInfo* const testInfo = ::testing::UnitTest::GetInstance()->current_test_info(); - ALOGV("Begin test: %s.%s", testInfo->test_case_name(), testInfo->name()); + ALOGD("Begin test: %s.%s", testInfo->test_case_name(), testInfo->name()); mEglDisplay = eglGetDisplay(EGL_DEFAULT_DISPLAY); ASSERT_EQ(EGL_SUCCESS, eglGetError()); @@ -135,7 +135,7 @@ void GLTest::TearDown() { const ::testing::TestInfo* const testInfo = ::testing::UnitTest::GetInstance()->current_test_info(); - ALOGV("End test: %s.%s", testInfo->test_case_name(), testInfo->name()); + ALOGD("End test: %s.%s", testInfo->test_case_name(), testInfo->name()); } EGLint const* GLTest::getConfigAttribs() { diff --git a/libs/gui/tests/IGraphicBufferProducer_test.cpp b/libs/gui/tests/IGraphicBufferProducer_test.cpp index 3427731ffffff9926e5f8a7497a400bf68672e02..e6cb89cb833ab4f72fdd2ff51fded9dca81112f6 100644 --- a/libs/gui/tests/IGraphicBufferProducer_test.cpp +++ b/libs/gui/tests/IGraphicBufferProducer_test.cpp @@ -84,7 +84,7 @@ protected: virtual void SetUp() { const ::testing::TestInfo* const testInfo = ::testing::UnitTest::GetInstance()->current_test_info(); - ALOGV("Begin test: %s.%s", testInfo->test_case_name(), + ALOGD("Begin test: %s.%s", testInfo->test_case_name(), testInfo->name()); mMC = new MockConsumer; @@ -114,7 +114,7 @@ protected: virtual void TearDown() { const ::testing::TestInfo* const testInfo = ::testing::UnitTest::GetInstance()->current_test_info(); - ALOGV("End test: %s.%s", testInfo->test_case_name(), + ALOGD("End test: %s.%s", testInfo->test_case_name(), testInfo->name()); } diff --git a/libs/gui/tests/RegionSampling_test.cpp b/libs/gui/tests/RegionSampling_test.cpp index c9106bed4c7c92d6be5968bfeb3920b408588f81..b18b5442573f9ec102431d5024be3705ca884823 100644 --- a/libs/gui/tests/RegionSampling_test.cpp +++ b/libs/gui/tests/RegionSampling_test.cpp @@ -19,14 +19,16 @@ #include #include +#include #include #include #include #include -#include +#include #include using namespace std::chrono_literals; +using android::gui::aidl_utils::statusTFromBinderStatus; namespace android::test { @@ -242,24 +244,33 @@ protected: }; TEST_F(RegionSamplingTest, invalidLayerHandle_doesNotCrash) { - sp composer = ComposerService::getComposerService(); + sp composer = ComposerServiceAIDL::getComposerService(); sp listener = new Listener(); - const Rect sampleArea{100, 100, 200, 200}; + gui::ARect sampleArea; + sampleArea.left = 100; + sampleArea.top = 100; + sampleArea.right = 200; + sampleArea.bottom = 200; // Passing in composer service as the layer handle should not crash, we'll // treat it as a layer that no longer exists and silently allow sampling to // occur. - status_t status = composer->addRegionSamplingListener(sampleArea, - IInterface::asBinder(composer), listener); - ASSERT_EQ(NO_ERROR, status); + binder::Status status = + composer->addRegionSamplingListener(sampleArea, IInterface::asBinder(composer), + listener); + ASSERT_EQ(NO_ERROR, statusTFromBinderStatus(status)); composer->removeRegionSamplingListener(listener); } TEST_F(RegionSamplingTest, DISABLED_CollectsLuma) { fill_render(rgba_green); - sp composer = ComposerService::getComposerService(); + sp composer = ComposerServiceAIDL::getComposerService(); sp listener = new Listener(); - const Rect sampleArea{100, 100, 200, 200}; + 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(300ms)) << "timed out waiting for luma event to be received"; @@ -271,9 +282,13 @@ TEST_F(RegionSamplingTest, DISABLED_CollectsLuma) { TEST_F(RegionSamplingTest, DISABLED_CollectsChangingLuma) { fill_render(rgba_green); - sp composer = ComposerService::getComposerService(); + sp composer = ComposerServiceAIDL::getComposerService(); sp listener = new Listener(); - const Rect sampleArea{100, 100, 200, 200}; + 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(300ms)) << "timed out waiting for luma event to be received"; @@ -291,13 +306,21 @@ TEST_F(RegionSamplingTest, DISABLED_CollectsChangingLuma) { TEST_F(RegionSamplingTest, DISABLED_CollectsLumaFromTwoRegions) { fill_render(rgba_green); - sp composer = ComposerService::getComposerService(); + sp composer = ComposerServiceAIDL::getComposerService(); sp greenListener = new Listener(); - const Rect greenSampleArea{100, 100, 200, 200}; + gui::ARect greenSampleArea; + greenSampleArea.left = 100; + greenSampleArea.top = 100; + greenSampleArea.right = 200; + greenSampleArea.bottom = 200; composer->addRegionSamplingListener(greenSampleArea, mTopLayer->getHandle(), greenListener); sp grayListener = new Listener(); - const Rect graySampleArea{500, 100, 600, 200}; + gui::ARect graySampleArea; + graySampleArea.left = 500; + graySampleArea.top = 100; + graySampleArea.right = 600; + graySampleArea.bottom = 200; composer->addRegionSamplingListener(graySampleArea, mTopLayer->getHandle(), grayListener); EXPECT_TRUE(grayListener->wait_event(300ms)) @@ -312,29 +335,49 @@ TEST_F(RegionSamplingTest, DISABLED_CollectsLumaFromTwoRegions) { } TEST_F(RegionSamplingTest, DISABLED_TestIfInvalidInputParameters) { - sp composer = ComposerService::getComposerService(); + sp composer = ComposerServiceAIDL::getComposerService(); sp listener = new Listener(); - const Rect sampleArea{100, 100, 200, 200}; + + gui::ARect invalidRect; + invalidRect.left = Rect::INVALID_RECT.left; + invalidRect.top = Rect::INVALID_RECT.top; + invalidRect.right = Rect::INVALID_RECT.right; + invalidRect.bottom = Rect::INVALID_RECT.bottom; + + gui::ARect sampleArea; + sampleArea.left = 100; + sampleArea.top = 100; + sampleArea.right = 200; + sampleArea.bottom = 200; // Invalid input sampleArea EXPECT_EQ(BAD_VALUE, - composer->addRegionSamplingListener(Rect::INVALID_RECT, mTopLayer->getHandle(), - listener)); + statusTFromBinderStatus(composer->addRegionSamplingListener(invalidRect, + mTopLayer->getHandle(), + listener))); listener->reset(); // Invalid input binder - EXPECT_EQ(NO_ERROR, composer->addRegionSamplingListener(sampleArea, NULL, listener)); + EXPECT_EQ(NO_ERROR, + statusTFromBinderStatus( + composer->addRegionSamplingListener(sampleArea, NULL, listener))); // Invalid input listener EXPECT_EQ(BAD_VALUE, - composer->addRegionSamplingListener(sampleArea, mTopLayer->getHandle(), NULL)); - EXPECT_EQ(BAD_VALUE, composer->removeRegionSamplingListener(NULL)); + statusTFromBinderStatus(composer->addRegionSamplingListener(sampleArea, + mTopLayer->getHandle(), + NULL))); + EXPECT_EQ(BAD_VALUE, statusTFromBinderStatus(composer->removeRegionSamplingListener(NULL))); // remove the listener composer->removeRegionSamplingListener(listener); } TEST_F(RegionSamplingTest, DISABLED_TestCallbackAfterRemoveListener) { fill_render(rgba_green); - sp composer = ComposerService::getComposerService(); + sp composer = ComposerServiceAIDL::getComposerService(); sp listener = new Listener(); - const Rect sampleArea{100, 100, 200, 200}; + gui::ARect sampleArea; + sampleArea.left = 100; + sampleArea.top = 100; + sampleArea.right = 200; + sampleArea.bottom = 200; composer->addRegionSamplingListener(sampleArea, mTopLayer->getHandle(), listener); fill_render(rgba_green); @@ -349,13 +392,18 @@ TEST_F(RegionSamplingTest, DISABLED_TestCallbackAfterRemoveListener) { } TEST_F(RegionSamplingTest, DISABLED_CollectsLumaFromMovingLayer) { - sp composer = ComposerService::getComposerService(); + sp composer = ComposerServiceAIDL::getComposerService(); sp listener = new Listener(); Rect sampleArea{100, 100, 200, 200}; + gui::ARect sampleAreaA; + sampleAreaA.left = sampleArea.left; + sampleAreaA.top = sampleArea.top; + sampleAreaA.right = sampleArea.right; + sampleAreaA.bottom = sampleArea.bottom; // Test: listener in (100, 100). See layer before move, no layer after move. fill_render(rgba_blue); - composer->addRegionSamplingListener(sampleArea, mTopLayer->getHandle(), listener); + composer->addRegionSamplingListener(sampleAreaA, mTopLayer->getHandle(), listener); EXPECT_TRUE(listener->wait_event(300ms)) << "timed out waiting for luma event to be received"; EXPECT_NEAR(listener->luma(), luma_blue, error_margin); listener->reset(); @@ -367,7 +415,11 @@ TEST_F(RegionSamplingTest, DISABLED_CollectsLumaFromMovingLayer) { // Test: listener offset to (600, 600). No layer before move, see layer after move. fill_render(rgba_green); sampleArea.offsetTo(600, 600); - composer->addRegionSamplingListener(sampleArea, mTopLayer->getHandle(), listener); + sampleAreaA.left = sampleArea.left; + sampleAreaA.top = sampleArea.top; + 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_NEAR(listener->luma(), luma_gray, error_margin); listener->reset(); diff --git a/libs/gui/tests/SamplingDemo.cpp b/libs/gui/tests/SamplingDemo.cpp index a083a228a629c59e2b0a0282007cf4546e717d55..f98437b4f8c10784818c761b512981e414b4ed62 100644 --- a/libs/gui/tests/SamplingDemo.cpp +++ b/libs/gui/tests/SamplingDemo.cpp @@ -26,7 +26,7 @@ #include #include #include -#include +#include #include using namespace std::chrono_literals; @@ -121,10 +121,22 @@ int main(int, const char**) { const Rect backButtonArea{200, 1606, 248, 1654}; sp backButton = new android::Button("BackButton", backButtonArea); - sp composer = ComposerService::getComposerService(); - composer->addRegionSamplingListener(homeButtonArea, homeButton->getStopLayerHandle(), + gui::ARect homeButtonAreaA; + homeButtonAreaA.left = 490; + homeButtonAreaA.top = 1606; + homeButtonAreaA.right = 590; + homeButtonAreaA.bottom = 1654; + + gui::ARect backButtonAreaA; + backButtonAreaA.left = 200; + backButtonAreaA.top = 1606; + backButtonAreaA.right = 248; + backButtonAreaA.bottom = 1654; + + sp composer = ComposerServiceAIDL::getComposerService(); + composer->addRegionSamplingListener(homeButtonAreaA, homeButton->getStopLayerHandle(), homeButton); - composer->addRegionSamplingListener(backButtonArea, backButton->getStopLayerHandle(), + composer->addRegionSamplingListener(backButtonAreaA, backButton->getStopLayerHandle(), backButton); ProcessState::self()->startThreadPool(); diff --git a/libs/gui/tests/StreamSplitter_test.cpp b/libs/gui/tests/StreamSplitter_test.cpp index b65cddaea36f17a06de688c9c739a6919fd9835e..2f14924a15bc9ea1a19d9f710ef3fbf3895d3fa8 100644 --- a/libs/gui/tests/StreamSplitter_test.cpp +++ b/libs/gui/tests/StreamSplitter_test.cpp @@ -36,14 +36,14 @@ protected: StreamSplitterTest() { const ::testing::TestInfo* const testInfo = ::testing::UnitTest::GetInstance()->current_test_info(); - ALOGV("Begin test: %s.%s", testInfo->test_case_name(), + ALOGD("Begin test: %s.%s", testInfo->test_case_name(), testInfo->name()); } ~StreamSplitterTest() { const ::testing::TestInfo* const testInfo = ::testing::UnitTest::GetInstance()->current_test_info(); - ALOGV("End test: %s.%s", testInfo->test_case_name(), + ALOGD("End test: %s.%s", testInfo->test_case_name(), testInfo->name()); } }; diff --git a/libs/gui/tests/SurfaceTextureClient_test.cpp b/libs/gui/tests/SurfaceTextureClient_test.cpp index c7458a3755c9b9a6a5b5068fb5c0bcba1c940a45..82b66972d9fae3737b00d3f3438851a0a8e94495 100644 --- a/libs/gui/tests/SurfaceTextureClient_test.cpp +++ b/libs/gui/tests/SurfaceTextureClient_test.cpp @@ -42,7 +42,7 @@ protected: virtual void SetUp() { const ::testing::TestInfo* const testInfo = ::testing::UnitTest::GetInstance()->current_test_info(); - ALOGV("Begin test: %s.%s", testInfo->test_case_name(), + ALOGD("Begin test: %s.%s", testInfo->test_case_name(), testInfo->name()); sp producer; @@ -99,7 +99,7 @@ protected: const ::testing::TestInfo* const testInfo = ::testing::UnitTest::GetInstance()->current_test_info(); - ALOGV("End test: %s.%s", testInfo->test_case_name(), + ALOGD("End test: %s.%s", testInfo->test_case_name(), testInfo->name()); } diff --git a/libs/gui/tests/Surface_test.cpp b/libs/gui/tests/Surface_test.cpp index cb977f04c9ed5e452ea50359573cf20a7fc45bb2..c1b67b4396f1f683edf7cee631c44beeda2599e1 100644 --- a/libs/gui/tests/Surface_test.cpp +++ b/libs/gui/tests/Surface_test.cpp @@ -24,6 +24,7 @@ #include #include #include +#include #include #include #include @@ -212,11 +213,12 @@ protected: const sp captureListener = new SyncScreenCaptureListener(); binder::Status status = sf->captureDisplay(captureArgs, captureListener); - if (status.transactionError() != NO_ERROR) { - return status.transactionError(); + status_t err = gui::aidl_utils::statusTFromBinderStatus(status); + if (err != NO_ERROR) { + return err; } captureResults = captureListener->waitForResults(); - return captureResults.result; + return fenceStatus(captureResults.fenceResult); } sp mSurface; @@ -261,7 +263,10 @@ TEST_F(SurfaceTest, ScreenshotsOfProtectedBuffersDontSucceed) { sp anw(mSurface); // Verify the screenshot works with no protected buffers. - const sp display = ComposerServiceAIDL::getInstance().getInternalDisplayToken(); + const auto ids = SurfaceComposerClient::getPhysicalDisplayIds(); + ASSERT_FALSE(ids.empty()); + // display 0 is picked for now, can extend to support all displays if needed + const sp display = SurfaceComposerClient::getPhysicalDisplayToken(ids.front()); ASSERT_FALSE(display == nullptr); DisplayCaptureArgs captureArgs; @@ -690,278 +695,257 @@ public: mSupportsPresent = supportsPresent; } - sp createConnection() override { return nullptr; } - sp createDisplayEventConnection( - ISurfaceComposer::VsyncSource, ISurfaceComposer::EventRegistrationFlags) override { - return nullptr; - } - status_t setTransactionState(const FrameTimelineInfo& /*frameTimelineInfo*/, - const Vector& /*state*/, - const Vector& /*displays*/, uint32_t /*flags*/, - const sp& /*applyToken*/, - const InputWindowCommands& /*inputWindowCommands*/, - int64_t /*desiredPresentTime*/, bool /*isAutoTimestamp*/, - const client_cache_t& /*cachedBuffer*/, - bool /*hasListenerCallbacks*/, - const std::vector& /*listenerCallbacks*/, - uint64_t /*transactionId*/) override { + status_t setTransactionState( + const FrameTimelineInfo& /*frameTimelineInfo*/, Vector& /*state*/, + const Vector& /*displays*/, uint32_t /*flags*/, + const sp& /*applyToken*/, InputWindowCommands /*inputWindowCommands*/, + int64_t /*desiredPresentTime*/, bool /*isAutoTimestamp*/, + const std::vector& /*cachedBuffer*/, bool /*hasListenerCallbacks*/, + const std::vector& /*listenerCallbacks*/, uint64_t /*transactionId*/, + const std::vector& /*mergedTransactionIds*/) override { return NO_ERROR; } - void bootFinished() override {} - bool authenticateSurfaceTexture( - const sp& /*surface*/) const override { - return false; - } +protected: + IBinder* onAsBinder() override { return nullptr; } - status_t getSupportedFrameTimestamps(std::vector* outSupported) - const override { - *outSupported = { - FrameEvent::REQUESTED_PRESENT, - FrameEvent::ACQUIRE, - FrameEvent::LATCH, - FrameEvent::FIRST_REFRESH_START, - FrameEvent::LAST_REFRESH_START, - FrameEvent::GPU_COMPOSITION_DONE, - FrameEvent::DEQUEUE_READY, - FrameEvent::RELEASE - }; - if (mSupportsPresent) { - outSupported->push_back( - FrameEvent::DISPLAY_PRESENT); - } - return NO_ERROR; - } +private: + bool mSupportsPresent{true}; +}; - status_t getStaticDisplayInfo(const sp& /*display*/, ui::StaticDisplayInfo*) override { - return NO_ERROR; - } - status_t getDynamicDisplayInfo(const sp& /*display*/, - ui::DynamicDisplayInfo*) override { - return NO_ERROR; - } - status_t getDisplayNativePrimaries(const sp& /*display*/, - ui::DisplayPrimaries& /*primaries*/) override { - return NO_ERROR; - } - status_t setActiveColorMode(const sp& /*display*/, ColorMode /*colorMode*/) override { - return NO_ERROR; - } - status_t setBootDisplayMode(const sp& /*display*/, ui::DisplayModeId /*id*/) override { - return NO_ERROR; - } +class FakeSurfaceComposerAIDL : public gui::ISurfaceComposer { +public: + ~FakeSurfaceComposerAIDL() override {} - status_t clearAnimationFrameStats() override { return NO_ERROR; } - status_t getAnimationFrameStats(FrameStats* /*outStats*/) const override { - return NO_ERROR; + void setSupportsPresent(bool supportsPresent) { mSupportsPresent = supportsPresent; } + + binder::Status bootFinished() override { return binder::Status::ok(); } + + binder::Status createDisplayEventConnection( + VsyncSource /*vsyncSource*/, EventRegistration /*eventRegistration*/, + const sp& /*layerHandle*/, + sp* outConnection) override { + *outConnection = nullptr; + return binder::Status::ok(); } - status_t overrideHdrTypes(const sp& /*display*/, - const std::vector& /*hdrTypes*/) override { - return NO_ERROR; + + binder::Status createConnection(sp* outClient) override { + *outClient = nullptr; + return binder::Status::ok(); } - status_t onPullAtom(const int32_t /*atomId*/, std::string* /*outData*/, - bool* /*success*/) override { - return NO_ERROR; + + binder::Status createDisplay(const std::string& /*displayName*/, bool /*secure*/, + float /*requestedRefreshRate*/, + sp* /*outDisplay*/) override { + return binder::Status::ok(); } - status_t enableVSyncInjections(bool /*enable*/) override { - return NO_ERROR; + + binder::Status destroyDisplay(const sp& /*display*/) override { + return binder::Status::ok(); } - status_t injectVSync(nsecs_t /*when*/) override { return NO_ERROR; } - status_t getLayerDebugInfo(std::vector* /*layers*/) override { - return NO_ERROR; + + binder::Status getPhysicalDisplayIds(std::vector* /*outDisplayIds*/) override { + return binder::Status::ok(); } - status_t getCompositionPreference( - ui::Dataspace* /*outDefaultDataspace*/, ui::PixelFormat* /*outDefaultPixelFormat*/, - ui::Dataspace* /*outWideColorGamutDataspace*/, - ui::PixelFormat* /*outWideColorGamutPixelFormat*/) const override { - return NO_ERROR; + + binder::Status getPhysicalDisplayToken(int64_t /*displayId*/, + sp* /*outDisplay*/) override { + return binder::Status::ok(); } - status_t getDisplayedContentSamplingAttributes(const sp& /*display*/, - ui::PixelFormat* /*outFormat*/, - ui::Dataspace* /*outDataspace*/, - uint8_t* /*outComponentMask*/) const override { - return NO_ERROR; + + binder::Status setPowerMode(const sp& /*display*/, int /*mode*/) override { + return binder::Status::ok(); } - status_t setDisplayContentSamplingEnabled(const sp& /*display*/, bool /*enable*/, - uint8_t /*componentMask*/, - uint64_t /*maxFrames*/) override { - return NO_ERROR; + + binder::Status getSupportedFrameTimestamps(std::vector* outSupported) override { + *outSupported = {FrameEvent::REQUESTED_PRESENT, + FrameEvent::ACQUIRE, + FrameEvent::LATCH, + FrameEvent::FIRST_REFRESH_START, + FrameEvent::LAST_REFRESH_START, + FrameEvent::GPU_COMPOSITION_DONE, + FrameEvent::DEQUEUE_READY, + FrameEvent::RELEASE}; + if (mSupportsPresent) { + outSupported->push_back(FrameEvent::DISPLAY_PRESENT); + } + return binder::Status::ok(); } - status_t getDisplayedContentSample(const sp& /*display*/, uint64_t /*maxFrames*/, - uint64_t /*timestamp*/, - DisplayedFrameStats* /*outStats*/) const override { - return NO_ERROR; + + binder::Status getDisplayStats(const sp& /*display*/, + gui::DisplayStatInfo* /*outStatInfo*/) override { + return binder::Status::ok(); } - status_t getColorManagement(bool* /*outGetColorManagement*/) const override { return NO_ERROR; } - status_t getProtectedContentSupport(bool* /*outSupported*/) const override { return NO_ERROR; } + binder::Status getDisplayState(const sp& /*display*/, + gui::DisplayState* /*outState*/) override { + return binder::Status::ok(); + } - status_t addRegionSamplingListener(const Rect& /*samplingArea*/, - const sp& /*stopLayerHandle*/, - const sp& /*listener*/) override { - return NO_ERROR; + binder::Status getStaticDisplayInfo(int64_t /*displayId*/, + gui::StaticDisplayInfo* /*outInfo*/) override { + return binder::Status::ok(); } - status_t removeRegionSamplingListener( - const sp& /*listener*/) override { - return NO_ERROR; + + binder::Status getDynamicDisplayInfoFromId(int64_t /*displayId*/, + gui::DynamicDisplayInfo* /*outInfo*/) override { + return binder::Status::ok(); } - status_t addFpsListener(int32_t /*taskId*/, const sp& /*listener*/) { - return NO_ERROR; + + binder::Status getDynamicDisplayInfoFromToken(const sp& /*display*/, + gui::DynamicDisplayInfo* /*outInfo*/) override { + return binder::Status::ok(); } - status_t removeFpsListener(const sp& /*listener*/) { return NO_ERROR; } - status_t addTunnelModeEnabledListener(const sp& /*listener*/) { - return NO_ERROR; + binder::Status getDisplayNativePrimaries(const sp& /*display*/, + gui::DisplayPrimaries* /*outPrimaries*/) override { + return binder::Status::ok(); } - status_t removeTunnelModeEnabledListener( - const sp& /*listener*/) { - return NO_ERROR; + binder::Status setActiveColorMode(const sp& /*display*/, int /*colorMode*/) override { + return binder::Status::ok(); } - status_t setDesiredDisplayModeSpecs(const sp& /*displayToken*/, - ui::DisplayModeId /*defaultMode*/, - bool /*allowGroupSwitching*/, - float /*primaryRefreshRateMin*/, - float /*primaryRefreshRateMax*/, - float /*appRequestRefreshRateMin*/, - float /*appRequestRefreshRateMax*/) { - return NO_ERROR; + binder::Status setBootDisplayMode(const sp& /*display*/, + int /*displayModeId*/) override { + return binder::Status::ok(); } - status_t getDesiredDisplayModeSpecs(const sp& /*displayToken*/, - ui::DisplayModeId* /*outDefaultMode*/, - bool* /*outAllowGroupSwitching*/, - float* /*outPrimaryRefreshRateMin*/, - float* /*outPrimaryRefreshRateMax*/, - float* /*outAppRequestRefreshRateMin*/, - float* /*outAppRequestRefreshRateMax*/) override { - return NO_ERROR; - }; - status_t setGlobalShadowSettings(const half4& /*ambientColor*/, const half4& /*spotColor*/, - float /*lightPosY*/, float /*lightPosZ*/, - float /*lightRadius*/) override { - return NO_ERROR; + binder::Status clearBootDisplayMode(const sp& /*display*/) override { + return binder::Status::ok(); } - status_t getDisplayDecorationSupport( - const sp& /*displayToken*/, - std::optional* /*outSupport*/) const override { - return NO_ERROR; + binder::Status getBootDisplayModeSupport(bool* /*outMode*/) override { + return binder::Status::ok(); } - status_t setFrameRate(const sp& /*surface*/, float /*frameRate*/, - int8_t /*compatibility*/, int8_t /*changeFrameRateStrategy*/) override { - return NO_ERROR; + binder::Status getHdrConversionCapabilities( + std::vector*) override { + return binder::Status::ok(); } - status_t setFrameTimelineInfo(const sp& /*surface*/, - const FrameTimelineInfo& /*frameTimelineInfo*/) override { - return NO_ERROR; + binder::Status setHdrConversionStrategy( + const gui::HdrConversionStrategy& /*hdrConversionStrategy*/, + int32_t* /*outPreferredHdrOutputType*/) override { + return binder::Status::ok(); } - status_t addTransactionTraceListener( - const sp& /*listener*/) override { - return NO_ERROR; + binder::Status getHdrOutputConversionSupport(bool* /*outSupport*/) override { + return binder::Status::ok(); } - int getGPUContextPriority() override { return 0; }; + binder::Status setAutoLowLatencyMode(const sp& /*display*/, bool /*on*/) override { + return binder::Status::ok(); + } - status_t getMaxAcquiredBufferCount(int* /*buffers*/) const override { return NO_ERROR; } + binder::Status setGameContentType(const sp& /*display*/, bool /*on*/) override { + return binder::Status::ok(); + } - status_t addWindowInfosListener( - const sp& /*windowInfosListener*/) const override { - return NO_ERROR; + binder::Status captureDisplay(const DisplayCaptureArgs&, + const sp&) override { + return binder::Status::ok(); } - status_t removeWindowInfosListener( - const sp& /*windowInfosListener*/) const override { - return NO_ERROR; + binder::Status captureDisplayById(int64_t, const sp&) override { + return binder::Status::ok(); } - status_t setOverrideFrameRate(uid_t /*uid*/, float /*frameRate*/) override { return NO_ERROR; } + binder::Status captureLayers(const LayerCaptureArgs&, + const sp&) override { + return binder::Status::ok(); + } -protected: - IBinder* onAsBinder() override { return nullptr; } + binder::Status clearAnimationFrameStats() override { return binder::Status::ok(); } -private: - bool mSupportsPresent{true}; -}; + binder::Status getAnimationFrameStats(gui::FrameStats* /*outStats*/) override { + return binder::Status::ok(); + } -class FakeSurfaceComposerAIDL : public gui::ISurfaceComposer { -public: - ~FakeSurfaceComposerAIDL() override {} + binder::Status overrideHdrTypes(const sp& /*display*/, + const std::vector& /*hdrTypes*/) override { + return binder::Status::ok(); + } - void setSupportsPresent(bool supportsPresent) { mSupportsPresent = supportsPresent; } + binder::Status onPullAtom(int32_t /*atomId*/, gui::PullAtomData* /*outPullData*/) override { + return binder::Status::ok(); + } - binder::Status createDisplay(const std::string& /*displayName*/, bool /*secure*/, - sp* /*outDisplay*/) override { + binder::Status getLayerDebugInfo(std::vector* /*outLayers*/) override { return binder::Status::ok(); } - binder::Status destroyDisplay(const sp& /*display*/) override { + binder::Status getColorManagement(bool* /*outGetColorManagement*/) override { return binder::Status::ok(); } - binder::Status getPhysicalDisplayIds(std::vector* /*outDisplayIds*/) override { + binder::Status getCompositionPreference(gui::CompositionPreference* /*outPref*/) override { return binder::Status::ok(); } - binder::Status getPrimaryPhysicalDisplayId(int64_t* /*outDisplayId*/) override { + binder::Status getDisplayedContentSamplingAttributes( + const sp& /*display*/, gui::ContentSamplingAttributes* /*outAttrs*/) override { return binder::Status::ok(); } - binder::Status getPhysicalDisplayToken(int64_t /*displayId*/, - sp* /*outDisplay*/) override { + binder::Status setDisplayContentSamplingEnabled(const sp& /*display*/, bool /*enable*/, + int8_t /*componentMask*/, + int64_t /*maxFrames*/) override { return binder::Status::ok(); } - binder::Status setPowerMode(const sp& /*display*/, int /*mode*/) override { + binder::Status getProtectedContentSupport(bool* /*outSupporte*/) override { return binder::Status::ok(); } - binder::Status getDisplayStats(const sp& /*display*/, - gui::DisplayStatInfo* /*outStatInfo*/) override { + binder::Status getDisplayedContentSample(const sp& /*display*/, int64_t /*maxFrames*/, + int64_t /*timestamp*/, + gui::DisplayedFrameStats* /*outStats*/) override { return binder::Status::ok(); } - binder::Status getDisplayState(const sp& /*display*/, - gui::DisplayState* /*outState*/) override { + binder::Status isWideColorDisplay(const sp& /*token*/, + bool* /*outIsWideColorDisplay*/) override { return binder::Status::ok(); } - binder::Status clearBootDisplayMode(const sp& /*display*/) override { + binder::Status addRegionSamplingListener( + const gui::ARect& /*samplingArea*/, const sp& /*stopLayerHandle*/, + const sp& /*listener*/) override { return binder::Status::ok(); } - binder::Status getBootDisplayModeSupport(bool* /*outMode*/) override { + binder::Status removeRegionSamplingListener( + const sp& /*listener*/) override { return binder::Status::ok(); } - binder::Status setAutoLowLatencyMode(const sp& /*display*/, bool /*on*/) override { + binder::Status addFpsListener(int32_t /*taskId*/, + const sp& /*listener*/) override { return binder::Status::ok(); } - binder::Status setGameContentType(const sp& /*display*/, bool /*on*/) override { + binder::Status removeFpsListener(const sp& /*listener*/) override { return binder::Status::ok(); } - binder::Status captureDisplay(const DisplayCaptureArgs&, - const sp&) override { + binder::Status addTunnelModeEnabledListener( + const sp& /*listener*/) override { return binder::Status::ok(); } - binder::Status captureDisplayById(int64_t, const sp&) override { + binder::Status removeTunnelModeEnabledListener( + const sp& /*listener*/) override { return binder::Status::ok(); } - binder::Status captureLayers(const LayerCaptureArgs&, - const sp&) override { + binder::Status setDesiredDisplayModeSpecs(const sp& /*displayToken*/, + const gui::DisplayModeSpecs&) override { return binder::Status::ok(); } - binder::Status isWideColorDisplay(const sp& /*token*/, - bool* /*outIsWideColorDisplay*/) override { + binder::Status getDesiredDisplayModeSpecs(const sp& /*displayToken*/, + gui::DisplayModeSpecs*) override { return binder::Status::ok(); } @@ -989,6 +973,45 @@ public: binder::Status notifyPowerBoost(int /*boostId*/) override { return binder::Status::ok(); } + binder::Status setGlobalShadowSettings(const gui::Color& /*ambientColor*/, + const gui::Color& /*spotColor*/, float /*lightPosY*/, + float /*lightPosZ*/, float /*lightRadius*/) override { + return binder::Status::ok(); + } + + binder::Status getDisplayDecorationSupport( + const sp& /*displayToken*/, + std::optional* /*outSupport*/) override { + return binder::Status::ok(); + } + + binder::Status setOverrideFrameRate(int32_t /*uid*/, float /*frameRate*/) override { + return binder::Status::ok(); + } + + binder::Status getGpuContextPriority(int32_t* /*outPriority*/) override { + return binder::Status::ok(); + } + + binder::Status getMaxAcquiredBufferCount(int32_t* /*buffers*/) override { + return binder::Status::ok(); + } + + binder::Status addWindowInfosListener( + const sp& /*windowInfosListener*/, + gui::WindowInfosListenerInfo* /*outInfo*/) override { + return binder::Status::ok(); + } + + binder::Status removeWindowInfosListener( + const sp& /*windowInfosListener*/) override { + return binder::Status::ok(); + } + + binder::Status getOverlaySupport(gui::OverlayProperties* /*properties*/) override { + return binder::Status::ok(); + } + protected: IBinder* onAsBinder() override { return nullptr; } @@ -1034,10 +1057,10 @@ protected: class TestSurface : public Surface { public: - TestSurface(const sp& bufferProducer, - FenceToFenceTimeMap* fenceMap) - : Surface(bufferProducer), - mFakeSurfaceComposer(new FakeSurfaceComposer) { + TestSurface(const sp& bufferProducer, FenceToFenceTimeMap* fenceMap) + : Surface(bufferProducer), + mFakeSurfaceComposer(new FakeSurfaceComposer), + mFakeSurfaceComposerAIDL(new FakeSurfaceComposerAIDL) { mFakeFrameEventHistory = new FakeProducerFrameEventHistory(fenceMap); mFrameEventHistory.reset(mFakeFrameEventHistory); } @@ -1048,6 +1071,10 @@ public: return mFakeSurfaceComposer; } + sp composerServiceAIDL() const override { + return mFakeSurfaceComposerAIDL; + } + nsecs_t now() const override { return mNow; } @@ -1058,6 +1085,7 @@ public: public: sp mFakeSurfaceComposer; + sp mFakeSurfaceComposerAIDL; nsecs_t mNow = 0; // mFrameEventHistory owns the instance of FakeProducerFrameEventHistory, @@ -1070,20 +1098,30 @@ class GetFrameTimestampsTest : public ::testing::Test { protected: struct FenceAndFenceTime { explicit FenceAndFenceTime(FenceToFenceTimeMap& fenceMap) - : mFence(new Fence), - mFenceTime(fenceMap.createFenceTimeForTest(mFence)) {} - sp mFence { nullptr }; - std::shared_ptr mFenceTime { nullptr }; + : mFenceTime(fenceMap.createFenceTimeForTest(mFence)) {} + + sp mFence = sp::make(); + std::shared_ptr mFenceTime; }; + static CompositorTiming makeCompositorTiming(nsecs_t deadline = 1'000'000'000, + nsecs_t interval = 16'666'667, + nsecs_t presentLatency = 50'000'000) { + CompositorTiming timing; + timing.deadline = deadline; + timing.interval = interval; + timing.presentLatency = presentLatency; + return timing; + } + struct RefreshEvents { RefreshEvents(FenceToFenceTimeMap& fenceMap, nsecs_t refreshStart) - : mFenceMap(fenceMap), - kCompositorTiming( - {refreshStart, refreshStart + 1, refreshStart + 2 }), - kStartTime(refreshStart + 3), - kGpuCompositionDoneTime(refreshStart + 4), - kPresentTime(refreshStart + 5) {} + : mFenceMap(fenceMap), + kCompositorTiming( + makeCompositorTiming(refreshStart, refreshStart + 1, refreshStart + 2)), + kStartTime(refreshStart + 3), + kGpuCompositionDoneTime(refreshStart + 4), + kPresentTime(refreshStart + 5) {} void signalPostCompositeFences() { mFenceMap.signalAllForTest( @@ -1093,8 +1131,8 @@ protected: FenceToFenceTimeMap& mFenceMap; - FenceAndFenceTime mGpuCompositionDone { mFenceMap }; - FenceAndFenceTime mPresent { mFenceMap }; + FenceAndFenceTime mGpuCompositionDone{mFenceMap}; + FenceAndFenceTime mPresent{mFenceMap}; const CompositorTiming kCompositorTiming; @@ -1360,11 +1398,7 @@ TEST_F(GetFrameTimestampsTest, DefaultDisabled) { // This test verifies that the frame timestamps are retrieved if explicitly // enabled via native_window_enable_frame_timestamps. TEST_F(GetFrameTimestampsTest, EnabledSimple) { - CompositorTiming initialCompositorTiming { - 1000000000, // 1s deadline - 16666667, // 16ms interval - 50000000, // 50ms present latency - }; + const CompositorTiming initialCompositorTiming = makeCompositorTiming(); mCfeh->initializeCompositorTiming(initialCompositorTiming); enableFrameTimestamps(); @@ -1424,6 +1458,7 @@ TEST_F(GetFrameTimestampsTest, EnabledSimple) { TEST_F(GetFrameTimestampsTest, QueryPresentSupported) { bool displayPresentSupported = true; mSurface->mFakeSurfaceComposer->setSupportsPresent(displayPresentSupported); + mSurface->mFakeSurfaceComposerAIDL->setSupportsPresent(displayPresentSupported); // Verify supported bits are forwarded. int supportsPresent = -1; @@ -1435,6 +1470,7 @@ TEST_F(GetFrameTimestampsTest, QueryPresentSupported) { TEST_F(GetFrameTimestampsTest, QueryPresentNotSupported) { bool displayPresentSupported = false; mSurface->mFakeSurfaceComposer->setSupportsPresent(displayPresentSupported); + mSurface->mFakeSurfaceComposerAIDL->setSupportsPresent(displayPresentSupported); // Verify supported bits are forwarded. int supportsPresent = -1; @@ -1501,11 +1537,7 @@ TEST_F(GetFrameTimestampsTest, SnapToNextTickOverflow) { // This verifies the compositor timing is updated by refresh events // and piggy backed on a queue, dequeue, and enabling of timestamps.. TEST_F(GetFrameTimestampsTest, CompositorTimingUpdatesBasic) { - CompositorTiming initialCompositorTiming { - 1000000000, // 1s deadline - 16666667, // 16ms interval - 50000000, // 50ms present latency - }; + const CompositorTiming initialCompositorTiming = makeCompositorTiming(); mCfeh->initializeCompositorTiming(initialCompositorTiming); enableFrameTimestamps(); @@ -1586,11 +1618,7 @@ TEST_F(GetFrameTimestampsTest, CompositorTimingUpdatesBasic) { // This verifies the compositor deadline properly snaps to the the next // deadline based on the current time. TEST_F(GetFrameTimestampsTest, CompositorTimingDeadlineSnaps) { - CompositorTiming initialCompositorTiming { - 1000000000, // 1s deadline - 16666667, // 16ms interval - 50000000, // 50ms present latency - }; + const CompositorTiming initialCompositorTiming = makeCompositorTiming(); mCfeh->initializeCompositorTiming(initialCompositorTiming); enableFrameTimestamps(); @@ -2012,6 +2040,7 @@ TEST_F(GetFrameTimestampsTest, NoReleaseNoSync) { TEST_F(GetFrameTimestampsTest, PresentUnsupportedNoSync) { enableFrameTimestamps(); mSurface->mFakeSurfaceComposer->setSupportsPresent(false); + mSurface->mFakeSurfaceComposerAIDL->setSupportsPresent(false); // Dequeue and queue frame 1. const uint64_t fId1 = getNextFrameId(); diff --git a/libs/gui/tests/VsyncEventData_test.cpp b/libs/gui/tests/VsyncEventData_test.cpp index f114522951071adf3abee92c6882c0caf9eb6074..a2138f21446cd9a5d9455510ac02774b6d036e23 100644 --- a/libs/gui/tests/VsyncEventData_test.cpp +++ b/libs/gui/tests/VsyncEventData_test.cpp @@ -36,6 +36,7 @@ TEST(ParcelableVsyncEventData, Parcelling) { FrameTimeline timeline1 = FrameTimeline{4, 5, 6}; data.vsync.frameTimelines[0] = timeline0; data.vsync.frameTimelines[1] = timeline1; + data.vsync.frameTimelinesLength = 2; Parcel p; data.writeToParcel(&p); @@ -45,7 +46,8 @@ TEST(ParcelableVsyncEventData, Parcelling) { data2.readFromParcel(&p); ASSERT_EQ(data.vsync.frameInterval, data2.vsync.frameInterval); ASSERT_EQ(data.vsync.preferredFrameTimelineIndex, data2.vsync.preferredFrameTimelineIndex); - for (int i = 0; i < VsyncEventData::kFrameTimelinesLength; i++) { + ASSERT_EQ(data.vsync.frameTimelinesLength, data2.vsync.frameTimelinesLength); + for (int i = 0; i < VsyncEventData::kFrameTimelinesCapacity; i++) { ASSERT_EQ(data.vsync.frameTimelines[i].vsyncId, data2.vsync.frameTimelines[i].vsyncId); ASSERT_EQ(data.vsync.frameTimelines[i].deadlineTimestamp, data2.vsync.frameTimelines[i].deadlineTimestamp); diff --git a/libs/gui/tests/WindowInfo_test.cpp b/libs/gui/tests/WindowInfo_test.cpp index 99658ccd4bfde9849c2998375ba7493d2483eb8e..11b87efda70304ed2d018fd30bd834102ef9bea9 100644 --- a/libs/gui/tests/WindowInfo_test.cpp +++ b/libs/gui/tests/WindowInfo_test.cpp @@ -71,7 +71,7 @@ TEST(WindowInfo, Parcelling) { i.applicationInfo.name = "ApplicationFooBar"; i.applicationInfo.token = new BBinder(); i.applicationInfo.dispatchingTimeoutMillis = 0x12345678ABCD; - i.isClone = true; + i.focusTransferTarget = new BBinder(); Parcel p; i.writeToParcel(&p); @@ -102,7 +102,7 @@ TEST(WindowInfo, Parcelling) { ASSERT_EQ(i.replaceTouchableRegionWithCrop, i2.replaceTouchableRegionWithCrop); ASSERT_EQ(i.touchableRegionCropHandle, i2.touchableRegionCropHandle); ASSERT_EQ(i.applicationInfo, i2.applicationInfo); - ASSERT_EQ(i.isClone, i2.isClone); + ASSERT_EQ(i.focusTransferTarget, i2.focusTransferTarget); } TEST(InputApplicationInfo, Parcelling) { diff --git a/libs/input/Android.bp b/libs/input/Android.bp index b2fec7917b9000a63fcd09a23572db98d841acab..869458c407e8afe849274612cd0c3e585fa3f6ed 100644 --- a/libs/input/Android.bp +++ b/libs/input/Android.bp @@ -26,7 +26,6 @@ package { filegroup { name: "inputconstants_aidl", srcs: [ - "android/os/BlockUntrustedTouchesMode.aidl", "android/os/IInputConstants.aidl", "android/os/InputEventInjectionResult.aidl", "android/os/InputEventInjectionSync.aidl", @@ -42,34 +41,53 @@ cc_library { "-Wall", "-Wextra", "-Werror", + "-Wno-unused-parameter", ], srcs: [ "Input.cpp", "InputDevice.cpp", "InputEventLabels.cpp", + "InputVerifier.cpp", "Keyboard.cpp", "KeyCharacterMap.cpp", "KeyLayoutMap.cpp", + "MotionPredictor.cpp", "PrintTools.cpp", "PropertyMap.cpp", + "TfLiteMotionPredictor.cpp", "TouchVideoFrame.cpp", "VelocityControl.cpp", "VelocityTracker.cpp", + "VirtualInputDevice.cpp", "VirtualKeyMap.cpp", ], - header_libs: ["jni_headers"], + header_libs: [ + "flatbuffer_headers", + "jni_headers", + "tensorflow_headers", + ], export_header_lib_headers: ["jni_headers"], + generated_headers: [ + "toolbox_input_labels", + ], + shared_libs: [ "libbase", - "liblog", "libcutils", + "liblog", + "libPlatformProperties", "libvintf", ], + ldflags: [ + "-Wl,--exclude-libs=libtflite_static.a", + ], + static_libs: [ "libui-types", + "libtflite_static", ], export_static_lib_headers: [ @@ -89,7 +107,6 @@ cc_library { shared_libs: [ "libutils", "libbinder", - "libui", ], static_libs: [ @@ -103,12 +120,18 @@ cc_library { sanitize: { misc_undefined: ["integer"], }, + + required: [ + "motion_predictor_model_prebuilt", + ], }, host: { shared: { enabled: false, }, include_dirs: [ + "bionic/libc/kernel/android/uapi/", + "bionic/libc/kernel/uapi", "frameworks/native/libs/arect/include", ], }, @@ -144,6 +167,7 @@ cc_library { cc_defaults { name: "libinput_fuzz_defaults", + cpp_std: "c++20", host_supported: true, shared_libs: [ "libutils", diff --git a/libs/input/Input.cpp b/libs/input/Input.cpp index 2581668429c25173ce0751127747495eb203ee0f..00925ba555cb07d7a12be9a5bc0f9e48f1d37a23 100644 --- a/libs/input/Input.cpp +++ b/libs/input/Input.cpp @@ -21,7 +21,9 @@ #include #include #include +#include +#include #include #include #include @@ -34,7 +36,7 @@ #ifdef __linux__ #include #endif -#ifdef __ANDROID__ +#if defined(__ANDROID__) #include #endif @@ -44,25 +46,6 @@ namespace android { namespace { -float transformAngle(const ui::Transform& transform, float angleRadians) { - // 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); - float y = -cosf(angleRadians); - vec2 transformedPoint = transform.transform(x, y); - - // Determine how the origin is transformed by the matrix so that we - // can transform orientation vectors. - const vec2 origin = transform.transform(0, 0); - - transformedPoint.x -= origin.x; - transformedPoint.y -= origin.y; - - // 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); -} - bool shouldDisregardTransformation(uint32_t source) { // Do not apply any transformations to axes from joysticks, touchpads, or relative mice. return isFromSource(source, AINPUT_SOURCE_CLASS_JOYSTICK) || @@ -87,38 +70,41 @@ const char* motionClassificationToString(MotionClassification classification) { return "AMBIGUOUS_GESTURE"; case MotionClassification::DEEP_PRESS: return "DEEP_PRESS"; + case MotionClassification::TWO_FINGER_SWIPE: + return "TWO_FINGER_SWIPE"; + case MotionClassification::MULTI_FINGER_SWIPE: + return "MULTI_FINGER_SWIPE"; + case MotionClassification::PINCH: + return "PINCH"; } } -const char* motionToolTypeToString(int32_t toolType) { - switch (toolType) { - case AMOTION_EVENT_TOOL_TYPE_UNKNOWN: - return "UNKNOWN"; - case AMOTION_EVENT_TOOL_TYPE_FINGER: - return "FINGER"; - case AMOTION_EVENT_TOOL_TYPE_STYLUS: - return "STYLUS"; - case AMOTION_EVENT_TOOL_TYPE_MOUSE: - return "MOUSE"; - case AMOTION_EVENT_TOOL_TYPE_ERASER: - return "ERASER"; - case AMOTION_EVENT_TOOL_TYPE_PALM: - return "PALM"; - default: - return "INVALID"; +// --- IdGenerator --- +#if defined(__ANDROID__) +[[maybe_unused]] +#endif +static status_t +getRandomBytes(uint8_t* data, size_t size) { + int ret = TEMP_FAILURE_RETRY(open("/dev/urandom", O_RDONLY | O_CLOEXEC | O_NOFOLLOW)); + if (ret == -1) { + return -errno; + } + + base::unique_fd fd(ret); + if (!base::ReadFully(fd, data, size)) { + return -errno; } + return OK; } -// --- IdGenerator --- IdGenerator::IdGenerator(Source source) : mSource(source) {} int32_t IdGenerator::nextId() const { constexpr uint32_t SEQUENCE_NUMBER_MASK = ~SOURCE_MASK; int32_t id = 0; -// Avoid building against syscall getrandom(2) on host, which will fail build on Mac. Host doesn't -// use sequence number so just always return mSource. -#ifdef __ANDROID__ +#if defined(__ANDROID__) + // On device, prefer 'getrandom' to '/dev/urandom' because it's faster. constexpr size_t BUF_LEN = sizeof(id); size_t totalBytes = 0; while (totalBytes < BUF_LEN) { @@ -130,8 +116,17 @@ int32_t IdGenerator::nextId() const { } totalBytes += bytes; } +#else +#if defined(__linux__) + // On host, / GRND_NONBLOCK is not available + while (true) { + status_t result = getRandomBytes(reinterpret_cast(&id), sizeof(id)); + if (result == OK) { + break; + } + } +#endif // __linux__ #endif // __ANDROID__ - return (id & SEQUENCE_NUMBER_MASK) | static_cast(mSource); } @@ -156,28 +151,23 @@ vec2 transformWithoutTranslation(const ui::Transform& transform, const vec2& xy) return roundTransformedCoords(transformedXy - transformedOrigin); } -const char* inputEventTypeToString(int32_t type) { - switch (type) { - case AINPUT_EVENT_TYPE_KEY: { - return "KEY"; - } - case AINPUT_EVENT_TYPE_MOTION: { - return "MOTION"; - } - case AINPUT_EVENT_TYPE_FOCUS: { - return "FOCUS"; - } - case AINPUT_EVENT_TYPE_CAPTURE: { - return "CAPTURE"; - } - case AINPUT_EVENT_TYPE_DRAG: { - return "DRAG"; - } - case AINPUT_EVENT_TYPE_TOUCH_MODE: { - return "TOUCH_MODE"; - } - } - return "UNKNOWN"; +float transformAngle(const ui::Transform& transform, float angleRadians) { + // 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); + float y = -cosf(angleRadians); + vec2 transformedPoint = transform.transform(x, y); + + // Determine how the origin is transformed by the matrix so that we + // can transform orientation vectors. + const vec2 origin = transform.transform(0, 0); + + transformedPoint.x -= origin.x; + transformedPoint.y -= origin.y; + + // 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); } std::string inputEventSourceToString(int32_t source) { @@ -223,6 +213,10 @@ bool isFromSource(uint32_t source, uint32_t test) { return (source & test) == test; } +bool isStylusToolType(ToolType toolType) { + return toolType == ToolType::STYLUS || toolType == ToolType::ERASER; +} + VerifiedKeyEvent verifiedKeyEventFromKeyEvent(const KeyEvent& event) { return {{VerifiedInputEvent::Type::KEY, event.getDeviceId(), event.getEventTime(), event.getSource(), event.getDisplayId()}, @@ -269,13 +263,44 @@ int32_t InputEvent::nextId() { return idGen.nextId(); } +std::ostream& operator<<(std::ostream& out, const InputEvent& event) { + switch (event.getType()) { + case InputEventType::KEY: { + const KeyEvent& keyEvent = static_cast(event); + out << keyEvent; + return out; + } + case InputEventType::MOTION: { + const MotionEvent& motionEvent = static_cast(event); + out << motionEvent; + return out; + } + case InputEventType::FOCUS: { + out << "FocusEvent"; + return out; + } + case InputEventType::CAPTURE: { + out << "CaptureEvent"; + return out; + } + case InputEventType::DRAG: { + out << "DragEvent"; + return out; + } + case InputEventType::TOUCH_MODE: { + out << "TouchModeEvent"; + return out; + } + } +} + // --- KeyEvent --- const char* KeyEvent::getLabel(int32_t keyCode) { return InputEventLookup::getLabelByKeyCode(keyCode); } -int32_t KeyEvent::getKeyCodeFromLabel(const char* label) { +std::optional KeyEvent::getKeyCodeFromLabel(const char* label) { return InputEventLookup::getKeyCodeByLabel(label); } @@ -319,6 +344,28 @@ const char* KeyEvent::actionToString(int32_t action) { return "UNKNOWN"; } +std::ostream& operator<<(std::ostream& out, const KeyEvent& event) { + out << "KeyEvent { action=" << KeyEvent::actionToString(event.getAction()); + + out << ", keycode=" << event.getKeyCode() << "(" << KeyEvent::getLabel(event.getKeyCode()) + << ")"; + + if (event.getMetaState() != 0) { + out << ", metaState=" << event.getMetaState(); + } + + out << ", eventTime=" << event.getEventTime(); + out << ", downTime=" << event.getDownTime(); + out << ", flags=" << std::hex << event.getFlags() << std::dec; + out << ", repeatCount=" << event.getRepeatCount(); + out << ", deviceId=" << event.getDeviceId(); + out << ", source=" << inputEventSourceToString(event.getSource()); + out << ", displayId=" << event.getDisplayId(); + out << ", eventId=" << event.getId(); + out << "}"; + return out; +} + // --- PointerCoords --- float PointerCoords::getAxisValue(int32_t axis) const { @@ -391,6 +438,8 @@ status_t PointerCoords::readFromParcel(Parcel* parcel) { for (uint32_t i = 0; i < count; i++) { values[i] = parcel->readFloat(); } + + isResampled = parcel->readBool(); return OK; } @@ -401,6 +450,8 @@ status_t PointerCoords::writeToParcel(Parcel* parcel) const { for (uint32_t i = 0; i < count; i++) { parcel->writeFloat(values[i]); } + + parcel->writeBool(isResampled); return OK; } #endif @@ -420,15 +471,10 @@ bool PointerCoords::operator==(const PointerCoords& other) const { return false; } } - return true; -} - -void PointerCoords::copyFrom(const PointerCoords& other) { - bits = other.bits; - uint32_t count = BitSet64::count(bits); - for (uint32_t i = 0; i < count; i++) { - values[i] = other.values[i]; + if (isResampled != other.isResampled) { + return false; } + return true; } void PointerCoords::transform(const ui::Transform& transform) { @@ -541,21 +587,21 @@ void MotionEvent::addSample( &pointerCoords[getPointerCount()]); } -int MotionEvent::getSurfaceRotation() const { +std::optional MotionEvent::getSurfaceRotation() const { // The surface rotation is the rotation from the window's coordinate space to that of the // display. Since the event's transform takes display space coordinates to window space, the // returned surface rotation is the inverse of the rotation for the surface. switch (mTransform.getOrientation()) { case ui::Transform::ROT_0: - return DISPLAY_ORIENTATION_0; + return ui::ROTATION_0; case ui::Transform::ROT_90: - return DISPLAY_ORIENTATION_270; + return ui::ROTATION_270; case ui::Transform::ROT_180: - return DISPLAY_ORIENTATION_180; + return ui::ROTATION_180; case ui::Transform::ROT_270: - return DISPLAY_ORIENTATION_90; + return ui::ROTATION_90; default: - return -1; + return std::nullopt; } } @@ -752,7 +798,7 @@ status_t MotionEvent::readFromParcel(Parcel* parcel) { mPointerProperties.push_back({}); PointerProperties& properties = mPointerProperties.back(); properties.id = parcel->readInt32(); - properties.toolType = parcel->readInt32(); + properties.toolType = static_cast(parcel->readInt32()); } while (sampleCount > 0) { @@ -808,7 +854,7 @@ status_t MotionEvent::writeToParcel(Parcel* parcel) const { for (size_t i = 0; i < pointerCount; i++) { const PointerProperties& properties = mPointerProperties[i]; parcel->writeInt32(properties.id); - parcel->writeInt32(properties.toolType); + parcel->writeInt32(static_cast(properties.toolType)); } const PointerCoords* pc = mSamplePointerCoords.data(); @@ -846,7 +892,7 @@ const char* MotionEvent::getLabel(int32_t axis) { return InputEventLookup::getAxisLabel(axis); } -int32_t MotionEvent::getAxisFromLabel(const char* label) { +std::optional MotionEvent::getAxisFromLabel(const char* label) { return InputEventLookup::getAxisByLabel(label); } @@ -972,9 +1018,9 @@ std::ostream& operator<<(std::ostream& out, const MotionEvent& event) { out << ", x[" << i << "]=" << x; out << ", y[" << i << "]=" << y; } - int toolType = event.getToolType(i); - if (toolType != AMOTION_EVENT_TOOL_TYPE_FINGER) { - out << ", toolType[" << i << "]=" << toolType; + ToolType toolType = event.getToolType(i); + if (toolType != ToolType::FINGER) { + out << ", toolType[" << i << "]=" << ftl::enum_string(toolType); } } if (event.getButtonState() != 0) { @@ -1126,44 +1172,51 @@ TouchModeEvent* PooledInputEventFactory::createTouchModeEvent() { void PooledInputEventFactory::recycle(InputEvent* event) { switch (event->getType()) { - case AINPUT_EVENT_TYPE_KEY: - if (mKeyEventPool.size() < mMaxPoolSize) { - mKeyEventPool.push(std::unique_ptr(static_cast(event))); - return; + case InputEventType::KEY: { + if (mKeyEventPool.size() < mMaxPoolSize) { + mKeyEventPool.push(std::unique_ptr(static_cast(event))); + return; + } + break; } - break; - case AINPUT_EVENT_TYPE_MOTION: - if (mMotionEventPool.size() < mMaxPoolSize) { - mMotionEventPool.push(std::unique_ptr(static_cast(event))); - return; + case InputEventType::MOTION: { + if (mMotionEventPool.size() < mMaxPoolSize) { + mMotionEventPool.push( + std::unique_ptr(static_cast(event))); + return; + } + break; } - break; - case AINPUT_EVENT_TYPE_FOCUS: - if (mFocusEventPool.size() < mMaxPoolSize) { - mFocusEventPool.push(std::unique_ptr(static_cast(event))); - return; + case InputEventType::FOCUS: { + if (mFocusEventPool.size() < mMaxPoolSize) { + mFocusEventPool.push(std::unique_ptr(static_cast(event))); + return; + } + break; } - break; - case AINPUT_EVENT_TYPE_CAPTURE: - if (mCaptureEventPool.size() < mMaxPoolSize) { - mCaptureEventPool.push( - std::unique_ptr(static_cast(event))); - return; + case InputEventType::CAPTURE: { + if (mCaptureEventPool.size() < mMaxPoolSize) { + mCaptureEventPool.push( + std::unique_ptr(static_cast(event))); + return; + } + break; } - break; - case AINPUT_EVENT_TYPE_DRAG: - if (mDragEventPool.size() < mMaxPoolSize) { - mDragEventPool.push(std::unique_ptr(static_cast(event))); - return; + case InputEventType::DRAG: { + if (mDragEventPool.size() < mMaxPoolSize) { + mDragEventPool.push(std::unique_ptr(static_cast(event))); + return; + } + break; } - break; - case AINPUT_EVENT_TYPE_TOUCH_MODE: - if (mTouchModeEventPool.size() < mMaxPoolSize) { - mTouchModeEventPool.push( - std::unique_ptr(static_cast(event))); - return; + case InputEventType::TOUCH_MODE: { + if (mTouchModeEventPool.size() < mMaxPoolSize) { + mTouchModeEventPool.push( + std::unique_ptr(static_cast(event))); + return; + } + break; } - break; } delete event; } diff --git a/libs/input/InputDevice.cpp b/libs/input/InputDevice.cpp index a9089690b06e1bb6e58ed9c71553161b13de4864..9c7c0c19ed8f9ecf4c1507b5ede3334420d4d05a 100644 --- a/libs/input/InputDevice.cpp +++ b/libs/input/InputDevice.cpp @@ -22,6 +22,7 @@ #include #include +#include #include +#include +#include + +#include +#include +#include +#define ATRACE_TAG ATRACE_TAG_INPUT +#include +#include + +#include "tensorflow/lite/core/api/error_reporter.h" +#include "tensorflow/lite/core/api/op_resolver.h" +#include "tensorflow/lite/interpreter.h" +#include "tensorflow/lite/kernels/builtin_op_kernels.h" +#include "tensorflow/lite/model.h" +#include "tensorflow/lite/mutable_op_resolver.h" + +namespace android { +namespace { + +constexpr char SIGNATURE_KEY[] = "serving_default"; + +// Input tensor names. +constexpr char INPUT_R[] = "r"; +constexpr char INPUT_PHI[] = "phi"; +constexpr char INPUT_PRESSURE[] = "pressure"; +constexpr char INPUT_TILT[] = "tilt"; +constexpr char INPUT_ORIENTATION[] = "orientation"; + +// Output tensor names. +constexpr char OUTPUT_R[] = "r"; +constexpr char OUTPUT_PHI[] = "phi"; +constexpr char OUTPUT_PRESSURE[] = "pressure"; + +// Ideally, we would just use std::filesystem::exists here, but it requires libc++fs, which causes +// build issues in other parts of the system. +#if defined(__ANDROID__) +bool fileExists(const char* filename) { + struct stat buffer; + return stat(filename, &buffer) == 0; +} +#endif + +std::string getModelPath() { +#if defined(__ANDROID__) + static const char* oemModel = "/vendor/etc/motion_predictor_model.fb"; + if (fileExists(oemModel)) { + return oemModel; + } + return "/system/etc/motion_predictor_model.fb"; +#else + return base::GetExecutableDirectory() + "/motion_predictor_model.fb"; +#endif +} + +// A TFLite ErrorReporter that logs to logcat. +class LoggingErrorReporter : public tflite::ErrorReporter { +public: + int Report(const char* format, va_list args) override { + return LOG_PRI_VA(ANDROID_LOG_ERROR, LOG_TAG, format, args); + } +}; + +// Searches a runner for an input tensor. +TfLiteTensor* findInputTensor(const char* name, tflite::SignatureRunner* runner) { + TfLiteTensor* tensor = runner->input_tensor(name); + LOG_ALWAYS_FATAL_IF(!tensor, "Failed to find input tensor '%s'", name); + return tensor; +} + +// Searches a runner for an output tensor. +const TfLiteTensor* findOutputTensor(const char* name, tflite::SignatureRunner* runner) { + const TfLiteTensor* tensor = runner->output_tensor(name); + LOG_ALWAYS_FATAL_IF(!tensor, "Failed to find output tensor '%s'", name); + return tensor; +} + +// Returns the buffer for a tensor of type T. +template +std::span getTensorBuffer(typename std::conditional::value, const TfLiteTensor*, + TfLiteTensor*>::type tensor) { + LOG_ALWAYS_FATAL_IF(!tensor); + + const TfLiteType type = tflite::typeToTfLiteType::type>(); + LOG_ALWAYS_FATAL_IF(tensor->type != type, "Unexpected type for '%s' tensor: %s (expected %s)", + tensor->name, TfLiteTypeGetName(tensor->type), TfLiteTypeGetName(type)); + + LOG_ALWAYS_FATAL_IF(!tensor->data.data); + return {reinterpret_cast(tensor->data.data), + static_cast::index_type>(tensor->bytes / sizeof(T))}; +} + +// Verifies that a tensor exists and has an underlying buffer of type T. +template +void checkTensor(const TfLiteTensor* tensor) { + LOG_ALWAYS_FATAL_IF(!tensor); + + const auto buffer = getTensorBuffer(tensor); + LOG_ALWAYS_FATAL_IF(buffer.empty(), "No buffer for tensor '%s'", tensor->name); +} + +std::unique_ptr createOpResolver() { + auto resolver = std::make_unique(); + resolver->AddBuiltin(::tflite::BuiltinOperator_CONCATENATION, + ::tflite::ops::builtin::Register_CONCATENATION()); + resolver->AddBuiltin(::tflite::BuiltinOperator_FULLY_CONNECTED, + ::tflite::ops::builtin::Register_FULLY_CONNECTED()); + return resolver; +} + +} // namespace + +TfLiteMotionPredictorBuffers::TfLiteMotionPredictorBuffers(size_t inputLength) + : mInputR(inputLength, 0), + mInputPhi(inputLength, 0), + mInputPressure(inputLength, 0), + mInputTilt(inputLength, 0), + mInputOrientation(inputLength, 0) { + LOG_ALWAYS_FATAL_IF(inputLength == 0, "Buffer input size must be greater than 0"); +} + +void TfLiteMotionPredictorBuffers::reset() { + std::fill(mInputR.begin(), mInputR.end(), 0); + std::fill(mInputPhi.begin(), mInputPhi.end(), 0); + std::fill(mInputPressure.begin(), mInputPressure.end(), 0); + std::fill(mInputTilt.begin(), mInputTilt.end(), 0); + std::fill(mInputOrientation.begin(), mInputOrientation.end(), 0); + mAxisFrom.reset(); + mAxisTo.reset(); +} + +void TfLiteMotionPredictorBuffers::copyTo(TfLiteMotionPredictorModel& model) const { + LOG_ALWAYS_FATAL_IF(mInputR.size() != model.inputLength(), + "Buffer length %zu doesn't match model input length %zu", mInputR.size(), + model.inputLength()); + LOG_ALWAYS_FATAL_IF(!isReady(), "Buffers are incomplete"); + + std::copy(mInputR.begin(), mInputR.end(), model.inputR().begin()); + std::copy(mInputPhi.begin(), mInputPhi.end(), model.inputPhi().begin()); + std::copy(mInputPressure.begin(), mInputPressure.end(), model.inputPressure().begin()); + std::copy(mInputTilt.begin(), mInputTilt.end(), model.inputTilt().begin()); + std::copy(mInputOrientation.begin(), mInputOrientation.end(), model.inputOrientation().begin()); +} + +void TfLiteMotionPredictorBuffers::pushSample(int64_t timestamp, + const TfLiteMotionPredictorSample sample) { + // Convert the sample (x, y) into polar (r, φ) based on a reference axis + // from the preceding two points (mAxisFrom/mAxisTo). + + mTimestamp = timestamp; + + if (!mAxisTo) { // First point. + mAxisTo = sample; + return; + } + + // Vector from the last point to the current sample point. + const TfLiteMotionPredictorSample::Point v = sample.position - mAxisTo->position; + + const float r = std::hypot(v.x, v.y); + float phi = 0; + float orientation = 0; + + // Ignore the sample if there is no movement. These samples can occur when there's change to a + // property other than the coordinates and pollute the input to the model. + if (r == 0) { + return; + } + + if (!mAxisFrom) { // Second point. + // We can only determine the distance from the first point, and not any + // angle. However, if the second point forms an axis, the orientation can + // be transformed relative to that axis. + const float axisPhi = std::atan2(v.y, v.x); + // A MotionEvent's orientation is measured clockwise from the vertical + // axis, but axisPhi is measured counter-clockwise from the horizontal + // axis. + orientation = M_PI_2 - sample.orientation - axisPhi; + } else { + const TfLiteMotionPredictorSample::Point axis = mAxisTo->position - mAxisFrom->position; + const float axisPhi = std::atan2(axis.y, axis.x); + phi = std::atan2(v.y, v.x) - axisPhi; + + if (std::hypot(axis.x, axis.y) > 0) { + // See note above. + orientation = M_PI_2 - sample.orientation - axisPhi; + } + } + + // Update the axis for the next point. + mAxisFrom = mAxisTo; + mAxisTo = sample; + + // Push the current sample onto the end of the input buffers. + mInputR.pushBack(r); + mInputPhi.pushBack(phi); + mInputPressure.pushBack(sample.pressure); + mInputTilt.pushBack(sample.tilt); + mInputOrientation.pushBack(orientation); +} + +std::unique_ptr TfLiteMotionPredictorModel::create() { + const std::string modelPath = getModelPath(); + android::base::unique_fd fd(open(modelPath.c_str(), O_RDONLY)); + if (fd == -1) { + PLOG(FATAL) << "Could not read model from " << modelPath; + } + + const off_t fdSize = lseek(fd, 0, SEEK_END); + if (fdSize == -1) { + PLOG(FATAL) << "Failed to determine file size"; + } + + std::unique_ptr modelBuffer = + android::base::MappedFile::FromFd(fd, /*offset=*/0, fdSize, PROT_READ); + if (!modelBuffer) { + PLOG(FATAL) << "Failed to mmap model"; + } + + return std::unique_ptr( + new TfLiteMotionPredictorModel(std::move(modelBuffer))); +} + +TfLiteMotionPredictorModel::TfLiteMotionPredictorModel( + std::unique_ptr model) + : mFlatBuffer(std::move(model)) { + CHECK(mFlatBuffer); + mErrorReporter = std::make_unique(); + mModel = tflite::FlatBufferModel::VerifyAndBuildFromBuffer(mFlatBuffer->data(), + mFlatBuffer->size(), + /*extra_verifier=*/nullptr, + mErrorReporter.get()); + LOG_ALWAYS_FATAL_IF(!mModel); + + auto resolver = createOpResolver(); + tflite::InterpreterBuilder builder(*mModel, *resolver); + + if (builder(&mInterpreter) != kTfLiteOk || !mInterpreter) { + LOG_ALWAYS_FATAL("Failed to build interpreter"); + } + + mRunner = mInterpreter->GetSignatureRunner(SIGNATURE_KEY); + LOG_ALWAYS_FATAL_IF(!mRunner, "Failed to find runner for signature '%s'", SIGNATURE_KEY); + + allocateTensors(); +} + +TfLiteMotionPredictorModel::~TfLiteMotionPredictorModel() {} + +void TfLiteMotionPredictorModel::allocateTensors() { + if (mRunner->AllocateTensors() != kTfLiteOk) { + LOG_ALWAYS_FATAL("Failed to allocate tensors"); + } + + attachInputTensors(); + attachOutputTensors(); + + checkTensor(mInputR); + checkTensor(mInputPhi); + checkTensor(mInputPressure); + checkTensor(mInputTilt); + checkTensor(mInputOrientation); + checkTensor(mOutputR); + checkTensor(mOutputPhi); + checkTensor(mOutputPressure); + + const auto checkInputTensorSize = [this](const TfLiteTensor* tensor) { + const size_t size = getTensorBuffer(tensor).size(); + LOG_ALWAYS_FATAL_IF(size != inputLength(), + "Tensor '%s' length %zu does not match input length %zu", tensor->name, + size, inputLength()); + }; + + checkInputTensorSize(mInputR); + checkInputTensorSize(mInputPhi); + checkInputTensorSize(mInputPressure); + checkInputTensorSize(mInputTilt); + checkInputTensorSize(mInputOrientation); +} + +void TfLiteMotionPredictorModel::attachInputTensors() { + mInputR = findInputTensor(INPUT_R, mRunner); + mInputPhi = findInputTensor(INPUT_PHI, mRunner); + mInputPressure = findInputTensor(INPUT_PRESSURE, mRunner); + mInputTilt = findInputTensor(INPUT_TILT, mRunner); + mInputOrientation = findInputTensor(INPUT_ORIENTATION, mRunner); +} + +void TfLiteMotionPredictorModel::attachOutputTensors() { + mOutputR = findOutputTensor(OUTPUT_R, mRunner); + mOutputPhi = findOutputTensor(OUTPUT_PHI, mRunner); + mOutputPressure = findOutputTensor(OUTPUT_PRESSURE, mRunner); +} + +bool TfLiteMotionPredictorModel::invoke() { + ATRACE_BEGIN("TfLiteMotionPredictorModel::invoke"); + TfLiteStatus result = mRunner->Invoke(); + ATRACE_END(); + + if (result != kTfLiteOk) { + return false; + } + + // Invoke() might reallocate tensors, so they need to be reattached. + attachInputTensors(); + attachOutputTensors(); + + if (outputR().size() != outputPhi().size() || outputR().size() != outputPressure().size()) { + LOG_ALWAYS_FATAL("Output size mismatch: (r: %zu, phi: %zu, pressure: %zu)", + outputR().size(), outputPhi().size(), outputPressure().size()); + } + + return true; +} + +size_t TfLiteMotionPredictorModel::inputLength() const { + return getTensorBuffer(mInputR).size(); +} + +size_t TfLiteMotionPredictorModel::outputLength() const { + return getTensorBuffer(mOutputR).size(); +} + +std::span TfLiteMotionPredictorModel::inputR() { + return getTensorBuffer(mInputR); +} + +std::span TfLiteMotionPredictorModel::inputPhi() { + return getTensorBuffer(mInputPhi); +} + +std::span TfLiteMotionPredictorModel::inputPressure() { + return getTensorBuffer(mInputPressure); +} + +std::span TfLiteMotionPredictorModel::inputTilt() { + return getTensorBuffer(mInputTilt); +} + +std::span TfLiteMotionPredictorModel::inputOrientation() { + return getTensorBuffer(mInputOrientation); +} + +std::span TfLiteMotionPredictorModel::outputR() const { + return getTensorBuffer(mOutputR); +} + +std::span TfLiteMotionPredictorModel::outputPhi() const { + return getTensorBuffer(mOutputPhi); +} + +std::span TfLiteMotionPredictorModel::outputPressure() const { + return getTensorBuffer(mOutputPressure); +} + +} // namespace android diff --git a/libs/input/TouchVideoFrame.cpp b/libs/input/TouchVideoFrame.cpp index c62e0985f19136b9960a8f5215a3b59888b1aa10..6d7d5614c6ca999b9b31d92dbf306ae73ac1f61e 100644 --- a/libs/input/TouchVideoFrame.cpp +++ b/libs/input/TouchVideoFrame.cpp @@ -40,16 +40,19 @@ const std::vector& TouchVideoFrame::getData() const { return mData; } const struct timeval& TouchVideoFrame::getTimestamp() const { return mTimestamp; } -void TouchVideoFrame::rotate(int32_t orientation) { +void TouchVideoFrame::rotate(ui::Rotation orientation) { switch (orientation) { - case DISPLAY_ORIENTATION_90: - rotateQuarterTurn(false /*clockwise*/); + case ui::ROTATION_90: + rotateQuarterTurn(/*clockwise=*/false); break; - case DISPLAY_ORIENTATION_180: + case ui::ROTATION_180: rotate180(); break; - case DISPLAY_ORIENTATION_270: - rotateQuarterTurn(true /*clockwise*/); + case ui::ROTATION_270: + rotateQuarterTurn(/*clockwise=*/true); + break; + case ui::ROTATION_0: + // No need to rotate if there's no rotation. break; } } diff --git a/libs/input/VelocityControl.cpp b/libs/input/VelocityControl.cpp index 6e991e98bb6ffd27714b2db6f6389d4178a0af62..57200990334603ced8193af40202e66d547a830b 100644 --- a/libs/input/VelocityControl.cpp +++ b/libs/input/VelocityControl.cpp @@ -37,6 +37,10 @@ VelocityControl::VelocityControl() { reset(); } +VelocityControlParameters& VelocityControl::getParameters() { + return mParameters; +} + void VelocityControl::setParameters(const VelocityControlParameters& parameters) { mParameters = parameters; reset(); @@ -44,8 +48,8 @@ void VelocityControl::setParameters(const VelocityControlParameters& parameters) void VelocityControl::reset() { mLastMovementTime = LLONG_MIN; - mRawPosition.x = 0; - mRawPosition.y = 0; + mRawPositionX = 0; + mRawPositionY = 0; mVelocityTracker.clear(); } @@ -61,17 +65,21 @@ void VelocityControl::move(nsecs_t eventTime, float* deltaX, float* deltaY) { mLastMovementTime = eventTime; if (deltaX) { - mRawPosition.x += *deltaX; + mRawPositionX += *deltaX; } if (deltaY) { - mRawPosition.y += *deltaY; + mRawPositionY += *deltaY; } - mVelocityTracker.addMovement(eventTime, BitSet32(BitSet32::valueForBit(0)), {mRawPosition}); + mVelocityTracker.addMovement(eventTime, /*pointerId=*/0, AMOTION_EVENT_AXIS_X, + mRawPositionX); + mVelocityTracker.addMovement(eventTime, /*pointerId=*/0, AMOTION_EVENT_AXIS_Y, + mRawPositionY); - float vx, vy; + std::optional vx = mVelocityTracker.getVelocity(AMOTION_EVENT_AXIS_X, 0); + std::optional vy = mVelocityTracker.getVelocity(AMOTION_EVENT_AXIS_Y, 0); float scale = mParameters.scale; - if (mVelocityTracker.getVelocity(0, &vx, &vy)) { - float speed = hypotf(vx, vy) * scale; + if (vx && vy) { + float speed = hypotf(*vx, *vy) * scale; if (speed >= mParameters.highThreshold) { // Apply full acceleration above the high speed threshold. scale *= mParameters.acceleration; @@ -85,10 +93,9 @@ void VelocityControl::move(nsecs_t eventTime, float* deltaX, float* deltaY) { if (DEBUG_ACCELERATION) { ALOGD("VelocityControl(%0.3f, %0.3f, %0.3f, %0.3f): " - "vx=%0.3f, vy=%0.3f, speed=%0.3f, accel=%0.3f", - mParameters.scale, mParameters.lowThreshold, mParameters.highThreshold, - mParameters.acceleration, - vx, vy, speed, scale / mParameters.scale); + "vx=%0.3f, vy=%0.3f, speed=%0.3f, accel=%0.3f", + mParameters.scale, mParameters.lowThreshold, mParameters.highThreshold, + mParameters.acceleration, *vx, *vy, speed, scale / mParameters.scale); } } else { diff --git a/libs/input/VelocityTracker.cpp b/libs/input/VelocityTracker.cpp index 7f427f2364ea71c20e2d50c0a138144ed584d658..8551e5fa1cd636247a5bdd98b0e0404ba0c4a85d 100644 --- a/libs/input/VelocityTracker.cpp +++ b/libs/input/VelocityTracker.cpp @@ -23,10 +23,13 @@ #include #include +#include #include #include #include +using std::literals::chrono_literals::operator""ms; + namespace android { /** @@ -53,12 +56,34 @@ const bool DEBUG_IMPULSE = // Nanoseconds per milliseconds. static const nsecs_t NANOS_PER_MS = 1000000; +// All axes supported for velocity tracking, mapped to their default strategies. +// Although other strategies are available for testing and comparison purposes, +// the default strategy is the one that applications will actually use. Be very careful +// when adjusting the default strategy because it can dramatically affect +// (often in a bad way) the user experience. +static const std::map DEFAULT_STRATEGY_BY_AXIS = + {{AMOTION_EVENT_AXIS_X, VelocityTracker::Strategy::LSQ2}, + {AMOTION_EVENT_AXIS_Y, VelocityTracker::Strategy::LSQ2}, + {AMOTION_EVENT_AXIS_SCROLL, VelocityTracker::Strategy::IMPULSE}}; + +// Axes specifying location on a 2D plane (i.e. X and Y). +static const std::set PLANAR_AXES = {AMOTION_EVENT_AXIS_X, AMOTION_EVENT_AXIS_Y}; + +// Axes whose motion values are differential values (i.e. deltas). +static const std::set DIFFERENTIAL_AXES = {AMOTION_EVENT_AXIS_SCROLL}; + // Threshold for determining that a pointer has stopped moving. // Some input devices do not send ACTION_MOVE events in the case where a pointer has // stopped. We need to detect this case so that we can accurately predict the // velocity after the pointer starts moving again. -static const nsecs_t ASSUME_POINTER_STOPPED_TIME = 40 * NANOS_PER_MS; +static const std::chrono::duration ASSUME_POINTER_STOPPED_TIME = 40ms; +static std::string toString(std::chrono::nanoseconds t) { + std::stringstream stream; + stream.precision(1); + stream << std::fixed << std::chrono::duration(t).count() << " ms"; + return stream.str(); +} static float vectorDot(const float* a, const float* b, uint32_t m) { float r = 0; @@ -118,46 +143,43 @@ static std::string matrixToString(const float* a, uint32_t m, uint32_t n, bool r // --- VelocityTracker --- VelocityTracker::VelocityTracker(const Strategy strategy) - : mLastEventTime(0), mCurrentPointerIdBits(0), mActivePointerId(-1) { - // Configure the strategy. - if (!configureStrategy(strategy)) { - ALOGE("Unrecognized velocity tracker strategy %" PRId32 ".", strategy); - if (!configureStrategy(VelocityTracker::DEFAULT_STRATEGY)) { - LOG_ALWAYS_FATAL("Could not create the default velocity tracker strategy '%" PRId32 - "'!", - strategy); - } - } -} + : mLastEventTime(0), mCurrentPointerIdBits(0), mOverrideStrategy(strategy) {} VelocityTracker::~VelocityTracker() { } -bool VelocityTracker::configureStrategy(Strategy strategy) { - if (strategy == VelocityTracker::Strategy::DEFAULT) { - mStrategy = createStrategy(VelocityTracker::DEFAULT_STRATEGY); +bool VelocityTracker::isAxisSupported(int32_t axis) { + return DEFAULT_STRATEGY_BY_AXIS.find(axis) != DEFAULT_STRATEGY_BY_AXIS.end(); +} + +void VelocityTracker::configureStrategy(int32_t axis) { + const bool isDifferentialAxis = DIFFERENTIAL_AXES.find(axis) != DIFFERENTIAL_AXES.end(); + + std::unique_ptr createdStrategy; + if (mOverrideStrategy != VelocityTracker::Strategy::DEFAULT) { + createdStrategy = createStrategy(mOverrideStrategy, /*deltaValues=*/isDifferentialAxis); } else { - mStrategy = createStrategy(strategy); + createdStrategy = createStrategy(DEFAULT_STRATEGY_BY_AXIS.at(axis), + /*deltaValues=*/isDifferentialAxis); } - return mStrategy != nullptr; + + LOG_ALWAYS_FATAL_IF(createdStrategy == nullptr, + "Could not create velocity tracker strategy for axis '%" PRId32 "'!", axis); + mConfiguredStrategies[axis] = std::move(createdStrategy); } std::unique_ptr VelocityTracker::createStrategy( - VelocityTracker::Strategy strategy) { + VelocityTracker::Strategy strategy, bool deltaValues) { switch (strategy) { case VelocityTracker::Strategy::IMPULSE: - if (DEBUG_STRATEGY) { - ALOGI("Initializing impulse strategy"); - } - return std::make_unique(); + ALOGI_IF(DEBUG_STRATEGY, "Initializing impulse strategy"); + return std::make_unique(deltaValues); case VelocityTracker::Strategy::LSQ1: return std::make_unique(1); case VelocityTracker::Strategy::LSQ2: - if (DEBUG_STRATEGY && !DEBUG_IMPULSE) { - ALOGI("Initializing lsq2 strategy"); - } + ALOGI_IF(DEBUG_STRATEGY && !DEBUG_IMPULSE, "Initializing lsq2 strategy"); return std::make_unique(2); case VelocityTracker::Strategy::LSQ3: @@ -167,17 +189,17 @@ std::unique_ptr VelocityTracker::createStrategy( return std::make_unique< LeastSquaresVelocityTrackerStrategy>(2, LeastSquaresVelocityTrackerStrategy:: - WEIGHTING_DELTA); + Weighting::DELTA); case VelocityTracker::Strategy::WLSQ2_CENTRAL: return std::make_unique< LeastSquaresVelocityTrackerStrategy>(2, LeastSquaresVelocityTrackerStrategy:: - WEIGHTING_CENTRAL); + Weighting::CENTRAL); case VelocityTracker::Strategy::WLSQ2_RECENT: return std::make_unique< LeastSquaresVelocityTrackerStrategy>(2, LeastSquaresVelocityTrackerStrategy:: - WEIGHTING_RECENT); + Weighting::RECENT); case VelocityTracker::Strategy::INT1: return std::make_unique(1); @@ -196,194 +218,211 @@ std::unique_ptr VelocityTracker::createStrategy( void VelocityTracker::clear() { mCurrentPointerIdBits.clear(); - mActivePointerId = -1; - - mStrategy->clear(); + mActivePointerId = std::nullopt; + mConfiguredStrategies.clear(); } -void VelocityTracker::clearPointers(BitSet32 idBits) { - BitSet32 remainingIdBits(mCurrentPointerIdBits.value & ~idBits.value); - mCurrentPointerIdBits = remainingIdBits; +void VelocityTracker::clearPointer(int32_t pointerId) { + mCurrentPointerIdBits.clearBit(pointerId); - if (mActivePointerId >= 0 && idBits.hasBit(mActivePointerId)) { - mActivePointerId = !remainingIdBits.isEmpty() ? remainingIdBits.firstMarkedBit() : -1; + if (mActivePointerId && *mActivePointerId == pointerId) { + // The active pointer id is being removed. Mark it invalid and try to find a new one + // from the remaining pointers. + mActivePointerId = std::nullopt; + if (!mCurrentPointerIdBits.isEmpty()) { + mActivePointerId = mCurrentPointerIdBits.firstMarkedBit(); + } } - mStrategy->clearPointers(idBits); + for (const auto& [_, strategy] : mConfiguredStrategies) { + strategy->clearPointer(pointerId); + } } -void VelocityTracker::addMovement(nsecs_t eventTime, BitSet32 idBits, - const std::vector& positions) { - LOG_ALWAYS_FATAL_IF(idBits.count() != positions.size(), - "Mismatching number of pointers, idBits=%" PRIu32 ", positions=%zu", - idBits.count(), positions.size()); - while (idBits.count() > MAX_POINTERS) { - idBits.clearLastMarkedBit(); - } +void VelocityTracker::addMovement(nsecs_t eventTime, int32_t pointerId, int32_t axis, + float position) { + if (mCurrentPointerIdBits.hasBit(pointerId) && + std::chrono::nanoseconds(eventTime - mLastEventTime) > ASSUME_POINTER_STOPPED_TIME) { + ALOGD_IF(DEBUG_VELOCITY, "VelocityTracker: stopped for %s, clearing state.", + toString(std::chrono::nanoseconds(eventTime - mLastEventTime)).c_str()); - if ((mCurrentPointerIdBits.value & idBits.value) - && eventTime >= mLastEventTime + ASSUME_POINTER_STOPPED_TIME) { - if (DEBUG_VELOCITY) { - ALOGD("VelocityTracker: stopped for %0.3f ms, clearing state.", - (eventTime - mLastEventTime) * 0.000001f); - } // We have not received any movements for too long. Assume that all pointers // have stopped. - mStrategy->clear(); + mConfiguredStrategies.clear(); } mLastEventTime = eventTime; - mCurrentPointerIdBits = idBits; - if (mActivePointerId < 0 || !idBits.hasBit(mActivePointerId)) { - mActivePointerId = idBits.isEmpty() ? -1 : idBits.firstMarkedBit(); + mCurrentPointerIdBits.markBit(pointerId); + if (!mActivePointerId) { + // Let this be the new active pointer if no active pointer is currently set + mActivePointerId = pointerId; } - mStrategy->addMovement(eventTime, idBits, positions); + if (mConfiguredStrategies.find(axis) == mConfiguredStrategies.end()) { + configureStrategy(axis); + } + mConfiguredStrategies[axis]->addMovement(eventTime, pointerId, position); if (DEBUG_VELOCITY) { - ALOGD("VelocityTracker: addMovement eventTime=%" PRId64 - ", idBits=0x%08x, activePointerId=%d", - eventTime, idBits.value, mActivePointerId); - for (BitSet32 iterBits(idBits); !iterBits.isEmpty();) { - uint32_t id = iterBits.firstMarkedBit(); - uint32_t index = idBits.getIndexOfBit(id); - iterBits.clearBit(id); - Estimator estimator; - getEstimator(id, &estimator); - ALOGD(" %d: position (%0.3f, %0.3f), " - "estimator (degree=%d, xCoeff=%s, yCoeff=%s, confidence=%f)", - id, positions[index].x, positions[index].y, int(estimator.degree), - vectorToString(estimator.xCoeff, estimator.degree + 1).c_str(), - vectorToString(estimator.yCoeff, estimator.degree + 1).c_str(), - estimator.confidence); - } + ALOGD("VelocityTracker: addMovement eventTime=%" PRId64 ", pointerId=%" PRId32 + ", activePointerId=%s", + eventTime, pointerId, toString(mActivePointerId).c_str()); + + std::optional estimator = getEstimator(axis, pointerId); + ALOGD(" %d: axis=%d, position=%0.3f, " + "estimator (degree=%d, coeff=%s, confidence=%f)", + pointerId, axis, position, int((*estimator).degree), + vectorToString((*estimator).coeff.data(), (*estimator).degree + 1).c_str(), + (*estimator).confidence); } } void VelocityTracker::addMovement(const MotionEvent* event) { + // Stores data about which axes to process based on the incoming motion event. + std::set axesToProcess; int32_t actionMasked = event->getActionMasked(); switch (actionMasked) { - case AMOTION_EVENT_ACTION_DOWN: - case AMOTION_EVENT_ACTION_HOVER_ENTER: - // Clear all pointers on down before adding the new movement. - clear(); - break; - case AMOTION_EVENT_ACTION_POINTER_DOWN: { - // Start a new movement trace for a pointer that just went down. - // We do this on down instead of on up because the client may want to query the - // final velocity for a pointer that just went up. - BitSet32 downIdBits; - downIdBits.markBit(event->getPointerId(event->getActionIndex())); - clearPointers(downIdBits); - break; - } - case AMOTION_EVENT_ACTION_MOVE: - case AMOTION_EVENT_ACTION_HOVER_MOVE: - break; - default: - // Ignore all other actions because they do not convey any new information about - // pointer movement. We also want to preserve the last known velocity of the pointers. - // Note that ACTION_UP and ACTION_POINTER_UP always report the last known position - // of the pointers that went up. ACTION_POINTER_UP does include the new position of - // pointers that remained down but we will also receive an ACTION_MOVE with this - // information if any of them actually moved. Since we don't know how many pointers - // will be going up at once it makes sense to just wait for the following ACTION_MOVE - // before adding the movement. - return; - } - - size_t pointerCount = event->getPointerCount(); - if (pointerCount > MAX_POINTERS) { - pointerCount = MAX_POINTERS; - } - - BitSet32 idBits; - for (size_t i = 0; i < pointerCount; i++) { - idBits.markBit(event->getPointerId(i)); - } - - uint32_t pointerIndex[MAX_POINTERS]; - for (size_t i = 0; i < pointerCount; i++) { - pointerIndex[i] = idBits.getIndexOfBit(event->getPointerId(i)); + case AMOTION_EVENT_ACTION_DOWN: + case AMOTION_EVENT_ACTION_HOVER_ENTER: + // Clear all pointers on down before adding the new movement. + clear(); + axesToProcess.insert(PLANAR_AXES.begin(), PLANAR_AXES.end()); + break; + case AMOTION_EVENT_ACTION_POINTER_DOWN: { + // Start a new movement trace for a pointer that just went down. + // We do this on down instead of on up because the client may want to query the + // final velocity for a pointer that just went up. + clearPointer(event->getPointerId(event->getActionIndex())); + axesToProcess.insert(PLANAR_AXES.begin(), PLANAR_AXES.end()); + break; + } + case AMOTION_EVENT_ACTION_MOVE: + case AMOTION_EVENT_ACTION_HOVER_MOVE: + axesToProcess.insert(PLANAR_AXES.begin(), PLANAR_AXES.end()); + break; + case AMOTION_EVENT_ACTION_POINTER_UP: + case AMOTION_EVENT_ACTION_UP: { + std::chrono::nanoseconds delaySinceLastEvent(event->getEventTime() - mLastEventTime); + if (delaySinceLastEvent > ASSUME_POINTER_STOPPED_TIME) { + ALOGD_IF(DEBUG_VELOCITY, + "VelocityTracker: stopped for %s, clearing state upon pointer liftoff.", + toString(delaySinceLastEvent).c_str()); + // We have not received any movements for too long. Assume that all pointers + // have stopped. + for (int32_t axis : PLANAR_AXES) { + mConfiguredStrategies.erase(axis); + } + } + // These actions because they do not convey any new information about + // pointer movement. We also want to preserve the last known velocity of the pointers. + // Note that ACTION_UP and ACTION_POINTER_UP always report the last known position + // of the pointers that went up. ACTION_POINTER_UP does include the new position of + // pointers that remained down but we will also receive an ACTION_MOVE with this + // information if any of them actually moved. Since we don't know how many pointers + // will be going up at once it makes sense to just wait for the following ACTION_MOVE + // before adding the movement. + return; + } + case AMOTION_EVENT_ACTION_SCROLL: + axesToProcess.insert(AMOTION_EVENT_AXIS_SCROLL); + break; + default: + // Ignore all other actions. + return; } - std::vector positions; - positions.resize(pointerCount); - - size_t historySize = event->getHistorySize(); + const size_t historySize = event->getHistorySize(); for (size_t h = 0; h <= historySize; h++) { - nsecs_t eventTime = event->getHistoricalEventTime(h); - for (size_t i = 0; i < pointerCount; i++) { - uint32_t index = pointerIndex[i]; - positions[index].x = event->getHistoricalX(i, h); - positions[index].y = event->getHistoricalY(i, h); + const nsecs_t eventTime = event->getHistoricalEventTime(h); + for (size_t i = 0; i < event->getPointerCount(); i++) { + if (event->isResampled(i, h)) { + continue; // skip resampled samples + } + const int32_t pointerId = event->getPointerId(i); + for (int32_t axis : axesToProcess) { + const float position = event->getHistoricalAxisValue(axis, i, h); + addMovement(eventTime, pointerId, axis, position); + } } - addMovement(eventTime, idBits, positions); } } -bool VelocityTracker::getVelocity(uint32_t id, float* outVx, float* outVy) const { - Estimator estimator; - if (getEstimator(id, &estimator) && estimator.degree >= 1) { - *outVx = estimator.xCoeff[1]; - *outVy = estimator.yCoeff[1]; - return true; +std::optional VelocityTracker::getVelocity(int32_t axis, int32_t pointerId) const { + std::optional estimator = getEstimator(axis, pointerId); + if (estimator && (*estimator).degree >= 1) { + return (*estimator).coeff[1]; } - *outVx = 0; - *outVy = 0; - return false; + return {}; } -bool VelocityTracker::getEstimator(uint32_t id, Estimator* outEstimator) const { - return mStrategy->getEstimator(id, outEstimator); +VelocityTracker::ComputedVelocity VelocityTracker::getComputedVelocity(int32_t units, + float maxVelocity) { + ComputedVelocity computedVelocity; + for (const auto& [axis, _] : mConfiguredStrategies) { + BitSet32 copyIdBits = BitSet32(mCurrentPointerIdBits); + while (!copyIdBits.isEmpty()) { + uint32_t id = copyIdBits.clearFirstMarkedBit(); + std::optional velocity = getVelocity(axis, id); + if (velocity) { + float adjustedVelocity = + std::clamp(*velocity * units / 1000, -maxVelocity, maxVelocity); + computedVelocity.addVelocity(axis, id, adjustedVelocity); + } + } + } + return computedVelocity; } +std::optional VelocityTracker::getEstimator(int32_t axis, + int32_t pointerId) const { + const auto& it = mConfiguredStrategies.find(axis); + if (it == mConfiguredStrategies.end()) { + return std::nullopt; + } + return it->second->getEstimator(pointerId); +} // --- LeastSquaresVelocityTrackerStrategy --- -LeastSquaresVelocityTrackerStrategy::LeastSquaresVelocityTrackerStrategy( - uint32_t degree, Weighting weighting) : - mDegree(degree), mWeighting(weighting) { - clear(); -} +LeastSquaresVelocityTrackerStrategy::LeastSquaresVelocityTrackerStrategy(uint32_t degree, + Weighting weighting) + : mDegree(degree), mWeighting(weighting) {} LeastSquaresVelocityTrackerStrategy::~LeastSquaresVelocityTrackerStrategy() { } -void LeastSquaresVelocityTrackerStrategy::clear() { - mIndex = 0; - mMovements[0].idBits.clear(); -} - -void LeastSquaresVelocityTrackerStrategy::clearPointers(BitSet32 idBits) { - BitSet32 remainingIdBits(mMovements[mIndex].idBits.value & ~idBits.value); - mMovements[mIndex].idBits = remainingIdBits; +void LeastSquaresVelocityTrackerStrategy::clearPointer(int32_t pointerId) { + mIndex.erase(pointerId); + mMovements.erase(pointerId); } -void LeastSquaresVelocityTrackerStrategy::addMovement( - nsecs_t eventTime, BitSet32 idBits, - const std::vector& positions) { - if (mMovements[mIndex].eventTime != eventTime) { +void LeastSquaresVelocityTrackerStrategy::addMovement(nsecs_t eventTime, int32_t pointerId, + float position) { + // If data for this pointer already exists, we have a valid entry at the position of + // mIndex[pointerId] and mMovements[pointerId]. In that case, we need to advance the index + // to the next position in the circular buffer and write the new Movement there. Otherwise, + // if this is a first movement for this pointer, we initialize the maps mIndex and mMovements + // for this pointer and write to the first position. + auto [movementIt, inserted] = mMovements.insert({pointerId, {}}); + auto [indexIt, _] = mIndex.insert({pointerId, 0}); + size_t& index = indexIt->second; + if (!inserted && movementIt->second[index].eventTime != eventTime) { // When ACTION_POINTER_DOWN happens, we will first receive ACTION_MOVE with the coordinates // of the existing pointers, and then ACTION_POINTER_DOWN with the coordinates that include // the new pointer. If the eventtimes for both events are identical, just update the data // for this time. // We only compare against the last value, as it is likely that addMovement is called // in chronological order as events occur. - mIndex++; + index++; } - if (mIndex == HISTORY_SIZE) { - mIndex = 0; + if (index == HISTORY_SIZE) { + index = 0; } - Movement& movement = mMovements[mIndex]; + Movement& movement = movementIt->second[index]; movement.eventTime = eventTime; - movement.idBits = idBits; - uint32_t count = idBits.count(); - for (uint32_t i = 0; i < count; i++) { - movement.positions[i] = positions[i]; - } + movement.position = position; } /** @@ -436,12 +475,14 @@ void LeastSquaresVelocityTrackerStrategy::addMovement( * http://en.wikipedia.org/wiki/Gram-Schmidt */ static bool solveLeastSquares(const std::vector& x, const std::vector& y, - const std::vector& w, uint32_t n, float* outB, float* outDet) { + const std::vector& w, uint32_t n, + std::array& outB, + float* outDet) { const size_t m = x.size(); - if (DEBUG_STRATEGY) { - ALOGD("solveLeastSquares: m=%d, n=%d, x=%s, y=%s, w=%s", int(m), int(n), - vectorToString(x).c_str(), vectorToString(y).c_str(), vectorToString(w).c_str()); - } + + ALOGD_IF(DEBUG_STRATEGY, "solveLeastSquares: m=%d, n=%d, x=%s, y=%s, w=%s", int(m), int(n), + vectorToString(x).c_str(), vectorToString(y).c_str(), vectorToString(w).c_str()); + LOG_ALWAYS_FATAL_IF(m != y.size() || m != w.size(), "Mismatched vector sizes"); // Expand the X vector to a matrix A, pre-multiplied by the weights. @@ -452,9 +493,9 @@ static bool solveLeastSquares(const std::vector& x, const std::vector& x, const std::vector& x, const std::vector& x, const std::vector& x, const std::vector& x, const std::vector 0.000001f ? 1.0f - (sserr / sstot) : 1; - if (DEBUG_STRATEGY) { - ALOGD(" - sserr=%f", sserr); - ALOGD(" - sstot=%f", sstot); - ALOGD(" - det=%f", *outDet); - } + + ALOGD_IF(DEBUG_STRATEGY, " - sserr=%f", sserr); + ALOGD_IF(DEBUG_STRATEGY, " - sstot=%f", sstot); + ALOGD_IF(DEBUG_STRATEGY, " - det=%f", *outDet); + return true; } @@ -608,40 +646,47 @@ static std::optional> solveUnweightedLeastSquaresDeg2( return std::make_optional(std::array({c, b, a})); } -bool LeastSquaresVelocityTrackerStrategy::getEstimator(uint32_t id, - VelocityTracker::Estimator* outEstimator) const { - outEstimator->clear(); - +std::optional LeastSquaresVelocityTrackerStrategy::getEstimator( + int32_t pointerId) const { + const auto movementIt = mMovements.find(pointerId); + if (movementIt == mMovements.end()) { + return std::nullopt; // no data + } // Iterate over movement samples in reverse time order and collect samples. - std::vector x; - std::vector y; + std::vector positions; std::vector w; std::vector time; - uint32_t index = mIndex; - const Movement& newestMovement = mMovements[mIndex]; + uint32_t index = mIndex.at(pointerId); + const Movement& newestMovement = movementIt->second[index]; do { - const Movement& movement = mMovements[index]; - if (!movement.idBits.hasBit(id)) { - break; - } + const Movement& movement = movementIt->second[index]; nsecs_t age = newestMovement.eventTime - movement.eventTime; if (age > HORIZON) { break; } - - const VelocityTracker::Position& position = movement.getPosition(id); - x.push_back(position.x); - y.push_back(position.y); - w.push_back(chooseWeight(index)); + if (movement.eventTime == 0 && index != 0) { + // All eventTime's are initialized to 0. In this fixed-width circular buffer, it's + // possible that not all entries are valid. We use a time=0 as a signal for those + // uninitialized values. If we encounter a time of 0 in a position + // that's > 0, it means that we hit the block where the data wasn't initialized. + // We still don't know whether the value at index=0, with eventTime=0 is valid. + // However, that's only possible when the value is by itself. So there's no hard in + // processing it anyways, since the velocity for a single point is zero, and this + // situation will only be encountered in artificial circumstances (in tests). + // In practice, time will never be 0. + break; + } + positions.push_back(movement.position); + w.push_back(chooseWeight(pointerId, index)); time.push_back(-age * 0.000000001f); index = (index == 0 ? HISTORY_SIZE : index) - 1; - } while (x.size() < HISTORY_SIZE); + } while (positions.size() < HISTORY_SIZE); - const size_t m = x.size(); + const size_t m = positions.size(); if (m == 0) { - return false; // no data + return std::nullopt; // no data } // Calculate a least squares polynomial fit. @@ -650,115 +695,116 @@ bool LeastSquaresVelocityTrackerStrategy::getEstimator(uint32_t id, degree = m - 1; } - if (degree == 2 && mWeighting == WEIGHTING_NONE) { + if (degree == 2 && mWeighting == Weighting::NONE) { // Optimize unweighted, quadratic polynomial fit - std::optional> xCoeff = solveUnweightedLeastSquaresDeg2(time, x); - std::optional> yCoeff = solveUnweightedLeastSquaresDeg2(time, y); - if (xCoeff && yCoeff) { - outEstimator->time = newestMovement.eventTime; - outEstimator->degree = 2; - outEstimator->confidence = 1; - for (size_t i = 0; i <= outEstimator->degree; i++) { - outEstimator->xCoeff[i] = (*xCoeff)[i]; - outEstimator->yCoeff[i] = (*yCoeff)[i]; + std::optional> coeff = + solveUnweightedLeastSquaresDeg2(time, positions); + if (coeff) { + VelocityTracker::Estimator estimator; + estimator.time = newestMovement.eventTime; + estimator.degree = 2; + estimator.confidence = 1; + for (size_t i = 0; i <= estimator.degree; i++) { + estimator.coeff[i] = (*coeff)[i]; } - return true; + return estimator; } } else if (degree >= 1) { // General case for an Nth degree polynomial fit - float xdet, ydet; + float det; uint32_t n = degree + 1; - if (solveLeastSquares(time, x, w, n, outEstimator->xCoeff, &xdet) && - solveLeastSquares(time, y, w, n, outEstimator->yCoeff, &ydet)) { - outEstimator->time = newestMovement.eventTime; - outEstimator->degree = degree; - outEstimator->confidence = xdet * ydet; - if (DEBUG_STRATEGY) { - ALOGD("estimate: degree=%d, xCoeff=%s, yCoeff=%s, confidence=%f", - int(outEstimator->degree), vectorToString(outEstimator->xCoeff, n).c_str(), - vectorToString(outEstimator->yCoeff, n).c_str(), outEstimator->confidence); - } - return true; + VelocityTracker::Estimator estimator; + if (solveLeastSquares(time, positions, w, n, estimator.coeff, &det)) { + estimator.time = newestMovement.eventTime; + estimator.degree = degree; + estimator.confidence = det; + + ALOGD_IF(DEBUG_STRATEGY, "estimate: degree=%d, coeff=%s, confidence=%f", + int(estimator.degree), vectorToString(estimator.coeff.data(), n).c_str(), + estimator.confidence); + + return estimator; } } // No velocity data available for this pointer, but we do have its current position. - outEstimator->xCoeff[0] = x[0]; - outEstimator->yCoeff[0] = y[0]; - outEstimator->time = newestMovement.eventTime; - outEstimator->degree = 0; - outEstimator->confidence = 1; - return true; + VelocityTracker::Estimator estimator; + estimator.coeff[0] = positions[0]; + estimator.time = newestMovement.eventTime; + estimator.degree = 0; + estimator.confidence = 1; + return estimator; } -float LeastSquaresVelocityTrackerStrategy::chooseWeight(uint32_t index) const { +float LeastSquaresVelocityTrackerStrategy::chooseWeight(int32_t pointerId, uint32_t index) const { + const std::array& movements = mMovements.at(pointerId); switch (mWeighting) { - case WEIGHTING_DELTA: { - // Weight points based on how much time elapsed between them and the next - // point so that points that "cover" a shorter time span are weighed less. - // delta 0ms: 0.5 - // delta 10ms: 1.0 - if (index == mIndex) { + case Weighting::DELTA: { + // Weight points based on how much time elapsed between them and the next + // point so that points that "cover" a shorter time span are weighed less. + // delta 0ms: 0.5 + // delta 10ms: 1.0 + if (index == mIndex.at(pointerId)) { + return 1.0f; + } + uint32_t nextIndex = (index + 1) % HISTORY_SIZE; + float deltaMillis = + (movements[nextIndex].eventTime - movements[index].eventTime) * 0.000001f; + if (deltaMillis < 0) { + return 0.5f; + } + if (deltaMillis < 10) { + return 0.5f + deltaMillis * 0.05; + } return 1.0f; } - uint32_t nextIndex = (index + 1) % HISTORY_SIZE; - float deltaMillis = (mMovements[nextIndex].eventTime- mMovements[index].eventTime) - * 0.000001f; - if (deltaMillis < 0) { + + case Weighting::CENTRAL: { + // Weight points based on their age, weighing very recent and very old points less. + // age 0ms: 0.5 + // age 10ms: 1.0 + // age 50ms: 1.0 + // age 60ms: 0.5 + float ageMillis = + (movements[mIndex.at(pointerId)].eventTime - movements[index].eventTime) * + 0.000001f; + if (ageMillis < 0) { + return 0.5f; + } + if (ageMillis < 10) { + return 0.5f + ageMillis * 0.05; + } + if (ageMillis < 50) { + return 1.0f; + } + if (ageMillis < 60) { + return 0.5f + (60 - ageMillis) * 0.05; + } return 0.5f; } - if (deltaMillis < 10) { - return 0.5f + deltaMillis * 0.05; - } - return 1.0f; - } - case WEIGHTING_CENTRAL: { - // Weight points based on their age, weighing very recent and very old points less. - // age 0ms: 0.5 - // age 10ms: 1.0 - // age 50ms: 1.0 - // age 60ms: 0.5 - float ageMillis = (mMovements[mIndex].eventTime - mMovements[index].eventTime) - * 0.000001f; - if (ageMillis < 0) { + case Weighting::RECENT: { + // Weight points based on their age, weighing older points less. + // age 0ms: 1.0 + // age 50ms: 1.0 + // age 100ms: 0.5 + float ageMillis = + (movements[mIndex.at(pointerId)].eventTime - movements[index].eventTime) * + 0.000001f; + if (ageMillis < 50) { + return 1.0f; + } + if (ageMillis < 100) { + return 0.5f + (100 - ageMillis) * 0.01f; + } return 0.5f; } - if (ageMillis < 10) { - return 0.5f + ageMillis * 0.05; - } - if (ageMillis < 50) { - return 1.0f; - } - if (ageMillis < 60) { - return 0.5f + (60 - ageMillis) * 0.05; - } - return 0.5f; - } - case WEIGHTING_RECENT: { - // Weight points based on their age, weighing older points less. - // age 0ms: 1.0 - // age 50ms: 1.0 - // age 100ms: 0.5 - float ageMillis = (mMovements[mIndex].eventTime - mMovements[index].eventTime) - * 0.000001f; - if (ageMillis < 50) { + case Weighting::NONE: return 1.0f; - } - if (ageMillis < 100) { - return 0.5f + (100 - ageMillis) * 0.01f; - } - return 0.5f; - } - - case WEIGHTING_NONE: - default: - return 1.0f; } } - // --- IntegratingVelocityTrackerStrategy --- IntegratingVelocityTrackerStrategy::IntegratingVelocityTrackerStrategy(uint32_t degree) : @@ -768,60 +814,46 @@ IntegratingVelocityTrackerStrategy::IntegratingVelocityTrackerStrategy(uint32_t IntegratingVelocityTrackerStrategy::~IntegratingVelocityTrackerStrategy() { } -void IntegratingVelocityTrackerStrategy::clear() { - mPointerIdBits.clear(); -} - -void IntegratingVelocityTrackerStrategy::clearPointers(BitSet32 idBits) { - mPointerIdBits.value &= ~idBits.value; +void IntegratingVelocityTrackerStrategy::clearPointer(int32_t pointerId) { + mPointerIdBits.clearBit(pointerId); } -void IntegratingVelocityTrackerStrategy::addMovement( - nsecs_t eventTime, BitSet32 idBits, - const std::vector& positions) { - uint32_t index = 0; - for (BitSet32 iterIdBits(idBits); !iterIdBits.isEmpty();) { - uint32_t id = iterIdBits.clearFirstMarkedBit(); - State& state = mPointerState[id]; - const VelocityTracker::Position& position = positions[index++]; - if (mPointerIdBits.hasBit(id)) { - updateState(state, eventTime, position.x, position.y); - } else { - initState(state, eventTime, position.x, position.y); - } +void IntegratingVelocityTrackerStrategy::addMovement(nsecs_t eventTime, int32_t pointerId, + float position) { + State& state = mPointerState[pointerId]; + if (mPointerIdBits.hasBit(pointerId)) { + updateState(state, eventTime, position); + } else { + initState(state, eventTime, position); } - mPointerIdBits = idBits; + mPointerIdBits.markBit(pointerId); } -bool IntegratingVelocityTrackerStrategy::getEstimator(uint32_t id, - VelocityTracker::Estimator* outEstimator) const { - outEstimator->clear(); - - if (mPointerIdBits.hasBit(id)) { - const State& state = mPointerState[id]; - populateEstimator(state, outEstimator); - return true; +std::optional IntegratingVelocityTrackerStrategy::getEstimator( + int32_t pointerId) const { + if (mPointerIdBits.hasBit(pointerId)) { + const State& state = mPointerState[pointerId]; + VelocityTracker::Estimator estimator; + populateEstimator(state, &estimator); + return estimator; } - return false; + return std::nullopt; } -void IntegratingVelocityTrackerStrategy::initState(State& state, - nsecs_t eventTime, float xpos, float ypos) const { +void IntegratingVelocityTrackerStrategy::initState(State& state, nsecs_t eventTime, + float pos) const { state.updateTime = eventTime; state.degree = 0; - state.xpos = xpos; - state.xvel = 0; - state.xaccel = 0; - state.ypos = ypos; - state.yvel = 0; - state.yaccel = 0; + state.pos = pos; + state.accel = 0; + state.vel = 0; } -void IntegratingVelocityTrackerStrategy::updateState(State& state, - nsecs_t eventTime, float xpos, float ypos) const { +void IntegratingVelocityTrackerStrategy::updateState(State& state, nsecs_t eventTime, + float pos) const { const nsecs_t MIN_TIME_DELTA = 2 * NANOS_PER_MS; const float FILTER_TIME_CONSTANT = 0.010f; // 10 milliseconds @@ -832,34 +864,26 @@ void IntegratingVelocityTrackerStrategy::updateState(State& state, float dt = (eventTime - state.updateTime) * 0.000000001f; state.updateTime = eventTime; - float xvel = (xpos - state.xpos) / dt; - float yvel = (ypos - state.ypos) / dt; + float vel = (pos - state.pos) / dt; if (state.degree == 0) { - state.xvel = xvel; - state.yvel = yvel; + state.vel = vel; state.degree = 1; } else { float alpha = dt / (FILTER_TIME_CONSTANT + dt); if (mDegree == 1) { - state.xvel += (xvel - state.xvel) * alpha; - state.yvel += (yvel - state.yvel) * alpha; + state.vel += (vel - state.vel) * alpha; } else { - float xaccel = (xvel - state.xvel) / dt; - float yaccel = (yvel - state.yvel) / dt; + float accel = (vel - state.vel) / dt; if (state.degree == 1) { - state.xaccel = xaccel; - state.yaccel = yaccel; + state.accel = accel; state.degree = 2; } else { - state.xaccel += (xaccel - state.xaccel) * alpha; - state.yaccel += (yaccel - state.yaccel) * alpha; + state.accel += (accel - state.accel) * alpha; } - state.xvel += (state.xaccel * dt) * alpha; - state.yvel += (state.yaccel * dt) * alpha; + state.vel += (state.accel * dt) * alpha; } } - state.xpos = xpos; - state.ypos = ypos; + state.pos = pos; } void IntegratingVelocityTrackerStrategy::populateEstimator(const State& state, @@ -867,68 +891,68 @@ void IntegratingVelocityTrackerStrategy::populateEstimator(const State& state, outEstimator->time = state.updateTime; outEstimator->confidence = 1.0f; outEstimator->degree = state.degree; - outEstimator->xCoeff[0] = state.xpos; - outEstimator->xCoeff[1] = state.xvel; - outEstimator->xCoeff[2] = state.xaccel / 2; - outEstimator->yCoeff[0] = state.ypos; - outEstimator->yCoeff[1] = state.yvel; - outEstimator->yCoeff[2] = state.yaccel / 2; + outEstimator->coeff[0] = state.pos; + outEstimator->coeff[1] = state.vel; + outEstimator->coeff[2] = state.accel / 2; } // --- LegacyVelocityTrackerStrategy --- -LegacyVelocityTrackerStrategy::LegacyVelocityTrackerStrategy() { - clear(); -} +LegacyVelocityTrackerStrategy::LegacyVelocityTrackerStrategy() {} LegacyVelocityTrackerStrategy::~LegacyVelocityTrackerStrategy() { } -void LegacyVelocityTrackerStrategy::clear() { - mIndex = 0; - mMovements[0].idBits.clear(); -} - -void LegacyVelocityTrackerStrategy::clearPointers(BitSet32 idBits) { - BitSet32 remainingIdBits(mMovements[mIndex].idBits.value & ~idBits.value); - mMovements[mIndex].idBits = remainingIdBits; +void LegacyVelocityTrackerStrategy::clearPointer(int32_t pointerId) { + mIndex.erase(pointerId); + mMovements.erase(pointerId); } -void LegacyVelocityTrackerStrategy::addMovement( - nsecs_t eventTime, BitSet32 idBits, - const std::vector& positions) { - if (++mIndex == HISTORY_SIZE) { - mIndex = 0; +void LegacyVelocityTrackerStrategy::addMovement(nsecs_t eventTime, int32_t pointerId, + float position) { + // If data for this pointer already exists, we have a valid entry at the position of + // mIndex[pointerId] and mMovements[pointerId]. In that case, we need to advance the index + // to the next position in the circular buffer and write the new Movement there. Otherwise, + // if this is a first movement for this pointer, we initialize the maps mIndex and mMovements + // for this pointer and write to the first position. + auto [movementIt, inserted] = mMovements.insert({pointerId, {}}); + auto [indexIt, _] = mIndex.insert({pointerId, 0}); + size_t& index = indexIt->second; + if (!inserted && movementIt->second[index].eventTime != eventTime) { + // When ACTION_POINTER_DOWN happens, we will first receive ACTION_MOVE with the coordinates + // of the existing pointers, and then ACTION_POINTER_DOWN with the coordinates that include + // the new pointer. If the eventtimes for both events are identical, just update the data + // for this time. + // We only compare against the last value, as it is likely that addMovement is called + // in chronological order as events occur. + index++; + } + if (index == HISTORY_SIZE) { + index = 0; } - Movement& movement = mMovements[mIndex]; + Movement& movement = movementIt->second[index]; movement.eventTime = eventTime; - movement.idBits = idBits; - uint32_t count = idBits.count(); - for (uint32_t i = 0; i < count; i++) { - movement.positions[i] = positions[i]; - } + movement.position = position; } -bool LegacyVelocityTrackerStrategy::getEstimator(uint32_t id, - VelocityTracker::Estimator* outEstimator) const { - outEstimator->clear(); - - const Movement& newestMovement = mMovements[mIndex]; - if (!newestMovement.idBits.hasBit(id)) { - return false; // no data +std::optional LegacyVelocityTrackerStrategy::getEstimator( + int32_t pointerId) const { + const auto movementIt = mMovements.find(pointerId); + if (movementIt == mMovements.end()) { + return std::nullopt; // no data } + const Movement& newestMovement = movementIt->second[mIndex.at(pointerId)]; // Find the oldest sample that contains the pointer and that is not older than HORIZON. nsecs_t minTime = newestMovement.eventTime - HORIZON; - uint32_t oldestIndex = mIndex; + uint32_t oldestIndex = mIndex.at(pointerId); uint32_t numTouches = 1; do { uint32_t nextOldestIndex = (oldestIndex == 0 ? HISTORY_SIZE : oldestIndex) - 1; - const Movement& nextOldestMovement = mMovements[nextOldestIndex]; - if (!nextOldestMovement.idBits.hasBit(id) - || nextOldestMovement.eventTime < minTime) { + const Movement& nextOldestMovement = mMovements.at(pointerId)[nextOldestIndex]; + if (nextOldestMovement.eventTime < minTime) { break; } oldestIndex = nextOldestIndex; @@ -945,94 +969,87 @@ bool LegacyVelocityTrackerStrategy::getEstimator(uint32_t id, // overestimate the velocity at that time point. Most samples might be measured // 16ms apart but some consecutive samples could be only 0.5sm apart because // the hardware or driver reports them irregularly or in bursts. - float accumVx = 0; - float accumVy = 0; + float accumV = 0; uint32_t index = oldestIndex; uint32_t samplesUsed = 0; - const Movement& oldestMovement = mMovements[oldestIndex]; - const VelocityTracker::Position& oldestPosition = oldestMovement.getPosition(id); + const Movement& oldestMovement = mMovements.at(pointerId)[oldestIndex]; + float oldestPosition = oldestMovement.position; nsecs_t lastDuration = 0; while (numTouches-- > 1) { if (++index == HISTORY_SIZE) { index = 0; } - const Movement& movement = mMovements[index]; + const Movement& movement = mMovements.at(pointerId)[index]; nsecs_t duration = movement.eventTime - oldestMovement.eventTime; // If the duration between samples is small, we may significantly overestimate // the velocity. Consequently, we impose a minimum duration constraint on the // samples that we include in the calculation. if (duration >= MIN_DURATION) { - const VelocityTracker::Position& position = movement.getPosition(id); + float position = movement.position; float scale = 1000000000.0f / duration; // one over time delta in seconds - float vx = (position.x - oldestPosition.x) * scale; - float vy = (position.y - oldestPosition.y) * scale; - accumVx = (accumVx * lastDuration + vx * duration) / (duration + lastDuration); - accumVy = (accumVy * lastDuration + vy * duration) / (duration + lastDuration); + float v = (position - oldestPosition) * scale; + accumV = (accumV * lastDuration + v * duration) / (duration + lastDuration); lastDuration = duration; samplesUsed += 1; } } // Report velocity. - const VelocityTracker::Position& newestPosition = newestMovement.getPosition(id); - outEstimator->time = newestMovement.eventTime; - outEstimator->confidence = 1; - outEstimator->xCoeff[0] = newestPosition.x; - outEstimator->yCoeff[0] = newestPosition.y; + float newestPosition = newestMovement.position; + VelocityTracker::Estimator estimator; + estimator.time = newestMovement.eventTime; + estimator.confidence = 1; + estimator.coeff[0] = newestPosition; if (samplesUsed) { - outEstimator->xCoeff[1] = accumVx; - outEstimator->yCoeff[1] = accumVy; - outEstimator->degree = 1; + estimator.coeff[1] = accumV; + estimator.degree = 1; } else { - outEstimator->degree = 0; + estimator.degree = 0; } - return true; + return estimator; } // --- ImpulseVelocityTrackerStrategy --- -ImpulseVelocityTrackerStrategy::ImpulseVelocityTrackerStrategy() { - clear(); -} +ImpulseVelocityTrackerStrategy::ImpulseVelocityTrackerStrategy(bool deltaValues) + : mDeltaValues(deltaValues) {} ImpulseVelocityTrackerStrategy::~ImpulseVelocityTrackerStrategy() { } -void ImpulseVelocityTrackerStrategy::clear() { - mIndex = 0; - mMovements[0].idBits.clear(); +void ImpulseVelocityTrackerStrategy::clearPointer(int32_t pointerId) { + mIndex.erase(pointerId); + mMovements.erase(pointerId); } -void ImpulseVelocityTrackerStrategy::clearPointers(BitSet32 idBits) { - BitSet32 remainingIdBits(mMovements[mIndex].idBits.value & ~idBits.value); - mMovements[mIndex].idBits = remainingIdBits; -} - -void ImpulseVelocityTrackerStrategy::addMovement( - nsecs_t eventTime, BitSet32 idBits, - const std::vector& positions) { - if (mMovements[mIndex].eventTime != eventTime) { +void ImpulseVelocityTrackerStrategy::addMovement(nsecs_t eventTime, int32_t pointerId, + float position) { + // If data for this pointer already exists, we have a valid entry at the position of + // mIndex[pointerId] and mMovements[pointerId]. In that case, we need to advance the index + // to the next position in the circular buffer and write the new Movement there. Otherwise, + // if this is a first movement for this pointer, we initialize the maps mIndex and mMovements + // for this pointer and write to the first position. + auto [movementIt, inserted] = mMovements.insert({pointerId, {}}); + auto [indexIt, _] = mIndex.insert({pointerId, 0}); + size_t& index = indexIt->second; + if (!inserted && movementIt->second[index].eventTime != eventTime) { // When ACTION_POINTER_DOWN happens, we will first receive ACTION_MOVE with the coordinates // of the existing pointers, and then ACTION_POINTER_DOWN with the coordinates that include // the new pointer. If the eventtimes for both events are identical, just update the data // for this time. // We only compare against the last value, as it is likely that addMovement is called // in chronological order as events occur. - mIndex++; + index++; } - if (mIndex == HISTORY_SIZE) { - mIndex = 0; + if (index == HISTORY_SIZE) { + index = 0; } - Movement& movement = mMovements[mIndex]; + Movement& movement = movementIt->second[index]; movement.eventTime = eventTime; - movement.idBits = idBits; - uint32_t count = idBits.count(); - for (uint32_t i = 0; i < count; i++) { - movement.positions[i] = positions[i]; - } + movement.position = position; } /** @@ -1109,7 +1126,8 @@ static float kineticEnergyToVelocity(float work) { return (work < 0 ? -1.0 : 1.0) * sqrtf(fabsf(work)) * sqrt2; } -static float calculateImpulseVelocity(const nsecs_t* t, const float* x, size_t count) { +static float calculateImpulseVelocity(const nsecs_t* t, const float* x, size_t count, + bool deltaValues) { // The input should be in reversed time order (most recent sample at index i=0) // t[i] is in nanoseconds, but due to FP arithmetic, convert to seconds inside this function static constexpr float SECONDS_PER_NANO = 1E-9; @@ -1120,12 +1138,26 @@ static float calculateImpulseVelocity(const nsecs_t* t, const float* x, size_t c if (t[1] > t[0]) { // Algorithm will still work, but not perfectly ALOGE("Samples provided to calculateImpulseVelocity in the wrong order"); } + + // If the data values are delta values, we do not have to calculate deltas here. + // We can use the delta values directly, along with the calculated time deltas. + // Since the data value input is in reversed time order: + // [a] for non-delta inputs, instantenous velocity = (x[i] - x[i-1])/(t[i] - t[i-1]) + // [b] for delta inputs, instantenous velocity = -x[i-1]/(t[i] - t[i - 1]) + // e.g., let the non-delta values are: V = [2, 3, 7], the equivalent deltas are D = [2, 1, 4]. + // Since the input is in reversed time order, the input values for this function would be + // V'=[7, 3, 2] and D'=[4, 1, 2] for the non-delta and delta values, respectively. + // + // The equivalent of {(V'[2] - V'[1]) = 2 - 3 = -1} would be {-D'[1] = -1} + // Similarly, the equivalent of {(V'[1] - V'[0]) = 3 - 7 = -4} would be {-D'[0] = -4} + if (count == 2) { // if 2 points, basic linear calculation if (t[1] == t[0]) { ALOGE("Events have identical time stamps t=%" PRId64 ", setting velocity = 0", t[0]); return 0; } - return (x[1] - x[0]) / (SECONDS_PER_NANO * (t[1] - t[0])); + const float deltaX = deltaValues ? -x[0] : x[1] - x[0]; + return deltaX / (SECONDS_PER_NANO * (t[1] - t[0])); } // Guaranteed to have at least 3 points here float work = 0; @@ -1135,7 +1167,8 @@ static float calculateImpulseVelocity(const nsecs_t* t, const float* x, size_t c continue; } float vprev = kineticEnergyToVelocity(work); // v[i-1] - float vcurr = (x[i] - x[i-1]) / (SECONDS_PER_NANO * (t[i] - t[i-1])); // v[i] + const float deltaX = deltaValues ? -x[i-1] : x[i] - x[i-1]; + float vcurr = deltaX / (SECONDS_PER_NANO * (t[i] - t[i-1])); // v[i] work += (vcurr - vprev) * fabsf(vcurr); if (i == count - 1) { work *= 0.5; // initial condition, case 2) above @@ -1144,69 +1177,70 @@ static float calculateImpulseVelocity(const nsecs_t* t, const float* x, size_t c return kineticEnergyToVelocity(work); } -bool ImpulseVelocityTrackerStrategy::getEstimator(uint32_t id, - VelocityTracker::Estimator* outEstimator) const { - outEstimator->clear(); +std::optional ImpulseVelocityTrackerStrategy::getEstimator( + int32_t pointerId) const { + const auto movementIt = mMovements.find(pointerId); + if (movementIt == mMovements.end()) { + return std::nullopt; // no data + } // Iterate over movement samples in reverse time order and collect samples. - float x[HISTORY_SIZE]; - float y[HISTORY_SIZE]; + float positions[HISTORY_SIZE]; nsecs_t time[HISTORY_SIZE]; size_t m = 0; // number of points that will be used for fitting - size_t index = mIndex; - const Movement& newestMovement = mMovements[mIndex]; + size_t index = mIndex.at(pointerId); + const Movement& newestMovement = movementIt->second[index]; do { - const Movement& movement = mMovements[index]; - if (!movement.idBits.hasBit(id)) { - break; - } + const Movement& movement = movementIt->second[index]; nsecs_t age = newestMovement.eventTime - movement.eventTime; if (age > HORIZON) { break; } + if (movement.eventTime == 0 && index != 0) { + // All eventTime's are initialized to 0. If we encounter a time of 0 in a position + // that's >0, it means that we hit the block where the data wasn't initialized. + // It's also possible that the sample at 0 would be invalid, but there's no harm in + // processing it, since it would be just a single point, and will only be encountered + // in artificial circumstances (in tests). + break; + } - const VelocityTracker::Position& position = movement.getPosition(id); - x[m] = position.x; - y[m] = position.y; + positions[m] = movement.position; time[m] = movement.eventTime; index = (index == 0 ? HISTORY_SIZE : index) - 1; } while (++m < HISTORY_SIZE); if (m == 0) { - return false; // no data - } - outEstimator->xCoeff[0] = 0; - outEstimator->yCoeff[0] = 0; - outEstimator->xCoeff[1] = calculateImpulseVelocity(time, x, m); - outEstimator->yCoeff[1] = calculateImpulseVelocity(time, y, m); - outEstimator->xCoeff[2] = 0; - outEstimator->yCoeff[2] = 0; - outEstimator->time = newestMovement.eventTime; - outEstimator->degree = 2; // similar results to 2nd degree fit - outEstimator->confidence = 1; - if (DEBUG_STRATEGY) { - ALOGD("velocity: (%.1f, %.1f)", outEstimator->xCoeff[1], outEstimator->yCoeff[1]); + return std::nullopt; // no data } + VelocityTracker::Estimator estimator; + estimator.coeff[0] = 0; + estimator.coeff[1] = calculateImpulseVelocity(time, positions, m, mDeltaValues); + estimator.coeff[2] = 0; + + estimator.time = newestMovement.eventTime; + estimator.degree = 2; // similar results to 2nd degree fit + estimator.confidence = 1; + + ALOGD_IF(DEBUG_STRATEGY, "velocity: %.1f", estimator.coeff[1]); + if (DEBUG_IMPULSE) { // TODO(b/134179997): delete this block once the switch to 'impulse' is complete. - // Calculate the lsq2 velocity for the same inputs to allow runtime comparisons + // Calculate the lsq2 velocity for the same inputs to allow runtime comparisons. + // X axis chosen arbitrarily for velocity comparisons. VelocityTracker lsq2(VelocityTracker::Strategy::LSQ2); - BitSet32 idBits; - const uint32_t pointerId = 0; - idBits.markBit(pointerId); for (ssize_t i = m - 1; i >= 0; i--) { - lsq2.addMovement(time[i], idBits, {{x[i], y[i]}}); + lsq2.addMovement(time[i], pointerId, AMOTION_EVENT_AXIS_X, positions[i]); } - float outVx = 0, outVy = 0; - const bool computed = lsq2.getVelocity(pointerId, &outVx, &outVy); - if (computed) { - ALOGD("lsq2 velocity: (%.1f, %.1f)", outVx, outVy); + std::optional v = lsq2.getVelocity(AMOTION_EVENT_AXIS_X, pointerId); + if (v) { + ALOGD("lsq2 velocity: %.1f", *v); } else { ALOGD("lsq2 velocity: could not compute velocity"); } } - return true; + return estimator; } } // namespace android diff --git a/libs/input/VirtualInputDevice.cpp b/libs/input/VirtualInputDevice.cpp new file mode 100644 index 0000000000000000000000000000000000000000..9a459b135cd45968db6a314fe3b884261baf46a8 --- /dev/null +++ b/libs/input/VirtualInputDevice.cpp @@ -0,0 +1,395 @@ +/* + * 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 "VirtualInputDevice" + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +using android::base::unique_fd; + +/** + * Log debug messages about native virtual input devices. + * Enable this via "adb shell setprop log.tag.VirtualInputDevice DEBUG" + */ +static bool isDebug() { + return __android_log_is_loggable(ANDROID_LOG_DEBUG, LOG_TAG, ANDROID_LOG_INFO); +} + +namespace android { +VirtualInputDevice::VirtualInputDevice(unique_fd fd) : mFd(std::move(fd)) {} +VirtualInputDevice::~VirtualInputDevice() { + ioctl(mFd, UI_DEV_DESTROY); +} + +bool VirtualInputDevice::writeInputEvent(uint16_t type, uint16_t code, int32_t value, + std::chrono::nanoseconds eventTime) { + std::chrono::seconds seconds = std::chrono::duration_cast(eventTime); + std::chrono::microseconds microseconds = + std::chrono::duration_cast(eventTime - seconds); + struct input_event ev = {.type = type, .code = code, .value = value}; + ev.input_event_sec = static_cast(seconds.count()); + ev.input_event_usec = static_cast(microseconds.count()); + + return TEMP_FAILURE_RETRY(write(mFd, &ev, sizeof(struct input_event))) == sizeof(ev); +} + +/** Utility method to write keyboard key events or mouse button events. */ +bool VirtualInputDevice::writeEvKeyEvent(int32_t androidCode, int32_t androidAction, + const std::map& evKeyCodeMapping, + const std::map& actionMapping, + std::chrono::nanoseconds eventTime) { + auto evKeyCodeIterator = evKeyCodeMapping.find(androidCode); + if (evKeyCodeIterator == evKeyCodeMapping.end()) { + ALOGE("Unsupported native EV keycode for android code %d", androidCode); + return false; + } + auto actionIterator = actionMapping.find(androidAction); + if (actionIterator == actionMapping.end()) { + return false; + } + if (!writeInputEvent(EV_KEY, static_cast(evKeyCodeIterator->second), + static_cast(actionIterator->second), eventTime)) { + return false; + } + if (!writeInputEvent(EV_SYN, SYN_REPORT, 0, eventTime)) { + return false; + } + return true; +} + +// --- VirtualKeyboard --- +const std::map VirtualKeyboard::KEY_ACTION_MAPPING = { + {AKEY_EVENT_ACTION_DOWN, UinputAction::PRESS}, + {AKEY_EVENT_ACTION_UP, UinputAction::RELEASE}, +}; +// Keycode mapping from https://source.android.com/devices/input/keyboard-devices +const std::map VirtualKeyboard::KEY_CODE_MAPPING = { + {AKEYCODE_0, KEY_0}, + {AKEYCODE_1, KEY_1}, + {AKEYCODE_2, KEY_2}, + {AKEYCODE_3, KEY_3}, + {AKEYCODE_4, KEY_4}, + {AKEYCODE_5, KEY_5}, + {AKEYCODE_6, KEY_6}, + {AKEYCODE_7, KEY_7}, + {AKEYCODE_8, KEY_8}, + {AKEYCODE_9, KEY_9}, + {AKEYCODE_A, KEY_A}, + {AKEYCODE_B, KEY_B}, + {AKEYCODE_C, KEY_C}, + {AKEYCODE_D, KEY_D}, + {AKEYCODE_E, KEY_E}, + {AKEYCODE_F, KEY_F}, + {AKEYCODE_G, KEY_G}, + {AKEYCODE_H, KEY_H}, + {AKEYCODE_I, KEY_I}, + {AKEYCODE_J, KEY_J}, + {AKEYCODE_K, KEY_K}, + {AKEYCODE_L, KEY_L}, + {AKEYCODE_M, KEY_M}, + {AKEYCODE_N, KEY_N}, + {AKEYCODE_O, KEY_O}, + {AKEYCODE_P, KEY_P}, + {AKEYCODE_Q, KEY_Q}, + {AKEYCODE_R, KEY_R}, + {AKEYCODE_S, KEY_S}, + {AKEYCODE_T, KEY_T}, + {AKEYCODE_U, KEY_U}, + {AKEYCODE_V, KEY_V}, + {AKEYCODE_W, KEY_W}, + {AKEYCODE_X, KEY_X}, + {AKEYCODE_Y, KEY_Y}, + {AKEYCODE_Z, KEY_Z}, + {AKEYCODE_GRAVE, KEY_GRAVE}, + {AKEYCODE_MINUS, KEY_MINUS}, + {AKEYCODE_EQUALS, KEY_EQUAL}, + {AKEYCODE_LEFT_BRACKET, KEY_LEFTBRACE}, + {AKEYCODE_RIGHT_BRACKET, KEY_RIGHTBRACE}, + {AKEYCODE_BACKSLASH, KEY_BACKSLASH}, + {AKEYCODE_SEMICOLON, KEY_SEMICOLON}, + {AKEYCODE_APOSTROPHE, KEY_APOSTROPHE}, + {AKEYCODE_COMMA, KEY_COMMA}, + {AKEYCODE_PERIOD, KEY_DOT}, + {AKEYCODE_SLASH, KEY_SLASH}, + {AKEYCODE_ALT_LEFT, KEY_LEFTALT}, + {AKEYCODE_ALT_RIGHT, KEY_RIGHTALT}, + {AKEYCODE_CTRL_LEFT, KEY_LEFTCTRL}, + {AKEYCODE_CTRL_RIGHT, KEY_RIGHTCTRL}, + {AKEYCODE_SHIFT_LEFT, KEY_LEFTSHIFT}, + {AKEYCODE_SHIFT_RIGHT, KEY_RIGHTSHIFT}, + {AKEYCODE_META_LEFT, KEY_LEFTMETA}, + {AKEYCODE_META_RIGHT, KEY_RIGHTMETA}, + {AKEYCODE_CAPS_LOCK, KEY_CAPSLOCK}, + {AKEYCODE_SCROLL_LOCK, KEY_SCROLLLOCK}, + {AKEYCODE_NUM_LOCK, KEY_NUMLOCK}, + {AKEYCODE_ENTER, KEY_ENTER}, + {AKEYCODE_TAB, KEY_TAB}, + {AKEYCODE_SPACE, KEY_SPACE}, + {AKEYCODE_DPAD_DOWN, KEY_DOWN}, + {AKEYCODE_DPAD_UP, KEY_UP}, + {AKEYCODE_DPAD_LEFT, KEY_LEFT}, + {AKEYCODE_DPAD_RIGHT, KEY_RIGHT}, + {AKEYCODE_MOVE_END, KEY_END}, + {AKEYCODE_MOVE_HOME, KEY_HOME}, + {AKEYCODE_PAGE_DOWN, KEY_PAGEDOWN}, + {AKEYCODE_PAGE_UP, KEY_PAGEUP}, + {AKEYCODE_DEL, KEY_BACKSPACE}, + {AKEYCODE_FORWARD_DEL, KEY_DELETE}, + {AKEYCODE_INSERT, KEY_INSERT}, + {AKEYCODE_ESCAPE, KEY_ESC}, + {AKEYCODE_BREAK, KEY_PAUSE}, + {AKEYCODE_F1, KEY_F1}, + {AKEYCODE_F2, KEY_F2}, + {AKEYCODE_F3, KEY_F3}, + {AKEYCODE_F4, KEY_F4}, + {AKEYCODE_F5, KEY_F5}, + {AKEYCODE_F6, KEY_F6}, + {AKEYCODE_F7, KEY_F7}, + {AKEYCODE_F8, KEY_F8}, + {AKEYCODE_F9, KEY_F9}, + {AKEYCODE_F10, KEY_F10}, + {AKEYCODE_F11, KEY_F11}, + {AKEYCODE_F12, KEY_F12}, + {AKEYCODE_BACK, KEY_BACK}, + {AKEYCODE_FORWARD, KEY_FORWARD}, + {AKEYCODE_NUMPAD_1, KEY_KP1}, + {AKEYCODE_NUMPAD_2, KEY_KP2}, + {AKEYCODE_NUMPAD_3, KEY_KP3}, + {AKEYCODE_NUMPAD_4, KEY_KP4}, + {AKEYCODE_NUMPAD_5, KEY_KP5}, + {AKEYCODE_NUMPAD_6, KEY_KP6}, + {AKEYCODE_NUMPAD_7, KEY_KP7}, + {AKEYCODE_NUMPAD_8, KEY_KP8}, + {AKEYCODE_NUMPAD_9, KEY_KP9}, + {AKEYCODE_NUMPAD_0, KEY_KP0}, + {AKEYCODE_NUMPAD_ADD, KEY_KPPLUS}, + {AKEYCODE_NUMPAD_SUBTRACT, KEY_KPMINUS}, + {AKEYCODE_NUMPAD_MULTIPLY, KEY_KPASTERISK}, + {AKEYCODE_NUMPAD_DIVIDE, KEY_KPSLASH}, + {AKEYCODE_NUMPAD_DOT, KEY_KPDOT}, + {AKEYCODE_NUMPAD_ENTER, KEY_KPENTER}, + {AKEYCODE_NUMPAD_EQUALS, KEY_KPEQUAL}, + {AKEYCODE_NUMPAD_COMMA, KEY_KPCOMMA}, +}; +VirtualKeyboard::VirtualKeyboard(unique_fd fd) : VirtualInputDevice(std::move(fd)) {} +VirtualKeyboard::~VirtualKeyboard() {} + +bool VirtualKeyboard::writeKeyEvent(int32_t androidKeyCode, int32_t androidAction, + std::chrono::nanoseconds eventTime) { + return writeEvKeyEvent(androidKeyCode, androidAction, KEY_CODE_MAPPING, KEY_ACTION_MAPPING, + eventTime); +} + +// --- VirtualDpad --- +// Dpad keycode mapping from https://source.android.com/devices/input/keyboard-devices +const std::map VirtualDpad::DPAD_KEY_CODE_MAPPING = { + // clang-format off + {AKEYCODE_DPAD_DOWN, KEY_DOWN}, + {AKEYCODE_DPAD_UP, KEY_UP}, + {AKEYCODE_DPAD_LEFT, KEY_LEFT}, + {AKEYCODE_DPAD_RIGHT, KEY_RIGHT}, + {AKEYCODE_DPAD_CENTER, KEY_SELECT}, + {AKEYCODE_BACK, KEY_BACK}, + // clang-format on +}; + +VirtualDpad::VirtualDpad(unique_fd fd) : VirtualInputDevice(std::move(fd)) {} + +VirtualDpad::~VirtualDpad() {} + +bool VirtualDpad::writeDpadKeyEvent(int32_t androidKeyCode, int32_t androidAction, + std::chrono::nanoseconds eventTime) { + return writeEvKeyEvent(androidKeyCode, androidAction, DPAD_KEY_CODE_MAPPING, + VirtualKeyboard::KEY_ACTION_MAPPING, eventTime); +} + +// --- VirtualMouse --- +const std::map VirtualMouse::BUTTON_ACTION_MAPPING = { + {AMOTION_EVENT_ACTION_BUTTON_PRESS, UinputAction::PRESS}, + {AMOTION_EVENT_ACTION_BUTTON_RELEASE, UinputAction::RELEASE}, +}; + +// Button code mapping from https://source.android.com/devices/input/touch-devices +const std::map VirtualMouse::BUTTON_CODE_MAPPING = { + // clang-format off + {AMOTION_EVENT_BUTTON_PRIMARY, BTN_LEFT}, + {AMOTION_EVENT_BUTTON_SECONDARY, BTN_RIGHT}, + {AMOTION_EVENT_BUTTON_TERTIARY, BTN_MIDDLE}, + {AMOTION_EVENT_BUTTON_BACK, BTN_BACK}, + {AMOTION_EVENT_BUTTON_FORWARD, BTN_FORWARD}, + // clang-format on +}; + +VirtualMouse::VirtualMouse(unique_fd fd) : VirtualInputDevice(std::move(fd)) {} + +VirtualMouse::~VirtualMouse() {} + +bool VirtualMouse::writeButtonEvent(int32_t androidButtonCode, int32_t androidAction, + std::chrono::nanoseconds eventTime) { + return writeEvKeyEvent(androidButtonCode, androidAction, BUTTON_CODE_MAPPING, + BUTTON_ACTION_MAPPING, eventTime); +} + +bool VirtualMouse::writeRelativeEvent(float relativeX, float relativeY, + std::chrono::nanoseconds eventTime) { + return writeInputEvent(EV_REL, REL_X, relativeX, eventTime) && + writeInputEvent(EV_REL, REL_Y, relativeY, eventTime) && + writeInputEvent(EV_SYN, SYN_REPORT, 0, eventTime); +} + +bool VirtualMouse::writeScrollEvent(float xAxisMovement, float yAxisMovement, + std::chrono::nanoseconds eventTime) { + return writeInputEvent(EV_REL, REL_HWHEEL, xAxisMovement, eventTime) && + writeInputEvent(EV_REL, REL_WHEEL, yAxisMovement, eventTime) && + writeInputEvent(EV_SYN, SYN_REPORT, 0, eventTime); +} + +// --- VirtualTouchscreen --- +const std::map VirtualTouchscreen::TOUCH_ACTION_MAPPING = { + {AMOTION_EVENT_ACTION_DOWN, UinputAction::PRESS}, + {AMOTION_EVENT_ACTION_UP, UinputAction::RELEASE}, + {AMOTION_EVENT_ACTION_MOVE, UinputAction::MOVE}, + {AMOTION_EVENT_ACTION_CANCEL, UinputAction::CANCEL}, +}; +// Tool type mapping from https://source.android.com/devices/input/touch-devices +const std::map VirtualTouchscreen::TOOL_TYPE_MAPPING = { + {AMOTION_EVENT_TOOL_TYPE_FINGER, MT_TOOL_FINGER}, + {AMOTION_EVENT_TOOL_TYPE_PALM, MT_TOOL_PALM}, +}; + +VirtualTouchscreen::VirtualTouchscreen(unique_fd fd) : VirtualInputDevice(std::move(fd)) {} + +VirtualTouchscreen::~VirtualTouchscreen() {} + +bool VirtualTouchscreen::isValidPointerId(int32_t pointerId, UinputAction uinputAction) { + if (pointerId < -1 || pointerId >= (int)MAX_POINTERS) { + ALOGE("Virtual touch event has invalid pointer id %d; value must be between -1 and %zu", + pointerId, MAX_POINTERS - 0); + return false; + } + + if (uinputAction == UinputAction::PRESS && mActivePointers.test(pointerId)) { + ALOGE("Repetitive action DOWN event received on a pointer %d that is already down.", + pointerId); + return false; + } + if (uinputAction == UinputAction::RELEASE && !mActivePointers.test(pointerId)) { + ALOGE("PointerId %d action UP received with no prior action DOWN on touchscreen %d.", + pointerId, mFd.get()); + return false; + } + return true; +} + +bool VirtualTouchscreen::writeTouchEvent(int32_t pointerId, int32_t toolType, int32_t action, + float locationX, float locationY, float pressure, + float majorAxisSize, std::chrono::nanoseconds eventTime) { + auto actionIterator = TOUCH_ACTION_MAPPING.find(action); + if (actionIterator == TOUCH_ACTION_MAPPING.end()) { + return false; + } + UinputAction uinputAction = actionIterator->second; + if (!isValidPointerId(pointerId, uinputAction)) { + return false; + } + if (!writeInputEvent(EV_ABS, ABS_MT_SLOT, pointerId, eventTime)) { + return false; + } + auto toolTypeIterator = TOOL_TYPE_MAPPING.find(toolType); + if (toolTypeIterator == TOOL_TYPE_MAPPING.end()) { + return false; + } + if (!writeInputEvent(EV_ABS, ABS_MT_TOOL_TYPE, static_cast(toolTypeIterator->second), + eventTime)) { + return false; + } + if (uinputAction == UinputAction::PRESS && !handleTouchDown(pointerId, eventTime)) { + return false; + } + if (uinputAction == UinputAction::RELEASE && !handleTouchUp(pointerId, eventTime)) { + return false; + } + if (!writeInputEvent(EV_ABS, ABS_MT_POSITION_X, locationX, eventTime)) { + return false; + } + if (!writeInputEvent(EV_ABS, ABS_MT_POSITION_Y, locationY, eventTime)) { + return false; + } + if (!isnan(pressure)) { + if (!writeInputEvent(EV_ABS, ABS_MT_PRESSURE, pressure, eventTime)) { + return false; + } + } + if (!isnan(majorAxisSize)) { + if (!writeInputEvent(EV_ABS, ABS_MT_TOUCH_MAJOR, majorAxisSize, eventTime)) { + return false; + } + } + return writeInputEvent(EV_SYN, SYN_REPORT, 0, eventTime); +} + +bool VirtualTouchscreen::handleTouchUp(int32_t pointerId, std::chrono::nanoseconds eventTime) { + if (!writeInputEvent(EV_ABS, ABS_MT_TRACKING_ID, static_cast(-1), eventTime)) { + return false; + } + // When a pointer is no longer in touch, remove the pointer id from the corresponding + // entry in the unreleased touches map. + mActivePointers.reset(pointerId); + ALOGD_IF(isDebug(), "Pointer %d erased from the touchscreen %d", pointerId, mFd.get()); + + // Only sends the BTN UP event when there's no pointers on the touchscreen. + if (mActivePointers.none()) { + if (!writeInputEvent(EV_KEY, BTN_TOUCH, static_cast(UinputAction::RELEASE), + eventTime)) { + return false; + } + ALOGD_IF(isDebug(), "No pointers on touchscreen %d, BTN UP event sent.", mFd.get()); + } + return true; +} + +bool VirtualTouchscreen::handleTouchDown(int32_t pointerId, std::chrono::nanoseconds eventTime) { + // When a new pointer is down on the touchscreen, add the pointer id in the corresponding + // entry in the unreleased touches map. + if (mActivePointers.none()) { + // Only sends the BTN Down event when the first pointer on the touchscreen is down. + if (!writeInputEvent(EV_KEY, BTN_TOUCH, static_cast(UinputAction::PRESS), + eventTime)) { + return false; + } + ALOGD_IF(isDebug(), "First pointer %d down under touchscreen %d, BTN DOWN event sent", + pointerId, mFd.get()); + } + + mActivePointers.set(pointerId); + ALOGD_IF(isDebug(), "Added pointer %d under touchscreen %d in the map", pointerId, mFd.get()); + if (!writeInputEvent(EV_ABS, ABS_MT_TRACKING_ID, static_cast(pointerId), eventTime)) { + return false; + } + return true; +} + +} // namespace android diff --git a/libs/input/android/os/IInputConstants.aidl b/libs/input/android/os/IInputConstants.aidl index 5ce10a4a50d7940956f63a7ee445889ef2c148d9..dab843b48f9f9d0cb7550c2c8f343d533c747f39 100644 --- a/libs/input/android/os/IInputConstants.aidl +++ b/libs/input/android/os/IInputConstants.aidl @@ -34,6 +34,14 @@ interface IInputConstants */ const int INVALID_INPUT_EVENT_ID = 0; + /** + * Every input device has an id. This constant value is used when a valid input device id is not + * available. + * The virtual keyboard uses -1 as the input device id. Therefore, we use -2 as the value for + * an invalid input device. + */ + const int INVALID_INPUT_DEVICE_ID = -2; + /** * The input event was injected from accessibility. Used in policyFlags for input event * injection. diff --git a/libs/input/android/os/InputConfig.aidl b/libs/input/android/os/InputConfig.aidl index 6d1b3967f77344a1e89add7dede7ce96b5aa22c3..4e644fff06174861760f4780aae15185b9bbaad6 100644 --- a/libs/input/android/os/InputConfig.aidl +++ b/libs/input/android/os/InputConfig.aidl @@ -144,4 +144,10 @@ enum InputConfig { * It is not valid to set this configuration if {@link #TRUSTED_OVERLAY} is not set. */ INTERCEPTS_STYLUS = 1 << 15, + + /** + * The window is a clone of another window. This may be treated differently since there's + * likely a duplicate window with the same client token, but different bounds. + */ + CLONE = 1 << 16, } diff --git a/libs/input/android/os/InputEventInjectionResult.aidl b/libs/input/android/os/InputEventInjectionResult.aidl index 3bc7068f3c88960d6013164aa4c4e959dc3aceca..e80c2a52dc9857e7badd3f33422adce539bd6c62 100644 --- a/libs/input/android/os/InputEventInjectionResult.aidl +++ b/libs/input/android/os/InputEventInjectionResult.aidl @@ -37,4 +37,7 @@ enum InputEventInjectionResult { /* Injection failed due to a timeout. */ TIMED_OUT = 3, + + ftl_first=PENDING, + ftl_last=TIMED_OUT, } diff --git a/libs/input/android/os/InputEventInjectionSync.aidl b/libs/input/android/os/InputEventInjectionSync.aidl index 95d24cb4436d12fe46577090b5325a81c9306450..2d225fa452e4962bc5f91e4628fad922fb26d8ff 100644 --- a/libs/input/android/os/InputEventInjectionSync.aidl +++ b/libs/input/android/os/InputEventInjectionSync.aidl @@ -33,4 +33,7 @@ enum InputEventInjectionSync { /* Waits for the input event to be completely processed. */ WAIT_FOR_FINISHED = 2, + + ftl_first = NONE, + ftl_last = WAIT_FOR_FINISHED, } diff --git a/libs/input/tests/Android.bp b/libs/input/tests/Android.bp index d947cd99e8ca925533e2d8de61526f3be6cc3fc5..42bdf57514df1f43847151cd599bc26c74dfdeb7 100644 --- a/libs/input/tests/Android.bp +++ b/libs/input/tests/Android.bp @@ -10,35 +10,62 @@ package { cc_test { name: "libinput_tests", + cpp_std: "c++20", + host_supported: true, srcs: [ "IdGenerator_test.cpp", "InputChannel_test.cpp", "InputDevice_test.cpp", "InputEvent_test.cpp", "InputPublisherAndConsumer_test.cpp", + "MotionPredictor_test.cpp", + "RingBuffer_test.cpp", + "TfLiteMotionPredictor_test.cpp", + "TouchResampling_test.cpp", "TouchVideoFrame_test.cpp", "VelocityTracker_test.cpp", "VerifiedInputEvent_test.cpp", ], + header_libs: [ + "flatbuffer_headers", + "tensorflow_headers", + ], static_libs: [ + "libgmock", "libgui_window_info_static", "libinput", + "libtflite_static", + "libui-types", ], cflags: [ "-Wall", "-Wextra", "-Werror", + "-Wno-unused-parameter", ], + sanitize: { + undefined: true, + all_undefined: true, + diag: { + undefined: true, + }, + }, shared_libs: [ "libbase", "libbinder", "libcutils", "liblog", - "libui", + "libPlatformProperties", "libutils", "libvintf", ], - data: ["data/*"], + data: [ + "data/*", + ":motion_predictor_model.fb", + ], + test_options: { + unit_test: true, + }, test_suites: ["device-tests"], } @@ -59,7 +86,6 @@ cc_library_static { "libcutils", "libutils", "libbinder", - "libui", "libbase", ], } diff --git a/libs/input/tests/InputDevice_test.cpp b/libs/input/tests/InputDevice_test.cpp index e872fa442b312ecbcf02981574703e49ca18e7b9..ee961f0ffc5942b1b3842b0f03665ef9a66f3251 100644 --- a/libs/input/tests/InputDevice_test.cpp +++ b/libs/input/tests/InputDevice_test.cpp @@ -65,6 +65,9 @@ protected: } void SetUp() override { +#if !defined(__ANDROID__) + GTEST_SKIP() << "b/253299089 Generic files are currently read directly from device."; +#endif loadKeyLayout("Generic"); loadKeyCharacterMap("Generic"); } @@ -130,7 +133,24 @@ TEST_F(InputDeviceKeyMapTest, keyCharacteMapApplyMultipleOverlaysTest) { ASSERT_EQ(*mKeyMap.keyCharacterMap, *frenchOverlaidKeyCharacterMap); } +TEST_F(InputDeviceKeyMapTest, keyCharacteMapBadAxisLabel) { + std::string klPath = base::GetExecutableDirectory() + "/data/bad_axis_label.kl"; + + base::Result> ret = KeyLayoutMap::load(klPath); + ASSERT_FALSE(ret.ok()) << "Should not be able to load KeyLayout at " << klPath; +} + +TEST_F(InputDeviceKeyMapTest, keyCharacteMapBadLedLabel) { + std::string klPath = base::GetExecutableDirectory() + "/data/bad_led_label.kl"; + + base::Result> ret = KeyLayoutMap::load(klPath); + ASSERT_FALSE(ret.ok()) << "Should not be able to load KeyLayout at " << klPath; +} + TEST(InputDeviceKeyLayoutTest, DoesNotLoadWhenRequiredKernelConfigIsMissing) { +#if !defined(__ANDROID__) + GTEST_SKIP() << "Can't check kernel configs on host"; +#endif std::string klPath = base::GetExecutableDirectory() + "/data/kl_with_required_fake_config.kl"; base::Result> ret = KeyLayoutMap::load(klPath); ASSERT_FALSE(ret.ok()) << "Should not be able to load KeyLayout at " << klPath; @@ -139,6 +159,9 @@ TEST(InputDeviceKeyLayoutTest, DoesNotLoadWhenRequiredKernelConfigIsMissing) { } TEST(InputDeviceKeyLayoutTest, LoadsWhenRequiredKernelConfigIsPresent) { +#if !defined(__ANDROID__) + GTEST_SKIP() << "Can't check kernel configs on host"; +#endif std::string klPath = base::GetExecutableDirectory() + "/data/kl_with_required_real_config.kl"; base::Result> ret = KeyLayoutMap::load(klPath); ASSERT_TRUE(ret.ok()) << "Cannot load KeyLayout at " << klPath; diff --git a/libs/input/tests/InputEvent_test.cpp b/libs/input/tests/InputEvent_test.cpp index 597b38912ba0ae7bbe08cffdcded987a36581024..a9655730fca63049b3ba490a6a04e69b2673e25a 100644 --- a/libs/input/tests/InputEvent_test.cpp +++ b/libs/input/tests/InputEvent_test.cpp @@ -48,6 +48,7 @@ TEST_F(PointerCoordsTest, ClearSetsBitsToZero) { coords.clear(); ASSERT_EQ(0ULL, coords.bits); + ASSERT_FALSE(coords.isResampled); } TEST_F(PointerCoordsTest, AxisValues) { @@ -160,11 +161,13 @@ TEST_F(PointerCoordsTest, Parcel) { outCoords.readFromParcel(&parcel); ASSERT_EQ(0ULL, outCoords.bits); + ASSERT_FALSE(outCoords.isResampled); // Round trip with some values. parcel.freeData(); inCoords.setAxisValue(2, 5); inCoords.setAxisValue(5, 8); + inCoords.isResampled = true; inCoords.writeToParcel(&parcel); parcel.setDataPosition(0); @@ -173,6 +176,7 @@ TEST_F(PointerCoordsTest, Parcel) { ASSERT_EQ(outCoords.bits, inCoords.bits); ASSERT_EQ(outCoords.values[0], inCoords.values[0]); ASSERT_EQ(outCoords.values[1], inCoords.values[1]); + ASSERT_TRUE(outCoords.isResampled); } @@ -193,7 +197,7 @@ TEST_F(KeyEventTest, Properties) { ARBITRARY_DOWN_TIME, ARBITRARY_EVENT_TIME); ASSERT_EQ(id, event.getId()); - ASSERT_EQ(AINPUT_EVENT_TYPE_KEY, event.getType()); + ASSERT_EQ(InputEventType::KEY, event.getType()); ASSERT_EQ(2, event.getDeviceId()); ASSERT_EQ(AINPUT_SOURCE_GAMEPAD, event.getSource()); ASSERT_EQ(DISPLAY_ID, event.getDisplayId()); @@ -255,10 +259,10 @@ void MotionEventTest::SetUp() { mPointerProperties[0].clear(); mPointerProperties[0].id = 1; - mPointerProperties[0].toolType = AMOTION_EVENT_TOOL_TYPE_FINGER; + mPointerProperties[0].toolType = ToolType::FINGER; mPointerProperties[1].clear(); mPointerProperties[1].id = 2; - mPointerProperties[1].toolType = AMOTION_EVENT_TOOL_TYPE_STYLUS; + mPointerProperties[1].toolType = ToolType::STYLUS; mSamples[0].pointerCoords[0].clear(); mSamples[0].pointerCoords[0].setAxisValue(AMOTION_EVENT_AXIS_X, 10); @@ -270,6 +274,7 @@ void MotionEventTest::SetUp() { mSamples[0].pointerCoords[0].setAxisValue(AMOTION_EVENT_AXIS_TOOL_MAJOR, 16); mSamples[0].pointerCoords[0].setAxisValue(AMOTION_EVENT_AXIS_TOOL_MINOR, 17); mSamples[0].pointerCoords[0].setAxisValue(AMOTION_EVENT_AXIS_ORIENTATION, 18); + mSamples[0].pointerCoords[0].isResampled = true; mSamples[0].pointerCoords[1].clear(); mSamples[0].pointerCoords[1].setAxisValue(AMOTION_EVENT_AXIS_X, 20); mSamples[0].pointerCoords[1].setAxisValue(AMOTION_EVENT_AXIS_Y, 21); @@ -291,6 +296,7 @@ void MotionEventTest::SetUp() { mSamples[1].pointerCoords[0].setAxisValue(AMOTION_EVENT_AXIS_TOOL_MAJOR, 116); mSamples[1].pointerCoords[0].setAxisValue(AMOTION_EVENT_AXIS_TOOL_MINOR, 117); mSamples[1].pointerCoords[0].setAxisValue(AMOTION_EVENT_AXIS_ORIENTATION, 118); + mSamples[1].pointerCoords[0].isResampled = true; mSamples[1].pointerCoords[1].clear(); mSamples[1].pointerCoords[1].setAxisValue(AMOTION_EVENT_AXIS_X, 120); mSamples[1].pointerCoords[1].setAxisValue(AMOTION_EVENT_AXIS_Y, 121); @@ -301,6 +307,7 @@ void MotionEventTest::SetUp() { mSamples[1].pointerCoords[1].setAxisValue(AMOTION_EVENT_AXIS_TOOL_MAJOR, 126); mSamples[1].pointerCoords[1].setAxisValue(AMOTION_EVENT_AXIS_TOOL_MINOR, 127); mSamples[1].pointerCoords[1].setAxisValue(AMOTION_EVENT_AXIS_ORIENTATION, 128); + mSamples[1].pointerCoords[1].isResampled = true; mSamples[2].pointerCoords[0].clear(); mSamples[2].pointerCoords[0].setAxisValue(AMOTION_EVENT_AXIS_X, 210); @@ -339,7 +346,7 @@ void MotionEventTest::initializeEventWithHistory(MotionEvent* event) { void MotionEventTest::assertEqualsEventWithHistory(const MotionEvent* event) { // Check properties. ASSERT_EQ(mId, event->getId()); - ASSERT_EQ(AINPUT_EVENT_TYPE_MOTION, event->getType()); + ASSERT_EQ(InputEventType::MOTION, event->getType()); ASSERT_EQ(2, event->getDeviceId()); ASSERT_EQ(AINPUT_SOURCE_TOUCHSCREEN, event->getSource()); ASSERT_EQ(DISPLAY_ID, event->getDisplayId()); @@ -359,9 +366,9 @@ void MotionEventTest::assertEqualsEventWithHistory(const MotionEvent* event) { ASSERT_EQ(2U, event->getPointerCount()); ASSERT_EQ(1, event->getPointerId(0)); - ASSERT_EQ(AMOTION_EVENT_TOOL_TYPE_FINGER, event->getToolType(0)); + ASSERT_EQ(ToolType::FINGER, event->getToolType(0)); ASSERT_EQ(2, event->getPointerId(1)); - ASSERT_EQ(AMOTION_EVENT_TOOL_TYPE_STYLUS, event->getToolType(1)); + ASSERT_EQ(ToolType::STYLUS, event->getToolType(1)); ASSERT_EQ(2U, event->getHistorySize()); @@ -485,6 +492,13 @@ void MotionEventTest::assertEqualsEventWithHistory(const MotionEvent* event) { ASSERT_EQ(toScaledOrientation(128), event->getHistoricalOrientation(1, 1)); ASSERT_EQ(toScaledOrientation(218), event->getOrientation(0)); ASSERT_EQ(toScaledOrientation(228), event->getOrientation(1)); + + ASSERT_TRUE(event->isResampled(0, 0)); + ASSERT_FALSE(event->isResampled(1, 0)); + ASSERT_TRUE(event->isResampled(0, 1)); + ASSERT_TRUE(event->isResampled(1, 1)); + ASSERT_FALSE(event->isResampled(0, 2)); + ASSERT_FALSE(event->isResampled(1, 2)); } TEST_F(MotionEventTest, Properties) { @@ -517,7 +531,7 @@ TEST_F(MotionEventTest, CopyFrom_KeepHistory) { initializeEventWithHistory(&event); MotionEvent copy; - copy.copyFrom(&event, true /*keepHistory*/); + copy.copyFrom(&event, /*keepHistory=*/true); ASSERT_NO_FATAL_FAILURE(assertEqualsEventWithHistory(&event)); } @@ -527,7 +541,7 @@ TEST_F(MotionEventTest, CopyFrom_DoNotKeepHistory) { initializeEventWithHistory(&event); MotionEvent copy; - copy.copyFrom(&event, false /*keepHistory*/); + copy.copyFrom(&event, /*keepHistory=*/false); ASSERT_EQ(event.getPointerCount(), copy.getPointerCount()); ASSERT_EQ(0U, copy.getHistorySize()); @@ -628,12 +642,12 @@ TEST_F(MotionEventTest, Transform) { } MotionEvent event; ui::Transform identityTransform; - event.initialize(InputEvent::nextId(), 0 /*deviceId*/, AINPUT_SOURCE_TOUCHSCREEN, DISPLAY_ID, - INVALID_HMAC, AMOTION_EVENT_ACTION_MOVE, 0 /*actionButton*/, 0 /*flags*/, - AMOTION_EVENT_EDGE_FLAG_NONE, AMETA_NONE, 0 /*buttonState*/, - MotionClassification::NONE, identityTransform, 0 /*xPrecision*/, - 0 /*yPrecision*/, 3 + RADIUS /*xCursorPosition*/, 2 /*yCursorPosition*/, - identityTransform, 0 /*downTime*/, 0 /*eventTime*/, pointerCount, + event.initialize(InputEvent::nextId(), /*deviceId=*/0, AINPUT_SOURCE_TOUCHSCREEN, DISPLAY_ID, + INVALID_HMAC, AMOTION_EVENT_ACTION_MOVE, /*actionButton=*/0, /*flags=*/0, + AMOTION_EVENT_EDGE_FLAG_NONE, AMETA_NONE, /*buttonState=*/0, + MotionClassification::NONE, identityTransform, /*xPrecision=*/0, + /*yPrecision=*/0, /*xCursorPosition=*/3 + RADIUS, /*yCursorPosition=*/2, + identityTransform, /*downTime=*/0, /*eventTime=*/0, pointerCount, pointerProperties, pointerCoords); float originalRawX = 0 + 3; float originalRawY = -RADIUS + 2; @@ -678,7 +692,7 @@ TEST_F(MotionEventTest, Transform) { MotionEvent createMotionEvent(int32_t source, uint32_t action, float x, float y, float dx, float dy, const ui::Transform& transform, const ui::Transform& rawTransform) { std::vector pointerProperties; - pointerProperties.push_back(PointerProperties{/* id */ 0, AMOTION_EVENT_TOOL_TYPE_FINGER}); + pointerProperties.push_back(PointerProperties{/*id=*/0, ToolType::FINGER}); std::vector pointerCoords; pointerCoords.emplace_back().clear(); pointerCoords.back().setAxisValue(AMOTION_EVENT_AXIS_X, x); @@ -834,12 +848,12 @@ TEST_F(MotionEventTest, Initialize_SetsClassification) { ui::Transform identityTransform; for (MotionClassification classification : classifications) { - event.initialize(InputEvent::nextId(), 0 /*deviceId*/, AINPUT_SOURCE_TOUCHSCREEN, + event.initialize(InputEvent::nextId(), /*deviceId=*/0, AINPUT_SOURCE_TOUCHSCREEN, DISPLAY_ID, INVALID_HMAC, AMOTION_EVENT_ACTION_DOWN, 0, 0, AMOTION_EVENT_EDGE_FLAG_NONE, AMETA_NONE, 0, classification, identityTransform, 0, 0, AMOTION_EVENT_INVALID_CURSOR_POSITION, - AMOTION_EVENT_INVALID_CURSOR_POSITION, identityTransform, 0 /*downTime*/, - 0 /*eventTime*/, pointerCount, pointerProperties, pointerCoords); + AMOTION_EVENT_INVALID_CURSOR_POSITION, identityTransform, /*downTime=*/0, + /*eventTime=*/0, pointerCount, pointerProperties, pointerCoords); ASSERT_EQ(classification, event.getClassification()); } } @@ -856,11 +870,11 @@ TEST_F(MotionEventTest, Initialize_SetsCursorPosition) { } ui::Transform identityTransform; - event.initialize(InputEvent::nextId(), 0 /*deviceId*/, AINPUT_SOURCE_MOUSE, DISPLAY_ID, + event.initialize(InputEvent::nextId(), /*deviceId=*/0, AINPUT_SOURCE_MOUSE, DISPLAY_ID, INVALID_HMAC, AMOTION_EVENT_ACTION_DOWN, 0, 0, AMOTION_EVENT_EDGE_FLAG_NONE, AMETA_NONE, 0, MotionClassification::NONE, identityTransform, 0, 0, - 280 /*xCursorPosition*/, 540 /*yCursorPosition*/, identityTransform, - 0 /*downTime*/, 0 /*eventTime*/, pointerCount, pointerProperties, + /*xCursorPosition=*/280, /*yCursorPosition=*/540, identityTransform, + /*downTime=*/0, /*eventTime=*/0, pointerCount, pointerProperties, pointerCoords); event.offsetLocation(20, 60); ASSERT_EQ(280, event.getRawXCursorPosition()); diff --git a/libs/input/tests/InputPublisherAndConsumer_test.cpp b/libs/input/tests/InputPublisherAndConsumer_test.cpp index 8393e99d650658123c6571b53722638247df459f..3ecf8eed50720792a1b2739f163b7f244afa9c81 100644 --- a/libs/input/tests/InputPublisherAndConsumer_test.cpp +++ b/libs/input/tests/InputPublisherAndConsumer_test.cpp @@ -16,17 +16,10 @@ #include "TestHelpers.h" -#include -#include -#include - #include -#include #include #include #include -#include -#include using android::base::Result; @@ -99,14 +92,13 @@ void InputPublisherAndConsumerTest::PublishAndConsumeKeyEvent() { uint32_t consumeSeq; InputEvent* event; - status = mConsumer->consume(&mEventFactory, true /*consumeBatches*/, -1, &consumeSeq, &event); + status = mConsumer->consume(&mEventFactory, /*consumeBatches=*/true, -1, &consumeSeq, &event); ASSERT_EQ(OK, status) << "consumer consume should return OK"; ASSERT_TRUE(event != nullptr) << "consumer should have returned non-NULL event"; - ASSERT_EQ(AINPUT_EVENT_TYPE_KEY, event->getType()) - << "consumer should have returned a key event"; + ASSERT_EQ(InputEventType::KEY, event->getType()) << "consumer should have returned a key event"; KeyEvent* keyEvent = static_cast(event); EXPECT_EQ(seq, consumeSeq); @@ -179,7 +171,7 @@ void InputPublisherAndConsumerTest::PublishAndConsumeMotionEvent() { for (size_t i = 0; i < pointerCount; i++) { pointerProperties[i].clear(); pointerProperties[i].id = (i + 2) % pointerCount; - pointerProperties[i].toolType = AMOTION_EVENT_TOOL_TYPE_FINGER; + pointerProperties[i].toolType = ToolType::FINGER; pointerCoords[i].clear(); pointerCoords[i].setAxisValue(AMOTION_EVENT_AXIS_X, 100 * i); @@ -208,13 +200,13 @@ void InputPublisherAndConsumerTest::PublishAndConsumeMotionEvent() { uint32_t consumeSeq; InputEvent* event; - status = mConsumer->consume(&mEventFactory, true /*consumeBatches*/, -1, &consumeSeq, &event); + status = mConsumer->consume(&mEventFactory, /*consumeBatches=*/true, -1, &consumeSeq, &event); ASSERT_EQ(OK, status) << "consumer consume should return OK"; ASSERT_TRUE(event != nullptr) << "consumer should have returned non-NULL event"; - ASSERT_EQ(AINPUT_EVENT_TYPE_MOTION, event->getType()) + ASSERT_EQ(InputEventType::MOTION, event->getType()) << "consumer should have returned a motion event"; MotionEvent* motionEvent = static_cast(event); @@ -301,11 +293,11 @@ void InputPublisherAndConsumerTest::PublishAndConsumeFocusEvent() { uint32_t consumeSeq; InputEvent* event; - status = mConsumer->consume(&mEventFactory, true /*consumeBatches*/, -1, &consumeSeq, &event); + status = mConsumer->consume(&mEventFactory, /*consumeBatches=*/true, -1, &consumeSeq, &event); ASSERT_EQ(OK, status) << "consumer consume should return OK"; ASSERT_TRUE(event != nullptr) << "consumer should have returned non-NULL event"; - ASSERT_EQ(AINPUT_EVENT_TYPE_FOCUS, event->getType()) + ASSERT_EQ(InputEventType::FOCUS, event->getType()) << "consumer should have returned a focus event"; FocusEvent* focusEvent = static_cast(event); @@ -342,11 +334,11 @@ void InputPublisherAndConsumerTest::PublishAndConsumeCaptureEvent() { uint32_t consumeSeq; InputEvent* event; - status = mConsumer->consume(&mEventFactory, true /*consumeBatches*/, -1, &consumeSeq, &event); + status = mConsumer->consume(&mEventFactory, /*consumeBatches=*/true, -1, &consumeSeq, &event); ASSERT_EQ(OK, status) << "consumer consume should return OK"; ASSERT_TRUE(event != nullptr) << "consumer should have returned non-NULL event"; - ASSERT_EQ(AINPUT_EVENT_TYPE_CAPTURE, event->getType()) + ASSERT_EQ(InputEventType::CAPTURE, event->getType()) << "consumer should have returned a capture event"; const CaptureEvent* captureEvent = static_cast(event); @@ -384,11 +376,11 @@ void InputPublisherAndConsumerTest::PublishAndConsumeDragEvent() { uint32_t consumeSeq; InputEvent* event; - status = mConsumer->consume(&mEventFactory, true /*consumeBatches*/, -1, &consumeSeq, &event); + status = mConsumer->consume(&mEventFactory, /*consumeBatches=*/true, -1, &consumeSeq, &event); ASSERT_EQ(OK, status) << "consumer consume should return OK"; ASSERT_TRUE(event != nullptr) << "consumer should have returned non-NULL event"; - ASSERT_EQ(AINPUT_EVENT_TYPE_DRAG, event->getType()) + ASSERT_EQ(InputEventType::DRAG, event->getType()) << "consumer should have returned a drag event"; const DragEvent& dragEvent = static_cast(*event); @@ -426,11 +418,11 @@ void InputPublisherAndConsumerTest::PublishAndConsumeTouchModeEvent() { uint32_t consumeSeq; InputEvent* event; - status = mConsumer->consume(&mEventFactory, true /*consumeBatches*/, -1, &consumeSeq, &event); + status = mConsumer->consume(&mEventFactory, /*consumeBatches=*/true, -1, &consumeSeq, &event); ASSERT_EQ(OK, status) << "consumer consume should return OK"; ASSERT_TRUE(event != nullptr) << "consumer should have returned non-NULL event"; - ASSERT_EQ(AINPUT_EVENT_TYPE_TOUCH_MODE, event->getType()) + ASSERT_EQ(InputEventType::TOUCH_MODE, event->getType()) << "consumer should have returned a touch mode event"; const TouchModeEvent& touchModeEvent = static_cast(*event); diff --git a/libs/input/tests/MotionPredictor_test.cpp b/libs/input/tests/MotionPredictor_test.cpp new file mode 100644 index 0000000000000000000000000000000000000000..7a62f5ec580e30542f9c802e44ddfa1daee28151 --- /dev/null +++ b/libs/input/tests/MotionPredictor_test.cpp @@ -0,0 +1,141 @@ +/* + * 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. + */ + +#include + +#include +#include +#include +#include +#include + +using namespace std::literals::chrono_literals; + +namespace android { + +using ::testing::IsEmpty; +using ::testing::SizeIs; +using ::testing::UnorderedElementsAre; + +constexpr int32_t DOWN = AMOTION_EVENT_ACTION_DOWN; +constexpr int32_t MOVE = AMOTION_EVENT_ACTION_MOVE; +constexpr int32_t UP = AMOTION_EVENT_ACTION_UP; +constexpr nsecs_t NSEC_PER_MSEC = 1'000'000; + +static MotionEvent getMotionEvent(int32_t action, float x, float y, + std::chrono::nanoseconds eventTime, int32_t deviceId = 0) { + MotionEvent event; + constexpr size_t pointerCount = 1; + std::vector pointerProperties; + std::vector pointerCoords; + for (size_t i = 0; i < pointerCount; i++) { + PointerProperties properties; + properties.clear(); + properties.id = i; + properties.toolType = ToolType::STYLUS; + pointerProperties.push_back(properties); + PointerCoords coords; + coords.clear(); + coords.setAxisValue(AMOTION_EVENT_AXIS_X, x); + coords.setAxisValue(AMOTION_EVENT_AXIS_Y, y); + pointerCoords.push_back(coords); + } + + 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, + AMETA_NONE, /*buttonState=*/0, MotionClassification::NONE, identityTransform, + /*xPrecision=*/0.1, + /*yPrecision=*/0.2, /*xCursorPosition=*/280, /*yCursorPosition=*/540, + identityTransform, /*downTime=*/100, eventTime.count(), pointerCount, + pointerProperties.data(), pointerCoords.data()); + return event; +} + +TEST(MotionPredictorTest, IsPredictionAvailable) { + MotionPredictor predictor(/*predictionTimestampOffsetNanos=*/0, + []() { return true /*enable prediction*/; }); + ASSERT_TRUE(predictor.isPredictionAvailable(/*deviceId=*/1, AINPUT_SOURCE_STYLUS)); + ASSERT_FALSE(predictor.isPredictionAvailable(/*deviceId=*/1, AINPUT_SOURCE_TOUCHSCREEN)); +} + +TEST(MotionPredictorTest, Offset) { + MotionPredictor predictor(/*predictionTimestampOffsetNanos=*/1, + []() { return true /*enable prediction*/; }); + predictor.record(getMotionEvent(DOWN, 0, 1, 30ms)); + predictor.record(getMotionEvent(MOVE, 0, 2, 35ms)); + std::unique_ptr predicted = predictor.predict(40 * NSEC_PER_MSEC); + ASSERT_NE(nullptr, predicted); + ASSERT_GE(predicted->getEventTime(), 41); +} + +TEST(MotionPredictorTest, FollowsGesture) { + MotionPredictor predictor(/*predictionTimestampOffsetNanos=*/0, + []() { return true /*enable prediction*/; }); + + // MOVE without a DOWN is ignored. + predictor.record(getMotionEvent(MOVE, 1, 3, 10ms)); + EXPECT_EQ(nullptr, predictor.predict(20 * NSEC_PER_MSEC)); + + predictor.record(getMotionEvent(DOWN, 2, 5, 20ms)); + predictor.record(getMotionEvent(MOVE, 2, 7, 30ms)); + predictor.record(getMotionEvent(MOVE, 3, 9, 40ms)); + EXPECT_NE(nullptr, predictor.predict(50 * NSEC_PER_MSEC)); + + predictor.record(getMotionEvent(UP, 4, 11, 50ms)); + EXPECT_EQ(nullptr, predictor.predict(20 * NSEC_PER_MSEC)); +} + +TEST(MotionPredictorTest, MultipleDevicesNotSupported) { + MotionPredictor predictor(/*predictionTimestampOffsetNanos=*/0, + []() { return true /*enable prediction*/; }); + + ASSERT_TRUE(predictor.record(getMotionEvent(DOWN, 1, 3, 0ms, /*deviceId=*/0)).ok()); + ASSERT_TRUE(predictor.record(getMotionEvent(MOVE, 1, 3, 10ms, /*deviceId=*/0)).ok()); + ASSERT_TRUE(predictor.record(getMotionEvent(MOVE, 2, 5, 20ms, /*deviceId=*/0)).ok()); + ASSERT_TRUE(predictor.record(getMotionEvent(MOVE, 3, 7, 30ms, /*deviceId=*/0)).ok()); + + ASSERT_FALSE(predictor.record(getMotionEvent(DOWN, 100, 300, 40ms, /*deviceId=*/1)).ok()); + ASSERT_FALSE(predictor.record(getMotionEvent(MOVE, 100, 300, 50ms, /*deviceId=*/1)).ok()); +} + +TEST(MotionPredictorTest, IndividualGesturesFromDifferentDevicesAreSupported) { + MotionPredictor predictor(/*predictionTimestampOffsetNanos=*/0, + []() { return true /*enable prediction*/; }); + + ASSERT_TRUE(predictor.record(getMotionEvent(DOWN, 1, 3, 0ms, /*deviceId=*/0)).ok()); + ASSERT_TRUE(predictor.record(getMotionEvent(MOVE, 1, 3, 10ms, /*deviceId=*/0)).ok()); + ASSERT_TRUE(predictor.record(getMotionEvent(MOVE, 2, 5, 20ms, /*deviceId=*/0)).ok()); + ASSERT_TRUE(predictor.record(getMotionEvent(UP, 2, 5, 30ms, /*deviceId=*/0)).ok()); + + // Now, send a gesture from a different device. Since we have no active gesture, the new gesture + // should be processed correctly. + ASSERT_TRUE(predictor.record(getMotionEvent(DOWN, 100, 300, 40ms, /*deviceId=*/1)).ok()); + ASSERT_TRUE(predictor.record(getMotionEvent(MOVE, 100, 300, 50ms, /*deviceId=*/1)).ok()); +} + +TEST(MotionPredictorTest, FlagDisablesPrediction) { + MotionPredictor predictor(/*predictionTimestampOffsetNanos=*/0, + []() { return false /*disable prediction*/; }); + predictor.record(getMotionEvent(DOWN, 0, 1, 30ms)); + predictor.record(getMotionEvent(MOVE, 0, 1, 35ms)); + std::unique_ptr predicted = predictor.predict(40 * NSEC_PER_MSEC); + ASSERT_EQ(nullptr, predicted); + ASSERT_FALSE(predictor.isPredictionAvailable(/*deviceId=*/1, AINPUT_SOURCE_STYLUS)); + ASSERT_FALSE(predictor.isPredictionAvailable(/*deviceId=*/1, AINPUT_SOURCE_TOUCHSCREEN)); +} + +} // namespace android diff --git a/libs/input/tests/RingBuffer_test.cpp b/libs/input/tests/RingBuffer_test.cpp new file mode 100644 index 0000000000000000000000000000000000000000..a2ef658934deb91ea71d437fbe42a7aebee98fb1 --- /dev/null +++ b/libs/input/tests/RingBuffer_test.cpp @@ -0,0 +1,223 @@ +/* + * 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. + */ + +#include +#include +#include +#include + +#include +#include +#include + +namespace android { +namespace { + +using ::testing::ElementsAre; +using ::testing::ElementsAreArray; +using ::testing::IsEmpty; +using ::testing::Not; +using ::testing::SizeIs; + +TEST(RingBufferTest, PushPop) { + RingBuffer buffer(/*capacity=*/3); + + buffer.pushBack(1); + buffer.pushBack(2); + buffer.pushBack(3); + EXPECT_THAT(buffer, ElementsAre(1, 2, 3)); + + buffer.pushBack(4); + EXPECT_THAT(buffer, ElementsAre(2, 3, 4)); + + buffer.pushFront(1); + EXPECT_THAT(buffer, ElementsAre(1, 2, 3)); + + EXPECT_EQ(1, buffer.popFront()); + EXPECT_THAT(buffer, ElementsAre(2, 3)); + + buffer.pushBack(4); + EXPECT_THAT(buffer, ElementsAre(2, 3, 4)); + + buffer.pushBack(5); + EXPECT_THAT(buffer, ElementsAre(3, 4, 5)); + + EXPECT_EQ(5, buffer.popBack()); + EXPECT_THAT(buffer, ElementsAre(3, 4)); + + EXPECT_EQ(4, buffer.popBack()); + EXPECT_THAT(buffer, ElementsAre(3)); + + EXPECT_EQ(3, buffer.popBack()); + EXPECT_THAT(buffer, ElementsAre()); + + buffer.pushBack(1); + EXPECT_THAT(buffer, ElementsAre(1)); + + EXPECT_EQ(1, buffer.popFront()); + EXPECT_THAT(buffer, ElementsAre()); +} + +TEST(RingBufferTest, ObjectType) { + RingBuffer> buffer(/*capacity=*/2); + buffer.pushBack(std::make_unique(1)); + buffer.pushBack(std::make_unique(2)); + buffer.pushBack(std::make_unique(3)); + + EXPECT_EQ(2, *buffer[0]); + EXPECT_EQ(3, *buffer[1]); +} + +TEST(RingBufferTest, ConstructConstantValue) { + RingBuffer buffer(/*count=*/3, /*value=*/10); + EXPECT_THAT(buffer, ElementsAre(10, 10, 10)); + EXPECT_EQ(3u, buffer.capacity()); +} + +TEST(RingBufferTest, Assignment) { + RingBuffer a(/*capacity=*/2); + a.pushBack(1); + a.pushBack(2); + + RingBuffer b(/*capacity=*/3); + b.pushBack(10); + b.pushBack(20); + b.pushBack(30); + + std::swap(a, b); + EXPECT_THAT(a, ElementsAre(10, 20, 30)); + EXPECT_THAT(b, ElementsAre(1, 2)); + + a = b; + EXPECT_THAT(a, ElementsAreArray(b)); + + RingBuffer c(b); + EXPECT_THAT(c, ElementsAreArray(b)); + + RingBuffer d(std::move(b)); + EXPECT_EQ(0u, b.capacity()); + EXPECT_THAT(b, ElementsAre()); + EXPECT_THAT(d, ElementsAre(1, 2)); + + b = std::move(d); + EXPECT_THAT(b, ElementsAre(1, 2)); + EXPECT_THAT(d, ElementsAre()); + EXPECT_EQ(0u, d.capacity()); +} + +TEST(RingBufferTest, FrontBackAccess) { + RingBuffer buffer(/*capacity=*/2); + buffer.pushBack(1); + EXPECT_EQ(1, buffer.front()); + EXPECT_EQ(1, buffer.back()); + + buffer.pushFront(0); + EXPECT_EQ(0, buffer.front()); + EXPECT_EQ(1, buffer.back()); + + buffer.pushFront(-1); + EXPECT_EQ(-1, buffer.front()); + EXPECT_EQ(0, buffer.back()); +} + +TEST(RingBufferTest, Subscripting) { + RingBuffer buffer(/*capacity=*/2); + buffer.pushBack(1); + EXPECT_EQ(1, buffer[0]); + + buffer.pushFront(0); + EXPECT_EQ(0, buffer[0]); + EXPECT_EQ(1, buffer[1]); + + buffer.pushFront(-1); + EXPECT_EQ(-1, buffer[0]); + EXPECT_EQ(0, buffer[1]); +} + +TEST(RingBufferTest, Iterator) { + RingBuffer buffer(/*capacity=*/3); + buffer.pushFront(2); + buffer.pushBack(3); + + auto begin = buffer.begin(); + auto end = buffer.end(); + + EXPECT_NE(begin, end); + EXPECT_LE(begin, end); + EXPECT_GT(end, begin); + EXPECT_EQ(end, begin + 2); + EXPECT_EQ(begin, end - 2); + + EXPECT_EQ(2, end - begin); + EXPECT_EQ(1, end - (begin + 1)); + + EXPECT_EQ(2, *begin); + ++begin; + EXPECT_EQ(3, *begin); + --begin; + EXPECT_EQ(2, *begin); + begin += 1; + EXPECT_EQ(3, *begin); + begin += -1; + EXPECT_EQ(2, *begin); + begin -= -1; + EXPECT_EQ(3, *begin); +} + +TEST(RingBufferTest, Clear) { + RingBuffer buffer(/*capacity=*/2); + EXPECT_THAT(buffer, ElementsAre()); + + buffer.pushBack(1); + EXPECT_THAT(buffer, ElementsAre(1)); + + buffer.clear(); + EXPECT_THAT(buffer, ElementsAre()); + EXPECT_THAT(buffer, SizeIs(0)); + EXPECT_THAT(buffer, IsEmpty()); + + buffer.pushFront(1); + EXPECT_THAT(buffer, ElementsAre(1)); +} + +TEST(RingBufferTest, SizeAndIsEmpty) { + RingBuffer buffer(/*capacity=*/2); + EXPECT_THAT(buffer, SizeIs(0)); + EXPECT_THAT(buffer, IsEmpty()); + + buffer.pushBack(1); + EXPECT_THAT(buffer, SizeIs(1)); + EXPECT_THAT(buffer, Not(IsEmpty())); + + buffer.pushBack(2); + EXPECT_THAT(buffer, SizeIs(2)); + EXPECT_THAT(buffer, Not(IsEmpty())); + + buffer.pushBack(3); + EXPECT_THAT(buffer, SizeIs(2)); + EXPECT_THAT(buffer, Not(IsEmpty())); + + buffer.popFront(); + EXPECT_THAT(buffer, SizeIs(1)); + EXPECT_THAT(buffer, Not(IsEmpty())); + + buffer.popBack(); + EXPECT_THAT(buffer, SizeIs(0)); + EXPECT_THAT(buffer, IsEmpty()); +} + +} // namespace +} // namespace android diff --git a/libs/input/tests/StructLayout_test.cpp b/libs/input/tests/StructLayout_test.cpp index 1c8658b69a59bf3e34797b2428528c5d48022f0f..024b6d3d4389ace0095afad8fd8a43638b154003 100644 --- a/libs/input/tests/StructLayout_test.cpp +++ b/libs/input/tests/StructLayout_test.cpp @@ -117,7 +117,7 @@ void TestHeaderSize() { void TestBodySize() { static_assert(sizeof(InputMessage::Body::Key) == 96); - static_assert(sizeof(InputMessage::Body::Motion::Pointer) == 136); + static_assert(sizeof(InputMessage::Body::Motion::Pointer) == 144); static_assert(sizeof(InputMessage::Body::Motion) == offsetof(InputMessage::Body::Motion, pointers) + sizeof(InputMessage::Body::Motion::Pointer) * MAX_POINTERS); @@ -137,8 +137,8 @@ void TestBodySize() { static_assert(sizeof(InputMessage::Body) == offsetof(InputMessage::Body::Motion, pointers) + sizeof(InputMessage::Body::Motion::Pointer) * MAX_POINTERS); - static_assert(sizeof(InputMessage::Body) == 160 + 136 * 16); - static_assert(sizeof(InputMessage::Body) == 2336); + static_assert(sizeof(InputMessage::Body) == 160 + 144 * 16); + static_assert(sizeof(InputMessage::Body) == 2464); } /** @@ -148,8 +148,8 @@ void TestBodySize() { * still helpful to compute to get an idea of the sizes that are involved. */ void TestWorstCaseInputMessageSize() { - static_assert(sizeof(InputMessage) == /*header*/ 8 + /*body*/ 2336); - static_assert(sizeof(InputMessage) == 2344); + static_assert(sizeof(InputMessage) == /*header*/ 8 + /*body*/ 2464); + static_assert(sizeof(InputMessage) == 2472); } /** @@ -159,8 +159,8 @@ void CalculateSinglePointerInputMessageSize() { constexpr size_t pointerCount = 1; constexpr size_t bodySize = offsetof(InputMessage::Body::Motion, pointers) + sizeof(InputMessage::Body::Motion::Pointer) * pointerCount; - static_assert(bodySize == 160 + 136); - static_assert(bodySize == 296); // For the total message size, add the small header + static_assert(bodySize == 160 + 144); + static_assert(bodySize == 304); // For the total message size, add the small header } // --- VerifiedInputEvent --- diff --git a/libs/input/tests/TfLiteMotionPredictor_test.cpp b/libs/input/tests/TfLiteMotionPredictor_test.cpp new file mode 100644 index 0000000000000000000000000000000000000000..b5ed9e4430eef2a8ef3080de1e7b3deab4cb6c8c --- /dev/null +++ b/libs/input/tests/TfLiteMotionPredictor_test.cpp @@ -0,0 +1,169 @@ +/* + * 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. + */ + +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +namespace android { +namespace { + +using ::testing::Each; +using ::testing::ElementsAre; +using ::testing::FloatNear; + +TEST(TfLiteMotionPredictorTest, BuffersReadiness) { + TfLiteMotionPredictorBuffers buffers(/*inputLength=*/5); + ASSERT_FALSE(buffers.isReady()); + + buffers.pushSample(/*timestamp=*/0, {.position = {.x = 100, .y = 100}}); + ASSERT_FALSE(buffers.isReady()); + + buffers.pushSample(/*timestamp=*/1, {.position = {.x = 100, .y = 100}}); + ASSERT_FALSE(buffers.isReady()); + + // Two samples with distinct positions are required. + buffers.pushSample(/*timestamp=*/2, {.position = {.x = 100, .y = 110}}); + ASSERT_TRUE(buffers.isReady()); + + buffers.reset(); + ASSERT_FALSE(buffers.isReady()); +} + +TEST(TfLiteMotionPredictorTest, BuffersRecentData) { + TfLiteMotionPredictorBuffers buffers(/*inputLength=*/5); + + buffers.pushSample(/*timestamp=*/1, {.position = {.x = 100, .y = 200}}); + ASSERT_EQ(buffers.lastTimestamp(), 1); + + buffers.pushSample(/*timestamp=*/2, {.position = {.x = 150, .y = 250}}); + ASSERT_EQ(buffers.lastTimestamp(), 2); + ASSERT_TRUE(buffers.isReady()); + ASSERT_EQ(buffers.axisFrom().position.x, 100); + ASSERT_EQ(buffers.axisFrom().position.y, 200); + ASSERT_EQ(buffers.axisTo().position.x, 150); + ASSERT_EQ(buffers.axisTo().position.y, 250); + + // Position doesn't change, so neither do the axes. + buffers.pushSample(/*timestamp=*/3, {.position = {.x = 150, .y = 250}}); + ASSERT_EQ(buffers.lastTimestamp(), 3); + ASSERT_TRUE(buffers.isReady()); + ASSERT_EQ(buffers.axisFrom().position.x, 100); + ASSERT_EQ(buffers.axisFrom().position.y, 200); + ASSERT_EQ(buffers.axisTo().position.x, 150); + ASSERT_EQ(buffers.axisTo().position.y, 250); + + buffers.pushSample(/*timestamp=*/4, {.position = {.x = 180, .y = 280}}); + ASSERT_EQ(buffers.lastTimestamp(), 4); + ASSERT_TRUE(buffers.isReady()); + ASSERT_EQ(buffers.axisFrom().position.x, 150); + ASSERT_EQ(buffers.axisFrom().position.y, 250); + ASSERT_EQ(buffers.axisTo().position.x, 180); + ASSERT_EQ(buffers.axisTo().position.y, 280); +} + +TEST(TfLiteMotionPredictorTest, BuffersCopyTo) { + std::unique_ptr model = TfLiteMotionPredictorModel::create(); + TfLiteMotionPredictorBuffers buffers(model->inputLength()); + + buffers.pushSample(/*timestamp=*/1, + {.position = {.x = 10, .y = 10}, + .pressure = 0, + .orientation = 0, + .tilt = 0.2}); + buffers.pushSample(/*timestamp=*/2, + {.position = {.x = 10, .y = 50}, + .pressure = 0.4, + .orientation = M_PI / 4, + .tilt = 0.3}); + buffers.pushSample(/*timestamp=*/3, + {.position = {.x = 30, .y = 50}, + .pressure = 0.5, + .orientation = -M_PI / 4, + .tilt = 0.4}); + buffers.pushSample(/*timestamp=*/3, + {.position = {.x = 30, .y = 60}, + .pressure = 0, + .orientation = 0, + .tilt = 0.5}); + buffers.copyTo(*model); + + const int zeroPadding = model->inputLength() - 3; + ASSERT_GE(zeroPadding, 0); + + EXPECT_THAT(model->inputR().subspan(0, zeroPadding), Each(0)); + EXPECT_THAT(model->inputPhi().subspan(0, zeroPadding), Each(0)); + EXPECT_THAT(model->inputPressure().subspan(0, zeroPadding), Each(0)); + EXPECT_THAT(model->inputTilt().subspan(0, zeroPadding), Each(0)); + EXPECT_THAT(model->inputOrientation().subspan(0, zeroPadding), Each(0)); + + EXPECT_THAT(model->inputR().subspan(zeroPadding), ElementsAre(40, 20, 10)); + EXPECT_THAT(model->inputPhi().subspan(zeroPadding), ElementsAre(0, -M_PI / 2, M_PI / 2)); + EXPECT_THAT(model->inputPressure().subspan(zeroPadding), ElementsAre(0.4, 0.5, 0)); + EXPECT_THAT(model->inputTilt().subspan(zeroPadding), ElementsAre(0.3, 0.4, 0.5)); + EXPECT_THAT(model->inputOrientation().subspan(zeroPadding), + ElementsAre(FloatNear(-M_PI / 4, 1e-5), FloatNear(M_PI / 4, 1e-5), + FloatNear(M_PI / 2, 1e-5))); +} + +TEST(TfLiteMotionPredictorTest, ModelInputOutputLength) { + std::unique_ptr model = TfLiteMotionPredictorModel::create(); + ASSERT_GT(model->inputLength(), 0u); + + const int inputLength = model->inputLength(); + ASSERT_EQ(inputLength, model->inputR().size()); + ASSERT_EQ(inputLength, model->inputPhi().size()); + ASSERT_EQ(inputLength, model->inputPressure().size()); + ASSERT_EQ(inputLength, model->inputOrientation().size()); + ASSERT_EQ(inputLength, model->inputTilt().size()); + + ASSERT_TRUE(model->invoke()); + + const int outputLength = model->outputLength(); + ASSERT_EQ(outputLength, model->outputR().size()); + ASSERT_EQ(outputLength, model->outputPhi().size()); + ASSERT_EQ(outputLength, model->outputPressure().size()); +} + +TEST(TfLiteMotionPredictorTest, ModelOutput) { + std::unique_ptr model = TfLiteMotionPredictorModel::create(); + TfLiteMotionPredictorBuffers buffers(model->inputLength()); + + buffers.pushSample(/*timestamp=*/1, {.position = {.x = 100, .y = 200}, .pressure = 0.2}); + buffers.pushSample(/*timestamp=*/2, {.position = {.x = 150, .y = 250}, .pressure = 0.4}); + buffers.pushSample(/*timestamp=*/3, {.position = {.x = 180, .y = 280}, .pressure = 0.6}); + buffers.copyTo(*model); + + ASSERT_TRUE(model->invoke()); + + // The actual model output is implementation-defined, but it should at least be non-zero and + // non-NaN. + const auto is_valid = [](float value) { return !isnan(value) && value != 0; }; + ASSERT_TRUE(std::all_of(model->outputR().begin(), model->outputR().end(), is_valid)); + ASSERT_TRUE(std::all_of(model->outputPhi().begin(), model->outputPhi().end(), is_valid)); + ASSERT_TRUE( + std::all_of(model->outputPressure().begin(), model->outputPressure().end(), is_valid)); +} + +} // namespace +} // namespace android diff --git a/libs/input/tests/TouchResampling_test.cpp b/libs/input/tests/TouchResampling_test.cpp new file mode 100644 index 0000000000000000000000000000000000000000..655de803ae7e0da9aebabcfdcfa88e6defee956a --- /dev/null +++ b/libs/input/tests/TouchResampling_test.cpp @@ -0,0 +1,577 @@ +/* + * 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. + */ + +#include "TestHelpers.h" + +#include +#include + +#include +#include +#include + +using namespace std::chrono_literals; + +namespace android { + +struct Pointer { + int32_t id; + float x; + float y; + bool isResampled = false; +}; + +struct InputEventEntry { + std::chrono::nanoseconds eventTime; + std::vector pointers; + int32_t action; +}; + +class TouchResamplingTest : public testing::Test { +protected: + std::unique_ptr mPublisher; + std::unique_ptr mConsumer; + PreallocatedInputEventFactory mEventFactory; + + uint32_t mSeq = 1; + + void SetUp() override { + std::unique_ptr serverChannel, clientChannel; + status_t result = + InputChannel::openInputChannelPair("channel name", serverChannel, clientChannel); + ASSERT_EQ(OK, result); + + mPublisher = std::make_unique(std::move(serverChannel)); + mConsumer = std::make_unique(std::move(clientChannel), + /*enableTouchResampling=*/true); + } + + status_t publishSimpleMotionEventWithCoords(int32_t action, nsecs_t eventTime, + const std::vector& properties, + const std::vector& coords); + void publishSimpleMotionEvent(int32_t action, nsecs_t eventTime, + const std::vector& pointers); + void publishInputEventEntries(const std::vector& entries); + void consumeInputEventEntries(const std::vector& entries, + std::chrono::nanoseconds frameTime); + void receiveResponseUntilSequence(uint32_t seq); +}; + +status_t TouchResamplingTest::publishSimpleMotionEventWithCoords( + int32_t action, nsecs_t eventTime, const std::vector& properties, + const std::vector& coords) { + const ui::Transform identityTransform; + const nsecs_t downTime = 0; + + if (action == AMOTION_EVENT_ACTION_DOWN && eventTime != 0) { + 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, + 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(), + coords.data()); +} + +void TouchResamplingTest::publishSimpleMotionEvent(int32_t action, nsecs_t eventTime, + const std::vector& pointers) { + std::vector properties; + std::vector coords; + + for (const Pointer& pointer : pointers) { + properties.push_back({}); + properties.back().clear(); + properties.back().id = pointer.id; + properties.back().toolType = ToolType::FINGER; + + coords.push_back({}); + coords.back().clear(); + coords.back().setAxisValue(AMOTION_EVENT_AXIS_X, pointer.x); + coords.back().setAxisValue(AMOTION_EVENT_AXIS_Y, pointer.y); + } + + status_t result = publishSimpleMotionEventWithCoords(action, eventTime, properties, coords); + ASSERT_EQ(OK, result); +} + +/** + * Each entry is published separately, one entry at a time. As a result, action is used here + * on a per-entry basis. + */ +void TouchResamplingTest::publishInputEventEntries(const std::vector& entries) { + for (const InputEventEntry& entry : entries) { + publishSimpleMotionEvent(entry.action, entry.eventTime.count(), entry.pointers); + } +} + +/** + * Inside the publisher, read responses repeatedly until the desired sequence number is returned. + * + * Sometimes, when you call 'sendFinishedSignal', you would be finishing a batch which is comprised + * of several input events. As a result, consumer will generate multiple 'finish' signals on your + * behalf. + * + * In this function, we call 'receiveConsumerResponse' in a loop until the desired sequence number + * is returned. + */ +void TouchResamplingTest::receiveResponseUntilSequence(uint32_t seq) { + size_t consumedEvents = 0; + while (consumedEvents < 100) { + android::base::Result response = + mPublisher->receiveConsumerResponse(); + ASSERT_TRUE(response.ok()); + ASSERT_TRUE(std::holds_alternative(*response)); + const InputPublisher::Finished& finish = std::get(*response); + ASSERT_TRUE(finish.handled) + << "publisher receiveFinishedSignal should have set handled to consumer's reply"; + if (finish.seq == seq) { + return; + } + consumedEvents++; + } + FAIL() << "Got " << consumedEvents << "events, but still no event with seq=" << seq; +} + +/** + * All entries are compared against a single MotionEvent, but the same data structure + * InputEventEntry is used here for simpler code. As a result, the entire array of InputEventEntry + * must contain identical values for the action field. + */ +void TouchResamplingTest::consumeInputEventEntries(const std::vector& entries, + std::chrono::nanoseconds frameTime) { + ASSERT_GE(entries.size(), 1U) << "Must have at least 1 InputEventEntry to compare against"; + + uint32_t consumeSeq; + InputEvent* event; + + status_t status = mConsumer->consume(&mEventFactory, /*consumeBatches=*/true, frameTime.count(), + &consumeSeq, &event); + ASSERT_EQ(OK, status); + MotionEvent* motionEvent = static_cast(event); + + ASSERT_EQ(entries.size() - 1, motionEvent->getHistorySize()); + for (size_t i = 0; i < entries.size(); i++) { // most recent sample is last + SCOPED_TRACE(i); + const InputEventEntry& entry = entries[i]; + ASSERT_EQ(entry.action, motionEvent->getAction()); + ASSERT_EQ(entry.eventTime.count(), motionEvent->getHistoricalEventTime(i)); + ASSERT_EQ(entry.pointers.size(), motionEvent->getPointerCount()); + + for (size_t p = 0; p < motionEvent->getPointerCount(); p++) { + SCOPED_TRACE(p); + // The pointers can be in any order, both in MotionEvent as well as InputEventEntry + ssize_t motionEventPointerIndex = motionEvent->findPointerIndex(entry.pointers[p].id); + ASSERT_GE(motionEventPointerIndex, 0) << "Pointer must be present in MotionEvent"; + ASSERT_EQ(entry.pointers[p].x, + motionEvent->getHistoricalAxisValue(AMOTION_EVENT_AXIS_X, + motionEventPointerIndex, i)); + ASSERT_EQ(entry.pointers[p].x, + motionEvent->getHistoricalRawAxisValue(AMOTION_EVENT_AXIS_X, + motionEventPointerIndex, i)); + ASSERT_EQ(entry.pointers[p].y, + motionEvent->getHistoricalAxisValue(AMOTION_EVENT_AXIS_Y, + motionEventPointerIndex, i)); + ASSERT_EQ(entry.pointers[p].y, + motionEvent->getHistoricalRawAxisValue(AMOTION_EVENT_AXIS_Y, + motionEventPointerIndex, i)); + ASSERT_EQ(entry.pointers[p].isResampled, + motionEvent->isResampled(motionEventPointerIndex, i)); + } + } + + status = mConsumer->sendFinishedSignal(consumeSeq, true); + ASSERT_EQ(OK, status); + + receiveResponseUntilSequence(consumeSeq); +} + +/** + * Timeline + * ---------+------------------+------------------+--------+-----------------+---------------------- + * 0 ms 10 ms 20 ms 25 ms 35 ms + * ACTION_DOWN ACTION_MOVE ACTION_MOVE ^ ^ + * | | + * resampled value | + * frameTime + * Typically, the prediction is made for time frameTime - RESAMPLE_LATENCY, or 30 ms in this case + * However, that would be 10 ms later than the last real sample (which came in at 20 ms). + * Therefore, the resampling should happen at 20 ms + RESAMPLE_MAX_PREDICTION = 28 ms. + * In this situation, though, resample time is further limited by taking half of the difference + * between the last two real events, which would put this time at: + * 20 ms + (20 ms - 10 ms) / 2 = 25 ms. + */ +TEST_F(TouchResamplingTest, EventIsResampled) { + 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}}, AMOTION_EVENT_ACTION_DOWN}, + }; + publishInputEventEntries(entries); + frameTime = 5ms; + expectedEntries = { + // id x y + {0ms, {{0, 10, 20}}, 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}}, AMOTION_EVENT_ACTION_MOVE}, + {20ms, {{0, 30, 30}}, AMOTION_EVENT_ACTION_MOVE}, + }; + publishInputEventEntries(entries); + frameTime = 35ms; + expectedEntries = { + // id x y + {10ms, {{0, 20, 30}}, AMOTION_EVENT_ACTION_MOVE}, + {20ms, {{0, 30, 30}}, AMOTION_EVENT_ACTION_MOVE}, + {25ms, {{0, 35, 30, .isResampled = true}}, AMOTION_EVENT_ACTION_MOVE}, + }; + consumeInputEventEntries(expectedEntries, frameTime); +} + +/** + * Same as above test, but use pointer id=1 instead of 0 to make sure that system does not + * have these hardcoded. + */ +TEST_F(TouchResamplingTest, EventIsResampledWithDifferentId) { + 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, {{1, 10, 20}}, AMOTION_EVENT_ACTION_DOWN}, + }; + publishInputEventEntries(entries); + frameTime = 5ms; + expectedEntries = { + // id x y + {0ms, {{1, 10, 20}}, 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, {{1, 20, 30}}, AMOTION_EVENT_ACTION_MOVE}, + {20ms, {{1, 30, 30}}, AMOTION_EVENT_ACTION_MOVE}, + }; + publishInputEventEntries(entries); + frameTime = 35ms; + expectedEntries = { + // id x y + {10ms, {{1, 20, 30}}, AMOTION_EVENT_ACTION_MOVE}, + {20ms, {{1, 30, 30}}, AMOTION_EVENT_ACTION_MOVE}, + {25ms, {{1, 35, 30, .isResampled = true}}, AMOTION_EVENT_ACTION_MOVE}, + }; + consumeInputEventEntries(expectedEntries, frameTime); +} + +/** + * Event should not be resampled when sample time is equal to event time. + */ +TEST_F(TouchResamplingTest, SampleTimeEqualsEventTime) { + 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}}, AMOTION_EVENT_ACTION_DOWN}, + }; + publishInputEventEntries(entries); + frameTime = 5ms; + expectedEntries = { + // id x y + {0ms, {{0, 10, 20}}, 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}}, AMOTION_EVENT_ACTION_MOVE}, + {20ms, {{0, 30, 30}}, AMOTION_EVENT_ACTION_MOVE}, + }; + publishInputEventEntries(entries); + frameTime = 20ms + 5ms /*RESAMPLE_LATENCY*/; + expectedEntries = { + // id x y + {10ms, {{0, 20, 30}}, AMOTION_EVENT_ACTION_MOVE}, + {20ms, {{0, 30, 30}}, AMOTION_EVENT_ACTION_MOVE}, + // no resampled event because the time of resample falls exactly on the existing event + }; + consumeInputEventEntries(expectedEntries, frameTime); +} + +/** + * Once we send a resampled value to the app, we should continue to "lie" if the pointer + * does not move. So, if the pointer keeps the same coordinates, resampled value should continue + * to be used. + */ +TEST_F(TouchResamplingTest, ResampledValueIsUsedForIdenticalCoordinates) { + 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}}, AMOTION_EVENT_ACTION_DOWN}, + }; + publishInputEventEntries(entries); + frameTime = 5ms; + expectedEntries = { + // id x y + {0ms, {{0, 10, 20}}, 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}}, AMOTION_EVENT_ACTION_MOVE}, + {20ms, {{0, 30, 30}}, AMOTION_EVENT_ACTION_MOVE}, + }; + publishInputEventEntries(entries); + frameTime = 35ms; + expectedEntries = { + // id x y + {10ms, {{0, 20, 30}}, AMOTION_EVENT_ACTION_MOVE}, + {20ms, {{0, 30, 30}}, AMOTION_EVENT_ACTION_MOVE}, + {25ms, {{0, 35, 30, .isResampled = true}}, AMOTION_EVENT_ACTION_MOVE}, + }; + consumeInputEventEntries(expectedEntries, frameTime); + + // Coordinate value 30 has been resampled to 35. When a new event comes in with value 30 again, + // the system should still report 35. + entries = { + // id x y + {40ms, {{0, 30, 30}}, AMOTION_EVENT_ACTION_MOVE}, + }; + publishInputEventEntries(entries); + frameTime = 45ms + 5ms /*RESAMPLE_LATENCY*/; + expectedEntries = { + // id x y + {40ms, + {{0, 35, 30, .isResampled = true}}, + AMOTION_EVENT_ACTION_MOVE}, // original event, rewritten + {45ms, + {{0, 35, 30, .isResampled = true}}, + AMOTION_EVENT_ACTION_MOVE}, // resampled event, rewritten + }; + consumeInputEventEntries(expectedEntries, frameTime); +} + +TEST_F(TouchResamplingTest, OldEventReceivedAfterResampleOccurs) { + 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}}, AMOTION_EVENT_ACTION_DOWN}, + }; + publishInputEventEntries(entries); + frameTime = 5ms; + expectedEntries = { + // id x y + {0ms, {{0, 10, 20}}, 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}}, AMOTION_EVENT_ACTION_MOVE}, + {20ms, {{0, 30, 30}}, AMOTION_EVENT_ACTION_MOVE}, + }; + publishInputEventEntries(entries); + frameTime = 35ms; + expectedEntries = { + // id x y + {10ms, {{0, 20, 30}}, AMOTION_EVENT_ACTION_MOVE}, + {20ms, {{0, 30, 30}}, AMOTION_EVENT_ACTION_MOVE}, + {25ms, {{0, 35, 30, .isResampled = true}}, AMOTION_EVENT_ACTION_MOVE}, + }; + consumeInputEventEntries(expectedEntries, frameTime); + // Above, the resampled event is at 25ms rather than at 30 ms = 35ms - RESAMPLE_LATENCY + // because we are further bound by how far we can extrapolate by the "last time delta". + // That's 50% of (20 ms - 10ms) => 5ms. So we can't predict more than 5 ms into the future + // from the event at 20ms, which is why the resampled event is at t = 25 ms. + + // We resampled the event to 25 ms. Now, an older 'real' event comes in. + entries = { + // id x y + {24ms, {{0, 40, 30}}, AMOTION_EVENT_ACTION_MOVE}, + }; + publishInputEventEntries(entries); + frameTime = 50ms; + expectedEntries = { + // id x y + {24ms, + {{0, 35, 30, .isResampled = true}}, + AMOTION_EVENT_ACTION_MOVE}, // original event, rewritten + {26ms, + {{0, 45, 30, .isResampled = true}}, + AMOTION_EVENT_ACTION_MOVE}, // resampled event, rewritten + }; + consumeInputEventEntries(expectedEntries, frameTime); +} + +TEST_F(TouchResamplingTest, TwoPointersAreResampledIndependently) { + std::chrono::nanoseconds frameTime; + std::vector entries, expectedEntries; + + // full action for when a pointer with id=1 appears (some other pointer must already be present) + constexpr int32_t actionPointer1Down = + AMOTION_EVENT_ACTION_POINTER_DOWN + (1 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT); + + // full action for when a pointer with id=0 disappears (some other pointer must still remain) + constexpr int32_t actionPointer0Up = + AMOTION_EVENT_ACTION_POINTER_UP + (0 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT); + + // 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, 100, 100}}, AMOTION_EVENT_ACTION_DOWN}, + }; + publishInputEventEntries(entries); + frameTime = 5ms; + expectedEntries = { + // id x y + {0ms, {{0, 100, 100}}, AMOTION_EVENT_ACTION_DOWN}, + }; + consumeInputEventEntries(expectedEntries, frameTime); + + entries = { + // id x y + {10ms, {{0, 100, 100}}, AMOTION_EVENT_ACTION_MOVE}, + }; + publishInputEventEntries(entries); + frameTime = 10ms + 5ms /*RESAMPLE_LATENCY*/; + expectedEntries = { + // id x y + {10ms, {{0, 100, 100}}, AMOTION_EVENT_ACTION_MOVE}, + // no resampled value because frameTime - RESAMPLE_LATENCY == eventTime + }; + consumeInputEventEntries(expectedEntries, frameTime); + + // Second pointer id=1 appears + entries = { + // id x y + {15ms, {{0, 100, 100}, {1, 500, 500}}, actionPointer1Down}, + }; + publishInputEventEntries(entries); + frameTime = 20ms + 5ms /*RESAMPLE_LATENCY*/; + expectedEntries = { + // id x y + {15ms, {{0, 100, 100}, {1, 500, 500}}, actionPointer1Down}, + // no resampled value because frameTime - RESAMPLE_LATENCY == eventTime + }; + consumeInputEventEntries(expectedEntries, frameTime); + + // Both pointers move + entries = { + // id x y + {30ms, {{0, 100, 100}, {1, 500, 500}}, AMOTION_EVENT_ACTION_MOVE}, + {40ms, {{0, 120, 120}, {1, 600, 600}}, AMOTION_EVENT_ACTION_MOVE}, + }; + publishInputEventEntries(entries); + frameTime = 45ms + 5ms /*RESAMPLE_LATENCY*/; + expectedEntries = { + // id x y + {30ms, {{0, 100, 100}, {1, 500, 500}}, AMOTION_EVENT_ACTION_MOVE}, + {40ms, {{0, 120, 120}, {1, 600, 600}}, AMOTION_EVENT_ACTION_MOVE}, + {45ms, + {{0, 130, 130, .isResampled = true}, {1, 650, 650, .isResampled = true}}, + AMOTION_EVENT_ACTION_MOVE}, + }; + consumeInputEventEntries(expectedEntries, frameTime); + + // Both pointers move again + entries = { + // id x y + {60ms, {{0, 120, 120}, {1, 600, 600}}, AMOTION_EVENT_ACTION_MOVE}, + {70ms, {{0, 130, 130}, {1, 700, 700}}, AMOTION_EVENT_ACTION_MOVE}, + }; + publishInputEventEntries(entries); + frameTime = 75ms + 5ms /*RESAMPLE_LATENCY*/; + /** + * The sample at t = 60, pointer id 0 is not equal to 120, because this value of 120 was + * received twice, and resampled to 130. So if we already reported it as "130", we continue + * to report it as such. Similar with pointer id 1. + */ + expectedEntries = { + {60ms, + {{0, 130, 130, .isResampled = true}, // not 120! because it matches previous real event + {1, 650, 650, .isResampled = true}}, + AMOTION_EVENT_ACTION_MOVE}, + {70ms, {{0, 130, 130}, {1, 700, 700}}, AMOTION_EVENT_ACTION_MOVE}, + {75ms, + {{0, 135, 135, .isResampled = true}, {1, 750, 750, .isResampled = true}}, + AMOTION_EVENT_ACTION_MOVE}, + }; + consumeInputEventEntries(expectedEntries, frameTime); + + // First pointer id=0 leaves the screen + entries = { + // id x y + {80ms, {{1, 600, 600}}, actionPointer0Up}, + }; + publishInputEventEntries(entries); + frameTime = 90ms; + expectedEntries = { + // id x y + {80ms, {{1, 600, 600}}, actionPointer0Up}, + // no resampled event for ACTION_POINTER_UP + }; + consumeInputEventEntries(expectedEntries, frameTime); + + // Remaining pointer id=1 is still present, but doesn't move + entries = { + // id x y + {90ms, {{1, 600, 600}}, AMOTION_EVENT_ACTION_MOVE}, + }; + publishInputEventEntries(entries); + frameTime = 100ms; + expectedEntries = { + // id x y + {90ms, {{1, 600, 600}}, AMOTION_EVENT_ACTION_MOVE}, + /** + * The latest event with ACTION_MOVE was at t = 70, coord = 700. + * Use that value for resampling here: (600 - 700) / (90 - 70) * 5 + 600 + */ + {95ms, {{1, 575, 575, .isResampled = true}}, AMOTION_EVENT_ACTION_MOVE}, + }; + consumeInputEventEntries(expectedEntries, frameTime); +} + +} // namespace android diff --git a/libs/input/tests/TouchVideoFrame_test.cpp b/libs/input/tests/TouchVideoFrame_test.cpp index 654b236bda894ecac065b56210b4d484247fa4c7..081a995a6f3337e9d2b2d113b5a0a6f570d2ea64 100644 --- a/libs/input/tests/TouchVideoFrame_test.cpp +++ b/libs/input/tests/TouchVideoFrame_test.cpp @@ -73,38 +73,38 @@ TEST(TouchVideoFrame, Equality) { TEST(TouchVideoFrame, Rotate90_0x0) { TouchVideoFrame frame(0, 0, {}, TIMESTAMP); TouchVideoFrame frameRotated(0, 0, {}, TIMESTAMP); - frame.rotate(DISPLAY_ORIENTATION_90); + frame.rotate(ui::ROTATION_90); ASSERT_EQ(frame, frameRotated); } TEST(TouchVideoFrame, Rotate90_1x1) { TouchVideoFrame frame(1, 1, {1}, TIMESTAMP); TouchVideoFrame frameRotated(1, 1, {1}, TIMESTAMP); - frame.rotate(DISPLAY_ORIENTATION_90); + frame.rotate(ui::ROTATION_90); ASSERT_EQ(frame, frameRotated); } TEST(TouchVideoFrame, Rotate90_2x2) { TouchVideoFrame frame(2, 2, {1, 2, 3, 4}, TIMESTAMP); TouchVideoFrame frameRotated(2, 2, {2, 4, 1, 3}, TIMESTAMP); - frame.rotate(DISPLAY_ORIENTATION_90); + frame.rotate(ui::ROTATION_90); ASSERT_EQ(frame, frameRotated); } TEST(TouchVideoFrame, Rotate90_3x2) { TouchVideoFrame frame(3, 2, {1, 2, 3, 4, 5, 6}, TIMESTAMP); TouchVideoFrame frameRotated(2, 3, {2, 4, 6, 1, 3, 5}, TIMESTAMP); - frame.rotate(DISPLAY_ORIENTATION_90); + frame.rotate(ui::ROTATION_90); ASSERT_EQ(frame, frameRotated); } TEST(TouchVideoFrame, Rotate90_3x2_4times) { TouchVideoFrame frame(3, 2, {1, 2, 3, 4, 5, 6}, TIMESTAMP); TouchVideoFrame frameOriginal(3, 2, {1, 2, 3, 4, 5, 6}, TIMESTAMP); - frame.rotate(DISPLAY_ORIENTATION_90); - frame.rotate(DISPLAY_ORIENTATION_90); - frame.rotate(DISPLAY_ORIENTATION_90); - frame.rotate(DISPLAY_ORIENTATION_90); + frame.rotate(ui::ROTATION_90); + frame.rotate(ui::ROTATION_90); + frame.rotate(ui::ROTATION_90); + frame.rotate(ui::ROTATION_90); ASSERT_EQ(frame, frameOriginal); } @@ -113,43 +113,43 @@ TEST(TouchVideoFrame, Rotate90_3x2_4times) { TEST(TouchVideoFrame, Rotate180_0x0) { TouchVideoFrame frame(0, 0, {}, TIMESTAMP); TouchVideoFrame frameRotated(0, 0, {}, TIMESTAMP); - frame.rotate(DISPLAY_ORIENTATION_180); + frame.rotate(ui::ROTATION_180); ASSERT_EQ(frame, frameRotated); } TEST(TouchVideoFrame, Rotate180_1x1) { TouchVideoFrame frame(1, 1, {1}, TIMESTAMP); TouchVideoFrame frameRotated(1, 1, {1}, TIMESTAMP); - frame.rotate(DISPLAY_ORIENTATION_180); + frame.rotate(ui::ROTATION_180); ASSERT_EQ(frame, frameRotated); } TEST(TouchVideoFrame, Rotate180_2x2) { TouchVideoFrame frame(2, 2, {1, 2, 3, 4}, TIMESTAMP); TouchVideoFrame frameRotated(2, 2, {4, 3, 2, 1}, TIMESTAMP); - frame.rotate(DISPLAY_ORIENTATION_180); + frame.rotate(ui::ROTATION_180); ASSERT_EQ(frame, frameRotated); } TEST(TouchVideoFrame, Rotate180_3x2) { TouchVideoFrame frame(3, 2, {1, 2, 3, 4, 5, 6}, TIMESTAMP); TouchVideoFrame frameRotated(3, 2, {6, 5, 4, 3, 2, 1}, TIMESTAMP); - frame.rotate(DISPLAY_ORIENTATION_180); + frame.rotate(ui::ROTATION_180); ASSERT_EQ(frame, frameRotated); } TEST(TouchVideoFrame, Rotate180_3x2_2times) { TouchVideoFrame frame(3, 2, {1, 2, 3, 4, 5, 6}, TIMESTAMP); TouchVideoFrame frameOriginal(3, 2, {1, 2, 3, 4, 5, 6}, TIMESTAMP); - frame.rotate(DISPLAY_ORIENTATION_180); - frame.rotate(DISPLAY_ORIENTATION_180); + frame.rotate(ui::ROTATION_180); + frame.rotate(ui::ROTATION_180); ASSERT_EQ(frame, frameOriginal); } TEST(TouchVideoFrame, Rotate180_3x3) { TouchVideoFrame frame(3, 3, {1, 2, 3, 4, 5, 6, 7, 8, 9}, TIMESTAMP); TouchVideoFrame frameRotated(3, 3, {9, 8, 7, 6, 5, 4, 3, 2, 1}, TIMESTAMP); - frame.rotate(DISPLAY_ORIENTATION_180); + frame.rotate(ui::ROTATION_180); ASSERT_EQ(frame, frameRotated); } @@ -158,38 +158,38 @@ TEST(TouchVideoFrame, Rotate180_3x3) { TEST(TouchVideoFrame, Rotate270_0x0) { TouchVideoFrame frame(0, 0, {}, TIMESTAMP); TouchVideoFrame frameRotated(0, 0, {}, TIMESTAMP); - frame.rotate(DISPLAY_ORIENTATION_270); + frame.rotate(ui::ROTATION_270); ASSERT_EQ(frame, frameRotated); } TEST(TouchVideoFrame, Rotate270_1x1) { TouchVideoFrame frame(1, 1, {1}, TIMESTAMP); TouchVideoFrame frameRotated(1, 1, {1}, TIMESTAMP); - frame.rotate(DISPLAY_ORIENTATION_270); + frame.rotate(ui::ROTATION_270); ASSERT_EQ(frame, frameRotated); } TEST(TouchVideoFrame, Rotate270_2x2) { TouchVideoFrame frame(2, 2, {1, 2, 3, 4}, TIMESTAMP); TouchVideoFrame frameRotated(2, 2, {3, 1, 4, 2}, TIMESTAMP); - frame.rotate(DISPLAY_ORIENTATION_270); + frame.rotate(ui::ROTATION_270); ASSERT_EQ(frame, frameRotated); } TEST(TouchVideoFrame, Rotate270_3x2) { TouchVideoFrame frame(3, 2, {1, 2, 3, 4, 5, 6}, TIMESTAMP); TouchVideoFrame frameRotated(2, 3, {5, 3, 1, 6, 4, 2}, TIMESTAMP); - frame.rotate(DISPLAY_ORIENTATION_270); + frame.rotate(ui::ROTATION_270); ASSERT_EQ(frame, frameRotated); } TEST(TouchVideoFrame, Rotate270_3x2_4times) { TouchVideoFrame frame(3, 2, {1, 2, 3, 4, 5, 6}, TIMESTAMP); TouchVideoFrame frameOriginal(3, 2, {1, 2, 3, 4, 5, 6}, TIMESTAMP); - frame.rotate(DISPLAY_ORIENTATION_270); - frame.rotate(DISPLAY_ORIENTATION_270); - frame.rotate(DISPLAY_ORIENTATION_270); - frame.rotate(DISPLAY_ORIENTATION_270); + frame.rotate(ui::ROTATION_270); + frame.rotate(ui::ROTATION_270); + frame.rotate(ui::ROTATION_270); + frame.rotate(ui::ROTATION_270); ASSERT_EQ(frame, frameOriginal); } diff --git a/libs/input/tests/VelocityTracker_test.cpp b/libs/input/tests/VelocityTracker_test.cpp index a87b1873f0a7981952499ca816cd9a4ccc897d1b..ae721093a08947815955adeacadf304891c0a2d8 100644 --- a/libs/input/tests/VelocityTracker_test.cpp +++ b/libs/input/tests/VelocityTracker_test.cpp @@ -16,9 +16,10 @@ #define LOG_TAG "VelocityTracker_test" +#include #include #include -#include +#include #include #include @@ -26,7 +27,9 @@ #include #include -using namespace std::chrono_literals; +using std::literals::chrono_literals::operator""ms; +using std::literals::chrono_literals::operator""ns; +using std::literals::chrono_literals::operator""us; using android::base::StringPrintf; namespace android { @@ -62,8 +65,15 @@ static void EXPECT_NEAR_BY_FRACTION(float actual, float target, float fraction) EXPECT_NEAR(actual, target, tolerance); } -static void checkVelocity(float Vactual, float Vtarget) { - EXPECT_NEAR_BY_FRACTION(Vactual, Vtarget, VELOCITY_TOLERANCE); +static void checkVelocity(std::optional Vactual, std::optional Vtarget) { + if (Vactual != std::nullopt) { + if (Vtarget == std::nullopt) { + FAIL() << "Expected no velocity, but found " << *Vactual; + } + EXPECT_NEAR_BY_FRACTION(*Vactual, *Vtarget, VELOCITY_TOLERANCE); + } else if (Vtarget != std::nullopt) { + FAIL() << "Expected velocity, but found no velocity"; + } } static void checkCoefficient(float actual, float target) { @@ -74,6 +84,8 @@ struct Position { float x; float y; + bool isResampled = false; + /** * If both values are NAN, then this is considered to be an empty entry (no pointer data). * If only one of the values is NAN, this is still a valid entry, @@ -84,7 +96,7 @@ struct Position { } }; -struct MotionEventEntry { +struct PlanarMotionEventEntry { std::chrono::nanoseconds eventTime; std::vector positions; }; @@ -133,15 +145,43 @@ static int32_t resolveAction(const std::vector& lastPositions, return AMOTION_EVENT_ACTION_MOVE; } -static std::vector createMotionEventStream( - const std::vector& motions) { +static std::vector createAxisScrollMotionEventStream( + const std::vector>& motions) { + std::vector events; + for (const auto& [timeStamp, value] : motions) { + EXPECT_TRUE(!isnan(value)) << "The entry at pointerId must be valid"; + + PointerCoords coords[1]; + coords[0].setAxisValue(AMOTION_EVENT_AXIS_SCROLL, value); + + PointerProperties properties[1]; + properties[0].id = DEFAULT_POINTER_ID; + + MotionEvent event; + ui::Transform identityTransform; + event.initialize(InputEvent::nextId(), /*deviceId=*/5, AINPUT_SOURCE_ROTARY_ENCODER, + 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, + AMOTION_EVENT_INVALID_CURSOR_POSITION, identityTransform, /*downTime=*/0, + timeStamp.count(), /*pointerCount=*/1, properties, coords); + + events.emplace_back(event); + } + + return events; +} + +static std::vector createTouchMotionEventStream( + const std::vector& motions) { if (motions.empty()) { ADD_FAILURE() << "Need at least 1 sample to create a MotionEvent. Received empty vector."; } std::vector events; for (size_t i = 0; i < motions.size(); i++) { - const MotionEventEntry& entry = motions[i]; + const PlanarMotionEventEntry& entry = motions[i]; BitSet32 pointers = getValidPointers(entry.positions); const uint32_t pointerCount = pointers.count(); @@ -149,12 +189,11 @@ static std::vector createMotionEventStream( if (i == 0) { action = AMOTION_EVENT_ACTION_DOWN; EXPECT_EQ(1U, pointerCount) << "First event should only have 1 pointer"; - } else if (i == motions.size() - 1) { - EXPECT_EQ(1U, pointerCount) << "Last event should only have 1 pointer"; + } else if ((i == motions.size() - 1) && pointerCount == 1) { action = AMOTION_EVENT_ACTION_UP; } else { - const MotionEventEntry& previousEntry = motions[i-1]; - const MotionEventEntry& nextEntry = motions[i+1]; + const PlanarMotionEventEntry& previousEntry = motions[i-1]; + const PlanarMotionEventEntry& nextEntry = motions[i+1]; action = resolveAction(previousEntry.positions, entry.positions, nextEntry.positions); } @@ -166,25 +205,26 @@ static std::vector createMotionEventStream( coords[pointerIndex].clear(); // We are treating column positions as pointerId - EXPECT_TRUE(entry.positions[pointerId].isValid()) << - "The entry at pointerId must be valid"; - coords[pointerIndex].setAxisValue(AMOTION_EVENT_AXIS_X, entry.positions[pointerId].x); - coords[pointerIndex].setAxisValue(AMOTION_EVENT_AXIS_Y, entry.positions[pointerId].y); + const Position& position = entry.positions[pointerId]; + EXPECT_TRUE(position.isValid()) << "The entry at " << pointerId << " must be valid"; + coords[pointerIndex].setAxisValue(AMOTION_EVENT_AXIS_X, position.x); + coords[pointerIndex].setAxisValue(AMOTION_EVENT_AXIS_Y, position.y); + coords[pointerIndex].isResampled = position.isResampled; properties[pointerIndex].id = pointerId; - properties[pointerIndex].toolType = AMOTION_EVENT_TOOL_TYPE_FINGER; + properties[pointerIndex].toolType = ToolType::FINGER; pointerIndex++; } EXPECT_EQ(pointerIndex, pointerCount); MotionEvent event; ui::Transform identityTransform; - event.initialize(InputEvent::nextId(), 0 /*deviceId*/, AINPUT_SOURCE_TOUCHSCREEN, - DISPLAY_ID, INVALID_HMAC, action, 0 /*actionButton*/, 0 /*flags*/, - AMOTION_EVENT_EDGE_FLAG_NONE, AMETA_NONE, 0 /*buttonState*/, - MotionClassification::NONE, identityTransform, 0 /*xPrecision*/, - 0 /*yPrecision*/, AMOTION_EVENT_INVALID_CURSOR_POSITION, - AMOTION_EVENT_INVALID_CURSOR_POSITION, identityTransform, 0 /*downTime*/, + event.initialize(InputEvent::nextId(), /*deviceId=*/0, AINPUT_SOURCE_TOUCHSCREEN, + DISPLAY_ID, INVALID_HMAC, action, /*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, + AMOTION_EVENT_INVALID_CURSOR_POSITION, identityTransform, /*downTime=*/0, entry.eventTime.count(), pointerCount, properties, coords); events.emplace_back(event); @@ -193,54 +233,254 @@ static std::vector createMotionEventStream( return events; } -static void computeAndCheckVelocity(const VelocityTracker::Strategy strategy, - const std::vector& motions, int32_t axis, - float targetVelocity) { +static std::optional computePlanarVelocity( + const VelocityTracker::Strategy strategy, + const std::vector& motions, int32_t axis, + uint32_t pointerId = DEFAULT_POINTER_ID) { VelocityTracker vt(strategy); - float Vx, Vy; - std::vector events = createMotionEventStream(motions); + std::vector events = createTouchMotionEventStream(motions); for (MotionEvent event : events) { vt.addMovement(&event); } - vt.getVelocity(DEFAULT_POINTER_ID, &Vx, &Vy); + return vt.getVelocity(axis, pointerId); +} +static std::vector createMotionEventStream( + int32_t axis, const std::vector>& motion) { switch (axis) { - case AMOTION_EVENT_AXIS_X: - checkVelocity(Vx, targetVelocity); - break; - case AMOTION_EVENT_AXIS_Y: - checkVelocity(Vy, targetVelocity); - break; - default: - FAIL() << "Axis must be either AMOTION_EVENT_AXIS_X or AMOTION_EVENT_AXIS_Y"; + case AMOTION_EVENT_AXIS_SCROLL: + return createAxisScrollMotionEventStream(motion); + default: + ADD_FAILURE() << "Axis " << axis << " is not supported"; + return {}; } } -static void computeAndCheckQuadraticEstimate(const std::vector& motions, - const std::array& coefficients) { +static std::optional computeVelocity( + const VelocityTracker::Strategy strategy, + const std::vector>& motions, int32_t axis) { + VelocityTracker vt(strategy); + + for (const MotionEvent& event : createMotionEventStream(axis, motions)) { + vt.addMovement(&event); + } + + return vt.getVelocity(axis, DEFAULT_POINTER_ID); +} + +static void computeAndCheckVelocity(const VelocityTracker::Strategy strategy, + const std::vector& motions, + int32_t axis, std::optional targetVelocity, + uint32_t pointerId = DEFAULT_POINTER_ID) { + checkVelocity(computePlanarVelocity(strategy, motions, axis, pointerId), targetVelocity); +} + +static void computeAndCheckAxisScrollVelocity( + const VelocityTracker::Strategy strategy, + const std::vector>& motions, + std::optional targetVelocity) { + checkVelocity(computeVelocity(strategy, motions, AMOTION_EVENT_AXIS_SCROLL), targetVelocity); +} + +static void computeAndCheckQuadraticEstimate(const std::vector& motions, + const std::array& coefficients) { VelocityTracker vt(VelocityTracker::Strategy::LSQ2); - std::vector events = createMotionEventStream(motions); + std::vector events = createTouchMotionEventStream(motions); for (MotionEvent event : events) { vt.addMovement(&event); } - VelocityTracker::Estimator estimator; - EXPECT_TRUE(vt.getEstimator(0, &estimator)); + std::optional estimatorX = vt.getEstimator(AMOTION_EVENT_AXIS_X, 0); + std::optional estimatorY = vt.getEstimator(AMOTION_EVENT_AXIS_Y, 0); + EXPECT_TRUE(estimatorX); + EXPECT_TRUE(estimatorY); for (size_t i = 0; i< coefficients.size(); i++) { - checkCoefficient(estimator.xCoeff[i], coefficients[i]); - checkCoefficient(estimator.yCoeff[i], coefficients[i]); + checkCoefficient((*estimatorX).coeff[i], coefficients[i]); + checkCoefficient((*estimatorY).coeff[i], coefficients[i]); + } +} + +/* + *================== VelocityTracker tests that do not require test motion data ==================== + */ +TEST(SimpleVelocityTrackerTest, TestSupportedAxis) { + // Note that we are testing up to the max possible axis value, plus 3 more values. We are going + // beyond the max value to add a bit more protection. "3" is chosen arbitrarily to cover a few + // more values beyond the max. + for (int32_t axis = 0; axis <= AMOTION_EVENT_MAXIMUM_VALID_AXIS_VALUE + 3; axis++) { + switch (axis) { + case AMOTION_EVENT_AXIS_X: + case AMOTION_EVENT_AXIS_Y: + case AMOTION_EVENT_AXIS_SCROLL: + EXPECT_TRUE(VelocityTracker::isAxisSupported(axis)) << axis << " is supported"; + break; + default: + EXPECT_FALSE(VelocityTracker::isAxisSupported(axis)) << axis << " is NOT supported"; + } } } /* * ================== VelocityTracker tests generated manually ===================================== */ +TEST_F(VelocityTrackerTest, TestDefaultStrategiesForPlanarAxes) { + std::vector motions = {{10ms, {{2, 4}}}, + {20ms, {{4, 12}}}, + {30ms, {{6, 20}}}, + {40ms, {{10, 30}}}}; + + EXPECT_EQ(computePlanarVelocity(VelocityTracker::Strategy::LSQ2, motions, AMOTION_EVENT_AXIS_X), + computePlanarVelocity(VelocityTracker::Strategy::DEFAULT, motions, + AMOTION_EVENT_AXIS_X)); + EXPECT_EQ(computePlanarVelocity(VelocityTracker::Strategy::LSQ2, motions, AMOTION_EVENT_AXIS_Y), + computePlanarVelocity(VelocityTracker::Strategy::DEFAULT, motions, + AMOTION_EVENT_AXIS_Y)); +} + +TEST_F(VelocityTrackerTest, TestComputedVelocity) { + VelocityTracker::ComputedVelocity computedVelocity; + + computedVelocity.addVelocity(AMOTION_EVENT_AXIS_X, /*id=*/0, /*velocity=*/200); + computedVelocity.addVelocity(AMOTION_EVENT_AXIS_X, /*id=*/26U, /*velocity=*/400); + computedVelocity.addVelocity(AMOTION_EVENT_AXIS_X, /*id=*/27U, /*velocity=*/650); + computedVelocity.addVelocity(AMOTION_EVENT_AXIS_X, MAX_POINTER_ID, /*velocity=*/750); + computedVelocity.addVelocity(AMOTION_EVENT_AXIS_Y, /*id=*/0, /*velocity=*/1000); + computedVelocity.addVelocity(AMOTION_EVENT_AXIS_Y, /*id=*/26U, /*velocity=*/2000); + computedVelocity.addVelocity(AMOTION_EVENT_AXIS_Y, /*id=*/27U, /*velocity=*/3000); + computedVelocity.addVelocity(AMOTION_EVENT_AXIS_Y, MAX_POINTER_ID, /*velocity=*/4000); + + // Check the axes/indices with velocity. + EXPECT_EQ(*(computedVelocity.getVelocity(AMOTION_EVENT_AXIS_X, /*id=*/0U)), 200); + EXPECT_EQ(*(computedVelocity.getVelocity(AMOTION_EVENT_AXIS_X, /*id=*/26U)), 400); + EXPECT_EQ(*(computedVelocity.getVelocity(AMOTION_EVENT_AXIS_X, /*id=*/27U)), 650); + EXPECT_EQ(*(computedVelocity.getVelocity(AMOTION_EVENT_AXIS_X, MAX_POINTER_ID)), 750); + EXPECT_EQ(*(computedVelocity.getVelocity(AMOTION_EVENT_AXIS_Y, /*id=*/0U)), 1000); + EXPECT_EQ(*(computedVelocity.getVelocity(AMOTION_EVENT_AXIS_Y, /*id=*/26U)), 2000); + EXPECT_EQ(*(computedVelocity.getVelocity(AMOTION_EVENT_AXIS_Y, /*id=*/27U)), 3000); + EXPECT_EQ(*(computedVelocity.getVelocity(AMOTION_EVENT_AXIS_Y, MAX_POINTER_ID)), 4000); + for (uint32_t id = 0; id <= MAX_POINTER_ID; id++) { + // Since no data was added for AXIS_SCROLL, expect empty value for the axis for any id. + EXPECT_FALSE(computedVelocity.getVelocity(AMOTION_EVENT_AXIS_SCROLL, id)) + << "Empty scroll data expected at id=" << id; + if (id == 0 || id == 26U || id == 27U || id == MAX_POINTER_ID) { + // Already checked above; continue. + continue; + } + // No data was added to X/Y for this id, expect empty value. + EXPECT_FALSE(computedVelocity.getVelocity(AMOTION_EVENT_AXIS_X, id)) + << "Empty X data expected at id=" << id; + EXPECT_FALSE(computedVelocity.getVelocity(AMOTION_EVENT_AXIS_Y, id)) + << "Empty Y data expected at id=" << id; + } + // Out-of-bounds ids should given empty values. + EXPECT_FALSE(computedVelocity.getVelocity(AMOTION_EVENT_AXIS_X, -1)); + EXPECT_FALSE(computedVelocity.getVelocity(AMOTION_EVENT_AXIS_X, MAX_POINTER_ID + 1)); +} + +/** + * For a single pointer, the resampled data is ignored. + */ +TEST_F(VelocityTrackerTest, SinglePointerResampledData) { + std::vector motions = {{10ms, {{1, 2}}}, + {20ms, {{2, 4}}}, + {30ms, {{3, 6}}}, + {35ms, {{30, 60, .isResampled = true}}}, + {40ms, {{4, 8}}}}; + + computeAndCheckVelocity(VelocityTracker::Strategy::DEFAULT, motions, AMOTION_EVENT_AXIS_X, 100); + computeAndCheckVelocity(VelocityTracker::Strategy::DEFAULT, motions, AMOTION_EVENT_AXIS_Y, 200); +} + +/** + * For multiple pointers, the resampled data is ignored on a per-pointer basis. If a certain pointer + * does not have a resampled value, all of the points are used. + */ +TEST_F(VelocityTrackerTest, MultiPointerResampledData) { + std::vector motions = { + {0ms, {{0, 0}}}, + {10ms, {{1, 0}, {1, 0}}}, + {20ms, {{2, 0}, {2, 0}}}, + {30ms, {{3, 0}, {3, 0}}}, + {35ms, {{30, 0, .isResampled = true}, {30, 0}}}, + {40ms, {{4, 0}, {4, 0}}}, + {45ms, {{5, 0}}}, // ACTION_UP + }; + + // Sample at t=35ms breaks trend. It's marked as resampled for the first pointer, so it should + // be ignored, and the resulting velocity should be linear. For the second pointer, it's not + // resampled, so it should cause the velocity to be non-linear. + computeAndCheckVelocity(VelocityTracker::Strategy::DEFAULT, motions, AMOTION_EVENT_AXIS_X, 100, + /*pointerId=*/0); + computeAndCheckVelocity(VelocityTracker::Strategy::DEFAULT, motions, AMOTION_EVENT_AXIS_X, 3455, + /*pointerId=*/1); +} + +TEST_F(VelocityTrackerTest, TestGetComputedVelocity) { + std::vector motions = { + {235089067457000ns, {{528.00, 0}}}, {235089084684000ns, {{527.00, 0}}}, + {235089093349000ns, {{527.00, 0}}}, {235089095677625ns, {{527.00, 0}}}, + {235089101859000ns, {{527.00, 0}}}, {235089110378000ns, {{528.00, 0}}}, + {235089112497111ns, {{528.25, 0}}}, {235089118760000ns, {{531.00, 0}}}, + {235089126686000ns, {{535.00, 0}}}, {235089129316820ns, {{536.33, 0}}}, + {235089135199000ns, {{540.00, 0}}}, {235089144297000ns, {{546.00, 0}}}, + {235089146136443ns, {{547.21, 0}}}, {235089152923000ns, {{553.00, 0}}}, + {235089160784000ns, {{559.00, 0}}}, {235089162955851ns, {{560.66, 0}}}, + {235089162955851ns, {{560.66, 0}}}, // ACTION_UP + }; + VelocityTracker vt(VelocityTracker::Strategy::IMPULSE); + std::vector events = createTouchMotionEventStream(motions); + for (const MotionEvent& event : events) { + vt.addMovement(&event); + } + + float maxFloat = std::numeric_limits::max(); + VelocityTracker::ComputedVelocity computedVelocity; + computedVelocity = vt.getComputedVelocity(/*units=*/1000, maxFloat); + checkVelocity(*(computedVelocity.getVelocity(AMOTION_EVENT_AXIS_X, DEFAULT_POINTER_ID)), + 764.345703); + + // Expect X velocity to be scaled with respective to provided units. + computedVelocity = vt.getComputedVelocity(/*units=*/1000000, maxFloat); + checkVelocity(*(computedVelocity.getVelocity(AMOTION_EVENT_AXIS_X, DEFAULT_POINTER_ID)), + 764345.703); + + // Expect X velocity to be clamped by provided max velocity. + computedVelocity = vt.getComputedVelocity(/*units=*/1000000, 1000); + checkVelocity(*(computedVelocity.getVelocity(AMOTION_EVENT_AXIS_X, DEFAULT_POINTER_ID)), 1000); + + // All 0 data for Y; expect 0 velocity. + EXPECT_EQ(*(computedVelocity.getVelocity(AMOTION_EVENT_AXIS_Y, DEFAULT_POINTER_ID)), 0); + + // No data for scroll-axis; expect empty velocity. + EXPECT_FALSE(computedVelocity.getVelocity(AMOTION_EVENT_AXIS_SCROLL, DEFAULT_POINTER_ID)); +} + +TEST_F(VelocityTrackerTest, TestApiInteractionsWithNoMotionEvents) { + VelocityTracker vt(VelocityTracker::Strategy::DEFAULT); + + EXPECT_FALSE(vt.getVelocity(AMOTION_EVENT_AXIS_X, DEFAULT_POINTER_ID)); + + EXPECT_FALSE(vt.getEstimator(AMOTION_EVENT_AXIS_X, DEFAULT_POINTER_ID)); + + VelocityTracker::ComputedVelocity computedVelocity = vt.getComputedVelocity(1000, 1000); + for (uint32_t id = 0; id <= MAX_POINTER_ID; id++) { + EXPECT_FALSE(computedVelocity.getVelocity(AMOTION_EVENT_AXIS_X, id)); + EXPECT_FALSE(computedVelocity.getVelocity(AMOTION_EVENT_AXIS_Y, id)); + } + + EXPECT_EQ(-1, vt.getActivePointerId()); + + // Make sure that the clearing functions execute without an issue. + vt.clearPointer(7U); + vt.clear(); +} + TEST_F(VelocityTrackerTest, ThreePointsPositiveVelocityTest) { // Same coordinate is reported 2 times in a row // It is difficult to determine the correct answer here, but at least the direction // of the reported velocity should be positive. - std::vector motions = { + std::vector motions = { {0ms, {{273, 0}}}, {12585us, {{293, 0}}}, {14730us, {{293, 0}}}, @@ -252,7 +492,7 @@ TEST_F(VelocityTrackerTest, ThreePointsPositiveVelocityTest) { TEST_F(VelocityTrackerTest, ThreePointsZeroVelocityTest) { // Same coordinate is reported 3 times in a row - std::vector motions = { + std::vector motions = { {0ms, {{293, 0}}}, {6132us, {{293, 0}}}, {11283us, {{293, 0}}}, @@ -264,7 +504,7 @@ TEST_F(VelocityTrackerTest, ThreePointsZeroVelocityTest) { TEST_F(VelocityTrackerTest, ThreePointsLinearVelocityTest) { // Fixed velocity at 5 points per 10 milliseconds - std::vector motions = { + std::vector motions = { {0ms, {{0, 0}}}, {10ms, {{5, 0}}}, {20ms, {{10, 0}}}, {20ms, {{10, 0}}}, // ACTION_UP }; computeAndCheckVelocity(VelocityTracker::Strategy::IMPULSE, motions, AMOTION_EVENT_AXIS_X, 500); @@ -288,7 +528,7 @@ TEST_F(VelocityTrackerTest, ThreePointsLinearVelocityTest) { // --------------- Recorded by hand on swordfish --------------------------------------------------- TEST_F(VelocityTrackerTest, SwordfishFlingDown) { // Recording of a fling on Swordfish that could cause a fling in the wrong direction - std::vector motions = { + std::vector motions = { { 0ms, {{271, 96}} }, { 16071042ns, {{269.786346, 106.922775}} }, { 35648403ns, {{267.983063, 156.660034}} }, @@ -323,7 +563,7 @@ TEST_F(VelocityTrackerTest, SwordfishFlingDown) { TEST_F(VelocityTrackerTest, SailfishFlingUpSlow1) { // Sailfish - fling up - slow - 1 - std::vector motions = { + std::vector motions = { { 235089067457000ns, {{528.00, 983.00}} }, { 235089084684000ns, {{527.00, 981.00}} }, { 235089093349000ns, {{527.00, 977.00}} }, @@ -355,7 +595,7 @@ TEST_F(VelocityTrackerTest, SailfishFlingUpSlow1) { TEST_F(VelocityTrackerTest, SailfishFlingUpSlow2) { // Sailfish - fling up - slow - 2 - std::vector motions = { + std::vector motions = { { 235110560704000ns, {{522.00, 1107.00}} }, { 235110575764000ns, {{522.00, 1107.00}} }, { 235110584385000ns, {{522.00, 1107.00}} }, @@ -384,7 +624,7 @@ TEST_F(VelocityTrackerTest, SailfishFlingUpSlow2) { TEST_F(VelocityTrackerTest, SailfishFlingUpSlow3) { // Sailfish - fling up - slow - 3 - std::vector motions = { + std::vector motions = { { 792536237000ns, {{580.00, 1317.00}} }, { 792541538987ns, {{580.63, 1311.94}} }, { 792544613000ns, {{581.00, 1309.00}} }, @@ -418,7 +658,7 @@ TEST_F(VelocityTrackerTest, SailfishFlingUpSlow3) { TEST_F(VelocityTrackerTest, SailfishFlingUpFaster1) { // Sailfish - fling up - faster - 1 - std::vector motions = { + std::vector motions = { { 235160420675000ns, {{610.00, 1042.00}} }, { 235160428220000ns, {{609.00, 1026.00}} }, { 235160436544000ns, {{609.00, 1024.00}} }, @@ -452,7 +692,7 @@ TEST_F(VelocityTrackerTest, SailfishFlingUpFaster1) { TEST_F(VelocityTrackerTest, SailfishFlingUpFaster2) { // Sailfish - fling up - faster - 2 - std::vector motions = { + std::vector motions = { { 847153808000ns, {{576.00, 1264.00}} }, { 847171174000ns, {{576.00, 1262.00}} }, { 847179640000ns, {{576.00, 1257.00}} }, @@ -478,7 +718,7 @@ TEST_F(VelocityTrackerTest, SailfishFlingUpFaster2) { TEST_F(VelocityTrackerTest, SailfishFlingUpFaster3) { // Sailfish - fling up - faster - 3 - std::vector motions = { + std::vector motions = { { 235200532789000ns, {{507.00, 1084.00}} }, { 235200549221000ns, {{507.00, 1083.00}} }, { 235200557841000ns, {{507.00, 1081.00}} }, @@ -504,7 +744,7 @@ TEST_F(VelocityTrackerTest, SailfishFlingUpFaster3) { TEST_F(VelocityTrackerTest, SailfishFlingUpFast1) { // Sailfish - fling up - fast - 1 - std::vector motions = { + std::vector motions = { { 920922149000ns, {{561.00, 1412.00}} }, { 920930185000ns, {{559.00, 1377.00}} }, { 920930262463ns, {{558.98, 1376.66}} }, @@ -533,7 +773,7 @@ TEST_F(VelocityTrackerTest, SailfishFlingUpFast1) { TEST_F(VelocityTrackerTest, SailfishFlingUpFast2) { // Sailfish - fling up - fast - 2 - std::vector motions = { + std::vector motions = { { 235247153233000ns, {{518.00, 1168.00}} }, { 235247170452000ns, {{517.00, 1167.00}} }, { 235247178908000ns, {{515.00, 1159.00}} }, @@ -556,7 +796,7 @@ TEST_F(VelocityTrackerTest, SailfishFlingUpFast2) { TEST_F(VelocityTrackerTest, SailfishFlingUpFast3) { // Sailfish - fling up - fast - 3 - std::vector motions = { + std::vector motions = { { 235302568736000ns, {{529.00, 1167.00}} }, { 235302576644000ns, {{523.00, 1140.00}} }, { 235302579395063ns, {{520.91, 1130.61}} }, @@ -577,7 +817,7 @@ TEST_F(VelocityTrackerTest, SailfishFlingUpFast3) { TEST_F(VelocityTrackerTest, SailfishFlingDownSlow1) { // Sailfish - fling down - slow - 1 - std::vector motions = { + std::vector motions = { { 235655749552755ns, {{582.00, 432.49}} }, { 235655750638000ns, {{582.00, 433.00}} }, { 235655758865000ns, {{582.00, 440.00}} }, @@ -611,7 +851,7 @@ TEST_F(VelocityTrackerTest, SailfishFlingDownSlow1) { TEST_F(VelocityTrackerTest, SailfishFlingDownSlow2) { // Sailfish - fling down - slow - 2 - std::vector motions = { + std::vector motions = { { 235671152083370ns, {{485.24, 558.28}} }, { 235671154126000ns, {{485.00, 559.00}} }, { 235671162497000ns, {{484.00, 566.00}} }, @@ -645,7 +885,7 @@ TEST_F(VelocityTrackerTest, SailfishFlingDownSlow2) { TEST_F(VelocityTrackerTest, SailfishFlingDownSlow3) { // Sailfish - fling down - slow - 3 - std::vector motions = { + std::vector motions = { { 170983201000ns, {{557.00, 533.00}} }, { 171000668000ns, {{556.00, 534.00}} }, { 171007359750ns, {{554.73, 535.27}} }, @@ -672,7 +912,7 @@ TEST_F(VelocityTrackerTest, SailfishFlingDownSlow3) { TEST_F(VelocityTrackerTest, SailfishFlingDownFaster1) { // Sailfish - fling down - faster - 1 - std::vector motions = { + std::vector motions = { { 235695280333000ns, {{558.00, 451.00}} }, { 235695283971237ns, {{558.43, 454.45}} }, { 235695289038000ns, {{559.00, 462.00}} }, @@ -702,7 +942,7 @@ TEST_F(VelocityTrackerTest, SailfishFlingDownFaster1) { TEST_F(VelocityTrackerTest, SailfishFlingDownFaster2) { // Sailfish - fling down - faster - 2 - std::vector motions = { + std::vector motions = { { 235709624766000ns, {{535.00, 579.00}} }, { 235709642256000ns, {{534.00, 580.00}} }, { 235709643350278ns, {{533.94, 580.06}} }, @@ -733,7 +973,7 @@ TEST_F(VelocityTrackerTest, SailfishFlingDownFaster2) { TEST_F(VelocityTrackerTest, SailfishFlingDownFaster3) { // Sailfish - fling down - faster - 3 - std::vector motions = { + std::vector motions = { { 235727628927000ns, {{540.00, 440.00}} }, { 235727636810000ns, {{537.00, 454.00}} }, { 235727646176000ns, {{536.00, 454.00}} }, @@ -762,7 +1002,7 @@ TEST_F(VelocityTrackerTest, SailfishFlingDownFaster3) { TEST_F(VelocityTrackerTest, SailfishFlingDownFast1) { // Sailfish - fling down - fast - 1 - std::vector motions = { + std::vector motions = { { 235762352849000ns, {{467.00, 286.00}} }, { 235762360250000ns, {{443.00, 344.00}} }, { 235762362787412ns, {{434.77, 363.89}} }, @@ -783,7 +1023,7 @@ TEST_F(VelocityTrackerTest, SailfishFlingDownFast1) { TEST_F(VelocityTrackerTest, SailfishFlingDownFast2) { // Sailfish - fling down - fast - 2 - std::vector motions = { + std::vector motions = { { 235772487188000ns, {{576.00, 204.00}} }, { 235772495159000ns, {{553.00, 236.00}} }, { 235772503568000ns, {{551.00, 240.00}} }, @@ -804,7 +1044,7 @@ TEST_F(VelocityTrackerTest, SailfishFlingDownFast2) { TEST_F(VelocityTrackerTest, SailfishFlingDownFast3) { // Sailfish - fling down - fast - 3 - std::vector motions = { + std::vector motions = { { 507650295000ns, {{628.00, 233.00}} }, { 507658234000ns, {{605.00, 269.00}} }, { 507666784000ns, {{601.00, 274.00}} }, @@ -830,12 +1070,12 @@ TEST_F(VelocityTrackerTest, SailfishFlingDownFast3) { /** * ================== Multiple pointers ============================================================ * - * Three fingers quickly tap the screen. Since this is a tap, the velocities should be zero. + * Three fingers quickly tap the screen. Since this is a tap, the velocities should be empty. * If the events with POINTER_UP or POINTER_DOWN are not handled correctly (these should not be * part of the fitted data), this can cause large velocity values to be reported instead. */ TEST_F(VelocityTrackerTest, LeastSquaresVelocityTrackerStrategyEstimator_ThreeFingerTap) { - std::vector motions = { + std::vector motions = { { 0us, {{1063, 1128}, {NAN, NAN}, {NAN, NAN}} }, { 10800us, {{1063, 1128}, {682, 1318}, {NAN, NAN}} }, // POINTER_DOWN { 10800us, {{1063, 1128}, {682, 1318}, {397, 1747}} }, // POINTER_DOWN @@ -844,12 +1084,78 @@ TEST_F(VelocityTrackerTest, LeastSquaresVelocityTrackerStrategyEstimator_ThreeFi { 272700us, {{1063, 1128}, {NAN, NAN}, {NAN, NAN}} }, }; - // Velocity should actually be zero, but we expect 0.016 here instead. - // This is close enough to zero, and is likely caused by division by a very small number. - computeAndCheckVelocity(VelocityTracker::Strategy::LSQ2, motions, AMOTION_EVENT_AXIS_X, -0.016); - computeAndCheckVelocity(VelocityTracker::Strategy::LSQ2, motions, AMOTION_EVENT_AXIS_Y, -0.016); - computeAndCheckVelocity(VelocityTracker::Strategy::IMPULSE, motions, AMOTION_EVENT_AXIS_X, 0); - computeAndCheckVelocity(VelocityTracker::Strategy::IMPULSE, motions, AMOTION_EVENT_AXIS_Y, 0); + computeAndCheckVelocity(VelocityTracker::Strategy::LSQ2, motions, AMOTION_EVENT_AXIS_X, + std::nullopt); + computeAndCheckVelocity(VelocityTracker::Strategy::LSQ2, motions, AMOTION_EVENT_AXIS_Y, + std::nullopt); + computeAndCheckVelocity(VelocityTracker::Strategy::IMPULSE, motions, AMOTION_EVENT_AXIS_X, + std::nullopt); + computeAndCheckVelocity(VelocityTracker::Strategy::IMPULSE, motions, AMOTION_EVENT_AXIS_Y, + std::nullopt); +} + +/** + * ================= Pointer liftoff =============================================================== + */ + +/** + * The last movement of a pointer is always ACTION_POINTER_UP or ACTION_UP. If there's a short delay + * between the last ACTION_MOVE and the next ACTION_POINTER_UP or ACTION_UP, velocity should not be + * affected by the liftoff. + */ +TEST_F(VelocityTrackerTest, ShortDelayBeforeActionUp) { + std::vector motions = { + {0ms, {{10, 0}}}, {10ms, {{20, 0}}}, {20ms, {{30, 0}}}, {30ms, {{30, 0}}}, // ACTION_UP + }; + computeAndCheckVelocity(VelocityTracker::Strategy::IMPULSE, motions, AMOTION_EVENT_AXIS_X, + 1000); + computeAndCheckVelocity(VelocityTracker::Strategy::LSQ2, motions, AMOTION_EVENT_AXIS_X, 1000); +} + +/** + * The last movement of a single pointer is ACTION_UP. If there's a long delay between the last + * ACTION_MOVE and the final ACTION_UP, velocity should be reported as empty because the pointer + * should be assumed to have stopped. + */ +TEST_F(VelocityTrackerTest, LongDelayBeforeActionUp) { + std::vector motions = { + {0ms, {{10, 0}}}, + {10ms, {{20, 0}}}, + {20ms, {{30, 0}}}, + {3000ms, {{30, 0}}}, // ACTION_UP + }; + computeAndCheckVelocity(VelocityTracker::Strategy::IMPULSE, motions, AMOTION_EVENT_AXIS_X, + std::nullopt); + computeAndCheckVelocity(VelocityTracker::Strategy::LSQ2, motions, AMOTION_EVENT_AXIS_X, + std::nullopt); +} + +/** + * The last movement of a pointer is always ACTION_POINTER_UP or ACTION_UP. If there's a long delay + * before ACTION_POINTER_UP event, the movement should be assumed to have stopped. + * The final velocity should be reported as empty for all pointers. + */ +TEST_F(VelocityTrackerTest, LongDelayBeforeActionPointerUp) { + std::vector motions = { + {0ms, {{10, 0}}}, + {10ms, {{20, 0}, {100, 0}}}, + {20ms, {{30, 0}, {200, 0}}}, + {30ms, {{30, 0}, {300, 0}}}, + {40ms, {{30, 0}, {400, 0}}}, + {3000ms, {{30, 0}}}, // ACTION_POINTER_UP + }; + computeAndCheckVelocity(VelocityTracker::Strategy::IMPULSE, motions, AMOTION_EVENT_AXIS_X, + std::nullopt, + /*pointerId*/ 0); + computeAndCheckVelocity(VelocityTracker::Strategy::LSQ2, motions, AMOTION_EVENT_AXIS_X, + std::nullopt, + /*pointerId*/ 0); + computeAndCheckVelocity(VelocityTracker::Strategy::IMPULSE, motions, AMOTION_EVENT_AXIS_X, + std::nullopt, + /*pointerId*/ 1); + computeAndCheckVelocity(VelocityTracker::Strategy::LSQ2, motions, AMOTION_EVENT_AXIS_X, + std::nullopt, + /*pointerId*/ 1); } /** @@ -878,7 +1184,7 @@ TEST_F(VelocityTrackerTest, LeastSquaresVelocityTrackerStrategyEstimator_ThreeFi * In the test, we would convert these coefficients to (0*(1E3)^0, 0*(1E3)^1, 1*(1E3)^2). */ TEST_F(VelocityTrackerTest, LeastSquaresVelocityTrackerStrategyEstimator_Constant) { - std::vector motions = { + std::vector motions = { { 0ms, {{1, 1}} }, // 0 s { 1ms, {{1, 1}} }, // 0.001 s { 2ms, {{1, 1}} }, // 0.002 s @@ -896,7 +1202,7 @@ TEST_F(VelocityTrackerTest, LeastSquaresVelocityTrackerStrategyEstimator_Constan * Straight line y = x :: the constant and quadratic coefficients are zero. */ TEST_F(VelocityTrackerTest, LeastSquaresVelocityTrackerStrategyEstimator_Linear) { - std::vector motions = { + std::vector motions = { { 0ms, {{-2, -2}} }, { 1ms, {{-1, -1}} }, { 2ms, {{-0, -0}} }, @@ -914,7 +1220,7 @@ TEST_F(VelocityTrackerTest, LeastSquaresVelocityTrackerStrategyEstimator_Linear) * Parabola */ TEST_F(VelocityTrackerTest, LeastSquaresVelocityTrackerStrategyEstimator_Parabolic) { - std::vector motions = { + std::vector motions = { { 0ms, {{1, 1}} }, { 1ms, {{4, 4}} }, { 2ms, {{8, 8}} }, @@ -932,7 +1238,7 @@ TEST_F(VelocityTrackerTest, LeastSquaresVelocityTrackerStrategyEstimator_Parabol * Parabola */ TEST_F(VelocityTrackerTest, LeastSquaresVelocityTrackerStrategyEstimator_Parabolic2) { - std::vector motions = { + std::vector motions = { { 0ms, {{1, 1}} }, { 1ms, {{4, 4}} }, { 2ms, {{9, 9}} }, @@ -950,7 +1256,7 @@ TEST_F(VelocityTrackerTest, LeastSquaresVelocityTrackerStrategyEstimator_Parabol * Parabola :: y = x^2 :: the constant and linear coefficients are zero. */ TEST_F(VelocityTrackerTest, LeastSquaresVelocityTrackerStrategyEstimator_Parabolic3) { - std::vector motions = { + std::vector motions = { { 0ms, {{4, 4}} }, { 1ms, {{1, 1}} }, { 2ms, {{0, 0}} }, @@ -964,4 +1270,114 @@ TEST_F(VelocityTrackerTest, LeastSquaresVelocityTrackerStrategyEstimator_Parabol computeAndCheckQuadraticEstimate(motions, std::array({0, 0E3, 1E6})); } +// Recorded by hand on sailfish, but only the diffs are taken to test cumulative axis velocity. +TEST_F(VelocityTrackerTest, AxisScrollVelocity) { + std::vector> motions = { + {235089067457000ns, 0.00}, {235089084684000ns, -1.00}, {235089093349000ns, 0.00}, + {235089095677625ns, 0.00}, {235089101859000ns, 0.00}, {235089110378000ns, 0.00}, + {235089112497111ns, 0.25}, {235089118760000ns, 1.75}, {235089126686000ns, 4.00}, + {235089129316820ns, 1.33}, {235089135199000ns, 3.67}, {235089144297000ns, 6.00}, + {235089146136443ns, 1.21}, {235089152923000ns, 5.79}, {235089160784000ns, 6.00}, + {235089162955851ns, 1.66}, + }; + + computeAndCheckAxisScrollVelocity(VelocityTracker::Strategy::IMPULSE, motions, {764.345703}); +} + +// --------------- Recorded by hand on a Wear OS device using a rotating side button --------------- +TEST_F(VelocityTrackerTest, AxisScrollVelocity_ScrollDown) { + std::vector> motions = { + {224598065152ns, -0.050100}, {224621871104ns, -0.133600}, {224645464064ns, -0.551100}, + {224669171712ns, -0.801600}, {224687063040ns, -1.035400}, {224706691072ns, -0.484300}, + {224738213888ns, -0.334000}, {224754401280ns, -0.083500}, + }; + + computeAndCheckAxisScrollVelocity(VelocityTracker::Strategy::IMPULSE, motions, {-27.86}); +} + +TEST_F(VelocityTrackerTest, AxisScrollVelocity_ScrollUp) { + std::vector> motions = { + {269606010880ns, 0.050100}, {269626064896ns, 0.217100}, {269641973760ns, 0.267200}, + {269658079232ns, 0.267200}, {269674217472ns, 0.267200}, {269690683392ns, 0.367400}, + {269706133504ns, 0.551100}, {269722173440ns, 0.501000}, + }; + + computeAndCheckAxisScrollVelocity(VelocityTracker::Strategy::IMPULSE, motions, {31.92}); +} + +TEST_F(VelocityTrackerTest, AxisScrollVelocity_ScrollDown_ThenUp_ThenDown) { + std::vector> motions = { + {2580534001664ns, -0.033400}, {2580549992448ns, -0.133600}, + {2580566769664ns, -0.250500}, {2580581974016ns, -0.183700}, + {2580597964800ns, -0.267200}, {2580613955584ns, -0.551100}, + {2580635189248ns, -0.601200}, {2580661927936ns, -0.450900}, + {2580683161600ns, -0.417500}, {2580705705984ns, -0.150300}, + {2580722745344ns, -0.016700}, {2580786446336ns, 0.050100}, + {2580801912832ns, 0.150300}, {2580822360064ns, 0.300600}, + {2580838088704ns, 0.300600}, {2580854341632ns, 0.400800}, + {2580869808128ns, 0.517700}, {2580886061056ns, 0.501000}, + {2580905984000ns, 0.350700}, {2580921974784ns, 0.350700}, + {2580937965568ns, 0.066800}, {2580974665728ns, 0.016700}, + {2581034434560ns, -0.066800}, {2581049901056ns, -0.116900}, + {2581070610432ns, -0.317300}, {2581086076928ns, -0.200400}, + {2581101805568ns, -0.233800}, {2581118058496ns, -0.417500}, + {2581134049280ns, -0.417500}, {2581150040064ns, -0.367400}, + {2581166030848ns, -0.267200}, {2581181759488ns, -0.150300}, + {2581199847424ns, -0.066800}, + }; + + computeAndCheckAxisScrollVelocity(VelocityTracker::Strategy::IMPULSE, motions, {-9.73}); +} + +// ------------------------------- Hand generated test cases --------------------------------------- +TEST_F(VelocityTrackerTest, TestDefaultStrategyForAxisScroll) { + std::vector> motions = { + {10ms, 20}, + {20ms, 25}, + {30ms, 50}, + {40ms, 100}, + }; + + EXPECT_EQ(computeVelocity(VelocityTracker::Strategy::IMPULSE, motions, + AMOTION_EVENT_AXIS_SCROLL), + computeVelocity(VelocityTracker::Strategy::DEFAULT, motions, + AMOTION_EVENT_AXIS_SCROLL)); +} + +TEST_F(VelocityTrackerTest, AxisScrollVelocity_SimilarDifferentialValues) { + std::vector> motions = {{1ns, 2.12}, {3ns, 2.12}, + {7ns, 2.12}, {8ns, 2.12}, + {15ns, 2.12}, {18ns, 2.12}}; + + computeAndCheckAxisScrollVelocity(VelocityTracker::Strategy::IMPULSE, motions, {1690236059.86}); +} + +TEST_F(VelocityTrackerTest, AxisScrollVelocity_OnlyTwoValues) { + std::vector> motions = {{1ms, 5}, {2ms, 10}}; + + computeAndCheckAxisScrollVelocity(VelocityTracker::Strategy::IMPULSE, motions, {10000}); +} + +TEST_F(VelocityTrackerTest, AxisScrollVelocity_ConstantVelocity) { + std::vector> motions = {{1ms, 20}, {2ms, 20}, + {3ms, 20}, {4ms, 20}, + {5ms, 20}, {6ms, 20}}; + + computeAndCheckAxisScrollVelocity(VelocityTracker::Strategy::IMPULSE, motions, {20000}); +} + +TEST_F(VelocityTrackerTest, AxisScrollVelocity_NoMotion) { + std::vector> motions = {{1ns, 0}, {2ns, 0}, + {3ns, 0}, {4ns, 0}, + {5ns, 0}, {6ns, 0}}; + + computeAndCheckAxisScrollVelocity(VelocityTracker::Strategy::IMPULSE, motions, {0}); +} + +TEST_F(VelocityTrackerTest, AxisScrollVelocity_NoData) { + std::vector> motions = {}; + + computeAndCheckAxisScrollVelocity(VelocityTracker::Strategy::IMPULSE, motions, std::nullopt); +} + } // namespace android diff --git a/libs/input/tests/VerifiedInputEvent_test.cpp b/libs/input/tests/VerifiedInputEvent_test.cpp index f2b59ea9abd8a7d832ce956d6c9bef20d34a81e9..277d74dd1c70e57206bfb64b5dae3e7f021f1b17 100644 --- a/libs/input/tests/VerifiedInputEvent_test.cpp +++ b/libs/input/tests/VerifiedInputEvent_test.cpp @@ -23,10 +23,10 @@ namespace android { static KeyEvent getKeyEventWithFlags(int32_t flags) { KeyEvent event; - event.initialize(InputEvent::nextId(), 2 /*deviceId*/, AINPUT_SOURCE_GAMEPAD, + event.initialize(InputEvent::nextId(), /*deviceId=*/2, AINPUT_SOURCE_GAMEPAD, ADISPLAY_ID_DEFAULT, INVALID_HMAC, AKEY_EVENT_ACTION_DOWN, flags, - AKEYCODE_BUTTON_X, 121 /*scanCode*/, AMETA_ALT_ON, 1 /*repeatCount*/, - 1000 /*downTime*/, 2000 /*eventTime*/); + AKEYCODE_BUTTON_X, /*scanCode=*/121, AMETA_ALT_ON, /*repeatCount=*/1, + /*downTime=*/1000, /*eventTime=*/2000); return event; } @@ -44,12 +44,12 @@ 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(), 0 /*deviceId*/, AINPUT_SOURCE_MOUSE, ADISPLAY_ID_DEFAULT, - INVALID_HMAC, AMOTION_EVENT_ACTION_DOWN, 0 /*actionButton*/, flags, - AMOTION_EVENT_EDGE_FLAG_NONE, AMETA_NONE, 0 /*buttonState*/, - MotionClassification::NONE, transform, 0.1 /*xPrecision*/, 0.2 /*yPrecision*/, - 280 /*xCursorPosition*/, 540 /*yCursorPosition*/, identity, 100 /*downTime*/, - 200 /*eventTime*/, pointerCount, pointerProperties, pointerCoords); + 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, + /*xCursorPosition=*/280, /*yCursorPosition=*/540, identity, /*downTime=*/100, + /*eventTime=*/200, pointerCount, pointerProperties, pointerCoords); return event; } diff --git a/libs/input/tests/data/bad_axis_label.kl b/libs/input/tests/data/bad_axis_label.kl new file mode 100644 index 0000000000000000000000000000000000000000..689738077c9c90807993fec24125a646a2735afd --- /dev/null +++ b/libs/input/tests/data/bad_axis_label.kl @@ -0,0 +1,17 @@ +# 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. + +# This KL should not be loaded because the axis label is not valid + +axis 0 DEFINITELY_NOT_AXIS_LABEL diff --git a/libs/input/tests/data/bad_led_label.kl b/libs/input/tests/data/bad_led_label.kl new file mode 100644 index 0000000000000000000000000000000000000000..293c0d2af4be8b8884c01c22fa6ee0fc6f1b484c --- /dev/null +++ b/libs/input/tests/data/bad_led_label.kl @@ -0,0 +1,17 @@ +# 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. + +# This KL should not be loaded because the led label is invalid + +led 0 ABSOLUTELY_NOT_LED_LABEL diff --git a/libs/nativedisplay/AChoreographer.cpp b/libs/nativedisplay/AChoreographer.cpp index 8240b08085595b462373af88382ac7ea9cc3c960..8f005a56f8a7b99208ac8552b7338673c6e8c4c5 100644 --- a/libs/nativedisplay/AChoreographer.cpp +++ b/libs/nativedisplay/AChoreographer.cpp @@ -14,12 +14,9 @@ * limitations under the License. */ -#define LOG_TAG "Choreographer" -//#define LOG_NDEBUG 0 - #include -#include -#include +#include +#include #include #include #include @@ -31,444 +28,9 @@ #include #include -namespace { -struct { - // Global JVM that is provided by zygote - JavaVM* jvm = nullptr; - struct { - jclass clazz; - jmethodID getInstance; - jmethodID registerNativeChoreographerForRefreshRateCallbacks; - jmethodID unregisterNativeChoreographerForRefreshRateCallbacks; - } displayManagerGlobal; -} gJni; - -// Gets the JNIEnv* for this thread, and performs one-off initialization if we -// have never retrieved a JNIEnv* pointer before. -JNIEnv* getJniEnv() { - if (gJni.jvm == nullptr) { - ALOGW("AChoreographer: No JVM provided!"); - return nullptr; - } - - JNIEnv* env = nullptr; - if (gJni.jvm->GetEnv((void**)&env, JNI_VERSION_1_4) != JNI_OK) { - ALOGD("Attaching thread to JVM for AChoreographer"); - JavaVMAttachArgs args = {JNI_VERSION_1_4, "AChoreographer_env", NULL}; - jint attachResult = gJni.jvm->AttachCurrentThreadAsDaemon(&env, (void*)&args); - if (attachResult != JNI_OK) { - ALOGE("Unable to attach thread. Error: %d", attachResult); - return nullptr; - } - } - if (env == nullptr) { - ALOGW("AChoreographer: No JNI env available!"); - } - return env; -} - -inline const char* toString(bool value) { - return value ? "true" : "false"; -} -} // namespace - -namespace android { -using gui::VsyncEventData; - -struct FrameCallback { - AChoreographer_frameCallback callback; - AChoreographer_frameCallback64 callback64; - AChoreographer_vsyncCallback vsyncCallback; - void* data; - nsecs_t dueTime; - - inline bool operator<(const FrameCallback& rhs) const { - // Note that this is intentionally flipped because we want callbacks due sooner to be at - // the head of the queue - return dueTime > rhs.dueTime; - } -}; - -struct RefreshRateCallback { - AChoreographer_refreshRateCallback callback; - void* data; - bool firstCallbackFired = false; -}; - -class Choreographer; - -/** - * Implementation of AChoreographerFrameCallbackData. - */ -struct ChoreographerFrameCallbackDataImpl { - int64_t frameTimeNanos{0}; - - VsyncEventData vsyncEventData; - - const Choreographer* choreographer; -}; - -struct { - std::mutex lock; - std::vector ptrs GUARDED_BY(lock); - std::map startTimes GUARDED_BY(lock); - bool registeredToDisplayManager GUARDED_BY(lock) = false; - - std::atomic mLastKnownVsync = -1; -} gChoreographers; - -class Choreographer : public DisplayEventDispatcher, public MessageHandler { -public: - explicit Choreographer(const sp& looper) EXCLUDES(gChoreographers.lock); - void postFrameCallbackDelayed(AChoreographer_frameCallback cb, - AChoreographer_frameCallback64 cb64, - AChoreographer_vsyncCallback vsyncCallback, void* data, - nsecs_t delay); - void registerRefreshRateCallback(AChoreographer_refreshRateCallback cb, void* data) - EXCLUDES(gChoreographers.lock); - void unregisterRefreshRateCallback(AChoreographer_refreshRateCallback cb, void* data); - // Drains the queue of pending vsync periods and dispatches refresh rate - // updates to callbacks. - // The assumption is that this method is only called on a single - // processing thread, either by looper or by AChoreographer_handleEvents - void handleRefreshRateUpdates(); - void scheduleLatestConfigRequest(); - - enum { - MSG_SCHEDULE_CALLBACKS = 0, - MSG_SCHEDULE_VSYNC = 1, - MSG_HANDLE_REFRESH_RATE_UPDATES = 2, - }; - virtual void handleMessage(const Message& message) override; - - static Choreographer* getForThread(); - virtual ~Choreographer() override EXCLUDES(gChoreographers.lock); - int64_t getFrameInterval() const; - bool inCallback() const; - -private: - Choreographer(const Choreographer&) = delete; - - void dispatchVsync(nsecs_t timestamp, PhysicalDisplayId displayId, uint32_t count, - VsyncEventData vsyncEventData) override; - void dispatchHotplug(nsecs_t timestamp, PhysicalDisplayId displayId, bool connected) override; - void dispatchModeChanged(nsecs_t timestamp, PhysicalDisplayId displayId, int32_t modeId, - nsecs_t vsyncPeriod) override; - void dispatchNullEvent(nsecs_t, PhysicalDisplayId) override; - void dispatchFrameRateOverrides(nsecs_t timestamp, PhysicalDisplayId displayId, - std::vector overrides) override; - - void scheduleCallbacks(); - - ChoreographerFrameCallbackDataImpl createFrameCallbackData(nsecs_t timestamp) const; - void registerStartTime() const; - - std::mutex mLock; - // Protected by mLock - std::priority_queue mFrameCallbacks; - std::vector mRefreshRateCallbacks; - - nsecs_t mLatestVsyncPeriod = -1; - VsyncEventData mLastVsyncEventData; - bool mInCallback = false; - - const sp mLooper; - const std::thread::id mThreadId; - - // Approximation of num_threads_using_choreographer * num_frames_of_history with leeway. - static constexpr size_t kMaxStartTimes = 250; -}; - -static thread_local Choreographer* gChoreographer; -Choreographer* Choreographer::getForThread() { - if (gChoreographer == nullptr) { - sp looper = Looper::getForThread(); - if (!looper.get()) { - ALOGW("No looper prepared for thread"); - return nullptr; - } - gChoreographer = new Choreographer(looper); - status_t result = gChoreographer->initialize(); - if (result != OK) { - ALOGW("Failed to initialize"); - return nullptr; - } - } - return gChoreographer; -} - -Choreographer::Choreographer(const sp& looper) - : DisplayEventDispatcher(looper, ISurfaceComposer::VsyncSource::eVsyncSourceApp), - mLooper(looper), - mThreadId(std::this_thread::get_id()) { - std::lock_guard _l(gChoreographers.lock); - gChoreographers.ptrs.push_back(this); -} - -Choreographer::~Choreographer() { - std::lock_guard _l(gChoreographers.lock); - gChoreographers.ptrs.erase(std::remove_if(gChoreographers.ptrs.begin(), - gChoreographers.ptrs.end(), - [=](Choreographer* c) { return c == this; }), - gChoreographers.ptrs.end()); - // Only poke DisplayManagerGlobal to unregister if we previously registered - // callbacks. - if (gChoreographers.ptrs.empty() && gChoreographers.registeredToDisplayManager) { - gChoreographers.registeredToDisplayManager = false; - JNIEnv* env = getJniEnv(); - if (env == nullptr) { - ALOGW("JNI environment is unavailable, skipping choreographer cleanup"); - return; - } - jobject dmg = env->CallStaticObjectMethod(gJni.displayManagerGlobal.clazz, - gJni.displayManagerGlobal.getInstance); - if (dmg == nullptr) { - ALOGW("DMS is not initialized yet, skipping choreographer cleanup"); - } else { - env->CallVoidMethod(dmg, - gJni.displayManagerGlobal - .unregisterNativeChoreographerForRefreshRateCallbacks); - env->DeleteLocalRef(dmg); - } - } -} - -void Choreographer::postFrameCallbackDelayed(AChoreographer_frameCallback cb, - AChoreographer_frameCallback64 cb64, - AChoreographer_vsyncCallback vsyncCallback, void* data, - nsecs_t delay) { - nsecs_t now = systemTime(SYSTEM_TIME_MONOTONIC); - FrameCallback callback{cb, cb64, vsyncCallback, data, now + delay}; - { - std::lock_guard _l{mLock}; - mFrameCallbacks.push(callback); - } - if (callback.dueTime <= now) { - if (std::this_thread::get_id() != mThreadId) { - if (mLooper != nullptr) { - Message m{MSG_SCHEDULE_VSYNC}; - mLooper->sendMessage(this, m); - } else { - scheduleVsync(); - } - } else { - scheduleVsync(); - } - } else { - if (mLooper != nullptr) { - Message m{MSG_SCHEDULE_CALLBACKS}; - mLooper->sendMessageDelayed(delay, this, m); - } else { - scheduleCallbacks(); - } - } -} - -void Choreographer::registerRefreshRateCallback(AChoreographer_refreshRateCallback cb, void* data) { - std::lock_guard _l{mLock}; - for (const auto& callback : mRefreshRateCallbacks) { - // Don't re-add callbacks. - if (cb == callback.callback && data == callback.data) { - return; - } - } - mRefreshRateCallbacks.emplace_back( - RefreshRateCallback{.callback = cb, .data = data, .firstCallbackFired = false}); - bool needsRegistration = false; - { - std::lock_guard _l2(gChoreographers.lock); - needsRegistration = !gChoreographers.registeredToDisplayManager; - } - if (needsRegistration) { - JNIEnv* env = getJniEnv(); - if (env == nullptr) { - ALOGW("JNI environment is unavailable, skipping registration"); - return; - } - jobject dmg = env->CallStaticObjectMethod(gJni.displayManagerGlobal.clazz, - gJni.displayManagerGlobal.getInstance); - if (dmg == nullptr) { - ALOGW("DMS is not initialized yet: skipping registration"); - return; - } else { - env->CallVoidMethod(dmg, - gJni.displayManagerGlobal - .registerNativeChoreographerForRefreshRateCallbacks, - reinterpret_cast(this)); - env->DeleteLocalRef(dmg); - { - std::lock_guard _l2(gChoreographers.lock); - gChoreographers.registeredToDisplayManager = true; - } - } - } else { - scheduleLatestConfigRequest(); - } -} - -void Choreographer::unregisterRefreshRateCallback(AChoreographer_refreshRateCallback cb, - void* data) { - std::lock_guard _l{mLock}; - mRefreshRateCallbacks.erase(std::remove_if(mRefreshRateCallbacks.begin(), - mRefreshRateCallbacks.end(), - [&](const RefreshRateCallback& callback) { - return cb == callback.callback && - data == callback.data; - }), - mRefreshRateCallbacks.end()); -} - -void Choreographer::scheduleLatestConfigRequest() { - if (mLooper != nullptr) { - Message m{MSG_HANDLE_REFRESH_RATE_UPDATES}; - mLooper->sendMessage(this, m); - } else { - // If the looper thread is detached from Choreographer, then refresh rate - // changes will be handled in AChoreographer_handlePendingEvents, so we - // need to wake up the looper thread by writing to the write-end of the - // socket the looper is listening on. - // Fortunately, these events are small so sending packets across the - // socket should be atomic across processes. - DisplayEventReceiver::Event event; - event.header = - DisplayEventReceiver::Event::Header{DisplayEventReceiver::DISPLAY_EVENT_NULL, - PhysicalDisplayId::fromPort(0), systemTime()}; - injectEvent(event); - } -} - -void Choreographer::scheduleCallbacks() { - const nsecs_t now = systemTime(SYSTEM_TIME_MONOTONIC); - nsecs_t dueTime; - { - std::lock_guard _l{mLock}; - // If there are no pending callbacks then don't schedule a vsync - if (mFrameCallbacks.empty()) { - return; - } - dueTime = mFrameCallbacks.top().dueTime; - } - - if (dueTime <= now) { - ALOGV("choreographer %p ~ scheduling vsync", this); - scheduleVsync(); - return; - } -} - -void Choreographer::handleRefreshRateUpdates() { - std::vector callbacks{}; - const nsecs_t pendingPeriod = gChoreographers.mLastKnownVsync.load(); - const nsecs_t lastPeriod = mLatestVsyncPeriod; - if (pendingPeriod > 0) { - mLatestVsyncPeriod = pendingPeriod; - } - { - std::lock_guard _l{mLock}; - for (auto& cb : mRefreshRateCallbacks) { - callbacks.push_back(cb); - cb.firstCallbackFired = true; - } - } - - for (auto& cb : callbacks) { - if (!cb.firstCallbackFired || (pendingPeriod > 0 && pendingPeriod != lastPeriod)) { - cb.callback(pendingPeriod, cb.data); - } - } -} - -// TODO(b/74619554): The PhysicalDisplayId is ignored because SF only emits VSYNC events for the -// internal display and DisplayEventReceiver::requestNextVsync only allows requesting VSYNC for -// the internal display implicitly. -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; - for (const auto& cb : callbacks) { - if (cb.vsyncCallback != nullptr) { - const ChoreographerFrameCallbackDataImpl frameCallbackData = - createFrameCallbackData(timestamp); - registerStartTime(); - mInCallback = true; - cb.vsyncCallback(reinterpret_cast( - &frameCallbackData), - cb.data); - mInCallback = false; - } else if (cb.callback64 != nullptr) { - cb.callback64(timestamp, cb.data); - } else if (cb.callback != nullptr) { - cb.callback(timestamp, cb.data); - } - } -} - -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)); -} - -void Choreographer::dispatchModeChanged(nsecs_t, PhysicalDisplayId, int32_t, nsecs_t) { - LOG_ALWAYS_FATAL("dispatchModeChanged was called but was never registered"); -} - -void Choreographer::dispatchFrameRateOverrides(nsecs_t, PhysicalDisplayId, - std::vector) { - LOG_ALWAYS_FATAL("dispatchFrameRateOverrides was called but was never registered"); -} - -void Choreographer::dispatchNullEvent(nsecs_t, PhysicalDisplayId) { - ALOGV("choreographer %p ~ received null event.", this); - handleRefreshRateUpdates(); -} - -void Choreographer::handleMessage(const Message& message) { - switch (message.what) { - case MSG_SCHEDULE_CALLBACKS: - scheduleCallbacks(); - break; - case MSG_SCHEDULE_VSYNC: - scheduleVsync(); - break; - case MSG_HANDLE_REFRESH_RATE_UPDATES: - handleRefreshRateUpdates(); - break; - } -} - -int64_t Choreographer::getFrameInterval() const { - return mLastVsyncEventData.frameInterval; -} - -bool Choreographer::inCallback() const { - return mInCallback; -} - -ChoreographerFrameCallbackDataImpl Choreographer::createFrameCallbackData(nsecs_t timestamp) const { - return {.frameTimeNanos = timestamp, - .vsyncEventData = mLastVsyncEventData, - .choreographer = this}; -} - -void Choreographer::registerStartTime() const { - std::scoped_lock _l(gChoreographers.lock); - for (VsyncEventData::FrameTimeline frameTimeline : mLastVsyncEventData.frameTimelines) { - while (gChoreographers.startTimes.size() >= kMaxStartTimes) { - gChoreographers.startTimes.erase(gChoreographers.startTimes.begin()); - } - gChoreographers.startTimes[frameTimeline.vsyncId] = systemTime(SYSTEM_TIME_MONOTONIC); - } -} +#undef LOG_TAG +#define LOG_TAG "AChoreographer" -} // namespace android using namespace android; static inline Choreographer* AChoreographer_to_Choreographer(AChoreographer* choreographer) { @@ -488,27 +50,12 @@ AChoreographerFrameCallbackData_to_ChoreographerFrameCallbackDataImpl( // Glue for private C api namespace android { -void AChoreographer_signalRefreshRateCallbacks(nsecs_t vsyncPeriod) EXCLUDES(gChoreographers.lock) { - std::lock_guard _l(gChoreographers.lock); - gChoreographers.mLastKnownVsync.store(vsyncPeriod); - for (auto c : gChoreographers.ptrs) { - c->scheduleLatestConfigRequest(); - } +void AChoreographer_signalRefreshRateCallbacks(nsecs_t vsyncPeriod) { + Choreographer::signalRefreshRateCallbacks(vsyncPeriod); } void AChoreographer_initJVM(JNIEnv* env) { - env->GetJavaVM(&gJni.jvm); - // Now we need to find the java classes. - jclass dmgClass = env->FindClass("android/hardware/display/DisplayManagerGlobal"); - gJni.displayManagerGlobal.clazz = static_cast(env->NewGlobalRef(dmgClass)); - gJni.displayManagerGlobal.getInstance = - env->GetStaticMethodID(dmgClass, "getInstance", - "()Landroid/hardware/display/DisplayManagerGlobal;"); - gJni.displayManagerGlobal.registerNativeChoreographerForRefreshRateCallbacks = - env->GetMethodID(dmgClass, "registerNativeChoreographerForRefreshRateCallbacks", "()V"); - gJni.displayManagerGlobal.unregisterNativeChoreographerForRefreshRateCallbacks = - env->GetMethodID(dmgClass, "unregisterNativeChoreographerForRefreshRateCallbacks", - "()V"); + Choreographer::initJVM(env); } AChoreographer* AChoreographer_routeGetInstance() { @@ -583,13 +130,7 @@ int64_t AChoreographer_getFrameInterval(const AChoreographer* choreographer) { } int64_t AChoreographer_getStartTimeNanosForVsyncId(AVsyncId vsyncId) { - std::scoped_lock _l(gChoreographers.lock); - const auto iter = gChoreographers.startTimes.find(vsyncId); - if (iter == gChoreographers.startTimes.end()) { - ALOGW("Start time was not found for vsync id: %" PRId64, vsyncId); - return 0; - } - return iter->second; + return Choreographer::getStartTimeNanosForVsyncId(vsyncId); } } // namespace android @@ -656,7 +197,7 @@ size_t AChoreographerFrameCallbackData_getFrameTimelinesLength( AChoreographerFrameCallbackData_to_ChoreographerFrameCallbackDataImpl(data); LOG_ALWAYS_FATAL_IF(!frameCallbackData->choreographer->inCallback(), "Data is only valid in callback"); - return VsyncEventData::kFrameTimelinesLength; + return frameCallbackData->vsyncEventData.frameTimelinesLength; } size_t AChoreographerFrameCallbackData_getPreferredFrameTimelineIndex( const AChoreographerFrameCallbackData* data) { @@ -672,7 +213,7 @@ AVsyncId AChoreographerFrameCallbackData_getFrameTimelineVsyncId( AChoreographerFrameCallbackData_to_ChoreographerFrameCallbackDataImpl(data); LOG_ALWAYS_FATAL_IF(!frameCallbackData->choreographer->inCallback(), "Data is only valid in callback"); - LOG_ALWAYS_FATAL_IF(index >= VsyncEventData::kFrameTimelinesLength, "Index out of bounds"); + LOG_ALWAYS_FATAL_IF(index >= VsyncEventData::kFrameTimelinesCapacity, "Index out of bounds"); return frameCallbackData->vsyncEventData.frameTimelines[index].vsyncId; } int64_t AChoreographerFrameCallbackData_getFrameTimelineExpectedPresentationTimeNanos( @@ -681,7 +222,7 @@ int64_t AChoreographerFrameCallbackData_getFrameTimelineExpectedPresentationTime AChoreographerFrameCallbackData_to_ChoreographerFrameCallbackDataImpl(data); LOG_ALWAYS_FATAL_IF(!frameCallbackData->choreographer->inCallback(), "Data is only valid in callback"); - LOG_ALWAYS_FATAL_IF(index >= VsyncEventData::kFrameTimelinesLength, "Index out of bounds"); + LOG_ALWAYS_FATAL_IF(index >= VsyncEventData::kFrameTimelinesCapacity, "Index out of bounds"); return frameCallbackData->vsyncEventData.frameTimelines[index].expectedPresentationTime; } int64_t AChoreographerFrameCallbackData_getFrameTimelineDeadlineNanos( @@ -690,7 +231,7 @@ int64_t AChoreographerFrameCallbackData_getFrameTimelineDeadlineNanos( AChoreographerFrameCallbackData_to_ChoreographerFrameCallbackDataImpl(data); LOG_ALWAYS_FATAL_IF(!frameCallbackData->choreographer->inCallback(), "Data is only valid in callback"); - LOG_ALWAYS_FATAL_IF(index >= VsyncEventData::kFrameTimelinesLength, "Index out of bounds"); + LOG_ALWAYS_FATAL_IF(index >= VsyncEventData::kFrameTimelinesCapacity, "Index out of bounds"); return frameCallbackData->vsyncEventData.frameTimelines[index].deadlineTimestamp; } diff --git a/libs/nativedisplay/ADisplay.cpp b/libs/nativedisplay/ADisplay.cpp index 76b85d60022773228ad39abf9a540041c4f8c0b6..bf0805b46c05a3e83e372518efda20c027136af1 100644 --- a/libs/nativedisplay/ADisplay.cpp +++ b/libs/nativedisplay/ADisplay.cpp @@ -117,15 +117,6 @@ using namespace android::display::impl; #define CHECK_NOT_NULL(name) \ LOG_ALWAYS_FATAL_IF(name == nullptr, "nullptr passed as " #name " argument"); -namespace { - -sp getToken(ADisplay* display) { - DisplayImpl* impl = reinterpret_cast(display); - return SurfaceComposerClient::getPhysicalDisplayToken(impl->id); -} - -} // namespace - namespace android { int ADisplay_acquirePhysicalDisplays(ADisplay*** outDisplays) { @@ -136,19 +127,20 @@ int ADisplay_acquirePhysicalDisplays(ADisplay*** outDisplays) { } std::vector modesPerDisplay[size]; + ui::DisplayConnectionType displayConnectionTypes[size]; int numModes = 0; for (int i = 0; i < size; ++i) { - const sp token = SurfaceComposerClient::getPhysicalDisplayToken(ids[i]); - ui::StaticDisplayInfo staticInfo; - if (const status_t status = SurfaceComposerClient::getStaticDisplayInfo(token, &staticInfo); + if (const status_t status = + SurfaceComposerClient::getStaticDisplayInfo(ids[i].value, &staticInfo); status != OK) { return status; } + displayConnectionTypes[i] = staticInfo.connectionType; ui::DynamicDisplayInfo dynamicInfo; if (const status_t status = - SurfaceComposerClient::getDynamicDisplayInfo(token, &dynamicInfo); + SurfaceComposerClient::getDynamicDisplayInfoFromId(ids[i].value, &dynamicInfo); status != OK) { return status; } @@ -168,8 +160,6 @@ int ADisplay_acquirePhysicalDisplays(ADisplay*** outDisplays) { } } - const std::optional internalId = - SurfaceComposerClient::getInternalDisplayId(); ui::Dataspace defaultDataspace; ui::PixelFormat defaultPixelFormat; ui::Dataspace wcgDataspace; @@ -201,8 +191,9 @@ int ADisplay_acquirePhysicalDisplays(ADisplay*** outDisplays) { for (size_t i = 0; i < size; ++i) { const PhysicalDisplayId id = ids[i]; - const ADisplayType type = (internalId == id) ? ADisplayType::DISPLAY_TYPE_INTERNAL - : ADisplayType::DISPLAY_TYPE_EXTERNAL; + const ADisplayType type = (displayConnectionTypes[i] == ui::DisplayConnectionType::Internal) + ? ADisplayType::DISPLAY_TYPE_INTERNAL + : ADisplayType::DISPLAY_TYPE_EXTERNAL; const std::vector& configs = modesPerDisplay[i]; memcpy(configData, configs.data(), sizeof(DisplayConfigImpl) * configs.size()); @@ -259,14 +250,15 @@ void ADisplay_getPreferredWideColorFormat(ADisplay* display, ADataSpace* outData int ADisplay_getCurrentConfig(ADisplay* display, ADisplayConfig** outConfig) { CHECK_NOT_NULL(display); - sp token = getToken(display); ui::DynamicDisplayInfo info; - if (const auto status = SurfaceComposerClient::getDynamicDisplayInfo(token, &info); + DisplayImpl* impl = reinterpret_cast(display); + + if (const auto status = + SurfaceComposerClient::getDynamicDisplayInfoFromId(impl->id.value, &info); status != OK) { return status; } - DisplayImpl* impl = reinterpret_cast(display); for (size_t i = 0; i < impl->numConfigs; i++) { auto* config = impl->configs + i; if (config->id == info.activeDisplayModeId) { diff --git a/libs/nativedisplay/Android.bp b/libs/nativedisplay/Android.bp index 4659b96b115ea497434b2ff549fd6b80d0e41806..8d8a2bc244b7064da4213f0e85a402ac2ff8fa9c 100644 --- a/libs/nativedisplay/Android.bp +++ b/libs/nativedisplay/Android.bp @@ -53,6 +53,7 @@ cc_library_shared { version_script: "libnativedisplay.map.txt", srcs: [ + ":libgui_frame_event_aidl", "AChoreographer.cpp", "ADisplay.cpp", "surfacetexture/surface_texture.cpp", diff --git a/libs/nativedisplay/libnativedisplay.map.txt b/libs/nativedisplay/libnativedisplay.map.txt index 969d9379f0c9e5c0d3bb796fd00fe1d0e23e28bd..9172d5ed13add23318e4d1571cfc6ac2731edf06 100644 --- a/libs/nativedisplay/libnativedisplay.map.txt +++ b/libs/nativedisplay/libnativedisplay.map.txt @@ -1,25 +1,25 @@ LIBNATIVEDISPLAY { global: - AChoreographer_getInstance; # apex # introduced=30 - AChoreographer_postFrameCallback; # apex # introduced=30 - AChoreographer_postFrameCallbackDelayed; # apex # introduced=30 - AChoreographer_postFrameCallback64; # apex # introduced=30 - AChoreographer_postFrameCallbackDelayed64; # apex # introduced=30 - AChoreographer_registerRefreshRateCallback; # apex # introduced=30 - AChoreographer_unregisterRefreshRateCallback; # apex # introduced=30 - AChoreographer_postVsyncCallback; # apex # introduced=33 - AChoreographerFrameCallbackData_getFrameTimeNanos; # apex # introduced=33 - AChoreographerFrameCallbackData_getFrameTimelinesLength; # apex # introduced=33 - AChoreographerFrameCallbackData_getPreferredFrameTimelineIndex; # apex # introduced=33 - AChoreographerFrameCallbackData_getFrameTimelineVsyncId; # apex # introduced=33 - AChoreographerFrameCallbackData_getFrameTimelineExpectedPresentationTimeNanos; # apex # introduced=33 - AChoreographerFrameCallbackData_getFrameTimelineDeadlineNanos; # apex # introduced=33 - AChoreographer_create; # apex # introduced=30 - AChoreographer_destroy; # apex # introduced=30 - AChoreographer_getFd; # apex # introduced=30 - AChoreographer_handlePendingEvents; # apex # introduced=30 - ASurfaceTexture_fromSurfaceTexture; # apex # introduced=30 - ASurfaceTexture_release; # apex # introduced=30 + AChoreographer_getInstance; # systemapi # introduced=30 + AChoreographer_postFrameCallback; # systemapi # introduced=30 + AChoreographer_postFrameCallbackDelayed; # systemapi # introduced=30 + AChoreographer_postFrameCallback64; # systemapi # introduced=30 + AChoreographer_postFrameCallbackDelayed64; # systemapi # introduced=30 + AChoreographer_registerRefreshRateCallback; # systemapi # introduced=30 + AChoreographer_unregisterRefreshRateCallback; # systemapi # introduced=30 + AChoreographer_postVsyncCallback; # systemapi # introduced=33 + AChoreographerFrameCallbackData_getFrameTimeNanos; # systemapi # introduced=33 + AChoreographerFrameCallbackData_getFrameTimelinesLength; # systemapi # introduced=33 + AChoreographerFrameCallbackData_getPreferredFrameTimelineIndex; # systemapi # introduced=33 + AChoreographerFrameCallbackData_getFrameTimelineVsyncId; # systemapi # introduced=33 + AChoreographerFrameCallbackData_getFrameTimelineExpectedPresentationTimeNanos; # systemapi # introduced=33 + AChoreographerFrameCallbackData_getFrameTimelineDeadlineNanos; # systemapi # introduced=33 + AChoreographer_create; # systemapi # introduced=30 + AChoreographer_destroy; # systemapi # introduced=30 + AChoreographer_getFd; # systemapi # introduced=30 + AChoreographer_handlePendingEvents; # systemapi # introduced=30 + ASurfaceTexture_fromSurfaceTexture; # systemapi # introduced=30 + ASurfaceTexture_release; # systemapi # introduced=30 local: *; }; diff --git a/libs/nativewindow/AHardwareBuffer.cpp b/libs/nativewindow/AHardwareBuffer.cpp index 2e0add5f303a1733cbee7dc332e9e2dba17da168..80607055ed16c36528d47c72e651d59e9554a9e5 100644 --- a/libs/nativewindow/AHardwareBuffer.cpp +++ b/libs/nativewindow/AHardwareBuffer.cpp @@ -360,12 +360,12 @@ int AHardwareBuffer_recvHandleFromUnixSocket(int socketFd, AHardwareBuffer** out return INVALID_OPERATION; } - GraphicBuffer* gBuffer = new GraphicBuffer(); + sp gBuffer(new GraphicBuffer()); status_t err = gBuffer->unflatten(data, dataLen, fdData, fdCount); if (err != NO_ERROR) { return err; } - *outBuffer = AHardwareBuffer_from_GraphicBuffer(gBuffer); + *outBuffer = AHardwareBuffer_from_GraphicBuffer(gBuffer.get()); // Ensure the buffer has a positive ref-count. AHardwareBuffer_acquire(*outBuffer); @@ -715,6 +715,14 @@ uint32_t AHardwareBuffer_convertToPixelFormat(uint32_t ahardwarebuffer_format) { return ahardwarebuffer_format; } +int32_t AHardwareBuffer_getDataSpace(AHardwareBuffer* buffer) { + GraphicBuffer* gb = AHardwareBuffer_to_GraphicBuffer(buffer); + auto& mapper = GraphicBufferMapper::get(); + ui::Dataspace dataspace = ui::Dataspace::UNKNOWN; + mapper.getDataspace(gb->handle, &dataspace); + return static_cast(dataspace); +} + uint64_t AHardwareBuffer_convertToGrallocUsageBits(uint64_t usage) { using android::hardware::graphics::common::V1_1::BufferUsage; static_assert(AHARDWAREBUFFER_USAGE_CPU_READ_NEVER == (uint64_t)BufferUsage::CPU_READ_NEVER, diff --git a/libs/nativewindow/ANativeWindow.cpp b/libs/nativewindow/ANativeWindow.cpp index 73a05fcf1576e65b510687529dfe599edd462719..dd5958de28541d38f722067390afb93e63897288 100644 --- a/libs/nativewindow/ANativeWindow.cpp +++ b/libs/nativewindow/ANativeWindow.cpp @@ -79,27 +79,6 @@ static int64_t query64(ANativeWindow* window, int what) { return res < 0 ? res : value; } -static bool isDataSpaceValid(ANativeWindow* window, int32_t dataSpace) { - bool supported = false; - switch (dataSpace) { - case HAL_DATASPACE_UNKNOWN: - case HAL_DATASPACE_V0_SRGB: - return true; - // These data space need wide gamut support. - case HAL_DATASPACE_V0_SCRGB_LINEAR: - case HAL_DATASPACE_V0_SCRGB: - case HAL_DATASPACE_DISPLAY_P3: - native_window_get_wide_color_support(window, &supported); - return supported; - // These data space need HDR support. - case HAL_DATASPACE_BT2020_PQ: - native_window_get_hdr_support(window, &supported); - return supported; - default: - return false; - } -} - /************************************************************************************************** * NDK **************************************************************************************************/ @@ -216,11 +195,10 @@ int32_t ANativeWindow_setBuffersDataSpace(ANativeWindow* window, int32_t dataSpa static_cast(HAL_DATASPACE_BT2020_HLG)); static_assert(static_cast(ADATASPACE_BT2020_ITU_HLG) == static_cast(HAL_DATASPACE_BT2020_ITU_HLG)); - static_assert(static_cast(DEPTH) == static_cast(HAL_DATASPACE_DEPTH)); - static_assert(static_cast(DYNAMIC_DEPTH) == static_cast(HAL_DATASPACE_DYNAMIC_DEPTH)); + static_assert(static_cast(ADATASPACE_DEPTH) == static_cast(HAL_DATASPACE_DEPTH)); + static_assert(static_cast(ADATASPACE_DYNAMIC_DEPTH) == static_cast(HAL_DATASPACE_DYNAMIC_DEPTH)); - if (!window || !query(window, NATIVE_WINDOW_IS_VALID) || - !isDataSpaceValid(window, dataSpace)) { + if (!window || !query(window, NATIVE_WINDOW_IS_VALID)) { return -EINVAL; } return native_window_set_buffers_data_space(window, @@ -233,6 +211,13 @@ int32_t ANativeWindow_getBuffersDataSpace(ANativeWindow* window) { return query(window, NATIVE_WINDOW_DATASPACE); } +int32_t ANativeWindow_getBuffersDefaultDataSpace(ANativeWindow* window) { + if (!window || !query(window, NATIVE_WINDOW_IS_VALID)) { + return -EINVAL; + } + return query(window, NATIVE_WINDOW_DEFAULT_DATASPACE); +} + int32_t ANativeWindow_setFrameRate(ANativeWindow* window, float frameRate, int8_t compatibility) { return ANativeWindow_setFrameRateWithChangeStrategy(window, frameRate, compatibility, ANATIVEWINDOW_CHANGE_FRAME_RATE_ONLY_IF_SEAMLESS); diff --git a/libs/nativewindow/include-private/private/android/AHardwareBufferHelpers.h b/libs/nativewindow/include-private/private/android/AHardwareBufferHelpers.h index ddfd1d19180d4a3521e367c6f67e2173613de861..6d3d295a0c9bfa58fd2f7064be55d8e9b4a537f7 100644 --- a/libs/nativewindow/include-private/private/android/AHardwareBufferHelpers.h +++ b/libs/nativewindow/include-private/private/android/AHardwareBufferHelpers.h @@ -52,6 +52,11 @@ uint32_t AHardwareBuffer_convertFromPixelFormat(uint32_t format); // convert HAL format to AHardwareBuffer format (note: this is a no-op) uint32_t AHardwareBuffer_convertToPixelFormat(uint32_t format); +// retrieves a dataspace from the AHardwareBuffer metadata, if the device +// support gralloc metadata. Returns UNKNOWN if gralloc metadata is not +// supported. +int32_t AHardwareBuffer_getDataSpace(AHardwareBuffer* buffer); + // convert AHardwareBuffer usage bits to HAL usage bits (note: this is a no-op) uint64_t AHardwareBuffer_convertFromGrallocUsageBits(uint64_t usage); diff --git a/libs/nativewindow/include/android/data_space.h b/libs/nativewindow/include/android/data_space.h index ae6d22350c6b59d3e0e718404d40317de3034d4f..47a4bfc16697ae490ac1ad4f06d1d498f81dd819 100644 --- a/libs/nativewindow/include/android/data_space.h +++ b/libs/nativewindow/include/android/data_space.h @@ -550,14 +550,14 @@ enum ADataSpace { * * This value is valid with formats HAL_PIXEL_FORMAT_Y16 and HAL_PIXEL_FORMAT_BLOB. */ - DEPTH = 4096, + ADATASPACE_DEPTH = 4096, /** * ISO 16684-1:2011(E) Dynamic Depth: * * Embedded depth metadata following the dynamic depth specification. */ - DYNAMIC_DEPTH = 4098 + ADATASPACE_DYNAMIC_DEPTH = 4098 }; __END_DECLS diff --git a/libs/nativewindow/include/android/native_window.h b/libs/nativewindow/include/android/native_window.h index a593cd47d359bb0e22caab33bf846bb853ff06cd..be6623ee75f0f61c23917488cca6762d6e3ec191 100644 --- a/libs/nativewindow/include/android/native_window.h +++ b/libs/nativewindow/include/android/native_window.h @@ -227,6 +227,16 @@ int32_t ANativeWindow_setBuffersDataSpace(ANativeWindow* window, int32_t dataSpa */ int32_t ANativeWindow_getBuffersDataSpace(ANativeWindow* window) __INTRODUCED_IN(28); +/** + * Get the default dataspace of the buffers in window as set by the consumer. + * + * Available since API level 34. + * + * \return the dataspace of buffers in window, ADATASPACE_UNKNOWN is returned if + * dataspace is unknown, or -EINVAL if window is invalid. + */ +int32_t ANativeWindow_getBuffersDefaultDataSpace(ANativeWindow* window) __INTRODUCED_IN(34); + /** Compatibility value for ANativeWindow_setFrameRate. */ enum ANativeWindow_FrameRateCompatibility { /** @@ -303,6 +313,8 @@ enum ANativeWindow_ChangeFrameRateStrategy { * You can register for changes in the refresh rate using * \a AChoreographer_registerRefreshRateCallback. * + * See ANativeWindow_clearFrameRate(). + * * Available since API level 31. * * \param window pointer to an ANativeWindow object. @@ -332,6 +344,41 @@ int32_t ANativeWindow_setFrameRateWithChangeStrategy(ANativeWindow* window, floa int8_t compatibility, int8_t changeFrameRateStrategy) __INTRODUCED_IN(31); +/** + * Clears the frame rate which is set for this window. + * + * This is equivalent to calling + * ANativeWindow_setFrameRateWithChangeStrategy(window, 0, compatibility, changeFrameRateStrategy). + * + * Usage of this API won't introduce frame rate throttling, + * or affect other aspects of the application's frame production + * pipeline. However, because the system may change the display refresh rate, + * calls to this function may result in changes to Choreographer callback + * timings, and changes to the time interval at which the system releases + * buffers back to the application. + * + * Note that this only has an effect for windows presented on the display. If + * this ANativeWindow is consumed by something other than the system compositor, + * e.g. a media codec, this call has no effect. + * + * You can register for changes in the refresh rate using + * \a AChoreographer_registerRefreshRateCallback. + * + * See ANativeWindow_setFrameRateWithChangeStrategy(). + * + * Available since API level 34. + * + * \param window pointer to an ANativeWindow object. + * + * \return 0 for success, -EINVAL if the window value is invalid. + */ +inline int32_t ANativeWindow_clearFrameRate(ANativeWindow* window) + __INTRODUCED_IN(__ANDROID_API_U__) { + return ANativeWindow_setFrameRateWithChangeStrategy(window, 0, + ANATIVEWINDOW_FRAME_RATE_COMPATIBILITY_DEFAULT, + ANATIVEWINDOW_CHANGE_FRAME_RATE_ONLY_IF_SEAMLESS); +} + #ifdef __cplusplus } #endif diff --git a/libs/nativewindow/include/system/window.h b/libs/nativewindow/include/system/window.h index 86e76c4f2c9bd86714f3fc09ca048e1641af1e90..0fee3c112e752dd6cb3c2e526f7b0d0a7fada81b 100644 --- a/libs/nativewindow/include/system/window.h +++ b/libs/nativewindow/include/system/window.h @@ -235,8 +235,8 @@ enum { NATIVE_WINDOW_ENABLE_FRAME_TIMESTAMPS = 25, NATIVE_WINDOW_GET_COMPOSITOR_TIMING = 26, NATIVE_WINDOW_GET_FRAME_TIMESTAMPS = 27, - NATIVE_WINDOW_GET_WIDE_COLOR_SUPPORT = 28, - NATIVE_WINDOW_GET_HDR_SUPPORT = 29, + /* 28, removed: NATIVE_WINDOW_GET_WIDE_COLOR_SUPPORT */ + /* 29, removed: NATIVE_WINDOW_GET_HDR_SUPPORT */ NATIVE_WINDOW_SET_USAGE64 = ANATIVEWINDOW_PERFORM_SET_USAGE64, NATIVE_WINDOW_GET_CONSUMER_USAGE64 = 31, NATIVE_WINDOW_SET_BUFFERS_SMPTE2086_METADATA = 32, @@ -988,15 +988,34 @@ static inline int native_window_get_frame_timestamps( outDequeueReadyTime, outReleaseTime); } -static inline int native_window_get_wide_color_support( - struct ANativeWindow* window, bool* outSupport) { - return window->perform(window, NATIVE_WINDOW_GET_WIDE_COLOR_SUPPORT, - outSupport); +/* deprecated. Always returns 0 and outSupport holds true. Don't call. */ +static inline int native_window_get_wide_color_support ( + struct ANativeWindow* window __UNUSED, bool* outSupport) __deprecated; + +/* + Deprecated(b/242763577): to be removed, this method should not be used + Surface support should not be tied to the display + Return true since most displays should have this support +*/ +static inline int native_window_get_wide_color_support ( + struct ANativeWindow* window __UNUSED, bool* outSupport) { + *outSupport = true; + return 0; } -static inline int native_window_get_hdr_support(struct ANativeWindow* window, +/* deprecated. Always returns 0 and outSupport holds true. Don't call. */ +static inline int native_window_get_hdr_support(struct ANativeWindow* window __UNUSED, + bool* outSupport) __deprecated; + +/* + Deprecated(b/242763577): to be removed, this method should not be used + Surface support should not be tied to the display + Return true since most displays should have this support +*/ +static inline int native_window_get_hdr_support(struct ANativeWindow* window __UNUSED, bool* outSupport) { - return window->perform(window, NATIVE_WINDOW_GET_HDR_SUPPORT, outSupport); + *outSupport = true; + return 0; } static inline int native_window_get_consumer_usage(struct ANativeWindow* window, @@ -1034,6 +1053,11 @@ enum { * This surface is ignored while choosing the refresh rate. */ ANATIVEWINDOW_FRAME_RATE_NO_VOTE, + + /** + * This surface will vote for the minimum refresh rate. + */ + ANATIVEWINDOW_FRAME_RATE_MIN }; static inline int native_window_set_frame_rate(struct ANativeWindow* window, float frameRate, @@ -1042,13 +1066,12 @@ static inline int native_window_set_frame_rate(struct ANativeWindow* window, flo (int)compatibility, (int)changeFrameRateStrategy); } -static inline int native_window_set_frame_timeline_info(struct ANativeWindow* window, - uint64_t frameNumber, - int64_t frameTimelineVsyncId, - int32_t inputEventId, - int64_t startTimeNanos) { +static inline int native_window_set_frame_timeline_info( + struct ANativeWindow* window, uint64_t frameNumber, int64_t frameTimelineVsyncId, + int32_t inputEventId, int64_t startTimeNanos, int32_t useForRefreshRateSelection) { return window->perform(window, NATIVE_WINDOW_SET_FRAME_TIMELINE_INFO, frameNumber, - frameTimelineVsyncId, inputEventId, startTimeNanos); + frameTimelineVsyncId, inputEventId, startTimeNanos, + useForRefreshRateSelection); } // ------------------------------------------------------------------------------------------------ diff --git a/libs/nativewindow/libnativewindow.map.txt b/libs/nativewindow/libnativewindow.map.txt index d9ac568187c6bef51843cd82633fda0d0e1ca5d0..c2fd6efcdb94b9771dae1b36930163ae198aad10 100644 --- a/libs/nativewindow/libnativewindow.map.txt +++ b/libs/nativewindow/libnativewindow.map.txt @@ -23,6 +23,7 @@ LIBNATIVEWINDOW { ANativeWindow_cancelBuffer; # llndk ANativeWindow_dequeueBuffer; # llndk ANativeWindow_getBuffersDataSpace; # introduced=28 + ANativeWindow_getBuffersDefaultDataSpace; # introduced=34 ANativeWindow_getFormat; ANativeWindow_getHeight; ANativeWindow_getLastDequeueDuration; # systemapi # introduced=30 @@ -69,6 +70,7 @@ LIBNATIVEWINDOW_PLATFORM { android::AHardwareBuffer_convertToPixelFormat*; android::AHardwareBuffer_convertFromGrallocUsageBits*; android::AHardwareBuffer_convertToGrallocUsageBits*; + android::AHardwareBuffer_getDataSpace*; android::AHardwareBuffer_to_GraphicBuffer*; android::AHardwareBuffer_to_ANativeWindowBuffer*; android::AHardwareBuffer_from_GraphicBuffer*; diff --git a/libs/permission/AppOpsManager.cpp b/libs/permission/AppOpsManager.cpp index baa9d7511653dbdecefb8069a830da203c83e6ae..695927418d60d9f365bca8da01135c06199566d4 100644 --- a/libs/permission/AppOpsManager.cpp +++ b/libs/permission/AppOpsManager.cpp @@ -146,6 +146,14 @@ void AppOpsManager::startWatchingMode(int32_t op, const String16& packageName, } } +void AppOpsManager::startWatchingMode(int32_t op, const String16& packageName, int32_t flags, + const sp& callback) { + sp service = getService(); + if (service != nullptr) { + service->startWatchingModeWithFlags(op, packageName, flags, callback); + } +} + void AppOpsManager::stopWatchingMode(const sp& callback) { sp service = getService(); if (service != nullptr) { diff --git a/libs/permission/IAppOpsService.cpp b/libs/permission/IAppOpsService.cpp index d59f44562e721e40d104d94f50e80a12a226f686..7f235a45416c881c46fc546eedcc79f1e72881e2 100644 --- a/libs/permission/IAppOpsService.cpp +++ b/libs/permission/IAppOpsService.cpp @@ -166,6 +166,17 @@ public: } return reply.readBool(); } + + virtual void startWatchingModeWithFlags(int32_t op, const String16& packageName, + int32_t flags, const sp& callback) { + Parcel data, reply; + data.writeInterfaceToken(IAppOpsService::getInterfaceDescriptor()); + data.writeInt32(op); + data.writeString16(packageName); + data.writeInt32(flags); + data.writeStrongBinder(IInterface::asBinder(callback)); + remote()->transact(START_WATCHING_MODE_WITH_FLAGS_TRANSACTION, data, &reply); + } }; IMPLEMENT_META_INTERFACE(AppOpsService, "com.android.internal.app.IAppOpsService") diff --git a/libs/permission/include/binder/AppOpsManager.h b/libs/permission/include/binder/AppOpsManager.h index abcd52796692f3f4bb13ab32aa2537e78694a943..243532bc4de14e573e0c35067486e5755dbfce72 100644 --- a/libs/permission/include/binder/AppOpsManager.h +++ b/libs/permission/include/binder/AppOpsManager.h @@ -151,6 +151,10 @@ public: _NUM_OP = 117 }; + enum { + WATCH_FOREGROUND_CHANGES = 1 << 0 + }; + AppOpsManager(); int32_t checkOp(int32_t op, int32_t uid, const String16& callingPackage); @@ -174,6 +178,8 @@ public: const std::optional& attributionTag); void startWatchingMode(int32_t op, const String16& packageName, const sp& callback); + void startWatchingMode(int32_t op, const String16& packageName, int32_t flags, + const sp& callback); void stopWatchingMode(const sp& callback); int32_t permissionToOpCode(const String16& permission); void setCameraAudioRestriction(int32_t mode); diff --git a/libs/permission/include/binder/IAppOpsService.h b/libs/permission/include/binder/IAppOpsService.h index 22f056b2350dcd1a0e35435d7d9fefb3a9c5495b..918fcdbce1c3f7704d8de770c6062ea02b927372 100644 --- a/libs/permission/include/binder/IAppOpsService.h +++ b/libs/permission/include/binder/IAppOpsService.h @@ -52,6 +52,8 @@ public: const String16& packageName) = 0; virtual void setCameraAudioRestriction(int32_t mode) = 0; virtual bool shouldCollectNotes(int32_t opCode) = 0; + virtual void startWatchingModeWithFlags(int32_t op, const String16& packageName, + int32_t flags, const sp& callback) = 0; enum { CHECK_OPERATION_TRANSACTION = IBinder::FIRST_CALL_TRANSACTION, @@ -64,6 +66,7 @@ public: CHECK_AUDIO_OPERATION_TRANSACTION = IBinder::FIRST_CALL_TRANSACTION+7, SHOULD_COLLECT_NOTES_TRANSACTION = IBinder::FIRST_CALL_TRANSACTION+8, SET_CAMERA_AUDIO_RESTRICTION_TRANSACTION = IBinder::FIRST_CALL_TRANSACTION+9, + START_WATCHING_MODE_WITH_FLAGS_TRANSACTION = IBinder::FIRST_CALL_TRANSACTION+10, }; enum { diff --git a/libs/renderengine/Android.bp b/libs/renderengine/Android.bp index f6f57dde7d6d1e8d25d5b5f97e8c6c3b52efad5b..8d19c455276dcd9a385e6a47aa0412767157ac4c 100644 --- a/libs/renderengine/Android.bp +++ b/libs/renderengine/Android.bp @@ -21,13 +21,15 @@ cc_defaults { cc_defaults { name: "librenderengine_defaults", - defaults: ["renderengine_defaults"], + defaults: [ + "android.hardware.graphics.composer3-ndk_shared", + "renderengine_defaults", + ], cflags: [ "-DGL_GLEXT_PROTOTYPES", "-DEGL_EGLEXT_PROTOTYPES", ], shared_libs: [ - "android.hardware.graphics.composer3-V1-ndk", "libbase", "libcutils", "libEGL", @@ -40,6 +42,7 @@ cc_defaults { "libsync", "libui", "libutils", + "libvulkan", ], static_libs: [ @@ -95,6 +98,7 @@ filegroup { "skia/ColorSpaces.cpp", "skia/SkiaRenderEngine.cpp", "skia/SkiaGLRenderEngine.cpp", + "skia/SkiaVkRenderEngine.cpp", "skia/debug/CaptureTimer.cpp", "skia/debug/CommonPool.cpp", "skia/debug/SkiaCapture.cpp", @@ -107,9 +111,23 @@ filegroup { ], } +// Used to consolidate and simplify pulling Skia & Skia deps into targets that depend on +// librenderengine. This allows shared deps to be deduplicated (e.g. Perfetto), which doesn't seem +// possible if libskia_renderengine is just pulled into librenderengine via whole_static_libs. +cc_defaults { + name: "librenderengine_deps", + defaults: ["skia_renderengine_deps"], + static_libs: ["libskia_renderengine"], +} + +// Note: if compilation fails when adding librenderengine as a dependency, try adding +// librenderengine_deps to the defaults field of your dependent target. cc_library_static { name: "librenderengine", - defaults: ["librenderengine_defaults"], + defaults: [ + "librenderengine_defaults", + "librenderengine_deps", + ], double_loadable: true, cflags: [ "-fvisibility=hidden", @@ -128,7 +146,6 @@ cc_library_static { include_dirs: [ "external/skia/src/gpu", ], - whole_static_libs: ["libskia_renderengine"], lto: { thin: true, }, diff --git a/libs/renderengine/ExternalTexture.cpp b/libs/renderengine/ExternalTexture.cpp index 84771c091794d6688525b91e6448a72232dcdacc..9eb42cd8e166a20ee01d2a5ffeab6f45431c7c79 100644 --- a/libs/renderengine/ExternalTexture.cpp +++ b/libs/renderengine/ExternalTexture.cpp @@ -14,17 +14,17 @@ * limitations under the License. */ +#include #include #include #include - -#include "log/log_main.h" +#include namespace android::renderengine::impl { ExternalTexture::ExternalTexture(const sp& buffer, renderengine::RenderEngine& renderEngine, uint32_t usage) - : mBuffer(buffer), mRenderEngine(renderEngine) { + : mBuffer(buffer), mRenderEngine(renderEngine), mWritable(usage & WRITEABLE) { LOG_ALWAYS_FATAL_IF(buffer == nullptr, "Attempted to bind a null buffer to an external texture!"); // GLESRenderEngine has a separate texture cache for output buffers, @@ -35,11 +35,20 @@ ExternalTexture::ExternalTexture(const sp& buffer, renderengine::RenderEngine::RenderEngineType::THREADED)) { return; } - mRenderEngine.mapExternalTextureBuffer(mBuffer, usage & WRITEABLE); + mRenderEngine.mapExternalTextureBuffer(mBuffer, mWritable); } ExternalTexture::~ExternalTexture() { - mRenderEngine.unmapExternalTextureBuffer(mBuffer); + mRenderEngine.unmapExternalTextureBuffer(std::move(mBuffer)); +} + +void ExternalTexture::remapBuffer() { + ATRACE_CALL(); + { + auto buf = mBuffer; + mRenderEngine.unmapExternalTextureBuffer(std::move(buf)); + } + mRenderEngine.mapExternalTextureBuffer(mBuffer, mWritable); } } // namespace android::renderengine::impl diff --git a/libs/renderengine/RenderEngine.cpp b/libs/renderengine/RenderEngine.cpp index c7ad058ab9e866b808875d9b375eadd0ead558a2..d08c2213ad899403613db8dd8e954ed75fb2dc80 100644 --- a/libs/renderengine/RenderEngine.cpp +++ b/libs/renderengine/RenderEngine.cpp @@ -19,9 +19,11 @@ #include #include #include "gl/GLESRenderEngine.h" +#include "renderengine/ExternalTexture.h" #include "threaded/RenderEngineThreaded.h" #include "skia/SkiaGLRenderEngine.h" +#include "skia/SkiaVkRenderEngine.h" namespace android { namespace renderengine { @@ -36,6 +38,9 @@ std::unique_ptr RenderEngine::create(const RenderEngineCreationArg case RenderEngineType::SKIA_GL: ALOGD("RenderEngine with SkiaGL Backend"); return renderengine::skia::SkiaGLRenderEngine::create(args); + case RenderEngineType::SKIA_VK: + ALOGD("RenderEngine with SkiaVK Backend"); + return renderengine::skia::SkiaVkRenderEngine::create(args); case RenderEngineType::SKIA_GL_THREADED: { ALOGD("Threaded RenderEngine with SkiaGL Backend"); return renderengine::threaded::RenderEngineThreaded::create( @@ -44,6 +49,13 @@ std::unique_ptr RenderEngine::create(const RenderEngineCreationArg }, args.renderEngineType); } + case RenderEngineType::SKIA_VK_THREADED: + ALOGD("Threaded RenderEngine with SkiaVK Backend"); + return renderengine::threaded::RenderEngineThreaded::create( + [args]() { + return android::renderengine::skia::SkiaVkRenderEngine::create(args); + }, + args.renderEngineType); case RenderEngineType::GLES: default: ALOGD("RenderEngine with GLES Backend"); @@ -63,16 +75,29 @@ void RenderEngine::validateOutputBufferUsage(const sp& buffer) { "output buffer not gpu writeable"); } -std::future RenderEngine::drawLayers( - const DisplaySettings& display, const std::vector& layers, - const std::shared_ptr& buffer, const bool useFramebufferCache, - base::unique_fd&& bufferFence) { - const auto resultPromise = std::make_shared>(); - std::future resultFuture = resultPromise->get_future(); +ftl::Future RenderEngine::drawLayers(const DisplaySettings& display, + const std::vector& layers, + const std::shared_ptr& buffer, + const bool useFramebufferCache, + base::unique_fd&& bufferFence) { + const auto resultPromise = std::make_shared>(); + std::future resultFuture = resultPromise->get_future(); + updateProtectedContext(layers, buffer); drawLayersInternal(std::move(resultPromise), display, layers, buffer, useFramebufferCache, std::move(bufferFence)); return resultFuture; } +void RenderEngine::updateProtectedContext(const std::vector& layers, + const std::shared_ptr& buffer) { + const bool needsProtectedContext = + (buffer && (buffer->getUsage() & GRALLOC_USAGE_PROTECTED)) || + std::any_of(layers.begin(), layers.end(), [](const LayerSettings& layer) { + const std::shared_ptr& buffer = layer.source.buffer.buffer; + return buffer && (buffer->getUsage() & GRALLOC_USAGE_PROTECTED); + }); + useProtectedContext(needsProtectedContext); +} + } // namespace renderengine } // namespace android diff --git a/libs/renderengine/benchmark/Android.bp b/libs/renderengine/benchmark/Android.bp index 249fec5866aafc9dd945a575d48a517c5603888e..55c34cd059af4c18d209685670f7b0131e7454fa 100644 --- a/libs/renderengine/benchmark/Android.bp +++ b/libs/renderengine/benchmark/Android.bp @@ -24,7 +24,8 @@ package { cc_benchmark { name: "librenderengine_bench", defaults: [ - "skia_deps", + "android.hardware.graphics.composer3-ndk_shared", + "librenderengine_deps", "surfaceflinger_defaults", ], srcs: [ @@ -43,7 +44,6 @@ cc_benchmark { ], shared_libs: [ - "android.hardware.graphics.composer3-V1-ndk", "libbase", "libcutils", "libjnigraphics", diff --git a/libs/renderengine/benchmark/RenderEngineBench.cpp b/libs/renderengine/benchmark/RenderEngineBench.cpp index ead97cf5dfb3d17659228e94463aa35af52723ad..bd7b617ae7949d30f08c333fb046a7b93dd528b2 100644 --- a/libs/renderengine/benchmark/RenderEngineBench.cpp +++ b/libs/renderengine/benchmark/RenderEngineBench.cpp @@ -39,6 +39,10 @@ std::string RenderEngineTypeName(RenderEngine::RenderEngineType type) { return "skiaglthreaded"; case RenderEngine::RenderEngineType::SKIA_GL: return "skiagl"; + case RenderEngine::RenderEngineType::SKIA_VK: + return "skiavk"; + case RenderEngine::RenderEngineType::SKIA_VK_THREADED: + return "skiavkthreaded"; case RenderEngine::RenderEngineType::GLES: case RenderEngine::RenderEngineType::THREADED: LOG_ALWAYS_FATAL("GLESRenderEngine is deprecated - why time it?"); @@ -80,16 +84,26 @@ std::pair getDisplaySize() { std::once_flag once; std::call_once(once, []() { auto surfaceComposerClient = SurfaceComposerClient::getDefault(); - auto displayToken = surfaceComposerClient->getInternalDisplayToken(); - ui::DisplayMode displayMode; - if (surfaceComposerClient->getActiveDisplayMode(displayToken, &displayMode) < 0) { - LOG_ALWAYS_FATAL("Failed to get active display mode!"); + auto ids = SurfaceComposerClient::getPhysicalDisplayIds(); + LOG_ALWAYS_FATAL_IF(ids.empty(), "Failed to get any display!"); + ui::Size resolution = ui::kEmptySize; + // find the largest display resolution + for (auto id : ids) { + auto displayToken = surfaceComposerClient->getPhysicalDisplayToken(id); + ui::DisplayMode displayMode; + if (surfaceComposerClient->getActiveDisplayMode(displayToken, &displayMode) < 0) { + LOG_ALWAYS_FATAL("Failed to get active display mode!"); + } + auto tw = displayMode.resolution.width; + auto th = displayMode.resolution.height; + LOG_ALWAYS_FATAL_IF(tw <= 0 || th <= 0, "Invalid display size!"); + if (resolution.width * resolution.height < + displayMode.resolution.width * displayMode.resolution.height) { + resolution = displayMode.resolution; + } } - auto w = displayMode.resolution.width; - auto h = displayMode.resolution.height; - LOG_ALWAYS_FATAL_IF(w <= 0 || h <= 0, "Invalid display size!"); - width = static_cast(w); - height = static_cast(h); + width = static_cast(resolution.width); + height = static_cast(resolution.height); }); return std::pair(width, height); } @@ -117,11 +131,12 @@ static std::shared_ptr allocateBuffer(RenderEngine& re, uint32_ uint64_t extraUsageFlags = 0, std::string name = "output") { return std::make_shared< - impl::ExternalTexture>(new GraphicBuffer(width, height, HAL_PIXEL_FORMAT_RGBA_8888, 1, - GRALLOC_USAGE_HW_RENDER | - GRALLOC_USAGE_HW_TEXTURE | - extraUsageFlags, - std::move(name)), + impl::ExternalTexture>(sp::make(width, height, + HAL_PIXEL_FORMAT_RGBA_8888, 1u, + GRALLOC_USAGE_HW_RENDER | + GRALLOC_USAGE_HW_TEXTURE | + extraUsageFlags, + std::move(name)), re, impl::ExternalTexture::Usage::READABLE | impl::ExternalTexture::Usage::WRITEABLE); @@ -158,9 +173,10 @@ static std::shared_ptr copyBuffer(RenderEngine& re, }; auto layers = std::vector{layer}; - auto [status, drawFence] = - re.drawLayers(display, layers, texture, kUseFrameBufferCache, base::unique_fd()).get(); - sp waitFence = sp::make(std::move(drawFence)); + sp waitFence = + re.drawLayers(display, layers, texture, kUseFrameBufferCache, base::unique_fd()) + .get() + .value(); waitFence->waitForever(LOG_TAG); return texture; } @@ -189,10 +205,10 @@ static void benchDrawLayers(RenderEngine& re, const std::vector& // This loop starts and stops the timer. for (auto _ : benchState) { - auto [status, drawFence] = re.drawLayers(display, layers, outputBuffer, - kUseFrameBufferCache, base::unique_fd()) - .get(); - sp waitFence = sp::make(std::move(drawFence)); + sp waitFence = re.drawLayers(display, layers, outputBuffer, kUseFrameBufferCache, + base::unique_fd()) + .get() + .value(); waitFence->waitForever(LOG_TAG); } diff --git a/libs/renderengine/gl/GLESRenderEngine.cpp b/libs/renderengine/gl/GLESRenderEngine.cpp index 6dc01b916edf55a97e27dfb1db07b8f5c6745882..0d7df101f41ec9545ae93996fb0416122455507a 100644 --- a/libs/renderengine/gl/GLESRenderEngine.cpp +++ b/libs/renderengine/gl/GLESRenderEngine.cpp @@ -454,8 +454,9 @@ GLESRenderEngine::GLESRenderEngine(const RenderEngineCreationArgs& args, EGLDisp mImageManager->initThread(); mDrawingBuffer = createFramebuffer(); sp buf = - new GraphicBuffer(1, 1, PIXEL_FORMAT_RGBA_8888, 1, - GRALLOC_USAGE_HW_RENDER | GRALLOC_USAGE_HW_TEXTURE, "placeholder"); + sp::make(1, 1, PIXEL_FORMAT_RGBA_8888, 1, + GRALLOC_USAGE_HW_RENDER | GRALLOC_USAGE_HW_TEXTURE, + "placeholder"); const status_t err = buf->initCheck(); if (err != OK) { @@ -799,7 +800,7 @@ status_t GLESRenderEngine::cacheExternalTextureBufferInternal(const sp& buffer) { +void GLESRenderEngine::unmapExternalTextureBuffer(sp&& buffer) { mImageManager->releaseAsync(buffer->getId(), nullptr); } @@ -1080,14 +1081,14 @@ EGLImageKHR GLESRenderEngine::createFramebufferImageIfNeeded(ANativeWindowBuffer } void GLESRenderEngine::drawLayersInternal( - const std::shared_ptr>&& resultPromise, + const std::shared_ptr>&& resultPromise, const DisplaySettings& display, const std::vector& layers, const std::shared_ptr& buffer, const bool useFramebufferCache, base::unique_fd&& bufferFence) { ATRACE_CALL(); if (layers.empty()) { ALOGV("Drawing empty layer stack"); - resultPromise->set_value({NO_ERROR, base::unique_fd()}); + resultPromise->set_value(Fence::NO_FENCE); return; } @@ -1102,7 +1103,7 @@ void GLESRenderEngine::drawLayersInternal( if (buffer == nullptr) { ALOGE("No output buffer provided. Aborting GPU composition."); - resultPromise->set_value({BAD_VALUE, base::unique_fd()}); + resultPromise->set_value(base::unexpected(BAD_VALUE)); return; } @@ -1131,7 +1132,7 @@ void GLESRenderEngine::drawLayersInternal( ALOGE("Failed to bind framebuffer! Aborting GPU composition for buffer (%p).", buffer->getBuffer()->handle); checkErrors(); - resultPromise->set_value({fbo->getStatus(), base::unique_fd()}); + resultPromise->set_value(base::unexpected(fbo->getStatus())); return; } setViewportAndProjection(display.physicalDisplay, display.clip); @@ -1143,7 +1144,7 @@ void GLESRenderEngine::drawLayersInternal( ALOGE("Failed to prepare blur filter! Aborting GPU composition for buffer (%p).", buffer->getBuffer()->handle); checkErrors(); - resultPromise->set_value({status, base::unique_fd()}); + resultPromise->set_value(base::unexpected(status)); return; } } @@ -1177,7 +1178,7 @@ void GLESRenderEngine::drawLayersInternal( ALOGE("Failed to render blur effect! Aborting GPU composition for buffer (%p).", buffer->getBuffer()->handle); checkErrors("Can't render first blur pass"); - resultPromise->set_value({status, base::unique_fd()}); + resultPromise->set_value(base::unexpected(status)); return; } @@ -1200,7 +1201,7 @@ void GLESRenderEngine::drawLayersInternal( ALOGE("Failed to bind framebuffer! Aborting GPU composition for buffer (%p).", buffer->getBuffer()->handle); checkErrors("Can't bind native framebuffer"); - resultPromise->set_value({status, base::unique_fd()}); + resultPromise->set_value(base::unexpected(status)); return; } @@ -1209,7 +1210,7 @@ void GLESRenderEngine::drawLayersInternal( ALOGE("Failed to render blur effect! Aborting GPU composition for buffer (%p).", buffer->getBuffer()->handle); checkErrors("Can't render blur filter"); - resultPromise->set_value({status, base::unique_fd()}); + resultPromise->set_value(base::unexpected(status)); return; } } @@ -1261,7 +1262,7 @@ void GLESRenderEngine::drawLayersInternal( // Do not cache protected EGLImage, protected memory is limited. if (gBuf->getUsage() & GRALLOC_USAGE_PROTECTED) { - unmapExternalTextureBuffer(gBuf); + unmapExternalTextureBuffer(std::move(gBuf)); } } @@ -1309,7 +1310,7 @@ void GLESRenderEngine::drawLayersInternal( checkErrors(); // Chances are, something illegal happened (either the caller passed // us bad parameters, or we messed up our shader generation). - resultPromise->set_value({INVALID_OPERATION, std::move(drawFence)}); + resultPromise->set_value(base::unexpected(INVALID_OPERATION)); return; } mLastDrawFence = nullptr; @@ -1321,8 +1322,7 @@ void GLESRenderEngine::drawLayersInternal( mPriorResourcesCleaned = false; checkErrors(); - resultPromise->set_value({NO_ERROR, std::move(drawFence)}); - return; + resultPromise->set_value(sp::make(std::move(drawFence))); } void GLESRenderEngine::setViewportAndProjection(Rect viewport, Rect clip) { diff --git a/libs/renderengine/gl/GLESRenderEngine.h b/libs/renderengine/gl/GLESRenderEngine.h index 1d7c2cafb54f740e2ae525e36226cad106b0cc05..402ff529d722189277420dd9f87465efccdad155 100644 --- a/libs/renderengine/gl/GLESRenderEngine.h +++ b/libs/renderengine/gl/GLESRenderEngine.h @@ -31,6 +31,7 @@ #include #include #include +#include #include "GLShadowTexture.h" #include "ImageManager.h" @@ -60,7 +61,7 @@ public: std::future primeCache() override; void genTextures(size_t count, uint32_t* names) override; void deleteTextures(size_t count, uint32_t const* names) override; - bool isProtected() const override { return mInProtectedContext; } + bool isProtected() const { return mInProtectedContext; } bool supportsProtectedContent() const override; void useProtectedContext(bool useProtectedContext) override; void cleanupPostRender() override; @@ -100,9 +101,9 @@ protected: size_t getMaxViewportDims() const override; void mapExternalTextureBuffer(const sp& buffer, bool isRenderable) EXCLUDES(mRenderingMutex); - void unmapExternalTextureBuffer(const sp& buffer) EXCLUDES(mRenderingMutex); + void unmapExternalTextureBuffer(sp&& buffer) EXCLUDES(mRenderingMutex); bool canSkipPostRenderCleanup() const override; - void drawLayersInternal(const std::shared_ptr>&& resultPromise, + void drawLayersInternal(const std::shared_ptr>&& resultPromise, const DisplaySettings& display, const std::vector& layers, const std::shared_ptr& buffer, diff --git a/libs/renderengine/gl/ProgramCache.cpp b/libs/renderengine/gl/ProgramCache.cpp index b1bfa3a22e63053c4ad0455448f72260445f82a5..422b070b053206729e5b564557a96cc8f555e214 100644 --- a/libs/renderengine/gl/ProgramCache.cpp +++ b/libs/renderengine/gl/ProgramCache.cpp @@ -601,7 +601,7 @@ String8 ProgramCache::generateFragmentShader(const Key& needs) { } if (needs.hasTextureCoords()) { - fs << "varying vec2 outTexCoords;"; + fs << "varying highp vec2 outTexCoords;"; } if (needs.hasRoundedCorners()) { diff --git a/libs/renderengine/include/renderengine/BorderRenderInfo.h b/libs/renderengine/include/renderengine/BorderRenderInfo.h new file mode 100644 index 0000000000000000000000000000000000000000..0ee6661f33c6adc3c01bc01d7ffc4eddc8b3133a --- /dev/null +++ b/libs/renderengine/include/renderengine/BorderRenderInfo.h @@ -0,0 +1,36 @@ +/* + * 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 59ef991eec68a1530406dc0a935e265764a873d1..8d7c13cb1826020ceffd58fa0cce0b63cefd0db2 100644 --- a/libs/renderengine/include/renderengine/DisplaySettings.h +++ b/libs/renderengine/include/renderengine/DisplaySettings.h @@ -22,17 +22,25 @@ #include #include +#include +#include #include #include #include #include +#include + namespace android { namespace renderengine { // DisplaySettings contains the settings that are applicable when drawing all // layers for a given display. struct DisplaySettings { + // A string containing the name of the display, along with its id, if it has + // one. + std::string namePlusId; + // Rectangle describing the physical display. We will project from the // logical clip onto this rectangle. Rect physicalDisplay = Rect::INVALID_RECT; @@ -79,18 +87,21 @@ 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) { - return lhs.physicalDisplay == rhs.physicalDisplay && lhs.clip == rhs.clip && - lhs.maxLuminance == rhs.maxLuminance && + return lhs.namePlusId == rhs.namePlusId && lhs.physicalDisplay == rhs.physicalDisplay && + lhs.clip == rhs.clip && lhs.maxLuminance == rhs.maxLuminance && lhs.currentLuminanceNits == rhs.currentLuminanceNits && lhs.outputDataspace == rhs.outputDataspace && lhs.colorTransform == rhs.colorTransform && lhs.deviceHandlesColorTransform == rhs.deviceHandlesColorTransform && lhs.orientation == rhs.orientation && lhs.targetLuminanceNits == rhs.targetLuminanceNits && - lhs.dimmingStage == rhs.dimmingStage && lhs.renderIntent == rhs.renderIntent; + lhs.dimmingStage == rhs.dimmingStage && lhs.renderIntent == rhs.renderIntent && + lhs.borderInfoList == rhs.borderInfoList; } static const char* orientation_to_string(uint32_t orientation) { @@ -117,6 +128,7 @@ static const char* orientation_to_string(uint32_t orientation) { static inline void PrintTo(const DisplaySettings& settings, ::std::ostream* os) { *os << "DisplaySettings {"; + *os << "\n .display = " << settings.namePlusId; *os << "\n .physicalDisplay = "; PrintTo(settings.physicalDisplay, os); *os << "\n .clip = "; diff --git a/libs/renderengine/include/renderengine/ExternalTexture.h b/libs/renderengine/include/renderengine/ExternalTexture.h index 621a209afab4df747451da268e42e185d0190c19..82e5d83c30fc12a951524d754efee3729960da4d 100644 --- a/libs/renderengine/include/renderengine/ExternalTexture.h +++ b/libs/renderengine/include/renderengine/ExternalTexture.h @@ -46,6 +46,8 @@ public: // Retrieves the buffer that is bound to this texture. virtual const sp& getBuffer() const = 0; + virtual void remapBuffer() = 0; + Rect getBounds() const { return {0, 0, static_cast(getWidth()), static_cast(getHeight())}; } diff --git a/libs/renderengine/include/renderengine/RenderEngine.h b/libs/renderengine/include/renderengine/RenderEngine.h index 3e7f69ce0201522da60acbecd39995893f8f72c7..0d910c9b296e62f1cbc1c2aa6deef2427af7cdda 100644 --- a/libs/renderengine/include/renderengine/RenderEngine.h +++ b/libs/renderengine/include/renderengine/RenderEngine.h @@ -18,6 +18,7 @@ #define SF_RENDERENGINE_H_ #include +#include #include #include #include @@ -26,6 +27,7 @@ #include #include #include +#include #include #include @@ -68,7 +70,6 @@ class Image; class Mesh; class Texture; struct RenderEngineCreationArgs; -struct RenderEngineResult; namespace threaded { class RenderEngineThreaded; @@ -98,6 +99,8 @@ public: THREADED = 2, SKIA_GL = 3, SKIA_GL_THREADED = 4, + SKIA_VK = 5, + SKIA_VK_THREADED = 6, }; static std::unique_ptr create(const RenderEngineCreationArgs& args); @@ -125,12 +128,8 @@ public: // ----- BEGIN NEW INTERFACE ----- // queries that are required to be thread safe - virtual bool isProtected() const = 0; virtual bool supportsProtectedContent() const = 0; - // Attempt to switch RenderEngine into and out of protectedContext mode - virtual void useProtectedContext(bool useProtectedContext) = 0; - // Notify RenderEngine of changes to the dimensions of the active display // so that it can configure its internal caches accordingly. virtual void onActiveDisplaySizeChanged(ui::Size size) = 0; @@ -158,12 +157,13 @@ public: // parameter does nothing. // @param bufferFence Fence signalling that the buffer is ready to be drawn // to. - // @return A future object of RenderEngineResult struct indicating whether - // drawing was successful in async mode. - virtual std::future drawLayers( - const DisplaySettings& display, const std::vector& layers, - const std::shared_ptr& buffer, const bool useFramebufferCache, - base::unique_fd&& bufferFence); + // @return A future object of FenceResult indicating whether drawing was + // successful in async mode. + virtual ftl::Future drawLayers(const DisplaySettings& display, + const std::vector& layers, + const std::shared_ptr& buffer, + const bool useFramebufferCache, + base::unique_fd&& bufferFence); // Clean-up method that should be called on the main thread after the // drawFence returned by drawLayers fires. This method will free up @@ -172,9 +172,16 @@ public: virtual void cleanupPostRender() = 0; virtual void cleanFramebufferCache() = 0; - // Returns the priority this context was actually created with. Note: this may not be - // the same as specified at context creation time, due to implementation limits on the - // number of contexts that can be created at a specific priority level in the system. + + // Returns the priority this context was actually created with. Note: this + // may not be the same as specified at context creation time, due to + // implementation limits on the number of contexts that can be created at a + // specific priority level in the system. + // + // This should return a valid EGL context priority enum as described by + // https://registry.khronos.org/EGL/extensions/IMG/EGL_IMG_context_priority.txt + // or + // https://registry.khronos.org/EGL/extensions/NV/EGL_NV_context_priority_realtime.txt virtual int getContextPriority() = 0; // Returns true if blur was requested in the RenderEngineCreationArgs and the implementation @@ -224,7 +231,7 @@ protected: // asynchronously, but the caller can expect that map/unmap calls are performed in a manner // that's conflict serializable, i.e. unmap a buffer should never occur before binding the // buffer if the caller called mapExternalTextureBuffer before calling unmap. - virtual void unmapExternalTextureBuffer(const sp& buffer) = 0; + virtual void unmapExternalTextureBuffer(sp&& buffer) = 0; // A thread safe query to determine if any post rendering cleanup is necessary. Returning true // is a signal that calling the postRenderCleanup method would be a no-op and that callers can @@ -236,8 +243,15 @@ protected: friend class RenderEngineTest_cleanupPostRender_cleansUpOnce_Test; const RenderEngineType mRenderEngineType; + // Update protectedContext mode depending on whether or not any layer has a protected buffer. + void updateProtectedContext(const std::vector&, + const std::shared_ptr&); + + // Attempt to switch RenderEngine into and out of protectedContext mode + virtual void useProtectedContext(bool useProtectedContext) = 0; + virtual void drawLayersInternal( - const std::shared_ptr>&& resultPromise, + const std::shared_ptr>&& resultPromise, const DisplaySettings& display, const std::vector& layers, const std::shared_ptr& buffer, const bool useFramebufferCache, base::unique_fd&& bufferFence) = 0; @@ -327,13 +341,6 @@ private: RenderEngine::RenderEngineType::SKIA_GL_THREADED; }; -struct RenderEngineResult { - // status indicates if drawing is successful - status_t status; - // drawFence will fire when the buffer has been drawn to and is ready to be examined. - base::unique_fd drawFence; -}; - } // namespace renderengine } // namespace android diff --git a/libs/renderengine/include/renderengine/impl/ExternalTexture.h b/libs/renderengine/include/renderengine/impl/ExternalTexture.h index c0e24f0c1032002f58d5f5a355f4387ed3fa4880..d30262d9852f62a54a0a0774e433f0750f32d769 100644 --- a/libs/renderengine/include/renderengine/impl/ExternalTexture.h +++ b/libs/renderengine/include/renderengine/impl/ExternalTexture.h @@ -51,10 +51,12 @@ public: bool hasSameBuffer(const renderengine::ExternalTexture& other) const override { return getBuffer() == other.getBuffer(); } + void remapBuffer() override; private: sp mBuffer; android::renderengine::RenderEngine& mRenderEngine; + const bool mWritable; }; } // namespace android::renderengine::impl diff --git a/libs/renderengine/include/renderengine/mock/FakeExternalTexture.h b/libs/renderengine/include/renderengine/mock/FakeExternalTexture.h index 974e0fddde6b724f78781c0787a39d9e5e3687dc..474e2e72c87b25e2acf3ce4827e68fa669d834a7 100644 --- a/libs/renderengine/include/renderengine/mock/FakeExternalTexture.h +++ b/libs/renderengine/include/renderengine/mock/FakeExternalTexture.h @@ -23,7 +23,9 @@ namespace renderengine { namespace mock { class FakeExternalTexture : public renderengine::ExternalTexture { - const sp mNullBuffer = nullptr; + const sp mEmptyBuffer = + sp::make(1u, 1u, PIXEL_FORMAT_RGBA_8888, + GRALLOC_USAGE_SW_WRITE_OFTEN | GRALLOC_USAGE_SW_READ_OFTEN); uint32_t mWidth; uint32_t mHeight; uint64_t mId; @@ -34,7 +36,7 @@ public: FakeExternalTexture(uint32_t width, uint32_t height, uint64_t id, PixelFormat pixelFormat, uint64_t usage) : mWidth(width), mHeight(height), mId(id), mPixelFormat(pixelFormat), mUsage(usage) {} - const sp& getBuffer() const { return mNullBuffer; } + const sp& getBuffer() const { return mEmptyBuffer; } bool hasSameBuffer(const renderengine::ExternalTexture& other) const override { return getId() == other.getId(); } @@ -43,6 +45,7 @@ public: uint64_t getId() const override { return mId; } PixelFormat getPixelFormat() const override { return mPixelFormat; } uint64_t getUsage() const override { return mUsage; } + void remapBuffer() override {} ~FakeExternalTexture() = default; }; diff --git a/libs/renderengine/include/renderengine/mock/RenderEngine.h b/libs/renderengine/include/renderengine/mock/RenderEngine.h index 248bd652c0dfd864b2f1233acf88de6cad79344b..d3035e24a5dd09910ff17198c692ec0526647a72 100644 --- a/libs/renderengine/include/renderengine/mock/RenderEngine.h +++ b/libs/renderengine/include/renderengine/mock/RenderEngine.h @@ -48,14 +48,13 @@ public: MOCK_METHOD0(cleanupPostRender, void()); MOCK_CONST_METHOD0(canSkipPostRenderCleanup, bool()); MOCK_METHOD5(drawLayers, - std::future(const DisplaySettings&, - const std::vector&, - const std::shared_ptr&, - const bool, base::unique_fd&&)); + ftl::Future(const DisplaySettings&, const std::vector&, + const std::shared_ptr&, const bool, + base::unique_fd&&)); MOCK_METHOD6(drawLayersInternal, - void(const std::shared_ptr>&&, - const DisplaySettings&, const std::vector&, - const std::shared_ptr&, const bool, base::unique_fd&&)); + void(const std::shared_ptr>&&, const DisplaySettings&, + const std::vector&, const std::shared_ptr&, + const bool, base::unique_fd&&)); MOCK_METHOD0(cleanFramebufferCache, void()); MOCK_METHOD0(getContextPriority, int()); MOCK_METHOD0(supportsBackgroundBlur, bool()); @@ -64,7 +63,7 @@ public: protected: // mock renderengine still needs to implement these, but callers should never need to call them. void mapExternalTextureBuffer(const sp&, bool) {} - void unmapExternalTextureBuffer(const sp&) {} + void unmapExternalTextureBuffer(sp&&) {} }; } // namespace mock diff --git a/libs/renderengine/skia/AutoBackendTexture.cpp b/libs/renderengine/skia/AutoBackendTexture.cpp index 5c122d4154f8ccce51d77a05c947fc92f9e57548..c412c9cff7e697a3e8a6156a51542c5005358ac8 100644 --- a/libs/renderengine/skia/AutoBackendTexture.cpp +++ b/libs/renderengine/skia/AutoBackendTexture.cpp @@ -43,10 +43,12 @@ AutoBackendTexture::AutoBackendTexture(GrDirectContext* context, AHardwareBuffer createProtectedImage, backendFormat, isOutputBuffer); mColorType = GrAHardwareBufferUtils::GetSkColorTypeFromBufferFormat(desc.format); - ALOGE_IF(!mBackendTexture.isValid(), - "Failed to create a valid texture. [%p]:[%d,%d] isProtected:%d isWriteable:%d " - "format:%d", - this, desc.width, desc.height, createProtectedImage, isOutputBuffer, 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() { @@ -82,6 +84,18 @@ void AutoBackendTexture::releaseImageProc(SkImage::ReleaseContext releaseContext textureRelease->unref(false); } +void logFatalTexture(const char* msg, const GrBackendTexture& tex, ui::Dataspace dataspace, + SkColorType colorType) { + GrGLTextureInfo textureInfo; + bool retrievedTextureInfo = tex.getGLTextureInfo(&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(), dataspace, tex.width(), tex.height(), tex.hasMipmaps(), + tex.isProtected(), static_cast(tex.textureType()), retrievedTextureInfo, + textureInfo.fTarget, textureInfo.fFormat, colorType); +} + sk_sp AutoBackendTexture::makeImage(ui::Dataspace dataspace, SkAlphaType alphaType, GrDirectContext* context) { ATRACE_CALL(); @@ -107,9 +121,9 @@ sk_sp AutoBackendTexture::makeImage(ui::Dataspace dataspace, SkAlphaTyp mImage = image; mDataspace = dataspace; - LOG_ALWAYS_FATAL_IF(mImage == nullptr, - "Unable to generate SkImage. isTextureValid:%d dataspace:%d", - mBackendTexture.isValid(), dataspace); + if (!mImage) { + logFatalTexture("Unable to generate SkImage.", mBackendTexture, dataspace, colorType); + } return mImage; } @@ -131,9 +145,9 @@ sk_sp AutoBackendTexture::getOrCreateSurface(ui::Dataspace dataspace, } mDataspace = dataspace; - LOG_ALWAYS_FATAL_IF(mSurface == nullptr, - "Unable to generate SkSurface. isTextureValid:%d dataspace:%d", - mBackendTexture.isValid(), dataspace); + if (!mSurface) { + logFatalTexture("Unable to generate SkSurface.", mBackendTexture, dataspace, mColorType); + } return mSurface; } diff --git a/libs/renderengine/skia/Cache.cpp b/libs/renderengine/skia/Cache.cpp index c39f0a97fd75b8796326ff53b3c4d49c6663b512..f6b91839a3e34c1a68005c3350d164a04cd3de40 100644 --- a/libs/renderengine/skia/Cache.cpp +++ b/libs/renderengine/skia/Cache.cpp @@ -364,8 +364,8 @@ void Cache::primeShaderCache(SkiaRenderEngine* renderengine) { const int64_t usage = GRALLOC_USAGE_HW_RENDER | GRALLOC_USAGE_HW_TEXTURE; sp dstBuffer = - new GraphicBuffer(displayRect.width(), displayRect.height(), PIXEL_FORMAT_RGBA_8888, - 1, usage, "primeShaderCache_dst"); + sp::make(displayRect.width(), displayRect.height(), + PIXEL_FORMAT_RGBA_8888, 1, usage, "primeShaderCache_dst"); const auto dstTexture = std::make_shared(dstBuffer, *renderengine, @@ -375,8 +375,8 @@ void Cache::primeShaderCache(SkiaRenderEngine* renderengine) { // something, but the details are not important. Make use of the shadow layer drawing step // to populate it. sp srcBuffer = - new GraphicBuffer(displayRect.width(), displayRect.height(), PIXEL_FORMAT_RGBA_8888, - 1, usage, "drawImageLayer_src"); + sp::make(displayRect.width(), displayRect.height(), + PIXEL_FORMAT_RGBA_8888, 1, usage, "drawImageLayer_src"); const auto srcTexture = std::make_shared< impl::ExternalTexture>(srcBuffer, *renderengine, @@ -398,8 +398,9 @@ void Cache::primeShaderCache(SkiaRenderEngine* renderengine) { // GRALLOC_USAGE_HW_TEXTURE should be the same as AHARDWAREBUFFER_USAGE_GPU_SAMPLED_IMAGE. const int64_t usageExternal = GRALLOC_USAGE_HW_TEXTURE; sp externalBuffer = - new GraphicBuffer(displayRect.width(), displayRect.height(), PIXEL_FORMAT_RGBA_8888, - 1, usageExternal, "primeShaderCache_external"); + sp::make(displayRect.width(), displayRect.height(), + PIXEL_FORMAT_RGBA_8888, 1, usageExternal, + "primeShaderCache_external"); const auto externalTexture = std::make_shared(externalBuffer, *renderengine, impl::ExternalTexture::Usage::READABLE); @@ -409,8 +410,9 @@ void Cache::primeShaderCache(SkiaRenderEngine* renderengine) { // Another external texture with a different pixel format triggers useIsOpaqueWorkaround. // It doesn't have to be f16, but it can't be the usual 8888. sp f16ExternalBuffer = - new GraphicBuffer(displayRect.width(), displayRect.height(), PIXEL_FORMAT_RGBA_FP16, - 1, usageExternal, "primeShaderCache_external_f16"); + sp::make(displayRect.width(), displayRect.height(), + PIXEL_FORMAT_RGBA_FP16, 1, usageExternal, + "primeShaderCache_external_f16"); // The F16 texture may not be usable on all devices, so check first that it was created. status_t error = f16ExternalBuffer->initCheck(); if (!error) { diff --git a/libs/renderengine/skia/ColorSpaces.cpp b/libs/renderengine/skia/ColorSpaces.cpp index 37ff5dfedeb32a9ff4dc746e35a5bf50673cd238..92b01e07e6d2780f82b690bd03e547371bb8ada0 100644 --- a/libs/renderengine/skia/ColorSpaces.cpp +++ b/libs/renderengine/skia/ColorSpaces.cpp @@ -21,6 +21,8 @@ namespace renderengine { namespace skia { // please keep in sync with hwui/utils/Color.cpp +// TODO: Scale by the dimming ratio here instead of in a generic 3x3 transform +// Otherwise there may be luminance shift for e.g., HLG. sk_sp toSkColorSpace(ui::Dataspace dataspace) { skcms_Matrix3x3 gamut; switch (dataspace & HAL_DATASPACE_STANDARD_MASK) { @@ -61,13 +63,14 @@ sk_sp toSkColorSpace(ui::Dataspace dataspace) { case HAL_DATASPACE_TRANSFER_GAMMA2_8: return SkColorSpace::MakeRGB({2.8f, 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f}, gamut); case HAL_DATASPACE_TRANSFER_ST2084: - return SkColorSpace::MakeRGB(SkNamedTransferFn::kPQ, gamut); + return SkColorSpace::MakeRGB({-2.f, -1.55522297832f, 1.86045365631f, 32 / 2523.0f, + 2413 / 128.0f, -2392 / 128.0f, 8192 / 1305.0f}, + gamut); case HAL_DATASPACE_TRANSFER_SMPTE_170M: return SkColorSpace::MakeRGB(SkNamedTransferFn::kRec2020, gamut); case HAL_DATASPACE_TRANSFER_HLG: - // return HLG transfer but scale by 1/12 skcms_TransferFunction hlgFn; - if (skcms_TransferFunction_makeScaledHLGish(&hlgFn, 1.f / 12.f, 2.f, 2.f, + if (skcms_TransferFunction_makeScaledHLGish(&hlgFn, 0.314509843, 2.f, 2.f, 1.f / 0.17883277f, 0.28466892f, 0.55991073f)) { return SkColorSpace::MakeRGB(hlgFn, gamut); diff --git a/libs/renderengine/skia/SkiaGLRenderEngine.cpp b/libs/renderengine/skia/SkiaGLRenderEngine.cpp index 0caa9f2fbdd74bf91a73a4e97605883ffde482f1..ff598e7ab57e2ec10527ca02c2ac087f3ce9f1d2 100644 --- a/libs/renderengine/skia/SkiaGLRenderEngine.cpp +++ b/libs/renderengine/skia/SkiaGLRenderEngine.cpp @@ -24,24 +24,11 @@ #include #include #include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include #include #include #include #include -#include -#include #include -#include #include #include @@ -50,25 +37,7 @@ #include #include "../gl/GLExtensions.h" -#include "Cache.h" -#include "ColorSpaces.h" -#include "SkBlendMode.h" -#include "SkImageInfo.h" -#include "filters/BlurFilter.h" -#include "filters/GaussianBlurFilter.h" -#include "filters/KawaseBlurFilter.h" -#include "filters/LinearEffect.h" #include "log/log_main.h" -#include "skia/debug/SkiaCapture.h" -#include "skia/debug/SkiaMemoryReporter.h" -#include "skia/filters/StretchShaderFactory.h" -#include "system/graphics-base-v1.0.h" - -namespace { -// Debugging settings -static const bool kPrintLayerSettings = false; -static const bool kFlushAfterEveryLayer = kPrintLayerSettings; -} // namespace bool checkGlError(const char* op, int lineNumber); @@ -224,9 +193,10 @@ std::unique_ptr SkiaGLRenderEngine::create( } // initialize the renderer while GL is current - std::unique_ptr engine = - std::make_unique(args, display, ctxt, placeholder, protectedContext, - protectedPlaceholder); + std::unique_ptr engine(new SkiaGLRenderEngine(args, display, ctxt, + placeholder, protectedContext, + protectedPlaceholder)); + engine->ensureGrContextsCreated(); ALOGI("OpenGL ES informations:"); ALOGI("vendor : %s", extensions.getVendor()); @@ -239,11 +209,6 @@ std::unique_ptr SkiaGLRenderEngine::create( return engine; } -std::future SkiaGLRenderEngine::primeCache() { - Cache::primeShaderCache(this); - return {}; -} - EGLConfig SkiaGLRenderEngine::chooseEglConfig(EGLDisplay display, int format, bool logConfig) { status_t err; EGLConfig config; @@ -283,72 +248,20 @@ EGLConfig SkiaGLRenderEngine::chooseEglConfig(EGLDisplay display, int format, bo return config; } -sk_sp SkiaGLRenderEngine::SkSLCacheMonitor::load(const SkData& key) { - // This "cache" does not actually cache anything. It just allows us to - // monitor Skia's internal cache. So this method always returns null. - return nullptr; -} - -void SkiaGLRenderEngine::SkSLCacheMonitor::store(const SkData& key, const SkData& data, - const SkString& description) { - mShadersCachedSinceLastCall++; - mTotalShadersCompiled++; - ATRACE_FORMAT("SF cache: %i shaders", mTotalShadersCompiled); -} - -int SkiaGLRenderEngine::reportShadersCompiled() { - return mSkSLCacheMonitor.totalShadersCompiled(); -} - SkiaGLRenderEngine::SkiaGLRenderEngine(const RenderEngineCreationArgs& args, EGLDisplay display, EGLContext ctxt, EGLSurface placeholder, EGLContext protectedContext, EGLSurface protectedPlaceholder) - : SkiaRenderEngine(args.renderEngineType), + : SkiaRenderEngine(args.renderEngineType, + static_cast(args.pixelFormat), + args.useColorManagement, args.supportsBackgroundBlur), mEGLDisplay(display), mEGLContext(ctxt), mPlaceholderSurface(placeholder), mProtectedEGLContext(protectedContext), - mProtectedPlaceholderSurface(protectedPlaceholder), - mDefaultPixelFormat(static_cast(args.pixelFormat)), - mUseColorManagement(args.useColorManagement) { - sk_sp glInterface(GrGLCreateNativeInterface()); - LOG_ALWAYS_FATAL_IF(!glInterface.get()); - - GrContextOptions options; - options.fDisableDriverCorrectnessWorkarounds = true; - options.fDisableDistanceFieldPaths = true; - options.fReducedShaderVariations = true; - options.fPersistentCache = &mSkSLCacheMonitor; - mGrContext = GrDirectContext::MakeGL(glInterface, options); - if (supportsProtectedContent()) { - useProtectedContext(true); - mProtectedGrContext = GrDirectContext::MakeGL(glInterface, options); - useProtectedContext(false); - } - - if (args.supportsBackgroundBlur) { - ALOGD("Background Blurs Enabled"); - mBlurFilter = new KawaseBlurFilter(); - } - mCapture = std::make_unique(); -} + mProtectedPlaceholderSurface(protectedPlaceholder) { } SkiaGLRenderEngine::~SkiaGLRenderEngine() { - std::lock_guard lock(mRenderingMutex); - if (mBlurFilter) { - delete mBlurFilter; - } - - mCapture = nullptr; - - mGrContext->flushAndSubmit(true); - mGrContext->abandonContext(); - - if (mProtectedGrContext) { - mProtectedGrContext->flushAndSubmit(true); - mProtectedGrContext->abandonContext(); - } - + finishRenderingAndAbandonContext(); if (mPlaceholderSurface != EGL_NO_SURFACE) { eglDestroySurface(mEGLDisplay, mPlaceholderSurface); } @@ -366,71 +279,69 @@ SkiaGLRenderEngine::~SkiaGLRenderEngine() { eglReleaseThread(); } -bool SkiaGLRenderEngine::supportsProtectedContent() const { - return mProtectedEGLContext != EGL_NO_CONTEXT; -} +SkiaRenderEngine::Contexts SkiaGLRenderEngine::createDirectContexts( + const GrContextOptions& options) { -GrDirectContext* SkiaGLRenderEngine::getActiveGrContext() const { - return mInProtectedContext ? mProtectedGrContext.get() : mGrContext.get(); -} + LOG_ALWAYS_FATAL_IF(isProtected(), + "Cannot setup contexts while already in protected mode"); -void SkiaGLRenderEngine::useProtectedContext(bool useProtectedContext) { - if (useProtectedContext == mInProtectedContext || - (useProtectedContext && !supportsProtectedContent())) { - return; - } + sk_sp glInterface = GrGLMakeNativeInterface(); - // release any scratch resources before switching into a new mode - if (getActiveGrContext()) { - getActiveGrContext()->purgeUnlockedResources(true); - } + LOG_ALWAYS_FATAL_IF(!glInterface.get(), "GrGLMakeNativeInterface() failed"); - const EGLSurface surface = - useProtectedContext ? mProtectedPlaceholderSurface : mPlaceholderSurface; - const EGLContext context = useProtectedContext ? mProtectedEGLContext : mEGLContext; - - if (eglMakeCurrent(mEGLDisplay, surface, surface, context) == EGL_TRUE) { - mInProtectedContext = useProtectedContext; - // given that we are sharing the same thread between two GrContexts we need to - // make sure that the thread state is reset when switching between the two. - if (getActiveGrContext()) { - getActiveGrContext()->resetContext(); - } + SkiaRenderEngine::Contexts contexts; + contexts.first = GrDirectContext::MakeGL(glInterface, options); + if (supportsProtectedContentImpl()) { + useProtectedContextImpl(GrProtected::kYes); + contexts.second = GrDirectContext::MakeGL(glInterface, options); + useProtectedContextImpl(GrProtected::kNo); } -} -base::unique_fd SkiaGLRenderEngine::flush() { - ATRACE_CALL(); - if (!gl::GLExtensions::getInstance().hasNativeFenceSync()) { - return base::unique_fd(); - } - - EGLSyncKHR sync = eglCreateSyncKHR(mEGLDisplay, EGL_SYNC_NATIVE_FENCE_ANDROID, nullptr); - if (sync == EGL_NO_SYNC_KHR) { - ALOGW("failed to create EGL native fence sync: %#x", eglGetError()); - return base::unique_fd(); - } + return contexts; +} - // native fence fd will not be populated until flush() is done. - glFlush(); +bool SkiaGLRenderEngine::supportsProtectedContentImpl() const { + return mProtectedEGLContext != EGL_NO_CONTEXT; +} - // get the fence fd - base::unique_fd fenceFd(eglDupNativeFenceFDANDROID(mEGLDisplay, sync)); - eglDestroySyncKHR(mEGLDisplay, sync); - if (fenceFd == EGL_NO_NATIVE_FENCE_FD_ANDROID) { - ALOGW("failed to dup EGL native fence sync: %#x", eglGetError()); - } +bool SkiaGLRenderEngine::useProtectedContextImpl(GrProtected isProtected) { + const EGLSurface surface = + (isProtected == GrProtected::kYes) ? + mProtectedPlaceholderSurface : mPlaceholderSurface; + const EGLContext context = (isProtected == GrProtected::kYes) ? + mProtectedEGLContext : mEGLContext; - return fenceFd; + return eglMakeCurrent(mEGLDisplay, surface, surface, context) == EGL_TRUE; } -void SkiaGLRenderEngine::waitFence(base::borrowed_fd fenceFd) { +void SkiaGLRenderEngine::waitFence(GrDirectContext*, 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 drawFence = flush(); + + bool requireSync = drawFence.get() < 0; + if (requireSync) { + ATRACE_BEGIN("Submit(sync=true)"); + } else { + ATRACE_BEGIN("Submit(sync=false)"); + } + bool success = grContext->submit(requireSync); + ATRACE_END(); + if (!success) { + ALOGE("Failed to flush RenderEngine commands"); + // Chances are, something illegal happened (Skia's internal GPU object + // doesn't exist, or the context was abandoned). + return drawFence; + } + + return drawFence; +} + bool SkiaGLRenderEngine::waitGpuFence(base::borrowed_fd fenceFd) { if (!gl::GLExtensions::getInstance().hasNativeFenceSync() || !gl::GLExtensions::getInstance().hasWaitSync()) { @@ -466,960 +377,29 @@ bool SkiaGLRenderEngine::waitGpuFence(base::borrowed_fd fenceFd) { return true; } -static float toDegrees(uint32_t transform) { - switch (transform) { - case ui::Transform::ROT_90: - return 90.0; - case ui::Transform::ROT_180: - return 180.0; - case ui::Transform::ROT_270: - return 270.0; - default: - return 0.0; - } -} - -static SkColorMatrix toSkColorMatrix(const mat4& matrix) { - return SkColorMatrix(matrix[0][0], matrix[1][0], matrix[2][0], matrix[3][0], 0, matrix[0][1], - matrix[1][1], matrix[2][1], matrix[3][1], 0, matrix[0][2], matrix[1][2], - matrix[2][2], matrix[3][2], 0, matrix[0][3], matrix[1][3], matrix[2][3], - matrix[3][3], 0); -} - -static bool needsToneMapping(ui::Dataspace sourceDataspace, ui::Dataspace destinationDataspace) { - int64_t sourceTransfer = sourceDataspace & HAL_DATASPACE_TRANSFER_MASK; - int64_t destTransfer = destinationDataspace & HAL_DATASPACE_TRANSFER_MASK; - - // Treat unsupported dataspaces as srgb - if (destTransfer != HAL_DATASPACE_TRANSFER_LINEAR && - destTransfer != HAL_DATASPACE_TRANSFER_HLG && - destTransfer != HAL_DATASPACE_TRANSFER_ST2084) { - destTransfer = HAL_DATASPACE_TRANSFER_SRGB; - } - - if (sourceTransfer != HAL_DATASPACE_TRANSFER_LINEAR && - sourceTransfer != HAL_DATASPACE_TRANSFER_HLG && - sourceTransfer != HAL_DATASPACE_TRANSFER_ST2084) { - sourceTransfer = HAL_DATASPACE_TRANSFER_SRGB; - } - - const bool isSourceLinear = sourceTransfer == HAL_DATASPACE_TRANSFER_LINEAR; - const bool isSourceSRGB = sourceTransfer == HAL_DATASPACE_TRANSFER_SRGB; - const bool isDestLinear = destTransfer == HAL_DATASPACE_TRANSFER_LINEAR; - const bool isDestSRGB = destTransfer == HAL_DATASPACE_TRANSFER_SRGB; - - return !(isSourceLinear && isDestSRGB) && !(isSourceSRGB && isDestLinear) && - sourceTransfer != destTransfer; -} - -void SkiaGLRenderEngine::mapExternalTextureBuffer(const sp& buffer, - bool isRenderable) { - // Only run this if RE is running on its own thread. This way the access to GL - // operations is guaranteed to be happening on the same thread. - if (mRenderEngineType != RenderEngineType::SKIA_GL_THREADED) { - return; - } - // We currently don't attempt to map a buffer if the buffer contains protected content - // because GPU resources for protected buffers is much more limited. - const bool isProtectedBuffer = buffer->getUsage() & GRALLOC_USAGE_PROTECTED; - if (isProtectedBuffer) { - return; - } - ATRACE_CALL(); - - // If we were to support caching protected buffers then we will need to switch the - // currently bound context if we are not already using the protected context (and subsequently - // 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& cache = mTextureCache; - - std::lock_guard lock(mRenderingMutex); - mGraphicBufferExternalRefs[buffer->getId()]++; - - if (const auto& iter = cache.find(buffer->getId()); iter == cache.end()) { - std::shared_ptr imageTextureRef = - std::make_shared(grContext, - buffer->toAHardwareBuffer(), - isRenderable, mTextureCleanupMgr); - cache.insert({buffer->getId(), imageTextureRef}); - } -} - -void SkiaGLRenderEngine::unmapExternalTextureBuffer(const sp& buffer) { - ATRACE_CALL(); - std::lock_guard lock(mRenderingMutex); - if (const auto& iter = mGraphicBufferExternalRefs.find(buffer->getId()); - iter != mGraphicBufferExternalRefs.end()) { - if (iter->second == 0) { - ALOGW("Attempted to unmap GraphicBuffer from RenderEngine texture, but the " - "ref count was already zero!", - buffer->getId()); - mGraphicBufferExternalRefs.erase(buffer->getId()); - return; - } - - iter->second--; - - // Swap contexts if needed prior to deleting this buffer - // See Issue 1 of - // https://www.khronos.org/registry/EGL/extensions/EXT/EGL_EXT_protected_content.txt: even - // when a protected context and an unprotected context are part of the same share group, - // protected surfaces may not be accessed by an unprotected context, implying that protected - // surfaces may only be freed when a protected context is active. - const bool inProtected = mInProtectedContext; - useProtectedContext(buffer->getUsage() & GRALLOC_USAGE_PROTECTED); - - if (iter->second == 0) { - mTextureCache.erase(buffer->getId()); - mGraphicBufferExternalRefs.erase(buffer->getId()); - } - - // Swap back to the previous context so that cached values of isProtected in SurfaceFlinger - // are up-to-date. - if (inProtected != mInProtectedContext) { - useProtectedContext(inProtected); - } - } -} - -bool SkiaGLRenderEngine::canSkipPostRenderCleanup() const { - std::lock_guard lock(mRenderingMutex); - return mTextureCleanupMgr.isEmpty(); -} - -void SkiaGLRenderEngine::cleanupPostRender() { +base::unique_fd SkiaGLRenderEngine::flush() { ATRACE_CALL(); - std::lock_guard lock(mRenderingMutex); - mTextureCleanupMgr.cleanup(); -} - -// Helper class intended to be used on the stack to ensure that texture cleanup -// is deferred until after this class goes out of scope. -class DeferTextureCleanup final { -public: - DeferTextureCleanup(AutoBackendTexture::CleanupManager& mgr) : mMgr(mgr) { - mMgr.setDeferredStatus(true); - } - ~DeferTextureCleanup() { mMgr.setDeferredStatus(false); } - -private: - DISALLOW_COPY_AND_ASSIGN(DeferTextureCleanup); - AutoBackendTexture::CleanupManager& mMgr; -}; - -sk_sp SkiaGLRenderEngine::createRuntimeEffectShader( - const RuntimeEffectShaderParameters& parameters) { - // The given surface will be stretched by HWUI via matrix transformation - // which gets similar results for most surfaces - // Determine later on if we need to leverage the stertch shader within - // surface flinger - const auto& stretchEffect = parameters.layer.stretchEffect; - 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); - } - } - - if (parameters.requiresLinearEffect) { - const ui::Dataspace inputDataspace = mUseColorManagement ? parameters.layer.sourceDataspace - : ui::Dataspace::V0_SRGB_LINEAR; - const ui::Dataspace outputDataspace = mUseColorManagement - ? parameters.display.outputDataspace - : ui::Dataspace::V0_SRGB_LINEAR; - - auto effect = - shaders::LinearEffect{.inputDataspace = inputDataspace, - .outputDataspace = outputDataspace, - .undoPremultipliedAlpha = parameters.undoPremultipliedAlpha}; - - auto effectIter = mRuntimeEffects.find(effect); - sk_sp runtimeEffect = nullptr; - if (effectIter == mRuntimeEffects.end()) { - runtimeEffect = buildRuntimeEffect(effect); - mRuntimeEffects.insert({effect, runtimeEffect}); - } else { - runtimeEffect = effectIter->second; - } - mat4 colorTransform = parameters.layer.colorTransform; - - colorTransform *= - mat4::scale(vec4(parameters.layerDimmingRatio, parameters.layerDimmingRatio, - parameters.layerDimmingRatio, 1.f)); - const auto targetBuffer = parameters.layer.source.buffer.buffer; - const auto graphicBuffer = targetBuffer ? targetBuffer->getBuffer() : nullptr; - const auto hardwareBuffer = graphicBuffer ? graphicBuffer->toAHardwareBuffer() : nullptr; - return createLinearEffectShader(parameters.shader, effect, runtimeEffect, colorTransform, - parameters.display.maxLuminance, - parameters.display.currentLuminanceNits, - parameters.layer.source.buffer.maxLuminanceNits, - hardwareBuffer, parameters.display.renderIntent); - } - return parameters.shader; -} - -void SkiaGLRenderEngine::initCanvas(SkCanvas* canvas, const DisplaySettings& display) { - if (CC_UNLIKELY(mCapture->isCaptureRunning())) { - // Record display settings when capture is running. - std::stringstream displaySettings; - PrintTo(display, &displaySettings); - // Store the DisplaySettings in additional information. - canvas->drawAnnotation(SkRect::MakeEmpty(), "DisplaySettings", - SkData::MakeWithCString(displaySettings.str().c_str())); - } - - // Before doing any drawing, let's make sure that we'll start at the origin of the display. - // Some displays don't start at 0,0 for example when we're mirroring the screen. Also, virtual - // displays might have different scaling when compared to the physical screen. - - canvas->clipRect(getSkRect(display.physicalDisplay)); - canvas->translate(display.physicalDisplay.left, display.physicalDisplay.top); - - const auto clipWidth = display.clip.width(); - const auto clipHeight = display.clip.height(); - auto rotatedClipWidth = clipWidth; - auto rotatedClipHeight = clipHeight; - // Scale is contingent on the rotation result. - if (display.orientation & ui::Transform::ROT_90) { - std::swap(rotatedClipWidth, rotatedClipHeight); - } - const auto scaleX = static_cast(display.physicalDisplay.width()) / - static_cast(rotatedClipWidth); - const auto scaleY = static_cast(display.physicalDisplay.height()) / - static_cast(rotatedClipHeight); - canvas->scale(scaleX, scaleY); - - // Canvas rotation is done by centering the clip window at the origin, rotating, translating - // back so that the top left corner of the clip is at (0, 0). - canvas->translate(rotatedClipWidth / 2, rotatedClipHeight / 2); - canvas->rotate(toDegrees(display.orientation)); - canvas->translate(-clipWidth / 2, -clipHeight / 2); - canvas->translate(-display.clip.left, -display.clip.top); -} - -class AutoSaveRestore { -public: - AutoSaveRestore(SkCanvas* canvas) : mCanvas(canvas) { mSaveCount = canvas->save(); } - ~AutoSaveRestore() { restore(); } - void replace(SkCanvas* canvas) { - mCanvas = canvas; - mSaveCount = canvas->save(); - } - void restore() { - if (mCanvas) { - mCanvas->restoreToCount(mSaveCount); - mCanvas = nullptr; - } - } - -private: - SkCanvas* mCanvas; - int mSaveCount; -}; - -static SkRRect getBlurRRect(const BlurRegion& region) { - const auto rect = SkRect::MakeLTRB(region.left, region.top, region.right, region.bottom); - const SkVector radii[4] = {SkVector::Make(region.cornerRadiusTL, region.cornerRadiusTL), - SkVector::Make(region.cornerRadiusTR, region.cornerRadiusTR), - SkVector::Make(region.cornerRadiusBR, region.cornerRadiusBR), - SkVector::Make(region.cornerRadiusBL, region.cornerRadiusBL)}; - SkRRect roundedRect; - roundedRect.setRectRadii(rect, radii); - return roundedRect; -} - -// Arbitrary default margin which should be close enough to zero. -constexpr float kDefaultMargin = 0.0001f; -static bool equalsWithinMargin(float expected, float value, float margin = kDefaultMargin) { - LOG_ALWAYS_FATAL_IF(margin < 0.f, "Margin is negative!"); - return std::abs(expected - value) < margin; -} - -namespace { -template -void logSettings(const T& t) { - std::stringstream stream; - PrintTo(t, &stream); - auto string = stream.str(); - size_t pos = 0; - // Perfetto ignores \n, so split up manually into separate ALOGD statements. - const size_t size = string.size(); - while (pos < size) { - const size_t end = std::min(string.find("\n", pos), size); - ALOGD("%s", string.substr(pos, end - pos).c_str()); - pos = end + 1; - } -} -} // namespace - -void SkiaGLRenderEngine::drawLayersInternal( - const std::shared_ptr>&& resultPromise, - const DisplaySettings& display, const std::vector& layers, - const std::shared_ptr& buffer, const bool /*useFramebufferCache*/, - base::unique_fd&& bufferFence) { - ATRACE_NAME("SkiaGL::drawLayers"); - - std::lock_guard lock(mRenderingMutex); - if (layers.empty()) { - ALOGV("Drawing empty layer stack"); - resultPromise->set_value({NO_ERROR, base::unique_fd()}); - return; - } - - if (buffer == nullptr) { - ALOGE("No output buffer provided. Aborting GPU composition."); - resultPromise->set_value({BAD_VALUE, base::unique_fd()}); - return; - } - - validateOutputBufferUsage(buffer->getBuffer()); - - auto grContext = getActiveGrContext(); - auto& cache = mTextureCache; - - // any AutoBackendTexture deletions will now be deferred until cleanupPostRender is called - DeferTextureCleanup dtc(mTextureCleanupMgr); - - std::shared_ptr surfaceTextureRef; - if (const auto& it = cache.find(buffer->getBuffer()->getId()); it != cache.end()) { - surfaceTextureRef = it->second; - } else { - surfaceTextureRef = - std::make_shared(grContext, - buffer->getBuffer() - ->toAHardwareBuffer(), - true, mTextureCleanupMgr); - } - - // wait on the buffer to be ready to use prior to using it - waitFence(bufferFence); - - const ui::Dataspace dstDataspace = - mUseColorManagement ? display.outputDataspace : ui::Dataspace::V0_SRGB_LINEAR; - sk_sp dstSurface = surfaceTextureRef->getOrCreateSurface(dstDataspace, grContext); - - SkCanvas* dstCanvas = mCapture->tryCapture(dstSurface.get()); - if (dstCanvas == nullptr) { - ALOGE("Cannot acquire canvas from Skia."); - resultPromise->set_value({BAD_VALUE, base::unique_fd()}); - return; - } - - // setup color filter if necessary - sk_sp displayColorTransform; - if (display.colorTransform != mat4() && !display.deviceHandlesColorTransform) { - displayColorTransform = SkColorFilters::Matrix(toSkColorMatrix(display.colorTransform)); - } - const bool ctModifiesAlpha = - displayColorTransform && !displayColorTransform->isAlphaUnchanged(); - - // Find the max layer white point to determine the max luminance of the scene... - const float maxLayerWhitePoint = std::transform_reduce( - layers.cbegin(), layers.cend(), 0.f, - [](float left, float right) { return std::max(left, right); }, - [&](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 - ? maxLayerWhitePoint / display.targetLuminanceNits - : 1.f; - - // Find if any layers have requested blur, we'll use that info to decide when to render to an - // offscreen buffer and when to render to the native buffer. - sk_sp activeSurface(dstSurface); - SkCanvas* canvas = dstCanvas; - SkiaCapture::OffscreenState offscreenCaptureState; - const LayerSettings* blurCompositionLayer = nullptr; - if (mBlurFilter) { - bool requiresCompositionLayer = false; - for (const auto& layer : layers) { - // if the layer doesn't have blur or it is not visible then continue - if (!layerHasBlur(layer, ctModifiesAlpha)) { - continue; - } - if (layer.backgroundBlurRadius > 0 && - layer.backgroundBlurRadius < mBlurFilter->getMaxCrossFadeRadius()) { - requiresCompositionLayer = true; - } - for (auto region : layer.blurRegions) { - if (region.blurRadius < mBlurFilter->getMaxCrossFadeRadius()) { - requiresCompositionLayer = true; - } - } - if (requiresCompositionLayer) { - activeSurface = dstSurface->makeSurface(dstSurface->imageInfo()); - canvas = mCapture->tryOffscreenCapture(activeSurface.get(), &offscreenCaptureState); - blurCompositionLayer = &layer; - break; - } - } - } - - AutoSaveRestore surfaceAutoSaveRestore(canvas); - // Clear the entire canvas with a transparent black to prevent ghost images. - canvas->clear(SK_ColorTRANSPARENT); - initCanvas(canvas, display); - - if (kPrintLayerSettings) { - logSettings(display); - } - for (const auto& layer : layers) { - ATRACE_FORMAT("DrawLayer: %s", layer.name.c_str()); - - if (kPrintLayerSettings) { - logSettings(layer); - } - - sk_sp blurInput; - if (blurCompositionLayer == &layer) { - LOG_ALWAYS_FATAL_IF(activeSurface == dstSurface); - LOG_ALWAYS_FATAL_IF(canvas == dstCanvas); - - // save a snapshot of the activeSurface to use as input to the blur shaders - blurInput = activeSurface->makeImageSnapshot(); - - // blit the offscreen framebuffer into the destination AHB, but only - // if there are blur regions. backgroundBlurRadius blurs the entire - // image below, so it can skip this step. - if (layer.blurRegions.size()) { - SkPaint paint; - paint.setBlendMode(SkBlendMode::kSrc); - if (CC_UNLIKELY(mCapture->isCaptureRunning())) { - uint64_t id = mCapture->endOffscreenCapture(&offscreenCaptureState); - dstCanvas->drawAnnotation(SkRect::Make(dstCanvas->imageInfo().dimensions()), - String8::format("SurfaceID|%" PRId64, id).c_str(), - nullptr); - dstCanvas->drawImage(blurInput, 0, 0, SkSamplingOptions(), &paint); - } else { - activeSurface->draw(dstCanvas, 0, 0, SkSamplingOptions(), &paint); - } - } - - // assign dstCanvas to canvas and ensure that the canvas state is up to date - canvas = dstCanvas; - surfaceAutoSaveRestore.replace(canvas); - initCanvas(canvas, display); - - LOG_ALWAYS_FATAL_IF(activeSurface->getCanvas()->getSaveCount() != - dstSurface->getCanvas()->getSaveCount()); - LOG_ALWAYS_FATAL_IF(activeSurface->getCanvas()->getTotalMatrix() != - dstSurface->getCanvas()->getTotalMatrix()); - - // assign dstSurface to activeSurface - activeSurface = dstSurface; - } - - SkAutoCanvasRestore layerAutoSaveRestore(canvas, true); - if (CC_UNLIKELY(mCapture->isCaptureRunning())) { - // Record the name of the layer if the capture is running. - std::stringstream layerSettings; - PrintTo(layer, &layerSettings); - // Store the LayerSettings in additional information. - canvas->drawAnnotation(SkRect::MakeEmpty(), layer.name.c_str(), - SkData::MakeWithCString(layerSettings.str().c_str())); - } - // Layers have a local transform that should be applied to them - canvas->concat(getSkM44(layer.geometry.positionTransform).asM33()); - - const auto [bounds, roundRectClip] = - getBoundsAndClip(layer.geometry.boundaries, layer.geometry.roundedCornersCrop, - layer.geometry.roundedCornersRadius); - if (mBlurFilter && layerHasBlur(layer, ctModifiesAlpha)) { - std::unordered_map> cachedBlurs; - - // if multiple layers have blur, then we need to take a snapshot now because - // only the lowest layer will have blurImage populated earlier - if (!blurInput) { - blurInput = activeSurface->makeImageSnapshot(); - } - // rect to be blurred in the coordinate space of blurInput - const auto blurRect = canvas->getTotalMatrix().mapRect(bounds.rect()); - - // if the clip needs to be applied then apply it now and make sure - // it is restored before we attempt to draw any shadows. - SkAutoCanvasRestore acr(canvas, true); - if (!roundRectClip.isEmpty()) { - canvas->clipRRect(roundRectClip, true); - } - - // TODO(b/182216890): Filter out empty layers earlier - if (blurRect.width() > 0 && blurRect.height() > 0) { - if (layer.backgroundBlurRadius > 0) { - ATRACE_NAME("BackgroundBlur"); - auto blurredImage = mBlurFilter->generate(grContext, layer.backgroundBlurRadius, - blurInput, blurRect); - - cachedBlurs[layer.backgroundBlurRadius] = blurredImage; - - mBlurFilter->drawBlurRegion(canvas, bounds, layer.backgroundBlurRadius, 1.0f, - blurRect, blurredImage, blurInput); - } - - canvas->concat(getSkM44(layer.blurRegionTransform).asM33()); - for (auto region : layer.blurRegions) { - if (cachedBlurs[region.blurRadius] == nullptr) { - ATRACE_NAME("BlurRegion"); - cachedBlurs[region.blurRadius] = - mBlurFilter->generate(grContext, region.blurRadius, blurInput, - blurRect); - } - - mBlurFilter->drawBlurRegion(canvas, getBlurRRect(region), region.blurRadius, - region.alpha, blurRect, - cachedBlurs[region.blurRadius], blurInput); - } - } - } - - if (layer.shadow.length > 0) { - // This would require a new parameter/flag to SkShadowUtils::DrawShadow - LOG_ALWAYS_FATAL_IF(layer.disableBlending, "Cannot disableBlending with a shadow"); - - SkRRect shadowBounds, shadowClip; - if (layer.geometry.boundaries == layer.shadow.boundaries) { - shadowBounds = bounds; - shadowClip = roundRectClip; - } else { - std::tie(shadowBounds, shadowClip) = - getBoundsAndClip(layer.shadow.boundaries, layer.geometry.roundedCornersCrop, - layer.geometry.roundedCornersRadius); - } - - // Technically, if bounds is a rect and roundRectClip is not empty, - // it means that the bounds and roundedCornersCrop were different - // enough that we should intersect them to find the proper shadow. - // In practice, this often happens when the two rectangles appear to - // not match due to rounding errors. Draw the rounded version, which - // looks more like the intent. - const auto& rrect = - shadowBounds.isRect() && !shadowClip.isEmpty() ? shadowClip : shadowBounds; - drawShadow(canvas, rrect, layer.shadow); - } - - const float layerDimmingRatio = layer.whitePointNits <= 0.f - ? displayDimmingRatio - : (layer.whitePointNits / maxLayerWhitePoint) * displayDimmingRatio; - - const bool dimInLinearSpace = display.dimmingStage != - aidl::android::hardware::graphics::composer3::DimmingStage::GAMMA_OETF; - - const bool requiresLinearEffect = layer.colorTransform != mat4() || - (mUseColorManagement && - needsToneMapping(layer.sourceDataspace, display.outputDataspace)) || - (dimInLinearSpace && !equalsWithinMargin(1.f, layerDimmingRatio)); - - // quick abort from drawing the remaining portion of the layer - if (layer.skipContentDraw || - (layer.alpha == 0 && !requiresLinearEffect && !layer.disableBlending && - (!displayColorTransform || displayColorTransform->isAlphaUnchanged()))) { - continue; - } - - // If we need to map to linear space or color management is disabled, then mark the source - // image with the same colorspace as the destination surface so that Skia's color - // management is a no-op. - const ui::Dataspace layerDataspace = (!mUseColorManagement || requiresLinearEffect) - ? dstDataspace - : layer.sourceDataspace; - - SkPaint paint; - if (layer.source.buffer.buffer) { - ATRACE_NAME("DrawImage"); - validateInputBufferUsage(layer.source.buffer.buffer->getBuffer()); - const auto& item = layer.source.buffer; - std::shared_ptr imageTextureRef = nullptr; - - if (const auto& iter = cache.find(item.buffer->getBuffer()->getId()); - iter != cache.end()) { - imageTextureRef = iter->second; - } else { - // If we didn't find the image in the cache, then create a local ref but don't cache - // it. If we're using skia, we're guaranteed to run on a dedicated GPU thread so if - // we didn't find anything in the cache then we intentionally did not cache this - // buffer's resources. - imageTextureRef = std::make_shared< - AutoBackendTexture::LocalRef>(grContext, - item.buffer->getBuffer()->toAHardwareBuffer(), - false, mTextureCleanupMgr); - } - - // 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(layer.source.buffer.fence->get()); - } - - // isOpaque means we need to ignore the alpha in the image, - // replacing it with the alpha specified by the LayerSettings. See - // https://developer.android.com/reference/android/view/SurfaceControl.Builder#setOpaque(boolean) - // The proper way to do this is to use an SkColorType that ignores - // alpha, like kRGB_888x_SkColorType, and that is used if the - // incoming image is kRGBA_8888_SkColorType. However, the incoming - // image may be kRGBA_F16_SkColorType, for which there is no RGBX - // SkColorType, or kRGBA_1010102_SkColorType, for which we have - // kRGB_101010x_SkColorType, but it is not yet supported as a source - // on the GPU. (Adding both is tracked in skbug.com/12048.) In the - // meantime, we'll use a workaround that works unless we need to do - // any color conversion. The workaround requires that we pretend the - // image is already premultiplied, so that we do not premultiply it - // before applying SkBlendMode::kPlus. - const bool useIsOpaqueWorkaround = item.isOpaque && - (imageTextureRef->colorType() == kRGBA_1010102_SkColorType || - imageTextureRef->colorType() == kRGBA_F16_SkColorType); - const auto alphaType = useIsOpaqueWorkaround ? kPremul_SkAlphaType - : item.isOpaque ? kOpaque_SkAlphaType - : item.usePremultipliedAlpha ? kPremul_SkAlphaType - : kUnpremul_SkAlphaType; - sk_sp image = imageTextureRef->makeImage(layerDataspace, alphaType, grContext); - - auto texMatrix = getSkM44(item.textureTransform).asM33(); - // textureTansform was intended to be passed directly into a shader, so when - // building the total matrix with the textureTransform we need to first - // normalize it, then apply the textureTransform, then scale back up. - texMatrix.preScale(1.0f / bounds.width(), 1.0f / bounds.height()); - texMatrix.postScale(image->width(), image->height()); - - SkMatrix matrix; - if (!texMatrix.invert(&matrix)) { - matrix = texMatrix; - } - // The shader does not respect the translation, so we add it to the texture - // transform for the SkImage. This will make sure that the correct layer contents - // are drawn in the correct part of the screen. - matrix.postTranslate(bounds.rect().fLeft, bounds.rect().fTop); - - sk_sp shader; - - if (layer.source.buffer.useTextureFiltering) { - shader = image->makeShader(SkTileMode::kClamp, SkTileMode::kClamp, - SkSamplingOptions( - {SkFilterMode::kLinear, SkMipmapMode::kNone}), - &matrix); - } else { - shader = image->makeShader(SkSamplingOptions(), matrix); - } - - if (useIsOpaqueWorkaround) { - shader = SkShaders::Blend(SkBlendMode::kPlus, shader, - SkShaders::Color(SkColors::kBlack, - toSkColorSpace(layerDataspace))); - } - - paint.setShader(createRuntimeEffectShader( - RuntimeEffectShaderParameters{.shader = shader, - .layer = layer, - .display = display, - .undoPremultipliedAlpha = !item.isOpaque && - item.usePremultipliedAlpha, - .requiresLinearEffect = requiresLinearEffect, - .layerDimmingRatio = dimInLinearSpace - ? layerDimmingRatio - : 1.f})); - - // Turn on dithering when dimming beyond this (arbitrary) threshold... - static constexpr float kDimmingThreshold = 0.2f; - // ...or we're rendering an HDR layer down to an 8-bit target - // Most HDR standards require at least 10-bits of color depth for source content, so we - // can just extract the transfer function rather than dig into precise gralloc layout. - // Furthermore, we can assume that the only 8-bit target we support is RGBA8888. - const bool requiresDownsample = isHdrDataspace(layer.sourceDataspace) && - buffer->getPixelFormat() == PIXEL_FORMAT_RGBA_8888; - if (layerDimmingRatio <= kDimmingThreshold || requiresDownsample) { - paint.setDither(true); - } - paint.setAlphaf(layer.alpha); - - if (imageTextureRef->colorType() == kAlpha_8_SkColorType) { - LOG_ALWAYS_FATAL_IF(layer.disableBlending, "Cannot disableBlending with A8"); - - // SysUI creates the alpha layer as a coverage layer, which is - // appropriate for the DPU. Use a color matrix to convert it to - // a mask. - // TODO (b/219525258): Handle input as a mask. - // - // The color matrix will convert A8 pixels with no alpha to - // black, as described by this vector. If the display handles - // the color transform, we need to invert it to find the color - // that will result in black after the DPU applies the transform. - SkV4 black{0.0f, 0.0f, 0.0f, 1.0f}; // r, g, b, a - if (display.colorTransform != mat4() && display.deviceHandlesColorTransform) { - SkM44 colorSpaceMatrix = getSkM44(display.colorTransform); - if (colorSpaceMatrix.invert(&colorSpaceMatrix)) { - black = colorSpaceMatrix * black; - } else { - // We'll just have to use 0,0,0 as black, which should - // be close to correct. - ALOGI("Could not invert colorTransform!"); - } - } - SkColorMatrix colorMatrix(0, 0, 0, 0, black[0], - 0, 0, 0, 0, black[1], - 0, 0, 0, 0, black[2], - 0, 0, 0, -1, 1); - if (display.colorTransform != mat4() && !display.deviceHandlesColorTransform) { - // On the other hand, if the device doesn't handle it, we - // have to apply it ourselves. - colorMatrix.postConcat(toSkColorMatrix(display.colorTransform)); - } - paint.setColorFilter(SkColorFilters::Matrix(colorMatrix)); - } - } else { - ATRACE_NAME("DrawColor"); - const auto color = layer.source.solidColor; - sk_sp shader = SkShaders::Color(SkColor4f{.fR = color.r, - .fG = color.g, - .fB = color.b, - .fA = layer.alpha}, - toSkColorSpace(layerDataspace)); - paint.setShader(createRuntimeEffectShader( - RuntimeEffectShaderParameters{.shader = shader, - .layer = layer, - .display = display, - .undoPremultipliedAlpha = false, - .requiresLinearEffect = requiresLinearEffect, - .layerDimmingRatio = layerDimmingRatio})); - } - - if (layer.disableBlending) { - paint.setBlendMode(SkBlendMode::kSrc); - } - - // An A8 buffer will already have the proper color filter attached to - // its paint, including the displayColorTransform as needed. - if (!paint.getColorFilter()) { - if (!dimInLinearSpace && !equalsWithinMargin(1.0, layerDimmingRatio)) { - // If we don't dim in linear space, then when we gamma correct the dimming ratio we - // can assume a gamma 2.2 transfer function. - static constexpr float kInverseGamma22 = 1.f / 2.2f; - const auto gammaCorrectedDimmingRatio = - std::pow(layerDimmingRatio, kInverseGamma22); - auto dimmingMatrix = - mat4::scale(vec4(gammaCorrectedDimmingRatio, gammaCorrectedDimmingRatio, - gammaCorrectedDimmingRatio, 1.f)); - - const auto colorFilter = - SkColorFilters::Matrix(toSkColorMatrix(std::move(dimmingMatrix))); - paint.setColorFilter(displayColorTransform - ? displayColorTransform->makeComposed(colorFilter) - : colorFilter); - } else { - paint.setColorFilter(displayColorTransform); - } - } - - if (!roundRectClip.isEmpty()) { - canvas->clipRRect(roundRectClip, true); - } - - if (!bounds.isRect()) { - paint.setAntiAlias(true); - canvas->drawRRect(bounds, paint); - } else { - canvas->drawRect(bounds.rect(), paint); - } - if (kFlushAfterEveryLayer) { - ATRACE_NAME("flush surface"); - activeSurface->flush(); - } - } - surfaceAutoSaveRestore.restore(); - mCapture->endCapture(); - { - ATRACE_NAME("flush surface"); - LOG_ALWAYS_FATAL_IF(activeSurface != dstSurface); - activeSurface->flush(); - } - - base::unique_fd drawFence = flush(); - - // If flush failed or we don't support native fences, we need to force the - // gl command stream to be executed. - bool requireSync = drawFence.get() < 0; - if (requireSync) { - ATRACE_BEGIN("Submit(sync=true)"); - } else { - ATRACE_BEGIN("Submit(sync=false)"); - } - bool success = grContext->submit(requireSync); - ATRACE_END(); - if (!success) { - ALOGE("Failed to flush RenderEngine commands"); - // Chances are, something illegal happened (either the caller passed - // us bad parameters, or we messed up our shader generation). - resultPromise->set_value({INVALID_OPERATION, std::move(drawFence)}); - return; - } - - // checkErrors(); - resultPromise->set_value({NO_ERROR, std::move(drawFence)}); - return; -} - -inline SkRect SkiaGLRenderEngine::getSkRect(const FloatRect& rect) { - return SkRect::MakeLTRB(rect.left, rect.top, rect.right, rect.bottom); -} - -inline SkRect SkiaGLRenderEngine::getSkRect(const Rect& rect) { - return SkRect::MakeLTRB(rect.left, rect.top, rect.right, rect.bottom); -} - -/** - * Verifies that common, simple bounds + clip combinations can be converted into - * a single RRect draw call returning true if possible. If true the radii parameter - * will be filled with the correct radii values that combined with bounds param will - * produce the insected roundRect. If false, the returned state of the radii param is undefined. - */ -static bool intersectionIsRoundRect(const SkRect& bounds, const SkRect& crop, - const SkRect& insetCrop, const vec2& cornerRadius, - SkVector radii[4]) { - const bool leftEqual = bounds.fLeft == crop.fLeft; - const bool topEqual = bounds.fTop == crop.fTop; - const bool rightEqual = bounds.fRight == crop.fRight; - const bool bottomEqual = bounds.fBottom == crop.fBottom; - - // In the event that the corners of the bounds only partially align with the crop we - // need to ensure that the resulting shape can still be represented as a round rect. - // In particular the round rect implementation will scale the value of all corner radii - // if the sum of the radius along any edge is greater than the length of that edge. - // See https://www.w3.org/TR/css-backgrounds-3/#corner-overlap - const bool requiredWidth = bounds.width() > (cornerRadius.x * 2); - const bool requiredHeight = bounds.height() > (cornerRadius.y * 2); - if (!requiredWidth || !requiredHeight) { - return false; - } - - // Check each cropped corner to ensure that it exactly matches the crop or its corner is - // contained within the cropped shape and does not need rounded. - // compute the UpperLeft corner radius - if (leftEqual && topEqual) { - radii[0].set(cornerRadius.x, cornerRadius.y); - } else if ((leftEqual && bounds.fTop >= insetCrop.fTop) || - (topEqual && bounds.fLeft >= insetCrop.fLeft)) { - radii[0].set(0, 0); - } else { - return false; - } - // compute the UpperRight corner radius - if (rightEqual && topEqual) { - radii[1].set(cornerRadius.x, cornerRadius.y); - } else if ((rightEqual && bounds.fTop >= insetCrop.fTop) || - (topEqual && bounds.fRight <= insetCrop.fRight)) { - radii[1].set(0, 0); - } else { - return false; - } - // compute the BottomRight corner radius - if (rightEqual && bottomEqual) { - radii[2].set(cornerRadius.x, cornerRadius.y); - } else if ((rightEqual && bounds.fBottom <= insetCrop.fBottom) || - (bottomEqual && bounds.fRight <= insetCrop.fRight)) { - radii[2].set(0, 0); - } else { - return false; - } - // compute the BottomLeft corner radius - if (leftEqual && bottomEqual) { - radii[3].set(cornerRadius.x, cornerRadius.y); - } else if ((leftEqual && bounds.fBottom <= insetCrop.fBottom) || - (bottomEqual && bounds.fLeft >= insetCrop.fLeft)) { - radii[3].set(0, 0); - } else { - return false; + if (!gl::GLExtensions::getInstance().hasNativeFenceSync()) { + return base::unique_fd(); } - return true; -} - -inline std::pair SkiaGLRenderEngine::getBoundsAndClip(const FloatRect& boundsRect, - const FloatRect& cropRect, - const vec2& cornerRadius) { - const SkRect bounds = getSkRect(boundsRect); - const SkRect crop = getSkRect(cropRect); - - SkRRect clip; - if (cornerRadius.x > 0 && cornerRadius.y > 0) { - // it the crop and the bounds are equivalent or there is no crop then we don't need a clip - if (bounds == crop || crop.isEmpty()) { - return {SkRRect::MakeRectXY(bounds, cornerRadius.x, cornerRadius.y), clip}; - } - - // This makes an effort to speed up common, simple bounds + clip combinations by - // converting them to a single RRect draw. It is possible there are other cases - // that can be converted. - if (crop.contains(bounds)) { - const auto insetCrop = crop.makeInset(cornerRadius.x, cornerRadius.y); - if (insetCrop.contains(bounds)) { - return {SkRRect::MakeRect(bounds), clip}; // clip is empty - no rounding required - } - - SkVector radii[4]; - if (intersectionIsRoundRect(bounds, crop, insetCrop, cornerRadius, radii)) { - SkRRect intersectionBounds; - intersectionBounds.setRectRadii(bounds, radii); - return {intersectionBounds, clip}; - } - } - - // we didn't hit any of our fast paths so set the clip to the cropRect - clip.setRectXY(crop, cornerRadius.x, cornerRadius.y); + EGLSyncKHR sync = eglCreateSyncKHR(mEGLDisplay, EGL_SYNC_NATIVE_FENCE_ANDROID, nullptr); + if (sync == EGL_NO_SYNC_KHR) { + ALOGW("failed to create EGL native fence sync: %#x", eglGetError()); + return base::unique_fd(); } - // if we hit this point then we either don't have rounded corners or we are going to rely - // on the clip to round the corners for us - return {SkRRect::MakeRect(bounds), clip}; -} + // native fence fd will not be populated until flush() is done. + glFlush(); -inline bool SkiaGLRenderEngine::layerHasBlur(const LayerSettings& layer, - bool colorTransformModifiesAlpha) { - if (layer.backgroundBlurRadius > 0 || layer.blurRegions.size()) { - // return false if the content is opaque and would therefore occlude the blur - const bool opaqueContent = !layer.source.buffer.buffer || layer.source.buffer.isOpaque; - const bool opaqueAlpha = layer.alpha == 1.0f && !colorTransformModifiesAlpha; - return layer.skipContentDraw || !(opaqueContent && opaqueAlpha); + // get the fence fd + base::unique_fd fenceFd(eglDupNativeFenceFDANDROID(mEGLDisplay, sync)); + eglDestroySyncKHR(mEGLDisplay, sync); + if (fenceFd == EGL_NO_NATIVE_FENCE_FD_ANDROID) { + ALOGW("failed to dup EGL native fence sync: %#x", eglGetError()); } - return false; -} - -inline SkColor SkiaGLRenderEngine::getSkColor(const vec4& color) { - return SkColorSetARGB(color.a * 255, color.r * 255, color.g * 255, color.b * 255); -} - -inline SkM44 SkiaGLRenderEngine::getSkM44(const mat4& matrix) { - return SkM44(matrix[0][0], matrix[1][0], matrix[2][0], matrix[3][0], - matrix[0][1], matrix[1][1], matrix[2][1], matrix[3][1], - matrix[0][2], matrix[1][2], matrix[2][2], matrix[3][2], - matrix[0][3], matrix[1][3], matrix[2][3], matrix[3][3]); -} -inline SkPoint3 SkiaGLRenderEngine::getSkPoint3(const vec3& vector) { - return SkPoint3::Make(vector.x, vector.y, vector.z); -} - -size_t SkiaGLRenderEngine::getMaxTextureSize() const { - return mGrContext->maxTextureSize(); -} - -size_t SkiaGLRenderEngine::getMaxViewportDims() const { - return mGrContext->maxRenderTargetSize(); -} - -void SkiaGLRenderEngine::drawShadow(SkCanvas* canvas, const SkRRect& casterRRect, - const ShadowSettings& settings) { - ATRACE_CALL(); - const float casterZ = settings.length / 2.0f; - const auto flags = - settings.casterIsTranslucent ? kTransparentOccluder_ShadowFlag : kNone_ShadowFlag; - - SkShadowUtils::DrawShadow(canvas, SkPath::RRect(casterRRect), SkPoint3::Make(0, 0, casterZ), - getSkPoint3(settings.lightPos), settings.lightRadius, - getSkColor(settings.ambientColor), getSkColor(settings.spotColor), - flags); + return fenceFd; } EGLContext SkiaGLRenderEngine::createEglContext(EGLDisplay display, EGLConfig config, @@ -1539,114 +519,14 @@ int SkiaGLRenderEngine::getContextPriority() { return value; } -void SkiaGLRenderEngine::onActiveDisplaySizeChanged(ui::Size size) { - // This cache multiplier was selected based on review of cache sizes relative - // to the screen resolution. Looking at the worst case memory needed by blur (~1.5x), - // shadows (~1x), and general data structures (e.g. vertex buffers) we selected this as a - // conservative default based on that analysis. - const float SURFACE_SIZE_MULTIPLIER = 3.5f * bytesPerPixel(mDefaultPixelFormat); - const int maxResourceBytes = size.width * size.height * SURFACE_SIZE_MULTIPLIER; - - // start by resizing the current context - getActiveGrContext()->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); - // reset back to the initial context that was active when this method was called - useProtectedContext(originalProtectedState); - } -} - -void SkiaGLRenderEngine::dump(std::string& result) { +void SkiaGLRenderEngine::appendBackendSpecificInfoToDump(std::string& result) { const gl::GLExtensions& extensions = gl::GLExtensions::getInstance(); - - StringAppendF(&result, "\n ------------RE-----------------\n"); + StringAppendF(&result, "\n ------------RE GLES------------\n"); StringAppendF(&result, "EGL implementation : %s\n", extensions.getEGLVersion()); StringAppendF(&result, "%s\n", extensions.getEGLExtensions()); StringAppendF(&result, "GLES: %s, %s, %s\n", extensions.getVendor(), extensions.getRenderer(), extensions.getVersion()); StringAppendF(&result, "%s\n", extensions.getExtensions()); - StringAppendF(&result, "RenderEngine supports protected context: %d\n", - supportsProtectedContent()); - StringAppendF(&result, "RenderEngine is in protected context: %d\n", mInProtectedContext); - StringAppendF(&result, "RenderEngine shaders cached since last dump/primeCache: %d\n", - mSkSLCacheMonitor.shadersCachedSinceLastCall()); - - std::vector cpuResourceMap = { - {"skia/sk_resource_cache/bitmap_", "Bitmaps"}, - {"skia/sk_resource_cache/rrect-blur_", "Masks"}, - {"skia/sk_resource_cache/rects-blur_", "Masks"}, - {"skia/sk_resource_cache/tessellated", "Shadows"}, - {"skia", "Other"}, - }; - SkiaMemoryReporter cpuReporter(cpuResourceMap, false); - SkGraphics::DumpMemoryStatistics(&cpuReporter); - StringAppendF(&result, "Skia CPU Caches: "); - cpuReporter.logTotals(result); - cpuReporter.logOutput(result); - - { - std::lock_guard lock(mRenderingMutex); - - std::vector gpuResourceMap = { - {"texture_renderbuffer", "Texture/RenderBuffer"}, - {"texture", "Texture"}, - {"gr_text_blob_cache", "Text"}, - {"skia", "Other"}, - }; - SkiaMemoryReporter gpuReporter(gpuResourceMap, true); - mGrContext->dumpMemoryStatistics(&gpuReporter); - StringAppendF(&result, "Skia's GPU Caches: "); - gpuReporter.logTotals(result); - gpuReporter.logOutput(result); - StringAppendF(&result, "Skia's Wrapped Objects:\n"); - gpuReporter.logOutput(result, true); - - StringAppendF(&result, "RenderEngine tracked buffers: %zu\n", - mGraphicBufferExternalRefs.size()); - StringAppendF(&result, "Dumping buffer ids...\n"); - for (const auto& [id, refCounts] : mGraphicBufferExternalRefs) { - StringAppendF(&result, "- 0x%" PRIx64 " - %d refs \n", id, refCounts); - } - StringAppendF(&result, "RenderEngine AHB/BackendTexture cache size: %zu\n", - mTextureCache.size()); - StringAppendF(&result, "Dumping buffer ids...\n"); - // TODO(178539829): It would be nice to know which layer these are coming from and what - // the texture sizes are. - for (const auto& [id, unused] : mTextureCache) { - StringAppendF(&result, "- 0x%" PRIx64 "\n", id); - } - StringAppendF(&result, "\n"); - - SkiaMemoryReporter gpuProtectedReporter(gpuResourceMap, true); - if (mProtectedGrContext) { - mProtectedGrContext->dumpMemoryStatistics(&gpuProtectedReporter); - } - StringAppendF(&result, "Skia's GPU Protected Caches: "); - gpuProtectedReporter.logTotals(result); - gpuProtectedReporter.logOutput(result); - StringAppendF(&result, "Skia's Protected Wrapped Objects:\n"); - gpuProtectedReporter.logOutput(result, true); - - StringAppendF(&result, "\n"); - StringAppendF(&result, "RenderEngine runtime effects: %zu\n", mRuntimeEffects.size()); - for (const auto& [linearEffect, unused] : mRuntimeEffects) { - StringAppendF(&result, "- inputDataspace: %s\n", - dataspaceDetails( - static_cast(linearEffect.inputDataspace)) - .c_str()); - StringAppendF(&result, "- outputDataspace: %s\n", - dataspaceDetails( - static_cast(linearEffect.outputDataspace)) - .c_str()); - StringAppendF(&result, "undoPremultipliedAlpha: %s\n", - linearEffect.undoPremultipliedAlpha ? "true" : "false"); - } - } - StringAppendF(&result, "\n"); } } // namespace skia diff --git a/libs/renderengine/skia/SkiaGLRenderEngine.h b/libs/renderengine/skia/SkiaGLRenderEngine.h index 68c336327b35ff00937571c73b01714fa19e5228..af3311041de75462d9f404dd93c3305d05c8f1d4 100644 --- a/libs/renderengine/skia/SkiaGLRenderEngine.h +++ b/libs/renderengine/skia/SkiaGLRenderEngine.h @@ -41,6 +41,10 @@ #include "filters/LinearEffect.h" #include "filters/StretchShaderFactory.h" +class SkData; + +struct SkPoint3; + namespace android { namespace renderengine { namespace skia { @@ -48,36 +52,26 @@ namespace skia { class SkiaGLRenderEngine : public skia::SkiaRenderEngine { public: static std::unique_ptr create(const RenderEngineCreationArgs& args); - SkiaGLRenderEngine(const RenderEngineCreationArgs& args, EGLDisplay display, EGLContext ctxt, - EGLSurface placeholder, EGLContext protectedContext, - EGLSurface protectedPlaceholder); - ~SkiaGLRenderEngine() override EXCLUDES(mRenderingMutex); + ~SkiaGLRenderEngine() override; - std::future primeCache() override; - void cleanupPostRender() override; - void cleanFramebufferCache() override{}; int getContextPriority() override; - bool isProtected() const override { return mInProtectedContext; } - bool supportsProtectedContent() const override; - void useProtectedContext(bool useProtectedContext) override; - bool supportsBackgroundBlur() override { return mBlurFilter != nullptr; } - void onActiveDisplaySizeChanged(ui::Size size) override; - int reportShadersCompiled() override; protected: - void dump(std::string& result) override; - size_t getMaxTextureSize() const override; - size_t getMaxViewportDims() const override; - void mapExternalTextureBuffer(const sp& buffer, bool isRenderable) override; - void unmapExternalTextureBuffer(const sp& buffer) override; - bool canSkipPostRenderCleanup() const override; - void drawLayersInternal(const std::shared_ptr>&& resultPromise, - const DisplaySettings& display, - const std::vector& layers, - const std::shared_ptr& buffer, - const bool useFramebufferCache, base::unique_fd&& bufferFence) override; + // Implementations of abstract SkiaRenderEngine functions specific to + // rendering backend + virtual SkiaRenderEngine::Contexts createDirectContexts(const GrContextOptions& options); + 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 appendBackendSpecificInfoToDump(std::string& result) override; private: + SkiaGLRenderEngine(const RenderEngineCreationArgs& args, EGLDisplay display, EGLContext ctxt, + EGLSurface placeholder, EGLContext protectedContext, + EGLSurface protectedPlaceholder); + bool waitGpuFence(base::borrowed_fd fenceFd); + base::unique_fd flush(); static EGLConfig chooseEglConfig(EGLDisplay display, int format, bool logConfig); static EGLContext createEglContext(EGLDisplay display, EGLConfig config, EGLContext shareContext, @@ -85,107 +79,14 @@ private: Protection protection); static std::optional createContextPriority( const RenderEngineCreationArgs& args); - static EGLSurface createPlaceholderEglPbufferSurface(EGLDisplay display, EGLConfig config, - int hwcFormat, Protection protection); - inline SkRect getSkRect(const FloatRect& layer); - inline SkRect getSkRect(const Rect& layer); - inline std::pair getBoundsAndClip(const FloatRect& bounds, - const FloatRect& crop, - const vec2& cornerRadius); - inline bool layerHasBlur(const LayerSettings& layer, bool colorTransformModifiesAlpha); - inline SkColor getSkColor(const vec4& color); - inline SkM44 getSkM44(const mat4& matrix); - inline SkPoint3 getSkPoint3(const vec3& vector); - inline GrDirectContext* getActiveGrContext() const; - - base::unique_fd flush(); - // waitFence attempts to wait in the GPU, and if unable to waits on the CPU instead. - void waitFence(base::borrowed_fd fenceFd); - bool waitGpuFence(base::borrowed_fd fenceFd); - - void initCanvas(SkCanvas* canvas, const DisplaySettings& display); - void drawShadow(SkCanvas* canvas, const SkRRect& casterRRect, - const ShadowSettings& shadowSettings); - - // If requiresLinearEffect is true or the layer has a stretchEffect a new shader is returned. - // Otherwise it returns the input shader. - struct RuntimeEffectShaderParameters { - sk_sp shader; - const LayerSettings& layer; - const DisplaySettings& display; - bool undoPremultipliedAlpha; - bool requiresLinearEffect; - float layerDimmingRatio; - }; - sk_sp createRuntimeEffectShader(const RuntimeEffectShaderParameters&); + static EGLSurface createPlaceholderEglPbufferSurface( + EGLDisplay display, EGLConfig config, int hwcFormat, Protection protection); EGLDisplay mEGLDisplay; EGLContext mEGLContext; EGLSurface mPlaceholderSurface; EGLContext mProtectedEGLContext; EGLSurface mProtectedPlaceholderSurface; - BlurFilter* mBlurFilter = nullptr; - - const PixelFormat mDefaultPixelFormat; - const bool mUseColorManagement; - - // Identifier used or various mappings of layers to various - // textures or shaders - using GraphicBufferId = uint64_t; - - // Number of external holders of ExternalTexture references, per GraphicBuffer ID. - std::unordered_map mGraphicBufferExternalRefs - GUARDED_BY(mRenderingMutex); - // Cache of GL textures that we'll store per GraphicBuffer ID, shared between GPU contexts. - std::unordered_map> mTextureCache - GUARDED_BY(mRenderingMutex); - std::unordered_map, shaders::LinearEffectHasher> - mRuntimeEffects; - AutoBackendTexture::CleanupManager mTextureCleanupMgr GUARDED_BY(mRenderingMutex); - - StretchShaderFactory mStretchShaderFactory; - // Mutex guarding rendering operations, so that: - // 1. GL operations aren't interleaved, and - // 2. Internal state related to rendering that is potentially modified by - // multiple threads is guaranteed thread-safe. - mutable std::mutex mRenderingMutex; - - sp mLastDrawFence; - - // Graphics context used for creating surfaces and submitting commands - sk_sp mGrContext; - // Same as above, but for protected content (eg. DRM) - sk_sp mProtectedGrContext; - - bool mInProtectedContext = false; - // Object to capture commands send to Skia. - std::unique_ptr mCapture; - - // Implements PersistentCache as a way to monitor what SkSL shaders Skia has - // cached. - class SkSLCacheMonitor : public GrContextOptions::PersistentCache { - public: - SkSLCacheMonitor() = default; - ~SkSLCacheMonitor() override = default; - - sk_sp load(const SkData& key) override; - - void store(const SkData& key, const SkData& data, const SkString& description) override; - - int shadersCachedSinceLastCall() { - const int shadersCachedSinceLastCall = mShadersCachedSinceLastCall; - mShadersCachedSinceLastCall = 0; - return shadersCachedSinceLastCall; - } - - int totalShadersCompiled() const { return mTotalShadersCompiled; } - - private: - int mShadersCachedSinceLastCall = 0; - int mTotalShadersCompiled = 0; - }; - - SkSLCacheMonitor mSkSLCacheMonitor; }; } // namespace skia diff --git a/libs/renderengine/skia/SkiaRenderEngine.cpp b/libs/renderengine/skia/SkiaRenderEngine.cpp index 1fb24f504119ec050b1997b5ec8c0100c7506028..76ebf9d0c29b4e3db57e585326828895bfa2edeb 100644 --- a/libs/renderengine/skia/SkiaRenderEngine.cpp +++ b/libs/renderengine/skia/SkiaRenderEngine.cpp @@ -20,16 +20,1273 @@ #include "SkiaRenderEngine.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include #include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#include "Cache.h" +#include "ColorSpaces.h" +#include "filters/BlurFilter.h" +#include "filters/GaussianBlurFilter.h" +#include "filters/KawaseBlurFilter.h" +#include "filters/LinearEffect.h" +#include "log/log_main.h" +#include "skia/debug/SkiaCapture.h" +#include "skia/debug/SkiaMemoryReporter.h" +#include "skia/filters/StretchShaderFactory.h" +#include "system/graphics-base-v1.0.h" + +namespace { + +// Debugging settings +static const bool kPrintLayerSettings = false; +static const bool kFlushAfterEveryLayer = kPrintLayerSettings; +static constexpr bool kEnableLayerBrightening = true; + +} // namespace + +// Utility functions related to SkRect + +namespace { + +static inline SkRect getSkRect(const android::FloatRect& rect) { + return SkRect::MakeLTRB(rect.left, rect.top, rect.right, rect.bottom); +} + +static inline SkRect getSkRect(const android::Rect& rect) { + return SkRect::MakeLTRB(rect.left, rect.top, rect.right, rect.bottom); +} + +/** + * Verifies that common, simple bounds + clip combinations can be converted into + * a single RRect draw call returning true if possible. If true the radii parameter + * will be filled with the correct radii values that combined with bounds param will + * produce the insected roundRect. If false, the returned state of the radii param is undefined. + */ +static bool intersectionIsRoundRect(const SkRect& bounds, const SkRect& crop, + const SkRect& insetCrop, const android::vec2& cornerRadius, + SkVector radii[4]) { + const bool leftEqual = bounds.fLeft == crop.fLeft; + const bool topEqual = bounds.fTop == crop.fTop; + const bool rightEqual = bounds.fRight == crop.fRight; + const bool bottomEqual = bounds.fBottom == crop.fBottom; + + // In the event that the corners of the bounds only partially align with the crop we + // need to ensure that the resulting shape can still be represented as a round rect. + // In particular the round rect implementation will scale the value of all corner radii + // if the sum of the radius along any edge is greater than the length of that edge. + // See https://www.w3.org/TR/css-backgrounds-3/#corner-overlap + const bool requiredWidth = bounds.width() > (cornerRadius.x * 2); + const bool requiredHeight = bounds.height() > (cornerRadius.y * 2); + if (!requiredWidth || !requiredHeight) { + return false; + } + + // Check each cropped corner to ensure that it exactly matches the crop or its corner is + // contained within the cropped shape and does not need rounded. + // compute the UpperLeft corner radius + if (leftEqual && topEqual) { + radii[0].set(cornerRadius.x, cornerRadius.y); + } else if ((leftEqual && bounds.fTop >= insetCrop.fTop) || + (topEqual && bounds.fLeft >= insetCrop.fLeft)) { + radii[0].set(0, 0); + } else { + return false; + } + // compute the UpperRight corner radius + if (rightEqual && topEqual) { + radii[1].set(cornerRadius.x, cornerRadius.y); + } else if ((rightEqual && bounds.fTop >= insetCrop.fTop) || + (topEqual && bounds.fRight <= insetCrop.fRight)) { + radii[1].set(0, 0); + } else { + return false; + } + // compute the BottomRight corner radius + if (rightEqual && bottomEqual) { + radii[2].set(cornerRadius.x, cornerRadius.y); + } else if ((rightEqual && bounds.fBottom <= insetCrop.fBottom) || + (bottomEqual && bounds.fRight <= insetCrop.fRight)) { + radii[2].set(0, 0); + } else { + return false; + } + // compute the BottomLeft corner radius + if (leftEqual && bottomEqual) { + radii[3].set(cornerRadius.x, cornerRadius.y); + } else if ((leftEqual && bounds.fBottom <= insetCrop.fBottom) || + (bottomEqual && bounds.fLeft >= insetCrop.fLeft)) { + radii[3].set(0, 0); + } else { + return false; + } + + return true; +} + +static inline std::pair getBoundsAndClip(const android::FloatRect& boundsRect, + const android::FloatRect& cropRect, + const android::vec2& cornerRadius) { + const SkRect bounds = getSkRect(boundsRect); + const SkRect crop = getSkRect(cropRect); + + SkRRect clip; + if (cornerRadius.x > 0 && cornerRadius.y > 0) { + // it the crop and the bounds are equivalent or there is no crop then we don't need a clip + if (bounds == crop || crop.isEmpty()) { + return {SkRRect::MakeRectXY(bounds, cornerRadius.x, cornerRadius.y), clip}; + } + + // This makes an effort to speed up common, simple bounds + clip combinations by + // converting them to a single RRect draw. It is possible there are other cases + // that can be converted. + if (crop.contains(bounds)) { + const auto insetCrop = crop.makeInset(cornerRadius.x, cornerRadius.y); + if (insetCrop.contains(bounds)) { + return {SkRRect::MakeRect(bounds), clip}; // clip is empty - no rounding required + } + + SkVector radii[4]; + if (intersectionIsRoundRect(bounds, crop, insetCrop, cornerRadius, radii)) { + SkRRect intersectionBounds; + intersectionBounds.setRectRadii(bounds, radii); + return {intersectionBounds, clip}; + } + } + + // we didn't hit any of our fast paths so set the clip to the cropRect + clip.setRectXY(crop, cornerRadius.x, cornerRadius.y); + } + + // if we hit this point then we either don't have rounded corners or we are going to rely + // on the clip to round the corners for us + return {SkRRect::MakeRect(bounds), clip}; +} + +static inline bool layerHasBlur(const android::renderengine::LayerSettings& layer, + bool colorTransformModifiesAlpha) { + if (layer.backgroundBlurRadius > 0 || layer.blurRegions.size()) { + // return false if the content is opaque and would therefore occlude the blur + const bool opaqueContent = !layer.source.buffer.buffer || layer.source.buffer.isOpaque; + const bool opaqueAlpha = layer.alpha == 1.0f && !colorTransformModifiesAlpha; + return layer.skipContentDraw || !(opaqueContent && opaqueAlpha); + } + return false; +} + +static inline SkColor getSkColor(const android::vec4& color) { + return SkColorSetARGB(color.a * 255, color.r * 255, color.g * 255, color.b * 255); +} + +static inline SkM44 getSkM44(const android::mat4& matrix) { + return SkM44(matrix[0][0], matrix[1][0], matrix[2][0], matrix[3][0], + matrix[0][1], matrix[1][1], matrix[2][1], matrix[3][1], + matrix[0][2], matrix[1][2], matrix[2][2], matrix[3][2], + matrix[0][3], matrix[1][3], matrix[2][3], matrix[3][3]); +} + +static inline SkPoint3 getSkPoint3(const android::vec3& vector) { + return SkPoint3::Make(vector.x, vector.y, vector.z); +} +} // namespace namespace android { namespace renderengine { namespace skia { -SkiaRenderEngine::SkiaRenderEngine(RenderEngineType type) : RenderEngine(type) {} + +using base::StringAppendF; + +std::future SkiaRenderEngine::primeCache() { + Cache::primeShaderCache(this); + return {}; +} + +sk_sp SkiaRenderEngine::SkSLCacheMonitor::load(const SkData& key) { + // This "cache" does not actually cache anything. It just allows us to + // monitor Skia's internal cache. So this method always returns null. + return nullptr; +} + +void SkiaRenderEngine::SkSLCacheMonitor::store(const SkData& key, const SkData& data, + const SkString& description) { + mShadersCachedSinceLastCall++; + mTotalShadersCompiled++; + ATRACE_FORMAT("SF cache: %i shaders", mTotalShadersCompiled); +} + +int SkiaRenderEngine::reportShadersCompiled() { + return mSkSLCacheMonitor.totalShadersCompiled(); +} void SkiaRenderEngine::setEnableTracing(bool tracingEnabled) { SkAndroidFrameworkTraceUtil::setEnableTracing(tracingEnabled); } + +SkiaRenderEngine::SkiaRenderEngine(RenderEngineType type, PixelFormat pixelFormat, + bool useColorManagement, bool supportsBackgroundBlur) + : RenderEngine(type), + mDefaultPixelFormat(pixelFormat), + mUseColorManagement(useColorManagement) { + if (supportsBackgroundBlur) { + ALOGD("Background Blurs Enabled"); + mBlurFilter = new KawaseBlurFilter(); + } + mCapture = std::make_unique(); +} + +SkiaRenderEngine::~SkiaRenderEngine() { } + +// To be called from backend dtors. +void SkiaRenderEngine::finishRenderingAndAbandonContext() { + std::lock_guard lock(mRenderingMutex); + + if (mBlurFilter) { + delete mBlurFilter; + } + + if (mGrContext) { + mGrContext->flushAndSubmit(true); + mGrContext->abandonContext(); + } + + if (mProtectedGrContext) { + mProtectedGrContext->flushAndSubmit(true); + mProtectedGrContext->abandonContext(); + } +} + +void SkiaRenderEngine::useProtectedContext(bool useProtectedContext) { + if (useProtectedContext == mInProtectedContext || + (useProtectedContext && !supportsProtectedContent())) { + return; + } + + // release any scratch resources before switching into a new mode + if (getActiveGrContext()) { + getActiveGrContext()->purgeUnlockedResources(true); + } + + // 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 + // make sure that the thread state is reset when switching between the two. + if (getActiveGrContext()) { + getActiveGrContext()->resetContext(); + } + } +} + +GrDirectContext* SkiaRenderEngine::getActiveGrContext() { + return mInProtectedContext ? mProtectedGrContext.get() : mGrContext.get(); +} + +static float toDegrees(uint32_t transform) { + switch (transform) { + case ui::Transform::ROT_90: + return 90.0; + case ui::Transform::ROT_180: + return 180.0; + case ui::Transform::ROT_270: + return 270.0; + default: + return 0.0; + } +} + +static SkColorMatrix toSkColorMatrix(const android::mat4& matrix) { + return SkColorMatrix(matrix[0][0], matrix[1][0], matrix[2][0], matrix[3][0], 0, matrix[0][1], + matrix[1][1], matrix[2][1], matrix[3][1], 0, matrix[0][2], matrix[1][2], + matrix[2][2], matrix[3][2], 0, matrix[0][3], matrix[1][3], matrix[2][3], + matrix[3][3], 0); +} + +static bool needsToneMapping(ui::Dataspace sourceDataspace, ui::Dataspace destinationDataspace) { + int64_t sourceTransfer = sourceDataspace & HAL_DATASPACE_TRANSFER_MASK; + int64_t destTransfer = destinationDataspace & HAL_DATASPACE_TRANSFER_MASK; + + // Treat unsupported dataspaces as srgb + if (destTransfer != HAL_DATASPACE_TRANSFER_LINEAR && + destTransfer != HAL_DATASPACE_TRANSFER_HLG && + destTransfer != HAL_DATASPACE_TRANSFER_ST2084) { + destTransfer = HAL_DATASPACE_TRANSFER_SRGB; + } + + if (sourceTransfer != HAL_DATASPACE_TRANSFER_LINEAR && + sourceTransfer != HAL_DATASPACE_TRANSFER_HLG && + sourceTransfer != HAL_DATASPACE_TRANSFER_ST2084) { + sourceTransfer = HAL_DATASPACE_TRANSFER_SRGB; + } + + const bool isSourceLinear = sourceTransfer == HAL_DATASPACE_TRANSFER_LINEAR; + const bool isSourceSRGB = sourceTransfer == HAL_DATASPACE_TRANSFER_SRGB; + const bool isDestLinear = destTransfer == HAL_DATASPACE_TRANSFER_LINEAR; + const bool isDestSRGB = destTransfer == HAL_DATASPACE_TRANSFER_SRGB; + + return !(isSourceLinear && isDestSRGB) && !(isSourceSRGB && isDestLinear) && + sourceTransfer != destTransfer; +} + +void SkiaRenderEngine::ensureGrContextsCreated() { + if (mGrContext) { + return; + } + + GrContextOptions options; + options.fDisableDriverCorrectnessWorkarounds = true; + options.fDisableDistanceFieldPaths = true; + options.fReducedShaderVariations = true; + options.fPersistentCache = &mSkSLCacheMonitor; + std::tie(mGrContext, mProtectedGrContext) = createDirectContexts(options); +} + +void SkiaRenderEngine::mapExternalTextureBuffer(const sp& buffer, + bool isRenderable) { + // Only run this if RE is running on its own thread. This + // way the access to GL operations is guaranteed to be happening on the + // same thread. + if (mRenderEngineType != RenderEngineType::SKIA_GL_THREADED && + mRenderEngineType != RenderEngineType::SKIA_VK_THREADED) { + return; + } + // We don't attempt to map a buffer if the buffer contains protected content. In GL this is + // important because GPU resources for protected buffers are much more limited. (In Vk we + // simply match the existing behavior for protected buffers.) In Vk, we never cache any + // buffers while in a protected context, since Vk cannot share across contexts, and protected + // is less common. + const bool isProtectedBuffer = buffer->getUsage() & GRALLOC_USAGE_PROTECTED; + if (isProtectedBuffer || + (mRenderEngineType == RenderEngineType::SKIA_VK_THREADED && isProtected())) { + return; + } + ATRACE_CALL(); + + // If we were to support caching protected buffers then we will need to switch the + // currently bound context if we are not already using the protected context (and subsequently + // 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& cache = mTextureCache; + + std::lock_guard lock(mRenderingMutex); + mGraphicBufferExternalRefs[buffer->getId()]++; + + if (const auto& iter = cache.find(buffer->getId()); iter == cache.end()) { + std::shared_ptr imageTextureRef = + std::make_shared(grContext, + buffer->toAHardwareBuffer(), + isRenderable, mTextureCleanupMgr); + cache.insert({buffer->getId(), imageTextureRef}); + } +} + +void SkiaRenderEngine::unmapExternalTextureBuffer(sp&& buffer) { + ATRACE_CALL(); + std::lock_guard lock(mRenderingMutex); + if (const auto& iter = mGraphicBufferExternalRefs.find(buffer->getId()); + iter != mGraphicBufferExternalRefs.end()) { + if (iter->second == 0) { + ALOGW("Attempted to unmap GraphicBuffer from RenderEngine texture, but the " + "ref count was already zero!", + buffer->getId()); + mGraphicBufferExternalRefs.erase(buffer->getId()); + return; + } + + iter->second--; + + // Swap contexts if needed prior to deleting this buffer + // See Issue 1 of + // https://www.khronos.org/registry/EGL/extensions/EXT/EGL_EXT_protected_content.txt: even + // when a protected context and an unprotected context are part of the same share group, + // protected surfaces may not be accessed by an unprotected context, implying that protected + // surfaces may only be freed when a protected context is active. + const bool inProtected = mInProtectedContext; + useProtectedContext(buffer->getUsage() & GRALLOC_USAGE_PROTECTED); + + if (iter->second == 0) { + mTextureCache.erase(buffer->getId()); + mGraphicBufferExternalRefs.erase(buffer->getId()); + } + + // Swap back to the previous context so that cached values of isProtected in SurfaceFlinger + // are up-to-date. + if (inProtected != mInProtectedContext) { + useProtectedContext(inProtected); + } + } +} + +std::shared_ptr SkiaRenderEngine::getOrCreateBackendTexture( + const sp& buffer, bool isOutputBuffer) { + // Do not lookup the buffer in the cache for protected contexts with the SkiaVk back-end + if (mRenderEngineType == RenderEngineType::SKIA_GL_THREADED || + (mRenderEngineType == RenderEngineType::SKIA_VK_THREADED && !isProtected())) { + if (const auto& it = mTextureCache.find(buffer->getId()); it != mTextureCache.end()) { + return it->second; + } + } + return std::make_shared(getActiveGrContext(), + buffer->toAHardwareBuffer(), + isOutputBuffer, mTextureCleanupMgr); +} + +bool SkiaRenderEngine::canSkipPostRenderCleanup() const { + std::lock_guard lock(mRenderingMutex); + return mTextureCleanupMgr.isEmpty(); +} + +void SkiaRenderEngine::cleanupPostRender() { + ATRACE_CALL(); + std::lock_guard lock(mRenderingMutex); + mTextureCleanupMgr.cleanup(); +} + +sk_sp SkiaRenderEngine::createRuntimeEffectShader( + const RuntimeEffectShaderParameters& parameters) { + // The given surface will be stretched by HWUI via matrix transformation + // which gets similar results for most surfaces + // Determine later on if we need to leverage the stertch shader within + // surface flinger + const auto& stretchEffect = parameters.layer.stretchEffect; + 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); + } + } + + if (parameters.requiresLinearEffect) { + auto effect = + shaders::LinearEffect{.inputDataspace = parameters.layer.sourceDataspace, + .outputDataspace = parameters.outputDataSpace, + .undoPremultipliedAlpha = parameters.undoPremultipliedAlpha}; + + auto effectIter = mRuntimeEffects.find(effect); + sk_sp runtimeEffect = nullptr; + if (effectIter == mRuntimeEffects.end()) { + runtimeEffect = buildRuntimeEffect(effect); + mRuntimeEffects.insert({effect, runtimeEffect}); + } else { + runtimeEffect = effectIter->second; + } + + mat4 colorTransform = parameters.layer.colorTransform; + + colorTransform *= + mat4::scale(vec4(parameters.layerDimmingRatio, parameters.layerDimmingRatio, + parameters.layerDimmingRatio, 1.f)); + + const auto targetBuffer = parameters.layer.source.buffer.buffer; + const auto graphicBuffer = targetBuffer ? targetBuffer->getBuffer() : nullptr; + const auto hardwareBuffer = graphicBuffer ? graphicBuffer->toAHardwareBuffer() : nullptr; + return createLinearEffectShader(parameters.shader, effect, runtimeEffect, + std::move(colorTransform), parameters.display.maxLuminance, + parameters.display.currentLuminanceNits, + parameters.layer.source.buffer.maxLuminanceNits, + hardwareBuffer, parameters.display.renderIntent); + } + return parameters.shader; +} + +void SkiaRenderEngine::initCanvas(SkCanvas* canvas, const DisplaySettings& display) { + if (CC_UNLIKELY(mCapture->isCaptureRunning())) { + // Record display settings when capture is running. + std::stringstream displaySettings; + PrintTo(display, &displaySettings); + // Store the DisplaySettings in additional information. + canvas->drawAnnotation(SkRect::MakeEmpty(), "DisplaySettings", + SkData::MakeWithCString(displaySettings.str().c_str())); + } + + // Before doing any drawing, let's make sure that we'll start at the origin of the display. + // Some displays don't start at 0,0 for example when we're mirroring the screen. Also, virtual + // displays might have different scaling when compared to the physical screen. + + canvas->clipRect(getSkRect(display.physicalDisplay)); + canvas->translate(display.physicalDisplay.left, display.physicalDisplay.top); + + const auto clipWidth = display.clip.width(); + const auto clipHeight = display.clip.height(); + auto rotatedClipWidth = clipWidth; + auto rotatedClipHeight = clipHeight; + // Scale is contingent on the rotation result. + if (display.orientation & ui::Transform::ROT_90) { + std::swap(rotatedClipWidth, rotatedClipHeight); + } + const auto scaleX = static_cast(display.physicalDisplay.width()) / + static_cast(rotatedClipWidth); + const auto scaleY = static_cast(display.physicalDisplay.height()) / + static_cast(rotatedClipHeight); + canvas->scale(scaleX, scaleY); + + // Canvas rotation is done by centering the clip window at the origin, rotating, translating + // back so that the top left corner of the clip is at (0, 0). + canvas->translate(rotatedClipWidth / 2, rotatedClipHeight / 2); + canvas->rotate(toDegrees(display.orientation)); + canvas->translate(-clipWidth / 2, -clipHeight / 2); + canvas->translate(-display.clip.left, -display.clip.top); +} + +class AutoSaveRestore { +public: + AutoSaveRestore(SkCanvas* canvas) : mCanvas(canvas) { mSaveCount = canvas->save(); } + ~AutoSaveRestore() { restore(); } + void replace(SkCanvas* canvas) { + mCanvas = canvas; + mSaveCount = canvas->save(); + } + void restore() { + if (mCanvas) { + mCanvas->restoreToCount(mSaveCount); + mCanvas = nullptr; + } + } + +private: + SkCanvas* mCanvas; + int mSaveCount; +}; + +static SkRRect getBlurRRect(const BlurRegion& region) { + const auto rect = SkRect::MakeLTRB(region.left, region.top, region.right, region.bottom); + const SkVector radii[4] = {SkVector::Make(region.cornerRadiusTL, region.cornerRadiusTL), + SkVector::Make(region.cornerRadiusTR, region.cornerRadiusTR), + SkVector::Make(region.cornerRadiusBR, region.cornerRadiusBR), + SkVector::Make(region.cornerRadiusBL, region.cornerRadiusBL)}; + SkRRect roundedRect; + roundedRect.setRectRadii(rect, radii); + return roundedRect; +} + +// Arbitrary default margin which should be close enough to zero. +constexpr float kDefaultMargin = 0.0001f; +static bool equalsWithinMargin(float expected, float value, float margin = kDefaultMargin) { + LOG_ALWAYS_FATAL_IF(margin < 0.f, "Margin is negative!"); + return std::abs(expected - value) < margin; +} + +namespace { +template +void logSettings(const T& t) { + std::stringstream stream; + PrintTo(t, &stream); + auto string = stream.str(); + size_t pos = 0; + // Perfetto ignores \n, so split up manually into separate ALOGD statements. + const size_t size = string.size(); + while (pos < size) { + const size_t end = std::min(string.find("\n", pos), size); + ALOGD("%s", string.substr(pos, end - pos).c_str()); + pos = end + 1; + } +} +} // namespace + +// Helper class intended to be used on the stack to ensure that texture cleanup +// is deferred until after this class goes out of scope. +class DeferTextureCleanup final { +public: + DeferTextureCleanup(AutoBackendTexture::CleanupManager& mgr) : mMgr(mgr) { + mMgr.setDeferredStatus(true); + } + ~DeferTextureCleanup() { mMgr.setDeferredStatus(false); } + +private: + DISALLOW_COPY_AND_ASSIGN(DeferTextureCleanup); + AutoBackendTexture::CleanupManager& mMgr; +}; + +void SkiaRenderEngine::drawLayersInternal( + const std::shared_ptr>&& resultPromise, + const DisplaySettings& display, const std::vector& layers, + const std::shared_ptr& buffer, const bool /*useFramebufferCache*/, + base::unique_fd&& bufferFence) { + ATRACE_FORMAT("%s for %s", __func__, display.namePlusId.c_str()); + + std::lock_guard lock(mRenderingMutex); + + if (buffer == nullptr) { + ALOGE("No output buffer provided. Aborting GPU composition."); + resultPromise->set_value(base::unexpected(BAD_VALUE)); + return; + } + + validateOutputBufferUsage(buffer->getBuffer()); + + auto grContext = getActiveGrContext(); + + // any AutoBackendTexture deletions will now be deferred until cleanupPostRender is called + DeferTextureCleanup dtc(mTextureCleanupMgr); + + auto surfaceTextureRef = getOrCreateBackendTexture(buffer->getBuffer(), true); + + // wait on the buffer to be ready to use prior to using it + waitFence(grContext, bufferFence); + + sk_sp dstSurface = + surfaceTextureRef->getOrCreateSurface(display.outputDataspace, grContext); + + SkCanvas* dstCanvas = mCapture->tryCapture(dstSurface.get()); + if (dstCanvas == nullptr) { + ALOGE("Cannot acquire canvas from Skia."); + resultPromise->set_value(base::unexpected(BAD_VALUE)); + return; + } + + // setup color filter if necessary + sk_sp displayColorTransform; + if (display.colorTransform != mat4() && !display.deviceHandlesColorTransform) { + displayColorTransform = SkColorFilters::Matrix(toSkColorMatrix(display.colorTransform)); + } + const bool ctModifiesAlpha = + displayColorTransform && !displayColorTransform->isAlphaUnchanged(); + + // Find the max layer white point to determine the max luminance of the scene... + const float maxLayerWhitePoint = std::transform_reduce( + layers.cbegin(), layers.cend(), 0.f, + [](float left, float right) { return std::max(left, right); }, + [&](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 && + (kEnableLayerBrightening || display.targetLuminanceNits > maxLayerWhitePoint) + ? maxLayerWhitePoint / display.targetLuminanceNits + : 1.f; + + // Find if any layers have requested blur, we'll use that info to decide when to render to an + // offscreen buffer and when to render to the native buffer. + sk_sp activeSurface(dstSurface); + SkCanvas* canvas = dstCanvas; + SkiaCapture::OffscreenState offscreenCaptureState; + const LayerSettings* blurCompositionLayer = nullptr; + if (mBlurFilter) { + bool requiresCompositionLayer = false; + for (const auto& layer : layers) { + // if the layer doesn't have blur or it is not visible then continue + if (!layerHasBlur(layer, ctModifiesAlpha)) { + continue; + } + if (layer.backgroundBlurRadius > 0 && + layer.backgroundBlurRadius < mBlurFilter->getMaxCrossFadeRadius()) { + requiresCompositionLayer = true; + } + for (auto region : layer.blurRegions) { + if (region.blurRadius < mBlurFilter->getMaxCrossFadeRadius()) { + requiresCompositionLayer = true; + } + } + if (requiresCompositionLayer) { + activeSurface = dstSurface->makeSurface(dstSurface->imageInfo()); + canvas = mCapture->tryOffscreenCapture(activeSurface.get(), &offscreenCaptureState); + blurCompositionLayer = &layer; + break; + } + } + } + + AutoSaveRestore surfaceAutoSaveRestore(canvas); + // Clear the entire canvas with a transparent black to prevent ghost images. + canvas->clear(SK_ColorTRANSPARENT); + initCanvas(canvas, display); + + if (kPrintLayerSettings) { + logSettings(display); + } + for (const auto& layer : layers) { + ATRACE_FORMAT("DrawLayer: %s", layer.name.c_str()); + + if (kPrintLayerSettings) { + logSettings(layer); + } + + sk_sp blurInput; + if (blurCompositionLayer == &layer) { + LOG_ALWAYS_FATAL_IF(activeSurface == dstSurface); + LOG_ALWAYS_FATAL_IF(canvas == dstCanvas); + + // save a snapshot of the activeSurface to use as input to the blur shaders + blurInput = activeSurface->makeImageSnapshot(); + + // blit the offscreen framebuffer into the destination AHB, but only + // if there are blur regions. backgroundBlurRadius blurs the entire + // image below, so it can skip this step. + if (layer.blurRegions.size()) { + SkPaint paint; + paint.setBlendMode(SkBlendMode::kSrc); + if (CC_UNLIKELY(mCapture->isCaptureRunning())) { + uint64_t id = mCapture->endOffscreenCapture(&offscreenCaptureState); + dstCanvas->drawAnnotation(SkRect::Make(dstCanvas->imageInfo().dimensions()), + String8::format("SurfaceID|%" PRId64, id).c_str(), + nullptr); + dstCanvas->drawImage(blurInput, 0, 0, SkSamplingOptions(), &paint); + } else { + activeSurface->draw(dstCanvas, 0, 0, SkSamplingOptions(), &paint); + } + } + + // assign dstCanvas to canvas and ensure that the canvas state is up to date + canvas = dstCanvas; + surfaceAutoSaveRestore.replace(canvas); + initCanvas(canvas, display); + + LOG_ALWAYS_FATAL_IF(activeSurface->getCanvas()->getSaveCount() != + dstSurface->getCanvas()->getSaveCount()); + LOG_ALWAYS_FATAL_IF(activeSurface->getCanvas()->getTotalMatrix() != + dstSurface->getCanvas()->getTotalMatrix()); + + // assign dstSurface to activeSurface + activeSurface = dstSurface; + } + + SkAutoCanvasRestore layerAutoSaveRestore(canvas, true); + if (CC_UNLIKELY(mCapture->isCaptureRunning())) { + // Record the name of the layer if the capture is running. + std::stringstream layerSettings; + PrintTo(layer, &layerSettings); + // Store the LayerSettings in additional information. + canvas->drawAnnotation(SkRect::MakeEmpty(), layer.name.c_str(), + SkData::MakeWithCString(layerSettings.str().c_str())); + } + // Layers have a local transform that should be applied to them + canvas->concat(getSkM44(layer.geometry.positionTransform).asM33()); + + const auto [bounds, roundRectClip] = + getBoundsAndClip(layer.geometry.boundaries, layer.geometry.roundedCornersCrop, + layer.geometry.roundedCornersRadius); + if (mBlurFilter && layerHasBlur(layer, ctModifiesAlpha)) { + std::unordered_map> cachedBlurs; + + // if multiple layers have blur, then we need to take a snapshot now because + // only the lowest layer will have blurImage populated earlier + if (!blurInput) { + blurInput = activeSurface->makeImageSnapshot(); + } + + // rect to be blurred in the coordinate space of blurInput + SkRect blurRect = canvas->getTotalMatrix().mapRect(bounds.rect()); + + // Some layers may be much bigger than the screen. If we used + // `blurRect` directly, this would allocate a large buffer with no + // benefit. Apply the clip, which already takes the display size + // into account. The clipped size will then be used to calculate the + // size of the buffer we will create for blurring. + if (!blurRect.intersect(SkRect::Make(canvas->getDeviceClipBounds()))) { + // This should not happen, but if it did, we would use the full + // sized layer, which should still be fine. + ALOGW("blur bounds does not intersect display clip!"); + } + + // if the clip needs to be applied then apply it now and make sure + // it is restored before we attempt to draw any shadows. + SkAutoCanvasRestore acr(canvas, true); + if (!roundRectClip.isEmpty()) { + canvas->clipRRect(roundRectClip, true); + } + + // TODO(b/182216890): Filter out empty layers earlier + if (blurRect.width() > 0 && blurRect.height() > 0) { + if (layer.backgroundBlurRadius > 0) { + ATRACE_NAME("BackgroundBlur"); + auto blurredImage = mBlurFilter->generate(grContext, layer.backgroundBlurRadius, + blurInput, blurRect); + + cachedBlurs[layer.backgroundBlurRadius] = blurredImage; + + mBlurFilter->drawBlurRegion(canvas, bounds, layer.backgroundBlurRadius, 1.0f, + blurRect, blurredImage, blurInput); + } + + canvas->concat(getSkM44(layer.blurRegionTransform).asM33()); + for (auto region : layer.blurRegions) { + if (cachedBlurs[region.blurRadius] == nullptr) { + ATRACE_NAME("BlurRegion"); + cachedBlurs[region.blurRadius] = + mBlurFilter->generate(grContext, region.blurRadius, blurInput, + blurRect); + } + + mBlurFilter->drawBlurRegion(canvas, getBlurRRect(region), region.blurRadius, + region.alpha, blurRect, + cachedBlurs[region.blurRadius], blurInput); + } + } + } + + if (layer.shadow.length > 0) { + // This would require a new parameter/flag to SkShadowUtils::DrawShadow + LOG_ALWAYS_FATAL_IF(layer.disableBlending, "Cannot disableBlending with a shadow"); + + SkRRect shadowBounds, shadowClip; + if (layer.geometry.boundaries == layer.shadow.boundaries) { + shadowBounds = bounds; + shadowClip = roundRectClip; + } else { + std::tie(shadowBounds, shadowClip) = + getBoundsAndClip(layer.shadow.boundaries, layer.geometry.roundedCornersCrop, + layer.geometry.roundedCornersRadius); + } + + // Technically, if bounds is a rect and roundRectClip is not empty, + // it means that the bounds and roundedCornersCrop were different + // enough that we should intersect them to find the proper shadow. + // In practice, this often happens when the two rectangles appear to + // not match due to rounding errors. Draw the rounded version, which + // looks more like the intent. + const auto& rrect = + shadowBounds.isRect() && !shadowClip.isEmpty() ? shadowClip : shadowBounds; + drawShadow(canvas, rrect, layer.shadow); + } + + const float layerDimmingRatio = layer.whitePointNits <= 0.f + ? displayDimmingRatio + : (layer.whitePointNits / maxLayerWhitePoint) * displayDimmingRatio; + + const bool dimInLinearSpace = display.dimmingStage != + aidl::android::hardware::graphics::composer3::DimmingStage::GAMMA_OETF; + + const bool isExtendedHdr = (layer.sourceDataspace & ui::Dataspace::RANGE_MASK) == + static_cast(ui::Dataspace::RANGE_EXTENDED) && + (display.outputDataspace & ui::Dataspace::TRANSFER_MASK) == + static_cast(ui::Dataspace::TRANSFER_SRGB); + + const ui::Dataspace runtimeEffectDataspace = !dimInLinearSpace && isExtendedHdr + ? static_cast( + (display.outputDataspace & ui::Dataspace::STANDARD_MASK) | + ui::Dataspace::TRANSFER_GAMMA2_2 | + (display.outputDataspace & ui::Dataspace::RANGE_MASK)) + : display.outputDataspace; + + // If the input dataspace is range extended, the output dataspace transfer is sRGB + // and dimmingStage is GAMMA_OETF, dim in linear space instead, and + // set the output dataspace's transfer to be GAMMA2_2. + // This allows DPU side to use oetf_gamma_2p2 for extended HDR layer + // to avoid tone shift. + // The reason of tone shift here is because HDR layers manage white point + // luminance in linear space, which color pipelines request GAMMA_OETF break + // without a gamma 2.2 fixup. + const bool requiresLinearEffect = layer.colorTransform != mat4() || + (mUseColorManagement && + needsToneMapping(layer.sourceDataspace, display.outputDataspace)) || + (dimInLinearSpace && !equalsWithinMargin(1.f, layerDimmingRatio)) || + (!dimInLinearSpace && isExtendedHdr); + + // quick abort from drawing the remaining portion of the layer + if (layer.skipContentDraw || + (layer.alpha == 0 && !requiresLinearEffect && !layer.disableBlending && + (!displayColorTransform || displayColorTransform->isAlphaUnchanged()))) { + continue; + } + + // If color management is disabled, then mark the source image with the same colorspace as + // the destination surface so that Skia's color management is a no-op. + const ui::Dataspace layerDataspace = + !mUseColorManagement ? display.outputDataspace : layer.sourceDataspace; + + SkPaint paint; + if (layer.source.buffer.buffer) { + ATRACE_NAME("DrawImage"); + validateInputBufferUsage(layer.source.buffer.buffer->getBuffer()); + const auto& item = layer.source.buffer; + auto imageTextureRef = getOrCreateBackendTexture(item.buffer->getBuffer(), false); + + // 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()); + } + + // isOpaque means we need to ignore the alpha in the image, + // replacing it with the alpha specified by the LayerSettings. See + // https://developer.android.com/reference/android/view/SurfaceControl.Builder#setOpaque(boolean) + // The proper way to do this is to use an SkColorType that ignores + // alpha, like kRGB_888x_SkColorType, and that is used if the + // incoming image is kRGBA_8888_SkColorType. However, the incoming + // image may be kRGBA_F16_SkColorType, for which there is no RGBX + // SkColorType, or kRGBA_1010102_SkColorType, for which we have + // kRGB_101010x_SkColorType, but it is not yet supported as a source + // on the GPU. (Adding both is tracked in skbug.com/12048.) In the + // meantime, we'll use a workaround that works unless we need to do + // any color conversion. The workaround requires that we pretend the + // image is already premultiplied, so that we do not premultiply it + // before applying SkBlendMode::kPlus. + const bool useIsOpaqueWorkaround = item.isOpaque && + (imageTextureRef->colorType() == kRGBA_1010102_SkColorType || + imageTextureRef->colorType() == kRGBA_F16_SkColorType); + const auto alphaType = useIsOpaqueWorkaround ? kPremul_SkAlphaType + : item.isOpaque ? kOpaque_SkAlphaType + : item.usePremultipliedAlpha ? kPremul_SkAlphaType + : kUnpremul_SkAlphaType; + sk_sp image = imageTextureRef->makeImage(layerDataspace, alphaType, grContext); + + auto texMatrix = getSkM44(item.textureTransform).asM33(); + // textureTansform was intended to be passed directly into a shader, so when + // building the total matrix with the textureTransform we need to first + // normalize it, then apply the textureTransform, then scale back up. + texMatrix.preScale(1.0f / bounds.width(), 1.0f / bounds.height()); + texMatrix.postScale(image->width(), image->height()); + + SkMatrix matrix; + if (!texMatrix.invert(&matrix)) { + matrix = texMatrix; + } + // The shader does not respect the translation, so we add it to the texture + // transform for the SkImage. This will make sure that the correct layer contents + // are drawn in the correct part of the screen. + matrix.postTranslate(bounds.rect().fLeft, bounds.rect().fTop); + + sk_sp shader; + + if (layer.source.buffer.useTextureFiltering) { + shader = image->makeShader(SkTileMode::kClamp, SkTileMode::kClamp, + SkSamplingOptions( + {SkFilterMode::kLinear, SkMipmapMode::kNone}), + &matrix); + } else { + shader = image->makeShader(SkSamplingOptions(), matrix); + } + + if (useIsOpaqueWorkaround) { + shader = SkShaders::Blend(SkBlendMode::kPlus, shader, + SkShaders::Color(SkColors::kBlack, + toSkColorSpace(layerDataspace))); + } + + paint.setShader(createRuntimeEffectShader( + RuntimeEffectShaderParameters{.shader = shader, + .layer = layer, + .display = display, + .undoPremultipliedAlpha = !item.isOpaque && + item.usePremultipliedAlpha, + .requiresLinearEffect = requiresLinearEffect, + .layerDimmingRatio = dimInLinearSpace + ? layerDimmingRatio + : 1.f, + .outputDataSpace = runtimeEffectDataspace})); + + // Turn on dithering when dimming beyond this (arbitrary) threshold... + static constexpr float kDimmingThreshold = 0.2f; + // ...or we're rendering an HDR layer down to an 8-bit target + // Most HDR standards require at least 10-bits of color depth for source content, so we + // can just extract the transfer function rather than dig into precise gralloc layout. + // Furthermore, we can assume that the only 8-bit target we support is RGBA8888. + const bool requiresDownsample = isHdrDataspace(layer.sourceDataspace) && + buffer->getPixelFormat() == PIXEL_FORMAT_RGBA_8888; + if (layerDimmingRatio <= kDimmingThreshold || requiresDownsample) { + paint.setDither(true); + } + paint.setAlphaf(layer.alpha); + + if (imageTextureRef->colorType() == kAlpha_8_SkColorType) { + LOG_ALWAYS_FATAL_IF(layer.disableBlending, "Cannot disableBlending with A8"); + + // SysUI creates the alpha layer as a coverage layer, which is + // appropriate for the DPU. Use a color matrix to convert it to + // a mask. + // TODO (b/219525258): Handle input as a mask. + // + // The color matrix will convert A8 pixels with no alpha to + // black, as described by this vector. If the display handles + // the color transform, we need to invert it to find the color + // that will result in black after the DPU applies the transform. + SkV4 black{0.0f, 0.0f, 0.0f, 1.0f}; // r, g, b, a + if (display.colorTransform != mat4() && display.deviceHandlesColorTransform) { + SkM44 colorSpaceMatrix = getSkM44(display.colorTransform); + if (colorSpaceMatrix.invert(&colorSpaceMatrix)) { + black = colorSpaceMatrix * black; + } else { + // We'll just have to use 0,0,0 as black, which should + // be close to correct. + ALOGI("Could not invert colorTransform!"); + } + } + SkColorMatrix colorMatrix(0, 0, 0, 0, black[0], + 0, 0, 0, 0, black[1], + 0, 0, 0, 0, black[2], + 0, 0, 0, -1, 1); + if (display.colorTransform != mat4() && !display.deviceHandlesColorTransform) { + // On the other hand, if the device doesn't handle it, we + // have to apply it ourselves. + colorMatrix.postConcat(toSkColorMatrix(display.colorTransform)); + } + paint.setColorFilter(SkColorFilters::Matrix(colorMatrix)); + } + } else { + ATRACE_NAME("DrawColor"); + const auto color = layer.source.solidColor; + sk_sp shader = SkShaders::Color(SkColor4f{.fR = color.r, + .fG = color.g, + .fB = color.b, + .fA = layer.alpha}, + toSkColorSpace(layerDataspace)); + paint.setShader(createRuntimeEffectShader( + RuntimeEffectShaderParameters{.shader = shader, + .layer = layer, + .display = display, + .undoPremultipliedAlpha = false, + .requiresLinearEffect = requiresLinearEffect, + .layerDimmingRatio = layerDimmingRatio, + .outputDataSpace = runtimeEffectDataspace})); + } + + if (layer.disableBlending) { + paint.setBlendMode(SkBlendMode::kSrc); + } + + // An A8 buffer will already have the proper color filter attached to + // its paint, including the displayColorTransform as needed. + if (!paint.getColorFilter()) { + if (!dimInLinearSpace && !equalsWithinMargin(1.0, layerDimmingRatio)) { + // If we don't dim in linear space, then when we gamma correct the dimming ratio we + // can assume a gamma 2.2 transfer function. + static constexpr float kInverseGamma22 = 1.f / 2.2f; + const auto gammaCorrectedDimmingRatio = + std::pow(layerDimmingRatio, kInverseGamma22); + auto dimmingMatrix = + mat4::scale(vec4(gammaCorrectedDimmingRatio, gammaCorrectedDimmingRatio, + gammaCorrectedDimmingRatio, 1.f)); + + const auto colorFilter = + SkColorFilters::Matrix(toSkColorMatrix(std::move(dimmingMatrix))); + paint.setColorFilter(displayColorTransform + ? displayColorTransform->makeComposed(colorFilter) + : colorFilter); + } else { + paint.setColorFilter(displayColorTransform); + } + } + + if (!roundRectClip.isEmpty()) { + canvas->clipRRect(roundRectClip, true); + } + + if (!bounds.isRect()) { + paint.setAntiAlias(true); + canvas->drawRRect(bounds, paint); + } else { + canvas->drawRect(bounds.rect(), paint); + } + if (kFlushAfterEveryLayer) { + ATRACE_NAME("flush surface"); + activeSurface->flush(); + } + } + 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(); + { + ATRACE_NAME("flush surface"); + LOG_ALWAYS_FATAL_IF(activeSurface != dstSurface); + activeSurface->flush(); + } + + auto drawFence = sp::make(flushAndSubmit(grContext)); + + if (ATRACE_ENABLED()) { + static gui::FenceMonitor sMonitor("RE Completion"); + sMonitor.queueFence(drawFence); + } + resultPromise->set_value(std::move(drawFence)); +} + +size_t SkiaRenderEngine::getMaxTextureSize() const { + return mGrContext->maxTextureSize(); +} + +size_t SkiaRenderEngine::getMaxViewportDims() const { + return mGrContext->maxRenderTargetSize(); +} + +void SkiaRenderEngine::drawShadow(SkCanvas* canvas, + const SkRRect& casterRRect, + const ShadowSettings& settings) { + ATRACE_CALL(); + const float casterZ = settings.length / 2.0f; + const auto flags = + settings.casterIsTranslucent ? kTransparentOccluder_ShadowFlag : kNone_ShadowFlag; + + SkShadowUtils::DrawShadow(canvas, SkPath::RRect(casterRRect), SkPoint3::Make(0, 0, casterZ), + getSkPoint3(settings.lightPos), settings.lightRadius, + getSkColor(settings.ambientColor), getSkColor(settings.spotColor), + flags); +} + +void SkiaRenderEngine::onActiveDisplaySizeChanged(ui::Size size) { + // This cache multiplier was selected based on review of cache sizes relative + // to the screen resolution. Looking at the worst case memory needed by blur (~1.5x), + // shadows (~1x), and general data structures (e.g. vertex buffers) we selected this as a + // conservative default based on that analysis. + const float SURFACE_SIZE_MULTIPLIER = 3.5f * bytesPerPixel(mDefaultPixelFormat); + const int maxResourceBytes = size.width * size.height * SURFACE_SIZE_MULTIPLIER; + + // start by resizing the current context + getActiveGrContext()->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); + // reset back to the initial context that was active when this method was called + useProtectedContext(originalProtectedState); + } +} + +void SkiaRenderEngine::dump(std::string& result) { + // Dump for the specific backend (GLES or Vk) + appendBackendSpecificInfoToDump(result); + + // Info about protected content + StringAppendF(&result, "RenderEngine supports protected context: %d\n", + supportsProtectedContent()); + StringAppendF(&result, "RenderEngine is in protected context: %d\n", mInProtectedContext); + StringAppendF(&result, "RenderEngine shaders cached since last dump/primeCache: %d\n", + mSkSLCacheMonitor.shadersCachedSinceLastCall()); + + std::vector cpuResourceMap = { + {"skia/sk_resource_cache/bitmap_", "Bitmaps"}, + {"skia/sk_resource_cache/rrect-blur_", "Masks"}, + {"skia/sk_resource_cache/rects-blur_", "Masks"}, + {"skia/sk_resource_cache/tessellated", "Shadows"}, + {"skia", "Other"}, + }; + SkiaMemoryReporter cpuReporter(cpuResourceMap, false); + SkGraphics::DumpMemoryStatistics(&cpuReporter); + StringAppendF(&result, "Skia CPU Caches: "); + cpuReporter.logTotals(result); + cpuReporter.logOutput(result); + + { + std::lock_guard lock(mRenderingMutex); + + std::vector gpuResourceMap = { + {"texture_renderbuffer", "Texture/RenderBuffer"}, + {"texture", "Texture"}, + {"gr_text_blob_cache", "Text"}, + {"skia", "Other"}, + }; + SkiaMemoryReporter gpuReporter(gpuResourceMap, true); + mGrContext->dumpMemoryStatistics(&gpuReporter); + StringAppendF(&result, "Skia's GPU Caches: "); + gpuReporter.logTotals(result); + gpuReporter.logOutput(result); + StringAppendF(&result, "Skia's Wrapped Objects:\n"); + gpuReporter.logOutput(result, true); + + StringAppendF(&result, "RenderEngine tracked buffers: %zu\n", + mGraphicBufferExternalRefs.size()); + StringAppendF(&result, "Dumping buffer ids...\n"); + for (const auto& [id, refCounts] : mGraphicBufferExternalRefs) { + StringAppendF(&result, "- 0x%" PRIx64 " - %d refs \n", id, refCounts); + } + StringAppendF(&result, "RenderEngine AHB/BackendTexture cache size: %zu\n", + mTextureCache.size()); + StringAppendF(&result, "Dumping buffer ids...\n"); + // TODO(178539829): It would be nice to know which layer these are coming from and what + // the texture sizes are. + for (const auto& [id, unused] : mTextureCache) { + StringAppendF(&result, "- 0x%" PRIx64 "\n", id); + } + StringAppendF(&result, "\n"); + + SkiaMemoryReporter gpuProtectedReporter(gpuResourceMap, true); + if (mProtectedGrContext) { + mProtectedGrContext->dumpMemoryStatistics(&gpuProtectedReporter); + } + StringAppendF(&result, "Skia's GPU Protected Caches: "); + gpuProtectedReporter.logTotals(result); + gpuProtectedReporter.logOutput(result); + StringAppendF(&result, "Skia's Protected Wrapped Objects:\n"); + gpuProtectedReporter.logOutput(result, true); + + StringAppendF(&result, "\n"); + StringAppendF(&result, "RenderEngine runtime effects: %zu\n", mRuntimeEffects.size()); + for (const auto& [linearEffect, unused] : mRuntimeEffects) { + StringAppendF(&result, "- inputDataspace: %s\n", + dataspaceDetails( + static_cast(linearEffect.inputDataspace)) + .c_str()); + StringAppendF(&result, "- outputDataspace: %s\n", + dataspaceDetails( + static_cast(linearEffect.outputDataspace)) + .c_str()); + StringAppendF(&result, "undoPremultipliedAlpha: %s\n", + linearEffect.undoPremultipliedAlpha ? "true" : "false"); + } + } + StringAppendF(&result, "\n"); +} + } // namespace skia } // namespace renderengine } // namespace android diff --git a/libs/renderengine/skia/SkiaRenderEngine.h b/libs/renderengine/skia/SkiaRenderEngine.h index 160a18698e9d8bc4ce44ebc829f307b5c6ceec16..6457bfa9eb9334fc23ca5042586a43f477ae7e85 100644 --- a/libs/renderengine/skia/SkiaRenderEngine.h +++ b/libs/renderengine/skia/SkiaRenderEngine.h @@ -20,6 +20,31 @@ #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 "debug/SkiaCapture.h" +#include "filters/BlurFilter.h" +#include "filters/LinearEffect.h" +#include "filters/StretchShaderFactory.h" + +class SkData; + +struct SkPoint3; + namespace android { namespace renderengine { @@ -31,35 +56,147 @@ namespace skia { class BlurFilter; -// TODO: Put common skia stuff here that can be shared between the GL & Vulkan backends -// Currently mostly just handles all the no-op / missing APIs class SkiaRenderEngine : public RenderEngine { public: static std::unique_ptr create(const RenderEngineCreationArgs& args); - SkiaRenderEngine(RenderEngineType type); - ~SkiaRenderEngine() override {} - - virtual std::future primeCache() override { return {}; }; - virtual void genTextures(size_t /*count*/, uint32_t* /*names*/) override{}; - virtual void deleteTextures(size_t /*count*/, uint32_t const* /*names*/) override{}; - virtual bool isProtected() const override { return false; } // mInProtectedContext; } - virtual bool supportsProtectedContent() const override { return false; }; - virtual int getContextPriority() override { return 0; } - virtual int reportShadersCompiled() { return 0; } - virtual void setEnableTracing(bool tracingEnabled) override; + SkiaRenderEngine(RenderEngineType type, + PixelFormat pixelFormat, + bool useColorManagement, + bool supportsBackgroundBlur); + ~SkiaRenderEngine() override; + + std::future primeCache() override final; + void cleanupPostRender() override final; + void cleanFramebufferCache() override final{ } + bool supportsBackgroundBlur() override final { + return mBlurFilter != nullptr; + } + void onActiveDisplaySizeChanged(ui::Size size) override final; + int reportShadersCompiled(); + virtual void genTextures(size_t /*count*/, uint32_t* /*names*/) override final{}; + virtual void deleteTextures(size_t /*count*/, uint32_t const* /*names*/) override final{}; + virtual void setEnableTracing(bool tracingEnabled) override final; + + void useProtectedContext(bool useProtectedContext) override; + bool supportsProtectedContent() const override { + return supportsProtectedContentImpl(); + } + void ensureGrContextsCreated(); protected: - virtual void mapExternalTextureBuffer(const sp& /*buffer*/, - bool /*isRenderable*/) override = 0; - virtual void unmapExternalTextureBuffer(const sp& /*buffer*/) override = 0; - - virtual void drawLayersInternal( - const std::shared_ptr>&& resultPromise, - const DisplaySettings& display, const std::vector& layers, - const std::shared_ptr& buffer, const bool useFramebufferCache, - base::unique_fd&& bufferFence) override { - resultPromise->set_value({NO_ERROR, base::unique_fd()}); + // 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; + 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 appendBackendSpecificInfoToDump(std::string& result) = 0; + + size_t getMaxTextureSize() const override final; + size_t getMaxViewportDims() const override final; + GrDirectContext* getActiveGrContext(); + + bool isProtected() const { return mInProtectedContext; } + + // Implements PersistentCache as a way to monitor what SkSL shaders Skia has + // cached. + class SkSLCacheMonitor : public GrContextOptions::PersistentCache { + public: + SkSLCacheMonitor() = default; + ~SkSLCacheMonitor() override = default; + + sk_sp load(const SkData& key) override; + + void store(const SkData& key, const SkData& data, const SkString& description) override; + + int shadersCachedSinceLastCall() { + const int shadersCachedSinceLastCall = mShadersCachedSinceLastCall; + mShadersCachedSinceLastCall = 0; + return shadersCachedSinceLastCall; + } + + int totalShadersCompiled() const { return mTotalShadersCompiled; } + + private: + int mShadersCachedSinceLastCall = 0; + int mTotalShadersCompiled = 0; + }; + +private: + void mapExternalTextureBuffer(const sp& buffer, + bool isRenderable) override final; + void unmapExternalTextureBuffer(sp&& buffer) override final; + bool canSkipPostRenderCleanup() const override final; + + std::shared_ptr getOrCreateBackendTexture( + const sp& buffer, bool isOutputBuffer) REQUIRES(mRenderingMutex); + void initCanvas(SkCanvas* canvas, const DisplaySettings& display); + void drawShadow(SkCanvas* canvas, const SkRRect& casterRRect, + const ShadowSettings& shadowSettings); + void drawLayersInternal(const std::shared_ptr>&& resultPromise, + const DisplaySettings& display, + const std::vector& layers, + const std::shared_ptr& buffer, + const bool useFramebufferCache, + base::unique_fd&& bufferFence) override final; + + void dump(std::string& result) override final; + + // If requiresLinearEffect is true or the layer has a stretchEffect a new shader is returned. + // Otherwise it returns the input shader. + struct RuntimeEffectShaderParameters { + sk_sp shader; + const LayerSettings& layer; + const DisplaySettings& display; + bool undoPremultipliedAlpha; + bool requiresLinearEffect; + float layerDimmingRatio; + const ui::Dataspace outputDataSpace; }; + sk_sp createRuntimeEffectShader(const RuntimeEffectShaderParameters&); + + const PixelFormat mDefaultPixelFormat; + const bool mUseColorManagement; + + // Identifier used for various mappings of layers to various + // textures or shaders + using GraphicBufferId = uint64_t; + + // Number of external holders of ExternalTexture references, per GraphicBuffer ID. + std::unordered_map mGraphicBufferExternalRefs + GUARDED_BY(mRenderingMutex); + // For GL, this cache is shared between protected and unprotected contexts. For Vulkan, it is + // only used for the unprotected context, because Vulkan does not allow sharing between + // contexts, and protected is less common. + std::unordered_map> mTextureCache + GUARDED_BY(mRenderingMutex); + std::unordered_map, shaders::LinearEffectHasher> + mRuntimeEffects; + AutoBackendTexture::CleanupManager mTextureCleanupMgr GUARDED_BY(mRenderingMutex); + + StretchShaderFactory mStretchShaderFactory; + + sp mLastDrawFence; + BlurFilter* mBlurFilter = nullptr; + + // Object to capture commands send to Skia. + std::unique_ptr mCapture; + + // 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; + // Same as above, but for protected content (eg. DRM) + sk_sp mProtectedGrContext; + bool mInProtectedContext = false; }; } // namespace skia diff --git a/libs/renderengine/skia/SkiaVkRenderEngine.cpp b/libs/renderengine/skia/SkiaVkRenderEngine.cpp new file mode 100644 index 0000000000000000000000000000000000000000..b99e3853ee9c817b60a6a9da9ceac9bae7e72fc1 --- /dev/null +++ b/libs/renderengine/skia/SkiaVkRenderEngine.cpp @@ -0,0 +1,711 @@ +/* + * 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. + */ + +// #define LOG_NDEBUG 0 +#undef LOG_TAG +#define LOG_TAG "RenderEngine" +#define ATRACE_TAG ATRACE_TAG_GRAPHICS + +#include "SkiaVkRenderEngine.h" + +#include +#include +#include +#include + +#include +#include +#include +#include + +#include +#include +#include + +#include +#include "log/log_main.h" + +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) {} +}; + +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; + 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; + 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); + } +}; + +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) { + 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, GetPhysicalDeviceQueueFamilyProperties); + 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"); + } + + // 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; + vkGetPhysicalDeviceQueueFamilyProperties(physicalDevice, &queueCount, nullptr); + if (queueCount == 0) { + BAIL("Could not find queues for physical device"); + } + + std::vector queueProps(queueCount); + vkGetPhysicalDeviceQueueFamilyProperties(physicalDevice, &queueCount, queueProps.data()); + + int graphicsQueueIndex = -1; + for (uint32_t i = 0; i < queueCount; ++i) { + if (queueProps[i].queueFlags & VK_QUEUE_GRAPHICS_BIT) { + 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; + } + + 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; + + 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. + VK_QUEUE_GLOBAL_PRIORITY_REALTIME_EXT, + }; + + if (interface.grExtensions.hasExtension(VK_EXT_GLOBAL_PRIORITY_EXTENSION_NAME, 2)) { + queueNextPtr = &queuePriorityCreateInfo; + interface.isRealtimePriority = true; + } + + 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 + + ALOGD("%s: Success init Vulkan interface", __func__); + 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; + } + + interface->samplerYcbcrConversionFeatures = nullptr; + interface->physicalDeviceFeatures2 = nullptr; + interface->protectedMemoryFeatures = nullptr; +} + +static VulkanInterface sVulkanInterface; +static VulkanInterface sProtectedContentVulkanInterface; + +static void sSetupVulkanInterface() { + if (!sVulkanInterface.initialized) { + sVulkanInterface = initVulkanInterface(false /* no protected content */); + // We will have to abort if non-protected VkDevice creation fails (then nothing works). + LOG_ALWAYS_FATAL_IF(!sVulkanInterface.initialized, + "Could not initialize Vulkan RenderEngine!"); + } + if (!sProtectedContentVulkanInterface.initialized) { + sProtectedContentVulkanInterface = initVulkanInterface(true /* protected content */); + if (!sProtectedContentVulkanInterface.initialized) { + ALOGE("Could not initialize protected content Vulkan RenderEngine."); + } + } +} + +namespace skia { + +using base::StringAppendF; + +bool SkiaVkRenderEngine::canSupportSkiaVkRenderEngine() { + VulkanInterface temp = initVulkanInterface(false /* no protected content */); + ALOGD("SkiaVkRenderEngine::canSupportSkiaVkRenderEngine(): initialized == %s.", + temp.initialized ? "true" : "false"); + return temp.initialized; +} + +std::unique_ptr SkiaVkRenderEngine::create( + const RenderEngineCreationArgs& args) { + std::unique_ptr engine(new SkiaVkRenderEngine(args)); + engine->ensureGrContextsCreated(); + + if (sVulkanInterface.initialized) { + ALOGD("SkiaVkRenderEngine::%s: successfully initialized SkiaVkRenderEngine", __func__); + return engine; + } else { + ALOGD("SkiaVkRenderEngine::%s: could not create SkiaVkRenderEngine. " + "Likely insufficient Vulkan support", + __func__); + return {}; + } +} + +SkiaVkRenderEngine::SkiaVkRenderEngine(const RenderEngineCreationArgs& args) + : SkiaRenderEngine(args.renderEngineType, static_cast(args.pixelFormat), + args.useColorManagement, args.supportsBackgroundBlur) {} + +SkiaVkRenderEngine::~SkiaVkRenderEngine() { + finishRenderingAndAbandonContext(); +} + +SkiaRenderEngine::Contexts SkiaVkRenderEngine::createDirectContexts( + const GrContextOptions& options) { + sSetupVulkanInterface(); + + SkiaRenderEngine::Contexts contexts; + contexts.first = GrDirectContext::MakeVulkan(sVulkanInterface.getBackendContext(), options); + if (supportsProtectedContentImpl()) { + contexts.second = + GrDirectContext::MakeVulkan(sProtectedContentVulkanInterface.getBackendContext(), + options); + } + + return contexts; +} + +bool SkiaVkRenderEngine::supportsProtectedContentImpl() const { + return sProtectedContentVulkanInterface.initialized; +} + +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 VulkanInterface& getVulkanInterface(bool protectedContext) { + if (protectedContext) { + return sProtectedContentVulkanInterface; + } + return sVulkanInterface; +} + +void SkiaVkRenderEngine::waitFence(GrDirectContext* grContext, 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; + beSemaphore.initVulkan(waitSemaphore); + grContext->wait(1, &beSemaphore, true /* delete after wait */); +} + +base::unique_fd SkiaVkRenderEngine::flushAndSubmit(GrDirectContext* grContext) { + VulkanInterface& vi = getVulkanInterface(isProtected()); + VkSemaphore semaphore = vi.createExportableSemaphore(); + + GrBackendSemaphore backendSemaphore; + backendSemaphore.initVulkan(semaphore); + + GrFlushInfo flushInfo; + DestroySemaphoreInfo* destroySemaphoreInfo = nullptr; + if (semaphore != VK_NULL_HANDLE) { + destroySemaphoreInfo = new DestroySemaphoreInfo(semaphore); + flushInfo.fNumSemaphores = 1; + flushInfo.fSignalSemaphores = &backendSemaphore; + flushInfo.fFinishedProc = isProtected() ? delete_semaphore_protected : delete_semaphore; + flushInfo.fFinishedContext = destroySemaphoreInfo; + } + GrSemaphoresSubmitted submitted = grContext->flush(flushInfo); + grContext->submit(false /* no cpu sync */); + 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; + if (getVulkanInterface(isProtected()).isRealtimePriority) { + return kRealtimePriority; + } else { + return 0; + } +} + +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 protected device initialized: %d\n", + sProtectedContentVulkanInterface.initialized); + + if (!sVulkanInterface.initialized) { + return; + } + + StringAppendF(&result, "\n Instance extensions:\n"); + for (const auto& name : sVulkanInterface.instanceExtensionNames) { + StringAppendF(&result, "\n %s\n", name.c_str()); + } + + StringAppendF(&result, "\n Device extensions:\n"); + for (const auto& name : sVulkanInterface.deviceExtensionNames) { + StringAppendF(&result, "\n %s\n", name.c_str()); + } +} + +} // namespace skia +} // namespace renderengine +} // namespace android diff --git a/libs/renderengine/skia/SkiaVkRenderEngine.h b/libs/renderengine/skia/SkiaVkRenderEngine.h new file mode 100644 index 0000000000000000000000000000000000000000..2e0cf45220c8d030219bd3096b4aa29fa5c6a3d1 --- /dev/null +++ b/libs/renderengine/skia/SkiaVkRenderEngine.h @@ -0,0 +1,58 @@ +/* + * 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 SF_SKIAVKRENDERENGINE_H_ +#define SF_SKIAVKRENDERENGINE_H_ + +#include + +#include "SkiaRenderEngine.h" + +namespace android { +namespace renderengine { +namespace skia { + +class SkiaVkRenderEngine : public SkiaRenderEngine { +public: + // Returns false if Vulkan implementation can't support SkiaVkRenderEngine. + static bool canSupportSkiaVkRenderEngine(); + static std::unique_ptr create(const RenderEngineCreationArgs& args); + ~SkiaVkRenderEngine() override; + + int getContextPriority() override; + +protected: + // Implementations of abstract SkiaRenderEngine functions specific to + // rendering backend + virtual SkiaRenderEngine::Contexts createDirectContexts(const GrContextOptions& options); + 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 appendBackendSpecificInfoToDump(std::string& result) override; + +private: + SkiaVkRenderEngine(const RenderEngineCreationArgs& args); + base::unique_fd flush(); + + GrVkBackendContext mBackendContext; +}; + +} // namespace skia +} // namespace renderengine +} // namespace android + +#endif diff --git a/libs/renderengine/skia/debug/SkiaCapture.cpp b/libs/renderengine/skia/debug/SkiaCapture.cpp index 856fff4b01c15e57cd4b260bc37c40ab858a4695..b21b01cc1b62bff16d035d836f20b2a130832b51 100644 --- a/libs/renderengine/skia/debug/SkiaCapture.cpp +++ b/libs/renderengine/skia/debug/SkiaCapture.cpp @@ -27,6 +27,9 @@ #include #include "CommonPool.h" +#include "SkCanvas.h" +#include "SkRect.h" +#include "SkTypeface.h" #include "src/utils/SkMultiPictureDocument.h" namespace android { diff --git a/libs/renderengine/skia/debug/SkiaCapture.h b/libs/renderengine/skia/debug/SkiaCapture.h index f1946290ca81633e9df5d9f023b9270afed45882..d65a579916f8fbd27f5a2b90e7305b8b7b19714c 100644 --- a/libs/renderengine/skia/debug/SkiaCapture.h +++ b/libs/renderengine/skia/debug/SkiaCapture.h @@ -19,13 +19,15 @@ #include #include #include +#include +#include #include +#include "tools/SkSharingProc.h" #include #include #include "CaptureTimer.h" -#include "tools/SkSharingProc.h" namespace android { namespace renderengine { diff --git a/libs/renderengine/skia/filters/BlurFilter.cpp b/libs/renderengine/skia/filters/BlurFilter.cpp index 63cc02b7eae0d5b4c8c096d726d71cb80baac63a..2557ac977063fdbf99e091a3bdded3f4546b003e 100644 --- a/libs/renderengine/skia/filters/BlurFilter.cpp +++ b/libs/renderengine/skia/filters/BlurFilter.cpp @@ -17,7 +17,6 @@ #define ATRACE_TAG ATRACE_TAG_GRAPHICS #include "BlurFilter.h" #include -#include #include #include #include diff --git a/libs/renderengine/skia/filters/GaussianBlurFilter.cpp b/libs/renderengine/skia/filters/GaussianBlurFilter.cpp index 55867a95cc7b2a142e7e72ca26d78bb64567ef99..511d7c9350a49f98a95c3f5364298e4c2cad11b4 100644 --- a/libs/renderengine/skia/filters/GaussianBlurFilter.cpp +++ b/libs/renderengine/skia/filters/GaussianBlurFilter.cpp @@ -18,7 +18,6 @@ #include "GaussianBlurFilter.h" #include -#include #include #include #include @@ -26,6 +25,7 @@ #include #include #include +#include "include/gpu/GpuTypes.h" // from Skia #include #include @@ -45,7 +45,8 @@ sk_sp GaussianBlurFilter::generate(GrRecordingContext* context, const u // 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 = SkSurface::MakeRenderTarget(context, SkBudgeted::kNo, scaledInfo); + sk_sp surface = SkSurface::MakeRenderTarget(context, + skgpu::Budgeted::kNo, scaledInfo); SkPaint paint; paint.setBlendMode(SkBlendMode::kSrc); diff --git a/libs/renderengine/skia/filters/KawaseBlurFilter.cpp b/libs/renderengine/skia/filters/KawaseBlurFilter.cpp index bfde06fd9ae35699b8b8acea694b9e3355ee2e88..e370c39a941e7e02edb723efc356e31d3932ac91 100644 --- a/libs/renderengine/skia/filters/KawaseBlurFilter.cpp +++ b/libs/renderengine/skia/filters/KawaseBlurFilter.cpp @@ -18,7 +18,6 @@ #include "KawaseBlurFilter.h" #include -#include #include #include #include diff --git a/libs/renderengine/tests/Android.bp b/libs/renderengine/tests/Android.bp index bbab792dbaa9feb04c21ccc324a8027f0734d5c0..50e166d2a727bdeb89113faecd8487d23b164622 100644 --- a/libs/renderengine/tests/Android.bp +++ b/libs/renderengine/tests/Android.bp @@ -24,7 +24,8 @@ package { cc_test { name: "librenderengine_test", defaults: [ - "skia_deps", + "android.hardware.graphics.composer3-ndk_shared", + "librenderengine_deps", "surfaceflinger_defaults", ], test_suites: ["device-tests"], @@ -49,7 +50,6 @@ cc_test { ], shared_libs: [ - "android.hardware.graphics.composer3-V1-ndk", "libbase", "libcutils", "libEGL", diff --git a/libs/renderengine/tests/RenderEngineTest.cpp b/libs/renderengine/tests/RenderEngineTest.cpp index 8889f76ccf6d9d686a9ecce9f3595db440c237e2..f3f2da8a0e52695c87134ffd38a4d5f143fdf966 100644 --- a/libs/renderengine/tests/RenderEngineTest.cpp +++ b/libs/renderengine/tests/RenderEngineTest.cpp @@ -37,8 +37,8 @@ #include #include -#include "../gl/GLESRenderEngine.h" #include "../skia/SkiaGLRenderEngine.h" +#include "../skia/SkiaVkRenderEngine.h" #include "../threaded/RenderEngineThreaded.h" constexpr int DEFAULT_DISPLAY_WIDTH = 128; @@ -108,25 +108,24 @@ public: virtual std::string name() = 0; virtual renderengine::RenderEngine::RenderEngineType type() = 0; virtual std::unique_ptr createRenderEngine() = 0; - virtual std::unique_ptr createGLESRenderEngine() { - return nullptr; - } + virtual bool typeSupported() = 0; virtual bool useColorManagement() const = 0; }; -class GLESRenderEngineFactory : public RenderEngineFactory { +class SkiaVkRenderEngineFactory : public RenderEngineFactory { public: - std::string name() override { return "GLESRenderEngineFactory"; } + std::string name() override { return "SkiaVkRenderEngineFactory"; } renderengine::RenderEngine::RenderEngineType type() { - return renderengine::RenderEngine::RenderEngineType::GLES; + return renderengine::RenderEngine::RenderEngineType::SKIA_VK; } std::unique_ptr createRenderEngine() override { - return createGLESRenderEngine(); + std::unique_ptr re = createSkiaVkRenderEngine(); + return re; } - std::unique_ptr createGLESRenderEngine() { + std::unique_ptr createSkiaVkRenderEngine() { renderengine::RenderEngineCreationArgs reCreationArgs = renderengine::RenderEngineCreationArgs::Builder() .setPixelFormat(static_cast(ui::PixelFormat::RGBA_8888)) @@ -139,42 +138,20 @@ public: .setRenderEngineType(type()) .setUseColorManagerment(useColorManagement()) .build(); - return renderengine::gl::GLESRenderEngine::create(reCreationArgs); + return renderengine::skia::SkiaVkRenderEngine::create(reCreationArgs); } + bool typeSupported() override { + return skia::SkiaVkRenderEngine::canSupportSkiaVkRenderEngine(); + } bool useColorManagement() const override { return false; } + void skip() { GTEST_SKIP(); } }; -class GLESCMRenderEngineFactory : public RenderEngineFactory { +class SkiaVkCMRenderEngineFactory : public SkiaVkRenderEngineFactory { public: - std::string name() override { return "GLESCMRenderEngineFactory"; } - - renderengine::RenderEngine::RenderEngineType type() { - return renderengine::RenderEngine::RenderEngineType::GLES; - } - - std::unique_ptr createRenderEngine() override { - return createGLESRenderEngine(); - } - - std::unique_ptr createGLESRenderEngine() override { - renderengine::RenderEngineCreationArgs reCreationArgs = - renderengine::RenderEngineCreationArgs::Builder() - .setPixelFormat(static_cast(ui::PixelFormat::RGBA_8888)) - .setImageCacheSize(1) - .setEnableProtectedContext(false) - .setPrecacheToneMapperShaderOnly(false) - .setSupportsBackgroundBlur(true) - .setContextPriority(renderengine::RenderEngine::ContextPriority::MEDIUM) - .setRenderEngineType(type()) - .setUseColorManagerment(useColorManagement()) - .build(); - return renderengine::gl::GLESRenderEngine::create(reCreationArgs); - } - bool useColorManagement() const override { return true; } }; - class SkiaGLESRenderEngineFactory : public RenderEngineFactory { public: std::string name() override { return "SkiaGLRenderEngineFactory"; } @@ -198,6 +175,7 @@ public: return renderengine::skia::SkiaGLRenderEngine::create(reCreationArgs); } + bool typeSupported() override { return true; } bool useColorManagement() const override { return false; } }; @@ -224,6 +202,7 @@ public: return renderengine::skia::SkiaGLRenderEngine::create(reCreationArgs); } + bool typeSupported() override { return true; } bool useColorManagement() const override { return true; } }; @@ -232,14 +211,14 @@ public: std::shared_ptr allocateDefaultBuffer() { return std::make_shared< renderengine::impl:: - ExternalTexture>(new GraphicBuffer(DEFAULT_DISPLAY_WIDTH, - DEFAULT_DISPLAY_HEIGHT, - HAL_PIXEL_FORMAT_RGBA_8888, 1, - GRALLOC_USAGE_SW_READ_OFTEN | - GRALLOC_USAGE_SW_WRITE_OFTEN | - GRALLOC_USAGE_HW_RENDER | - GRALLOC_USAGE_HW_TEXTURE, - "output"), + ExternalTexture>(sp:: + make(DEFAULT_DISPLAY_WIDTH, DEFAULT_DISPLAY_HEIGHT, + HAL_PIXEL_FORMAT_RGBA_8888, 1, + GRALLOC_USAGE_SW_READ_OFTEN | + GRALLOC_USAGE_SW_WRITE_OFTEN | + GRALLOC_USAGE_HW_RENDER | + GRALLOC_USAGE_HW_TEXTURE, + "output"), *mRE, renderengine::impl::ExternalTexture::Usage::READABLE | renderengine::impl::ExternalTexture::Usage:: @@ -251,12 +230,12 @@ public: uint32_t height) { return std::make_shared< renderengine::impl:: - ExternalTexture>(new GraphicBuffer(width, height, - HAL_PIXEL_FORMAT_RGBA_8888, 1, - GRALLOC_USAGE_SW_READ_OFTEN | - GRALLOC_USAGE_SW_WRITE_OFTEN | - GRALLOC_USAGE_HW_TEXTURE, - "input"), + ExternalTexture>(sp:: + make(width, height, HAL_PIXEL_FORMAT_RGBA_8888, 1, + GRALLOC_USAGE_SW_READ_OFTEN | + GRALLOC_USAGE_SW_WRITE_OFTEN | + GRALLOC_USAGE_HW_TEXTURE, + "input"), *mRE, renderengine::impl::ExternalTexture::Usage::READABLE | renderengine::impl::ExternalTexture::Usage:: @@ -285,10 +264,12 @@ public: } std::shared_ptr allocateR8Buffer(int width, int height) { - auto buffer = new GraphicBuffer(width, height, android::PIXEL_FORMAT_R_8, 1, - GRALLOC_USAGE_SW_READ_OFTEN | GRALLOC_USAGE_SW_WRITE_OFTEN | - GRALLOC_USAGE_HW_TEXTURE, - "r8"); + const auto kUsageFlags = + static_cast(GRALLOC_USAGE_SW_READ_OFTEN | GRALLOC_USAGE_SW_WRITE_OFTEN | + GRALLOC_USAGE_HW_TEXTURE); + auto buffer = + sp::make(static_cast(width), static_cast(height), + android::PIXEL_FORMAT_R_8, 1u, kUsageFlags, "r8"); if (buffer->initCheck() != 0) { // Devices are not required to support R8. return nullptr; @@ -311,9 +292,6 @@ public: } for (uint32_t texName : mTexNames) { mRE->deleteTextures(1, &texName); - if (mGLESRE != nullptr) { - EXPECT_FALSE(mGLESRE->isTextureNameKnownForTesting(texName)); - } } const ::testing::TestInfo* const test_info = ::testing::UnitTest::GetInstance()->current_test_info(); @@ -526,20 +504,15 @@ public: void invokeDraw(const renderengine::DisplaySettings& settings, const std::vector& layers) { - std::future result = + ftl::Future future = mRE->drawLayers(settings, layers, mBuffer, true, base::unique_fd()); + ASSERT_TRUE(future.valid()); - ASSERT_TRUE(result.valid()); - auto [status, fence] = result.get(); + auto result = future.get(); + ASSERT_TRUE(result.ok()); - ASSERT_EQ(NO_ERROR, status); - if (fence.ok()) { - sync_wait(fence.get(), -1); - } - - if (layers.size() > 0 && mGLESRE != nullptr) { - ASSERT_TRUE(mGLESRE->isFramebufferImageCachedForTesting(mBuffer->getBuffer()->getId())); - } + auto fence = result.value(); + fence->waitForever(LOG_TAG); } void drawEmptyLayers() { @@ -662,26 +635,13 @@ public: std::unique_ptr mRE; std::shared_ptr mBuffer; - // GLESRenderEngine for testing GLES-specific behavior. - // Owened by mRE, but this is downcasted. - renderengine::gl::GLESRenderEngine* mGLESRE = nullptr; std::vector mTexNames; }; void RenderEngineTest::initializeRenderEngine() { const auto& renderEngineFactory = GetParam(); - if (renderEngineFactory->type() == renderengine::RenderEngine::RenderEngineType::GLES) { - // Only GLESRenderEngine exposes test-only methods. Provide a pointer to the - // GLESRenderEngine if we're using it so that we don't need to dynamic_cast - // every time. - std::unique_ptr renderEngine = - renderEngineFactory->createGLESRenderEngine(); - mGLESRE = renderEngine.get(); - mRE = std::move(renderEngine); - } else { - mRE = renderEngineFactory->createRenderEngine(); - } + mRE = renderEngineFactory->createRenderEngine(); mBuffer = allocateDefaultBuffer(); } @@ -1002,9 +962,9 @@ void RenderEngineTest::fillBufferWithColorTransformAndSourceDataspace( std::vector layers; renderengine::LayerSettings layer; - layer.sourceDataspace = sourceDataspace; layer.geometry.boundaries = Rect(1, 1).toFloatRect(); SourceVariant::fillColor(layer, 0.5f, 0.25f, 0.125f, this); + layer.sourceDataspace = sourceDataspace; layer.alpha = 1.0f; // construct a fake color matrix @@ -1030,13 +990,13 @@ void RenderEngineTest::fillBufferColorTransform() { template void RenderEngineTest::fillBufferColorTransformAndSourceDataspace() { unordered_map dataspaceToColorMap; - dataspaceToColorMap[ui::Dataspace::V0_BT709] = {172, 0, 0, 255}; - dataspaceToColorMap[ui::Dataspace::BT2020] = {172, 0, 0, 255}; - dataspaceToColorMap[ui::Dataspace::ADOBE_RGB] = {172, 0, 0, 255}; + dataspaceToColorMap[ui::Dataspace::V0_BT709] = {77, 0, 0, 255}; + dataspaceToColorMap[ui::Dataspace::BT2020] = {101, 0, 0, 255}; + dataspaceToColorMap[ui::Dataspace::ADOBE_RGB] = {75, 0, 0, 255}; ui::Dataspace customizedDataspace = static_cast( ui::Dataspace::STANDARD_BT709 | ui::Dataspace::TRANSFER_GAMMA2_2 | ui::Dataspace::RANGE_FULL); - dataspaceToColorMap[customizedDataspace] = {172, 0, 0, 255}; + dataspaceToColorMap[customizedDataspace] = {61, 0, 0, 255}; for (const auto& [sourceDataspace, color] : dataspaceToColorMap) { fillBufferWithColorTransformAndSourceDataspace(sourceDataspace); expectBufferColor(fullscreenRect(), color.r, color.g, color.b, color.a, 1); @@ -1076,13 +1036,13 @@ void RenderEngineTest::fillBufferWithColorTransformAndOutputDataspace( template void RenderEngineTest::fillBufferColorTransformAndOutputDataspace() { unordered_map dataspaceToColorMap; - dataspaceToColorMap[ui::Dataspace::V0_BT709] = {202, 0, 0, 255}; - dataspaceToColorMap[ui::Dataspace::BT2020] = {192, 0, 0, 255}; - dataspaceToColorMap[ui::Dataspace::ADOBE_RGB] = {202, 0, 0, 255}; + dataspaceToColorMap[ui::Dataspace::V0_BT709] = {198, 0, 0, 255}; + dataspaceToColorMap[ui::Dataspace::BT2020] = {187, 0, 0, 255}; + dataspaceToColorMap[ui::Dataspace::ADOBE_RGB] = {192, 0, 0, 255}; ui::Dataspace customizedDataspace = static_cast( ui::Dataspace::STANDARD_BT709 | ui::Dataspace::TRANSFER_GAMMA2_6 | ui::Dataspace::RANGE_FULL); - dataspaceToColorMap[customizedDataspace] = {202, 0, 0, 255}; + dataspaceToColorMap[customizedDataspace] = {205, 0, 0, 255}; for (const auto& [outputDataspace, color] : dataspaceToColorMap) { fillBufferWithColorTransformAndOutputDataspace(outputDataspace); expectBufferColor(fullscreenRect(), color.r, color.g, color.b, color.a, 1); @@ -1496,13 +1456,13 @@ void RenderEngineTest::tonemap(ui::Dataspace sourceDataspace, std::function(new GraphicBuffer(kGreyLevels, 1, HAL_PIXEL_FORMAT_RGBA_8888, - 1, - GRALLOC_USAGE_SW_READ_OFTEN | - GRALLOC_USAGE_SW_WRITE_OFTEN | - GRALLOC_USAGE_HW_RENDER | - GRALLOC_USAGE_HW_TEXTURE, - "input"), + ExternalTexture>(sp::make(kGreyLevels, 1, + HAL_PIXEL_FORMAT_RGBA_8888, 1, + GRALLOC_USAGE_SW_READ_OFTEN | + GRALLOC_USAGE_SW_WRITE_OFTEN | + GRALLOC_USAGE_HW_RENDER | + GRALLOC_USAGE_HW_TEXTURE, + "input"), *mRE, renderengine::impl::ExternalTexture::Usage::READABLE | renderengine::impl::ExternalTexture::Usage::WRITEABLE); @@ -1529,13 +1489,13 @@ void RenderEngineTest::tonemap(ui::Dataspace sourceDataspace, std::function(new GraphicBuffer(kGreyLevels, 1, HAL_PIXEL_FORMAT_RGBA_8888, - 1, - GRALLOC_USAGE_SW_READ_OFTEN | - GRALLOC_USAGE_SW_WRITE_OFTEN | - GRALLOC_USAGE_HW_RENDER | - GRALLOC_USAGE_HW_TEXTURE, - "output"), + ExternalTexture>(sp::make(kGreyLevels, 1, + HAL_PIXEL_FORMAT_RGBA_8888, 1, + GRALLOC_USAGE_SW_READ_OFTEN | + GRALLOC_USAGE_SW_WRITE_OFTEN | + GRALLOC_USAGE_HW_RENDER | + GRALLOC_USAGE_HW_TEXTURE, + "output"), *mRE, renderengine::impl::ExternalTexture::Usage::READABLE | renderengine::impl::ExternalTexture::Usage::WRITEABLE); @@ -1598,17 +1558,50 @@ void RenderEngineTest::tonemap(ui::Dataspace sourceDataspace, std::function(), - std::make_shared(), - std::make_shared(), - std::make_shared())); + testing::Values(std::make_shared(), + std::make_shared(), + std::make_shared(), + std::make_shared())); TEST_P(RenderEngineTest, drawLayers_noLayersToDraw) { + if (!GetParam()->typeSupported()) { + GTEST_SKIP(); + } initializeRenderEngine(); drawEmptyLayers(); } +TEST_P(RenderEngineTest, drawLayers_fillRedBufferAndEmptyBuffer) { + if (!GetParam()->typeSupported()) { + GTEST_SKIP(); + } + initializeRenderEngine(); + renderengine::DisplaySettings settings; + settings.physicalDisplay = fullscreenRect(); + settings.clip = fullscreenRect(); + settings.outputDataspace = ui::Dataspace::V0_SRGB_LINEAR; + + // add a red layer + renderengine::LayerSettings layerOne{ + .geometry.boundaries = fullscreenRect().toFloatRect(), + .source.solidColor = half3(1.0f, 0.0f, 0.0f), + .alpha = 1.f, + }; + + std::vector layersFirst{layerOne}; + invokeDraw(settings, layersFirst); + expectBufferColor(fullscreenRect(), 255, 0, 0, 255); + + // re-draw with an empty layer above it, and we get a transparent black one + std::vector layersSecond; + invokeDraw(settings, layersSecond); + expectBufferColor(fullscreenRect(), 0, 0, 0, 0); +} + TEST_P(RenderEngineTest, drawLayers_withoutBuffers_withColorTransform) { + if (!GetParam()->typeSupported()) { + GTEST_SKIP(); + } initializeRenderEngine(); renderengine::DisplaySettings settings; @@ -1640,111 +1633,111 @@ TEST_P(RenderEngineTest, drawLayers_withoutBuffers_withColorTransform) { } TEST_P(RenderEngineTest, drawLayers_nullOutputBuffer) { - initializeRenderEngine(); - - renderengine::DisplaySettings settings; - settings.outputDataspace = ui::Dataspace::V0_SRGB_LINEAR; - std::vector layers; - renderengine::LayerSettings layer; - layer.geometry.boundaries = fullscreenRect().toFloatRect(); - BufferSourceVariant::fillColor(layer, 1.0f, 0.0f, 0.0f, this); - layers.push_back(layer); - std::future result = - mRE->drawLayers(settings, layers, nullptr, true, base::unique_fd()); - - ASSERT_TRUE(result.valid()); - auto [status, fence] = result.get(); - ASSERT_EQ(BAD_VALUE, status); - ASSERT_FALSE(fence.ok()); -} - -TEST_P(RenderEngineTest, drawLayers_doesNotCacheFramebuffer) { - const auto& renderEngineFactory = GetParam(); - - if (renderEngineFactory->type() != renderengine::RenderEngine::RenderEngineType::GLES) { - // GLES-specific test - return; + if (!GetParam()->typeSupported()) { + GTEST_SKIP(); } - initializeRenderEngine(); renderengine::DisplaySettings settings; settings.outputDataspace = ui::Dataspace::V0_SRGB_LINEAR; - settings.physicalDisplay = fullscreenRect(); - settings.clip = fullscreenRect(); - std::vector layers; renderengine::LayerSettings layer; layer.geometry.boundaries = fullscreenRect().toFloatRect(); BufferSourceVariant::fillColor(layer, 1.0f, 0.0f, 0.0f, this); - layer.alpha = 1.0; layers.push_back(layer); + ftl::Future future = + mRE->drawLayers(settings, layers, nullptr, true, base::unique_fd()); - std::future result = - mRE->drawLayers(settings, layers, mBuffer, false, base::unique_fd()); - ASSERT_TRUE(result.valid()); - auto [status, fence] = result.get(); - - ASSERT_EQ(NO_ERROR, status); - if (fence.ok()) { - sync_wait(fence.get(), -1); - } - - ASSERT_FALSE(mGLESRE->isFramebufferImageCachedForTesting(mBuffer->getBuffer()->getId())); - expectBufferColor(fullscreenRect(), 255, 0, 0, 255); + ASSERT_TRUE(future.valid()); + auto result = future.get(); + ASSERT_FALSE(result.ok()); + ASSERT_EQ(BAD_VALUE, result.error()); } TEST_P(RenderEngineTest, drawLayers_fillRedBuffer_colorSource) { + if (!GetParam()->typeSupported()) { + GTEST_SKIP(); + } initializeRenderEngine(); fillRedBuffer(); } TEST_P(RenderEngineTest, drawLayers_fillGreenBuffer_colorSource) { + if (!GetParam()->typeSupported()) { + GTEST_SKIP(); + } initializeRenderEngine(); fillGreenBuffer(); } TEST_P(RenderEngineTest, drawLayers_fillBlueBuffer_colorSource) { + if (!GetParam()->typeSupported()) { + GTEST_SKIP(); + } initializeRenderEngine(); fillBlueBuffer(); } TEST_P(RenderEngineTest, drawLayers_fillRedTransparentBuffer_colorSource) { + if (!GetParam()->typeSupported()) { + GTEST_SKIP(); + } initializeRenderEngine(); fillRedTransparentBuffer(); } TEST_P(RenderEngineTest, drawLayers_fillBufferPhysicalOffset_colorSource) { + if (!GetParam()->typeSupported()) { + GTEST_SKIP(); + } initializeRenderEngine(); fillBufferPhysicalOffset(); } TEST_P(RenderEngineTest, drawLayers_fillBufferCheckersRotate0_colorSource) { + if (!GetParam()->typeSupported()) { + GTEST_SKIP(); + } initializeRenderEngine(); fillBufferCheckersRotate0(); } TEST_P(RenderEngineTest, drawLayers_fillBufferCheckersRotate90_colorSource) { + if (!GetParam()->typeSupported()) { + GTEST_SKIP(); + } initializeRenderEngine(); fillBufferCheckersRotate90(); } TEST_P(RenderEngineTest, drawLayers_fillBufferCheckersRotate180_colorSource) { + if (!GetParam()->typeSupported()) { + GTEST_SKIP(); + } initializeRenderEngine(); fillBufferCheckersRotate180(); } TEST_P(RenderEngineTest, drawLayers_fillBufferCheckersRotate270_colorSource) { + if (!GetParam()->typeSupported()) { + GTEST_SKIP(); + } initializeRenderEngine(); fillBufferCheckersRotate270(); } TEST_P(RenderEngineTest, drawLayers_fillBufferLayerTransform_colorSource) { + if (!GetParam()->typeSupported()) { + GTEST_SKIP(); + } initializeRenderEngine(); fillBufferLayerTransform(); } TEST_P(RenderEngineTest, drawLayers_fillBufferColorTransform_colorSource) { + if (!GetParam()->typeSupported()) { + GTEST_SKIP(); + } initializeRenderEngine(); fillBufferColorTransform(); } @@ -1752,12 +1745,8 @@ TEST_P(RenderEngineTest, drawLayers_fillBufferColorTransform_colorSource) { TEST_P(RenderEngineTest, drawLayers_fillBufferColorTransform_sourceDataspace) { const auto& renderEngineFactory = GetParam(); // skip for non color management - if (!renderEngineFactory->useColorManagement()) { - return; - } - // skip for GLESRenderEngine - if (renderEngineFactory->type() != renderengine::RenderEngine::RenderEngineType::GLES) { - return; + if (!renderEngineFactory->typeSupported() || !renderEngineFactory->useColorManagement()) { + GTEST_SKIP(); } initializeRenderEngine(); @@ -1767,12 +1756,8 @@ TEST_P(RenderEngineTest, drawLayers_fillBufferColorTransform_sourceDataspace) { TEST_P(RenderEngineTest, drawLayers_fillBufferColorTransform_outputDataspace) { const auto& renderEngineFactory = GetParam(); // skip for non color management - if (!renderEngineFactory->useColorManagement()) { - return; - } - // skip for GLESRenderEngine - if (renderEngineFactory->type() != renderengine::RenderEngine::RenderEngineType::GLES) { - return; + if (!renderEngineFactory->typeSupported() || !renderEngineFactory->useColorManagement()) { + GTEST_SKIP(); } initializeRenderEngine(); @@ -1780,81 +1765,129 @@ TEST_P(RenderEngineTest, drawLayers_fillBufferColorTransform_outputDataspace) { } TEST_P(RenderEngineTest, drawLayers_fillBufferRoundedCorners_colorSource) { + if (!GetParam()->typeSupported()) { + GTEST_SKIP(); + } initializeRenderEngine(); fillBufferWithRoundedCorners(); } TEST_P(RenderEngineTest, drawLayers_fillBufferColorTransformZeroLayerAlpha_colorSource) { + if (!GetParam()->typeSupported()) { + GTEST_SKIP(); + } initializeRenderEngine(); fillBufferColorTransformZeroLayerAlpha(); } TEST_P(RenderEngineTest, drawLayers_fillBufferAndBlurBackground_colorSource) { + if (!GetParam()->typeSupported()) { + GTEST_SKIP(); + } initializeRenderEngine(); fillBufferAndBlurBackground(); } TEST_P(RenderEngineTest, drawLayers_fillSmallLayerAndBlurBackground_colorSource) { + if (!GetParam()->typeSupported()) { + GTEST_SKIP(); + } initializeRenderEngine(); fillSmallLayerAndBlurBackground(); } TEST_P(RenderEngineTest, drawLayers_overlayCorners_colorSource) { + if (!GetParam()->typeSupported()) { + GTEST_SKIP(); + } initializeRenderEngine(); overlayCorners(); } TEST_P(RenderEngineTest, drawLayers_fillRedBuffer_opaqueBufferSource) { + if (!GetParam()->typeSupported()) { + GTEST_SKIP(); + } initializeRenderEngine(); fillRedBuffer>(); } TEST_P(RenderEngineTest, drawLayers_fillGreenBuffer_opaqueBufferSource) { + if (!GetParam()->typeSupported()) { + GTEST_SKIP(); + } initializeRenderEngine(); fillGreenBuffer>(); } TEST_P(RenderEngineTest, drawLayers_fillBlueBuffer_opaqueBufferSource) { + if (!GetParam()->typeSupported()) { + GTEST_SKIP(); + } initializeRenderEngine(); fillBlueBuffer>(); } TEST_P(RenderEngineTest, drawLayers_fillRedTransparentBuffer_opaqueBufferSource) { + if (!GetParam()->typeSupported()) { + GTEST_SKIP(); + } initializeRenderEngine(); fillRedTransparentBuffer>(); } TEST_P(RenderEngineTest, drawLayers_fillBufferPhysicalOffset_opaqueBufferSource) { + if (!GetParam()->typeSupported()) { + GTEST_SKIP(); + } initializeRenderEngine(); fillBufferPhysicalOffset>(); } TEST_P(RenderEngineTest, drawLayers_fillBufferCheckersRotate0_opaqueBufferSource) { + if (!GetParam()->typeSupported()) { + GTEST_SKIP(); + } initializeRenderEngine(); fillBufferCheckersRotate0>(); } TEST_P(RenderEngineTest, drawLayers_fillBufferCheckersRotate90_opaqueBufferSource) { + if (!GetParam()->typeSupported()) { + GTEST_SKIP(); + } initializeRenderEngine(); fillBufferCheckersRotate90>(); } TEST_P(RenderEngineTest, drawLayers_fillBufferCheckersRotate180_opaqueBufferSource) { + if (!GetParam()->typeSupported()) { + GTEST_SKIP(); + } initializeRenderEngine(); fillBufferCheckersRotate180>(); } TEST_P(RenderEngineTest, drawLayers_fillBufferCheckersRotate270_opaqueBufferSource) { + if (!GetParam()->typeSupported()) { + GTEST_SKIP(); + } initializeRenderEngine(); fillBufferCheckersRotate270>(); } TEST_P(RenderEngineTest, drawLayers_fillBufferLayerTransform_opaqueBufferSource) { + if (!GetParam()->typeSupported()) { + GTEST_SKIP(); + } initializeRenderEngine(); fillBufferLayerTransform>(); } TEST_P(RenderEngineTest, drawLayers_fillBufferColorTransform_opaqueBufferSource) { + if (!GetParam()->typeSupported()) { + GTEST_SKIP(); + } initializeRenderEngine(); fillBufferColorTransform>(); } @@ -1862,12 +1895,8 @@ TEST_P(RenderEngineTest, drawLayers_fillBufferColorTransform_opaqueBufferSource) TEST_P(RenderEngineTest, drawLayers_fillBufferColorTransformAndSourceDataspace_opaqueBufferSource) { const auto& renderEngineFactory = GetParam(); // skip for non color management - if (!renderEngineFactory->useColorManagement()) { - return; - } - // skip for GLESRenderEngine - if (renderEngineFactory->type() != renderengine::RenderEngine::RenderEngineType::GLES) { - return; + if (!renderEngineFactory->typeSupported() || !renderEngineFactory->useColorManagement()) { + GTEST_SKIP(); } initializeRenderEngine(); @@ -1877,12 +1906,8 @@ TEST_P(RenderEngineTest, drawLayers_fillBufferColorTransformAndSourceDataspace_o TEST_P(RenderEngineTest, drawLayers_fillBufferColorTransformAndOutputDataspace_opaqueBufferSource) { const auto& renderEngineFactory = GetParam(); // skip for non color management - if (!renderEngineFactory->useColorManagement()) { - return; - } - // skip for GLESRenderEngine - if (renderEngineFactory->type() != renderengine::RenderEngine::RenderEngineType::GLES) { - return; + if (!renderEngineFactory->typeSupported() || !renderEngineFactory->useColorManagement()) { + GTEST_SKIP(); } initializeRenderEngine(); @@ -1890,81 +1915,129 @@ TEST_P(RenderEngineTest, drawLayers_fillBufferColorTransformAndOutputDataspace_o } TEST_P(RenderEngineTest, drawLayers_fillBufferRoundedCorners_opaqueBufferSource) { + if (!GetParam()->typeSupported()) { + GTEST_SKIP(); + } initializeRenderEngine(); fillBufferWithRoundedCorners>(); } TEST_P(RenderEngineTest, drawLayers_fillBufferColorTransformZeroLayerAlpha_opaqueBufferSource) { + if (!GetParam()->typeSupported()) { + GTEST_SKIP(); + } initializeRenderEngine(); fillBufferColorTransformZeroLayerAlpha>(); } TEST_P(RenderEngineTest, drawLayers_fillBufferAndBlurBackground_opaqueBufferSource) { + if (!GetParam()->typeSupported()) { + GTEST_SKIP(); + } initializeRenderEngine(); fillBufferAndBlurBackground>(); } TEST_P(RenderEngineTest, drawLayers_fillSmallLayerAndBlurBackground_opaqueBufferSource) { + if (!GetParam()->typeSupported()) { + GTEST_SKIP(); + } initializeRenderEngine(); fillSmallLayerAndBlurBackground>(); } TEST_P(RenderEngineTest, drawLayers_overlayCorners_opaqueBufferSource) { + if (!GetParam()->typeSupported()) { + GTEST_SKIP(); + } initializeRenderEngine(); overlayCorners>(); } TEST_P(RenderEngineTest, drawLayers_fillRedBuffer_bufferSource) { + if (!GetParam()->typeSupported()) { + GTEST_SKIP(); + } initializeRenderEngine(); fillRedBuffer>(); } TEST_P(RenderEngineTest, drawLayers_fillGreenBuffer_bufferSource) { + if (!GetParam()->typeSupported()) { + GTEST_SKIP(); + } initializeRenderEngine(); fillGreenBuffer>(); } TEST_P(RenderEngineTest, drawLayers_fillBlueBuffer_bufferSource) { + if (!GetParam()->typeSupported()) { + GTEST_SKIP(); + } initializeRenderEngine(); fillBlueBuffer>(); } TEST_P(RenderEngineTest, drawLayers_fillRedTransparentBuffer_bufferSource) { + if (!GetParam()->typeSupported()) { + GTEST_SKIP(); + } initializeRenderEngine(); fillRedTransparentBuffer>(); } TEST_P(RenderEngineTest, drawLayers_fillBufferPhysicalOffset_bufferSource) { + if (!GetParam()->typeSupported()) { + GTEST_SKIP(); + } initializeRenderEngine(); fillBufferPhysicalOffset>(); } TEST_P(RenderEngineTest, drawLayers_fillBufferCheckersRotate0_bufferSource) { + if (!GetParam()->typeSupported()) { + GTEST_SKIP(); + } initializeRenderEngine(); fillBufferCheckersRotate0>(); } TEST_P(RenderEngineTest, drawLayers_fillBufferCheckersRotate90_bufferSource) { + if (!GetParam()->typeSupported()) { + GTEST_SKIP(); + } initializeRenderEngine(); fillBufferCheckersRotate90>(); } TEST_P(RenderEngineTest, drawLayers_fillBufferCheckersRotate180_bufferSource) { + if (!GetParam()->typeSupported()) { + GTEST_SKIP(); + } initializeRenderEngine(); fillBufferCheckersRotate180>(); } TEST_P(RenderEngineTest, drawLayers_fillBufferCheckersRotate270_bufferSource) { + if (!GetParam()->typeSupported()) { + GTEST_SKIP(); + } initializeRenderEngine(); fillBufferCheckersRotate270>(); } TEST_P(RenderEngineTest, drawLayers_fillBufferLayerTransform_bufferSource) { + if (!GetParam()->typeSupported()) { + GTEST_SKIP(); + } initializeRenderEngine(); fillBufferLayerTransform>(); } TEST_P(RenderEngineTest, drawLayers_fillBufferColorTransform_bufferSource) { + if (!GetParam()->typeSupported()) { + GTEST_SKIP(); + } initializeRenderEngine(); fillBufferColorTransform>(); } @@ -1972,12 +2045,8 @@ TEST_P(RenderEngineTest, drawLayers_fillBufferColorTransform_bufferSource) { TEST_P(RenderEngineTest, drawLayers_fillBufferColorTransformAndSourceDataspace_bufferSource) { const auto& renderEngineFactory = GetParam(); // skip for non color management - if (!renderEngineFactory->useColorManagement()) { - return; - } - // skip for GLESRenderEngine - if (renderEngineFactory->type() != renderengine::RenderEngine::RenderEngineType::GLES) { - return; + if (!renderEngineFactory->typeSupported() || !renderEngineFactory->useColorManagement()) { + GTEST_SKIP(); } initializeRenderEngine(); @@ -1987,12 +2056,8 @@ TEST_P(RenderEngineTest, drawLayers_fillBufferColorTransformAndSourceDataspace_b TEST_P(RenderEngineTest, drawLayers_fillBufferColorTransformAndOutputDataspace_bufferSource) { const auto& renderEngineFactory = GetParam(); // skip for non color management - if (!renderEngineFactory->useColorManagement()) { - return; - } - // skip for GLESRenderEngine - if (renderEngineFactory->type() != renderengine::RenderEngine::RenderEngineType::GLES) { - return; + if (!renderEngineFactory->typeSupported() || !renderEngineFactory->useColorManagement()) { + GTEST_SKIP(); } initializeRenderEngine(); @@ -2000,46 +2065,73 @@ TEST_P(RenderEngineTest, drawLayers_fillBufferColorTransformAndOutputDataspace_b } TEST_P(RenderEngineTest, drawLayers_fillBufferRoundedCorners_bufferSource) { + if (!GetParam()->typeSupported()) { + GTEST_SKIP(); + } initializeRenderEngine(); fillBufferWithRoundedCorners>(); } TEST_P(RenderEngineTest, drawLayers_fillBufferColorTransformZeroLayerAlpha_bufferSource) { + if (!GetParam()->typeSupported()) { + GTEST_SKIP(); + } initializeRenderEngine(); fillBufferColorTransformZeroLayerAlpha>(); } TEST_P(RenderEngineTest, drawLayers_fillBufferAndBlurBackground_bufferSource) { + if (!GetParam()->typeSupported()) { + GTEST_SKIP(); + } initializeRenderEngine(); fillBufferAndBlurBackground>(); } TEST_P(RenderEngineTest, drawLayers_fillSmallLayerAndBlurBackground_bufferSource) { + if (!GetParam()->typeSupported()) { + GTEST_SKIP(); + } initializeRenderEngine(); fillSmallLayerAndBlurBackground>(); } TEST_P(RenderEngineTest, drawLayers_overlayCorners_bufferSource) { + if (!GetParam()->typeSupported()) { + GTEST_SKIP(); + } initializeRenderEngine(); overlayCorners>(); } TEST_P(RenderEngineTest, drawLayers_fillBufferTextureTransform) { + if (!GetParam()->typeSupported()) { + GTEST_SKIP(); + } initializeRenderEngine(); fillBufferTextureTransform(); } TEST_P(RenderEngineTest, drawLayers_fillBuffer_premultipliesAlpha) { + if (!GetParam()->typeSupported()) { + GTEST_SKIP(); + } initializeRenderEngine(); fillBufferWithPremultiplyAlpha(); } TEST_P(RenderEngineTest, drawLayers_fillBuffer_withoutPremultiplyingAlpha) { + if (!GetParam()->typeSupported()) { + GTEST_SKIP(); + } initializeRenderEngine(); fillBufferWithoutPremultiplyAlpha(); } TEST_P(RenderEngineTest, drawLayers_fillShadow_castsWithoutCasterLayer) { + if (!GetParam()->typeSupported()) { + GTEST_SKIP(); + } initializeRenderEngine(); const ubyte4 backgroundColor(static_cast(255), static_cast(255), @@ -2056,6 +2148,9 @@ TEST_P(RenderEngineTest, drawLayers_fillShadow_castsWithoutCasterLayer) { } TEST_P(RenderEngineTest, drawLayers_fillShadow_casterLayerMinSize) { + if (!GetParam()->typeSupported()) { + GTEST_SKIP(); + } initializeRenderEngine(); const ubyte4 casterColor(static_cast(255), static_cast(0), @@ -2077,6 +2172,9 @@ TEST_P(RenderEngineTest, drawLayers_fillShadow_casterLayerMinSize) { } TEST_P(RenderEngineTest, drawLayers_fillShadow_casterColorLayer) { + if (!GetParam()->typeSupported()) { + GTEST_SKIP(); + } initializeRenderEngine(); const ubyte4 casterColor(static_cast(255), static_cast(0), @@ -2099,6 +2197,9 @@ TEST_P(RenderEngineTest, drawLayers_fillShadow_casterColorLayer) { } TEST_P(RenderEngineTest, drawLayers_fillShadow_casterOpaqueBufferLayer) { + if (!GetParam()->typeSupported()) { + GTEST_SKIP(); + } initializeRenderEngine(); const ubyte4 casterColor(static_cast(255), static_cast(0), @@ -2122,6 +2223,9 @@ TEST_P(RenderEngineTest, drawLayers_fillShadow_casterOpaqueBufferLayer) { } TEST_P(RenderEngineTest, drawLayers_fillShadow_casterWithRoundedCorner) { + if (!GetParam()->typeSupported()) { + GTEST_SKIP(); + } initializeRenderEngine(); const ubyte4 casterColor(static_cast(255), static_cast(0), @@ -2146,6 +2250,9 @@ TEST_P(RenderEngineTest, drawLayers_fillShadow_casterWithRoundedCorner) { } TEST_P(RenderEngineTest, drawLayers_fillShadow_translucentCasterWithAlpha) { + if (!GetParam()->typeSupported()) { + GTEST_SKIP(); + } initializeRenderEngine(); const ubyte4 casterColor(255, 0, 0, 255); @@ -2173,6 +2280,9 @@ TEST_P(RenderEngineTest, drawLayers_fillShadow_translucentCasterWithAlpha) { } TEST_P(RenderEngineTest, cleanupPostRender_cleansUpOnce) { + if (!GetParam()->typeSupported()) { + GTEST_SKIP(); + } initializeRenderEngine(); renderengine::DisplaySettings settings; @@ -2187,28 +2297,36 @@ TEST_P(RenderEngineTest, cleanupPostRender_cleansUpOnce) { layer.alpha = 1.0; layers.push_back(layer); - std::future resultOne = + ftl::Future futureOne = mRE->drawLayers(settings, layers, mBuffer, true, base::unique_fd()); - ASSERT_TRUE(resultOne.valid()); - auto [statusOne, fenceOne] = resultOne.get(); - ASSERT_EQ(NO_ERROR, statusOne); - - std::future resultTwo = - mRE->drawLayers(settings, layers, mBuffer, true, std::move(fenceOne)); - ASSERT_TRUE(resultTwo.valid()); - auto [statusTwo, fenceTwo] = resultTwo.get(); - ASSERT_EQ(NO_ERROR, statusTwo); - if (fenceTwo.ok()) { - sync_wait(fenceTwo.get(), -1); - } + ASSERT_TRUE(futureOne.valid()); + auto resultOne = futureOne.get(); + ASSERT_TRUE(resultOne.ok()); + auto fenceOne = resultOne.value(); + + ftl::Future futureTwo = + mRE->drawLayers(settings, layers, mBuffer, true, base::unique_fd(fenceOne->dup())); + ASSERT_TRUE(futureTwo.valid()); + auto resultTwo = futureTwo.get(); + ASSERT_TRUE(resultTwo.ok()); + auto fenceTwo = resultTwo.value(); + fenceTwo->waitForever(LOG_TAG); // Only cleanup the first time. - EXPECT_FALSE(mRE->canSkipPostRenderCleanup()); - mRE->cleanupPostRender(); - EXPECT_TRUE(mRE->canSkipPostRenderCleanup()); + if (mRE->canSkipPostRenderCleanup()) { + // Skia's Vk backend may keep the texture alive beyond drawLayersInternal, so + // it never gets added to the cleanup list. In those cases, we can skip. + EXPECT_TRUE(GetParam()->type() == renderengine::RenderEngine::RenderEngineType::SKIA_VK); + } else { + mRE->cleanupPostRender(); + EXPECT_TRUE(mRE->canSkipPostRenderCleanup()); + } } TEST_P(RenderEngineTest, testRoundedCornersCrop) { + if (!GetParam()->typeSupported()) { + GTEST_SKIP(); + } initializeRenderEngine(); renderengine::DisplaySettings settings; @@ -2259,6 +2377,9 @@ TEST_P(RenderEngineTest, testRoundedCornersCrop) { } TEST_P(RenderEngineTest, testRoundedCornersParentCrop) { + if (!GetParam()->typeSupported()) { + GTEST_SKIP(); + } initializeRenderEngine(); renderengine::DisplaySettings settings; @@ -2304,6 +2425,9 @@ TEST_P(RenderEngineTest, testRoundedCornersParentCrop) { } TEST_P(RenderEngineTest, testRoundedCornersParentCropSmallBounds) { + if (!GetParam()->typeSupported()) { + GTEST_SKIP(); + } initializeRenderEngine(); renderengine::DisplaySettings settings; @@ -2381,6 +2505,9 @@ TEST_P(RenderEngineTest, testRoundedCornersXY) { } TEST_P(RenderEngineTest, testClear) { + if (!GetParam()->typeSupported()) { + GTEST_SKIP(); + } initializeRenderEngine(); const auto rect = fullscreenRect(); @@ -2410,6 +2537,9 @@ TEST_P(RenderEngineTest, testClear) { } TEST_P(RenderEngineTest, testDisableBlendingBuffer) { + if (!GetParam()->typeSupported()) { + GTEST_SKIP(); + } initializeRenderEngine(); const auto rect = Rect(0, 0, 1, 1); @@ -2457,11 +2587,59 @@ TEST_P(RenderEngineTest, testDisableBlendingBuffer) { expectBufferColor(rect, 0, 128, 0, 128); } -TEST_P(RenderEngineTest, testDimming) { - if (GetParam()->type() == renderengine::RenderEngine::RenderEngineType::GLES) { +TEST_P(RenderEngineTest, testBorder) { + if (GetParam()->type() != renderengine::RenderEngine::RenderEngineType::SKIA_GL) { GTEST_SKIP(); } + if (!GetParam()->useColorManagement()) { + 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()->typeSupported()) { + GTEST_SKIP(); + } initializeRenderEngine(); const ui::Dataspace dataspace = ui::Dataspace::V0_SRGB_LINEAR; @@ -2534,7 +2712,7 @@ TEST_P(RenderEngineTest, testDimming) { } TEST_P(RenderEngineTest, testDimming_inGammaSpace) { - if (GetParam()->type() == renderengine::RenderEngine::RenderEngineType::GLES) { + if (!GetParam()->typeSupported()) { GTEST_SKIP(); } initializeRenderEngine(); @@ -2612,7 +2790,7 @@ TEST_P(RenderEngineTest, testDimming_inGammaSpace) { } TEST_P(RenderEngineTest, testDimming_inGammaSpace_withDisplayColorTransform) { - if (GetParam()->type() == renderengine::RenderEngine::RenderEngineType::GLES) { + if (!GetParam()->typeSupported()) { GTEST_SKIP(); } initializeRenderEngine(); @@ -2675,7 +2853,7 @@ TEST_P(RenderEngineTest, testDimming_inGammaSpace_withDisplayColorTransform) { } TEST_P(RenderEngineTest, testDimming_inGammaSpace_withDisplayColorTransform_deviceHandles) { - if (GetParam()->type() == renderengine::RenderEngine::RenderEngineType::GLES) { + if (!GetParam()->typeSupported()) { GTEST_SKIP(); } initializeRenderEngine(); @@ -2739,10 +2917,10 @@ TEST_P(RenderEngineTest, testDimming_inGammaSpace_withDisplayColorTransform_devi } TEST_P(RenderEngineTest, testDimming_withoutTargetLuminance) { - initializeRenderEngine(); - if (GetParam()->type() == renderengine::RenderEngine::RenderEngineType::GLES) { - return; + if (!GetParam()->typeSupported()) { + GTEST_SKIP(); } + initializeRenderEngine(); const auto displayRect = Rect(2, 1); const renderengine::DisplaySettings display{ @@ -2793,6 +2971,9 @@ TEST_P(RenderEngineTest, testDimming_withoutTargetLuminance) { } TEST_P(RenderEngineTest, test_isOpaque) { + if (!GetParam()->typeSupported()) { + GTEST_SKIP(); + } initializeRenderEngine(); const auto rect = Rect(0, 0, 1, 1); @@ -2844,11 +3025,7 @@ TEST_P(RenderEngineTest, test_isOpaque) { } TEST_P(RenderEngineTest, test_tonemapPQMatches) { - if (!GetParam()->useColorManagement()) { - GTEST_SKIP(); - } - - if (GetParam()->type() == renderengine::RenderEngine::RenderEngineType::GLES) { + if (!GetParam()->typeSupported() || !GetParam()->useColorManagement()) { GTEST_SKIP(); } @@ -2865,11 +3042,7 @@ TEST_P(RenderEngineTest, test_tonemapPQMatches) { } TEST_P(RenderEngineTest, test_tonemapHLGMatches) { - if (!GetParam()->useColorManagement()) { - GTEST_SKIP(); - } - - if (GetParam()->type() == renderengine::RenderEngine::RenderEngineType::GLES) { + if (!GetParam()->typeSupported() || !GetParam()->useColorManagement()) { GTEST_SKIP(); } @@ -2886,10 +3059,9 @@ TEST_P(RenderEngineTest, test_tonemapHLGMatches) { } TEST_P(RenderEngineTest, r8_behaves_as_mask) { - if (GetParam()->type() == renderengine::RenderEngine::RenderEngineType::GLES) { - return; + if (!GetParam()->typeSupported()) { + GTEST_SKIP(); } - initializeRenderEngine(); const auto r8Buffer = allocateR8Buffer(2, 1); @@ -2947,10 +3119,9 @@ TEST_P(RenderEngineTest, r8_behaves_as_mask) { } TEST_P(RenderEngineTest, r8_respects_color_transform) { - if (GetParam()->type() == renderengine::RenderEngine::RenderEngineType::GLES) { - return; + if (!GetParam()->typeSupported()) { + GTEST_SKIP(); } - initializeRenderEngine(); const auto r8Buffer = allocateR8Buffer(2, 1); @@ -3013,10 +3184,9 @@ TEST_P(RenderEngineTest, r8_respects_color_transform) { } TEST_P(RenderEngineTest, r8_respects_color_transform_when_device_handles) { - if (GetParam()->type() == renderengine::RenderEngine::RenderEngineType::GLES) { - return; + if (!GetParam()->typeSupported()) { + GTEST_SKIP(); } - initializeRenderEngine(); const auto r8Buffer = allocateR8Buffer(2, 1); @@ -3082,10 +3252,9 @@ TEST_P(RenderEngineTest, r8_respects_color_transform_when_device_handles) { } TEST_P(RenderEngineTest, primeShaderCache) { - if (GetParam()->type() == renderengine::RenderEngine::RenderEngineType::GLES) { + if (!GetParam()->typeSupported()) { GTEST_SKIP(); } - initializeRenderEngine(); auto fut = mRE->primeCache(); diff --git a/libs/renderengine/tests/RenderEngineThreadedTest.cpp b/libs/renderengine/tests/RenderEngineThreadedTest.cpp index 96851892b415e8346c710b36db8e34be0a267994..fe3a16d4bf32ad9e9e379b297da8dbadf7f1fdc9 100644 --- a/libs/renderengine/tests/RenderEngineThreadedTest.cpp +++ b/libs/renderengine/tests/RenderEngineThreadedTest.cpp @@ -17,8 +17,10 @@ #include #include #include +#include #include #include +#include #include "../threaded/RenderEngineThreaded.h" namespace android { @@ -95,18 +97,6 @@ TEST_F(RenderEngineThreadedTest, getMaxViewportDims_returns0) { ASSERT_EQ(dims, result); } -TEST_F(RenderEngineThreadedTest, isProtected_returnsFalse) { - EXPECT_CALL(*mRenderEngine, isProtected()).WillOnce(Return(false)); - status_t result = mThreadedRE->isProtected(); - ASSERT_EQ(false, result); -} - -TEST_F(RenderEngineThreadedTest, isProtected_returnsTrue) { - EXPECT_CALL(*mRenderEngine, isProtected()).WillOnce(Return(true)); - size_t result = mThreadedRE->isProtected(); - ASSERT_EQ(true, result); -} - TEST_F(RenderEngineThreadedTest, supportsProtectedContent_returnsFalse) { EXPECT_CALL(*mRenderEngine, supportsProtectedContent()).WillOnce(Return(false)); status_t result = mThreadedRE->supportsProtectedContent(); @@ -119,28 +109,6 @@ TEST_F(RenderEngineThreadedTest, supportsProtectedContent_returnsTrue) { ASSERT_EQ(true, result); } -TEST_F(RenderEngineThreadedTest, useProtectedContext) { - EXPECT_CALL(*mRenderEngine, useProtectedContext(true)); - auto& ipExpect = EXPECT_CALL(*mRenderEngine, isProtected()).WillOnce(Return(false)); - EXPECT_CALL(*mRenderEngine, supportsProtectedContent()).WillOnce(Return(true)); - EXPECT_CALL(*mRenderEngine, isProtected()).After(ipExpect).WillOnce(Return(true)); - - mThreadedRE->useProtectedContext(true); - ASSERT_EQ(true, mThreadedRE->isProtected()); - - // call ANY synchronous function to ensure that useProtectedContext has completed. - mThreadedRE->getContextPriority(); - ASSERT_EQ(true, mThreadedRE->isProtected()); -} - -TEST_F(RenderEngineThreadedTest, useProtectedContext_quickReject) { - EXPECT_CALL(*mRenderEngine, useProtectedContext(false)).Times(0); - EXPECT_CALL(*mRenderEngine, isProtected()).WillOnce(Return(false)); - mThreadedRE->useProtectedContext(false); - // call ANY synchronous function to ensure that useProtectedContext has completed. - mThreadedRE->getContextPriority(); -} - TEST_F(RenderEngineThreadedTest, PostRenderCleanup_skipped) { EXPECT_CALL(*mRenderEngine, canSkipPostRenderCleanup()).WillOnce(Return(true)); EXPECT_CALL(*mRenderEngine, cleanupPostRender()).Times(0); @@ -176,27 +144,86 @@ TEST_F(RenderEngineThreadedTest, drawLayers) { std::vector layers; std::shared_ptr buffer = std::make_shared< renderengine::impl:: - ExternalTexture>(new GraphicBuffer(), *mRenderEngine, + ExternalTexture>(sp::make(), *mRenderEngine, renderengine::impl::ExternalTexture::Usage::READABLE | renderengine::impl::ExternalTexture::Usage::WRITEABLE); base::unique_fd bufferFence; + EXPECT_CALL(*mRenderEngine, useProtectedContext(false)); + EXPECT_CALL(*mRenderEngine, drawLayersInternal) + .WillOnce([&](const std::shared_ptr>&& resultPromise, + const renderengine::DisplaySettings&, + const std::vector&, + const std::shared_ptr&, const bool, + base::unique_fd&&) { resultPromise->set_value(Fence::NO_FENCE); }); + + ftl::Future future = + mThreadedRE->drawLayers(settings, layers, buffer, false, std::move(bufferFence)); + ASSERT_TRUE(future.valid()); + auto result = future.get(); + ASSERT_TRUE(result.ok()); +} + +TEST_F(RenderEngineThreadedTest, drawLayers_protectedLayer) { + renderengine::DisplaySettings settings; + auto layerBuffer = sp::make(); + layerBuffer->usage |= GRALLOC_USAGE_PROTECTED; + renderengine::LayerSettings layer; + layer.source.buffer.buffer = std::make_shared< + renderengine::impl::ExternalTexture>(std::move(layerBuffer), *mRenderEngine, + renderengine::impl::ExternalTexture::Usage:: + READABLE); + std::vector layers = {std::move(layer)}; + std::shared_ptr buffer = std::make_shared< + renderengine::impl:: + ExternalTexture>(sp::make(), *mRenderEngine, + renderengine::impl::ExternalTexture::Usage::READABLE | + renderengine::impl::ExternalTexture::Usage::WRITEABLE); + + base::unique_fd bufferFence; + + EXPECT_CALL(*mRenderEngine, useProtectedContext(true)); + EXPECT_CALL(*mRenderEngine, drawLayersInternal) + .WillOnce([&](const std::shared_ptr>&& resultPromise, + const renderengine::DisplaySettings&, + const std::vector&, + const std::shared_ptr&, const bool, + base::unique_fd&&) { resultPromise->set_value(Fence::NO_FENCE); }); + + ftl::Future future = + mThreadedRE->drawLayers(settings, layers, buffer, false, std::move(bufferFence)); + ASSERT_TRUE(future.valid()); + auto result = future.get(); + ASSERT_TRUE(result.ok()); +} + +TEST_F(RenderEngineThreadedTest, drawLayers_protectedOutputBuffer) { + renderengine::DisplaySettings settings; + std::vector layers; + auto graphicBuffer = sp::make(); + graphicBuffer->usage |= GRALLOC_USAGE_PROTECTED; + std::shared_ptr buffer = std::make_shared< + renderengine::impl:: + ExternalTexture>(std::move(graphicBuffer), *mRenderEngine, + renderengine::impl::ExternalTexture::Usage::READABLE | + renderengine::impl::ExternalTexture::Usage::WRITEABLE); + + base::unique_fd bufferFence; + + EXPECT_CALL(*mRenderEngine, useProtectedContext(true)); EXPECT_CALL(*mRenderEngine, drawLayersInternal) - .WillOnce([&](const std::shared_ptr>&& - resultPromise, + .WillOnce([&](const std::shared_ptr>&& resultPromise, const renderengine::DisplaySettings&, const std::vector&, const std::shared_ptr&, const bool, - base::unique_fd&&) -> void { - resultPromise->set_value({NO_ERROR, base::unique_fd()}); - }); + base::unique_fd&&) { resultPromise->set_value(Fence::NO_FENCE); }); - std::future result = + ftl::Future future = mThreadedRE->drawLayers(settings, layers, buffer, false, std::move(bufferFence)); - ASSERT_TRUE(result.valid()); - auto [status, _] = result.get(); - ASSERT_EQ(NO_ERROR, status); + ASSERT_TRUE(future.valid()); + auto result = future.get(); + ASSERT_TRUE(result.ok()); } } // namespace android diff --git a/libs/renderengine/threaded/RenderEngineThreaded.cpp b/libs/renderengine/threaded/RenderEngineThreaded.cpp index 203bb5470152468653701792d95a3709291b12a7..6a1561abcdeafc97c36155790a64c90f747159e5 100644 --- a/libs/renderengine/threaded/RenderEngineThreaded.cpp +++ b/libs/renderengine/threaded/RenderEngineThreaded.cpp @@ -90,7 +90,6 @@ void RenderEngineThreaded::threadMain(CreateInstanceFactory factory) NO_THREAD_S } mRenderEngine = factory(); - mIsProtected = mRenderEngine->isProtected(); pthread_setname_np(pthread_self(), mThreadName); @@ -231,16 +230,17 @@ void RenderEngineThreaded::mapExternalTextureBuffer(const sp& buf mCondition.notify_one(); } -void RenderEngineThreaded::unmapExternalTextureBuffer(const sp& buffer) { +void RenderEngineThreaded::unmapExternalTextureBuffer(sp&& buffer) { ATRACE_CALL(); // This function is designed so it can run asynchronously, so we do not need to wait // for the futures. { std::lock_guard lock(mThreadMutex); - mFunctionCalls.push([=](renderengine::RenderEngine& instance) { - ATRACE_NAME("REThreaded::unmapExternalTextureBuffer"); - instance.unmapExternalTextureBuffer(buffer); - }); + mFunctionCalls.push( + [=, buffer = std::move(buffer)](renderengine::RenderEngine& instance) mutable { + ATRACE_NAME("REThreaded::unmapExternalTextureBuffer"); + instance.unmapExternalTextureBuffer(std::move(buffer)); + }); } mCondition.notify_one(); } @@ -255,41 +255,11 @@ size_t RenderEngineThreaded::getMaxViewportDims() const { return mRenderEngine->getMaxViewportDims(); } -bool RenderEngineThreaded::isProtected() const { - waitUntilInitialized(); - std::lock_guard lock(mThreadMutex); - return mIsProtected; -} - bool RenderEngineThreaded::supportsProtectedContent() const { waitUntilInitialized(); return mRenderEngine->supportsProtectedContent(); } -void RenderEngineThreaded::useProtectedContext(bool useProtectedContext) { - if (isProtected() == useProtectedContext || - (useProtectedContext && !supportsProtectedContent())) { - return; - } - - { - std::lock_guard lock(mThreadMutex); - mFunctionCalls.push([useProtectedContext, this](renderengine::RenderEngine& instance) { - ATRACE_NAME("REThreaded::useProtectedContext"); - instance.useProtectedContext(useProtectedContext); - if (instance.isProtected() != useProtectedContext) { - ALOGE("Failed to switch RenderEngine context."); - // reset the cached mIsProtected value to a good state, but this does not - // prevent other callers of this method and isProtected from reading the - // invalid cached value. - mIsProtected = instance.isProtected(); - } - }); - mIsProtected = useProtectedContext; - } - mCondition.notify_one(); -} - void RenderEngineThreaded::cleanupPostRender() { if (canSkipPostRenderCleanup()) { return; @@ -313,27 +283,28 @@ bool RenderEngineThreaded::canSkipPostRenderCleanup() const { } void RenderEngineThreaded::drawLayersInternal( - const std::shared_ptr>&& resultPromise, + const std::shared_ptr>&& resultPromise, const DisplaySettings& display, const std::vector& layers, const std::shared_ptr& buffer, const bool useFramebufferCache, base::unique_fd&& bufferFence) { - resultPromise->set_value({NO_ERROR, base::unique_fd()}); + resultPromise->set_value(Fence::NO_FENCE); return; } -std::future RenderEngineThreaded::drawLayers( +ftl::Future RenderEngineThreaded::drawLayers( const DisplaySettings& display, const std::vector& layers, const std::shared_ptr& buffer, const bool useFramebufferCache, base::unique_fd&& bufferFence) { ATRACE_CALL(); - const auto resultPromise = std::make_shared>(); - std::future resultFuture = resultPromise->get_future(); + const auto resultPromise = std::make_shared>(); + std::future resultFuture = resultPromise->get_future(); int fd = bufferFence.release(); { std::lock_guard lock(mThreadMutex); mFunctionCalls.push([resultPromise, display, layers, buffer, useFramebufferCache, fd](renderengine::RenderEngine& instance) { ATRACE_NAME("REThreaded::drawLayers"); + instance.updateProtectedContext(layers, buffer); instance.drawLayersInternal(std::move(resultPromise), display, layers, buffer, useFramebufferCache, base::unique_fd(fd)); }); diff --git a/libs/renderengine/threaded/RenderEngineThreaded.h b/libs/renderengine/threaded/RenderEngineThreaded.h index 13409021260c8b6dba3e42c77d93f043d833f7b8..6eb108e0642de2e2859f04f4aa27de42fb9ba4b9 100644 --- a/libs/renderengine/threaded/RenderEngineThreaded.h +++ b/libs/renderengine/threaded/RenderEngineThreaded.h @@ -51,16 +51,14 @@ public: size_t getMaxTextureSize() const override; size_t getMaxViewportDims() const override; - bool isProtected() const override; bool supportsProtectedContent() const override; - void useProtectedContext(bool useProtectedContext) override; void cleanupPostRender() override; - std::future drawLayers(const DisplaySettings& display, - const std::vector& layers, - const std::shared_ptr& buffer, - const bool useFramebufferCache, - base::unique_fd&& bufferFence) override; + ftl::Future drawLayers(const DisplaySettings& display, + const std::vector& layers, + const std::shared_ptr& buffer, + const bool useFramebufferCache, + base::unique_fd&& bufferFence) override; void cleanFramebufferCache() override; int getContextPriority() override; @@ -71,9 +69,9 @@ public: protected: void mapExternalTextureBuffer(const sp& buffer, bool isRenderable) override; - void unmapExternalTextureBuffer(const sp& buffer) override; + void unmapExternalTextureBuffer(sp&& buffer) override; bool canSkipPostRenderCleanup() const override; - void drawLayersInternal(const std::shared_ptr>&& resultPromise, + void drawLayersInternal(const std::shared_ptr>&& resultPromise, const DisplaySettings& display, const std::vector& layers, const std::shared_ptr& buffer, @@ -84,6 +82,9 @@ private: void waitUntilInitialized() const; static status_t setSchedFifo(bool enabled); + // No-op. This method is only called on leaf implementations of RenderEngine. + void useProtectedContext(bool) override {} + /* ------------------------------------------------------------------------ * Threading */ @@ -107,7 +108,6 @@ private: * Render Engine */ std::unique_ptr mRenderEngine; - std::atomic mIsProtected = false; }; } // namespace threaded } // namespace renderengine diff --git a/libs/sensor/ISensorServer.cpp b/libs/sensor/ISensorServer.cpp index 93c95b98c54abee28185aaf2b98f36b564f2a18d..019d6cb070366319434877064a220f09daa62228 100644 --- a/libs/sensor/ISensorServer.cpp +++ b/libs/sensor/ISensorServer.cpp @@ -22,12 +22,12 @@ #include #include #include -#include #include +#include -#include #include #include +#include #include #include @@ -42,6 +42,7 @@ enum { GET_DYNAMIC_SENSOR_LIST, CREATE_SENSOR_DIRECT_CONNECTION, SET_OPERATION_PARAMETER, + GET_RUNTIME_SENSOR_LIST, }; class BpSensorServer : public BpInterface @@ -98,6 +99,25 @@ public: return v; } + virtual Vector getRuntimeSensorList(const String16& opPackageName, int deviceId) + { + Parcel data, reply; + data.writeInterfaceToken(ISensorServer::getInterfaceDescriptor()); + data.writeString16(opPackageName); + data.writeInt32(deviceId); + remote()->transact(GET_RUNTIME_SENSOR_LIST, data, &reply); + Sensor s; + Vector v; + uint32_t n = reply.readUint32(); + v.setCapacity(n); + while (n) { + n--; + reply.read(s); + v.add(s); + } + return v; + } + virtual sp createSensorEventConnection(const String8& packageName, int mode, const String16& opPackageName, const String16& attributionTag) { @@ -119,10 +139,12 @@ public: } virtual sp createSensorDirectConnection(const String16& opPackageName, - uint32_t size, int32_t type, int32_t format, const native_handle_t *resource) { + int deviceId, uint32_t size, int32_t type, int32_t format, + const native_handle_t *resource) { Parcel data, reply; data.writeInterfaceToken(ISensorServer::getInterfaceDescriptor()); data.writeString16(opPackageName); + data.writeInt32(deviceId); data.writeUint32(size); data.writeInt32(type); data.writeInt32(format); @@ -202,9 +224,22 @@ status_t BnSensorServer::onTransact( } return NO_ERROR; } + case GET_RUNTIME_SENSOR_LIST: { + CHECK_INTERFACE(ISensorServer, data, reply); + const String16& opPackageName = data.readString16(); + const int deviceId = data.readInt32(); + Vector v(getRuntimeSensorList(opPackageName, deviceId)); + size_t n = v.size(); + reply->writeUint32(static_cast(n)); + for (size_t i = 0; i < n; i++) { + reply->write(v[i]); + } + return NO_ERROR; + } case CREATE_SENSOR_DIRECT_CONNECTION: { CHECK_INTERFACE(ISensorServer, data, reply); const String16& opPackageName = data.readString16(); + const int deviceId = data.readInt32(); uint32_t size = data.readUint32(); int32_t type = data.readInt32(); int32_t format = data.readInt32(); @@ -213,9 +248,10 @@ status_t BnSensorServer::onTransact( if (resource == nullptr) { return BAD_VALUE; } - sp ch = - createSensorDirectConnection(opPackageName, size, type, format, resource); - native_handle_close(resource); + native_handle_set_fdsan_tag(resource); + sp ch = createSensorDirectConnection( + opPackageName, deviceId, size, type, format, resource); + native_handle_close_with_tag(resource); native_handle_delete(resource); reply->writeStrongBinder(IInterface::asBinder(ch)); return NO_ERROR; diff --git a/libs/sensor/Sensor.cpp b/libs/sensor/Sensor.cpp index 1d618052045d77ef4f8cedbbfcff232d7150ac75..a1549ea385c32765dda588fb458c230b7cac91e6 100644 --- a/libs/sensor/Sensor.cpp +++ b/libs/sensor/Sensor.cpp @@ -264,10 +264,6 @@ Sensor::Sensor(struct sensor_t const& hwSensor, const uuid_t& uuid, int halVersi mStringType = SENSOR_STRING_TYPE_HEART_BEAT; mFlags |= SENSOR_FLAG_SPECIAL_REPORTING_MODE; break; - - // TODO: Placeholder for LLOB sensor type - - case SENSOR_TYPE_ACCELEROMETER_UNCALIBRATED: mStringType = SENSOR_STRING_TYPE_ACCELEROMETER_UNCALIBRATED; mFlags |= SENSOR_FLAG_CONTINUOUS_MODE; diff --git a/libs/sensor/SensorManager.cpp b/libs/sensor/SensorManager.cpp index 9f814f1c483af8c69f76ad1fde7e3f89ea8aa4d2..980f8d16d2485d4399027d7569353947aab60270 100644 --- a/libs/sensor/SensorManager.cpp +++ b/libs/sensor/SensorManager.cpp @@ -213,6 +213,19 @@ ssize_t SensorManager::getDynamicSensorList(Vector & dynamicSensors) { return static_cast(count); } +ssize_t SensorManager::getRuntimeSensorList(int deviceId, Vector& runtimeSensors) { + Mutex::Autolock _l(mLock); + status_t err = assertStateLocked(); + if (err < 0) { + return static_cast(err); + } + + runtimeSensors = mSensorServer->getRuntimeSensorList(mOpPackageName, deviceId); + size_t count = runtimeSensors.size(); + + return static_cast(count); +} + ssize_t SensorManager::getDynamicSensorList(Sensor const* const** list) { Mutex::Autolock _l(mLock); status_t err = assertStateLocked(); @@ -299,6 +312,12 @@ bool SensorManager::isDataInjectionEnabled() { int SensorManager::createDirectChannel( size_t size, int channelType, const native_handle_t *resourceHandle) { + static constexpr int DEFAULT_DEVICE_ID = 0; + return createDirectChannel(DEFAULT_DEVICE_ID, size, channelType, resourceHandle); +} + +int SensorManager::createDirectChannel( + int deviceId, size_t size, int channelType, const native_handle_t *resourceHandle) { Mutex::Autolock _l(mLock); if (assertStateLocked() != NO_ERROR) { return NO_INIT; @@ -311,7 +330,7 @@ int SensorManager::createDirectChannel( } sp conn = - mSensorServer->createSensorDirectConnection(mOpPackageName, + mSensorServer->createSensorDirectConnection(mOpPackageName, deviceId, static_cast(size), static_cast(channelType), SENSOR_DIRECT_FMT_SENSORS_EVENT, resourceHandle); diff --git a/libs/sensor/include/sensor/ISensorServer.h b/libs/sensor/include/sensor/ISensorServer.h index ce5c672da0d3d6ca9359af6962fbbdba2ef6db04..58157280b1139b84b598ddeb01491fd31e9fe7e3 100644 --- a/libs/sensor/include/sensor/ISensorServer.h +++ b/libs/sensor/include/sensor/ISensorServer.h @@ -43,13 +43,15 @@ public: virtual Vector getSensorList(const String16& opPackageName) = 0; virtual Vector getDynamicSensorList(const String16& opPackageName) = 0; + virtual Vector getRuntimeSensorList(const String16& opPackageName, int deviceId) = 0; virtual sp createSensorEventConnection(const String8& packageName, int mode, const String16& opPackageName, const String16& attributionTag) = 0; virtual int32_t isDataInjectionEnabled() = 0; virtual sp createSensorDirectConnection(const String16& opPackageName, - uint32_t size, int32_t type, int32_t format, const native_handle_t *resource) = 0; + int deviceId, uint32_t size, int32_t type, int32_t format, + const native_handle_t *resource) = 0; virtual int setOperationParameter( int32_t handle, int32_t type, const Vector &floats, const Vector &ints) = 0; diff --git a/libs/sensor/include/sensor/SensorManager.h b/libs/sensor/include/sensor/SensorManager.h index 7c9d604ff71da8eda98f37b64c9493451a985f8d..bb44cb88694535ef18c9190a12c60244685e861f 100644 --- a/libs/sensor/include/sensor/SensorManager.h +++ b/libs/sensor/include/sensor/SensorManager.h @@ -60,11 +60,14 @@ public: ssize_t getSensorList(Sensor const* const** list); ssize_t getDynamicSensorList(Vector& list); ssize_t getDynamicSensorList(Sensor const* const** list); + ssize_t getRuntimeSensorList(int deviceId, Vector& list); Sensor const* getDefaultSensor(int type); sp createEventQueue( String8 packageName = String8(""), int mode = 0, String16 attributionTag = String16("")); bool isDataInjectionEnabled(); int createDirectChannel(size_t size, int channelType, const native_handle_t *channelData); + int createDirectChannel( + int deviceId, size_t size, int channelType, const native_handle_t *channelData); void destroyDirectChannel(int channelNativeHandle); int configureDirectChannel(int channelNativeHandle, int sensorHandle, int rateLevel); int setOperationParameter(int handle, int type, const Vector &floats, const Vector &ints); diff --git a/libs/shaders/Android.bp b/libs/shaders/Android.bp index 847747948adb5fcb9d6e4f96582ec3cabd5c5dac..960f845488f8b6b78f578ac5932eec25df00cd13 100644 --- a/libs/shaders/Android.bp +++ b/libs/shaders/Android.bp @@ -23,13 +23,14 @@ package { cc_library_static { name: "libshaders", - + defaults: [ + "android.hardware.graphics.common-ndk_shared", + "android.hardware.graphics.composer3-ndk_shared", + ], export_include_dirs: ["include"], local_include_dirs: ["include"], shared_libs: [ - "android.hardware.graphics.common-V4-ndk", - "android.hardware.graphics.composer3-V1-ndk", "android.hardware.graphics.common@1.2", "libnativewindow", ], diff --git a/libs/shaders/include/shaders/shaders.h b/libs/shaders/include/shaders/shaders.h index 2a4a3700783f2c704e7da7b1b45cbec1731f550a..5a4aaab851ecbb84160f007389b58dfb11357f07 100644 --- a/libs/shaders/include/shaders/shaders.h +++ b/libs/shaders/include/shaders/shaders.h @@ -51,29 +51,29 @@ struct LinearEffect { // Input dataspace of the source colors. const ui::Dataspace inputDataspace = ui::Dataspace::SRGB; - // Working dataspace for the output surface, for conversion from linear space. + // Working dataspace for the output surface. const ui::Dataspace outputDataspace = ui::Dataspace::SRGB; // Sets whether alpha premultiplication must be undone. // This is required if the source colors use premultiplied alpha and is not opaque. const bool undoPremultipliedAlpha = false; - // "Fake" dataspace of the source colors. This is used for applying an EOTF to compute linear - // RGB. This is used when Skia is expected to color manage the input image based on the - // dataspace of the provided source image and destination surface. SkRuntimeEffects use the - // destination color space as the working color space. RenderEngine deliberately sets the color - // space for input images and destination surfaces to be the same whenever LinearEffects are - // expected to be used so that color-management is controlled by RenderEngine, but other users - // of a LinearEffect may not be able to control the color space of the images and surfaces. So - // fakeInputDataspace is used to essentially masquerade the input dataspace to be the output - // dataspace for correct conversion to linear colors. - ui::Dataspace fakeInputDataspace = ui::Dataspace::UNKNOWN; + // "Fake" dataspace of the destination colors. This is used for applying an OETF to compute + // non-linear RGB. This is used when Skia is expected to color manage the input image based on + // the dataspace of the provided source image and destination surface. Some use-cases in + // RenderEngine expect to apply a different OETF than what is expected by Skia. As in, + // RenderEngine will color manage to a custom destination and "cast" the result to Skia's + // working space. + ui::Dataspace fakeOutputDataspace = ui::Dataspace::UNKNOWN; + + enum SkSLType { Shader, ColorFilter }; + SkSLType type = Shader; }; static inline bool operator==(const LinearEffect& lhs, const LinearEffect& rhs) { return lhs.inputDataspace == rhs.inputDataspace && lhs.outputDataspace == rhs.outputDataspace && lhs.undoPremultipliedAlpha == rhs.undoPremultipliedAlpha && - lhs.fakeInputDataspace == rhs.fakeInputDataspace; + lhs.fakeOutputDataspace == rhs.fakeOutputDataspace; } struct LinearEffectHasher { @@ -86,7 +86,7 @@ struct LinearEffectHasher { size_t result = std::hash{}(le.inputDataspace); result = HashCombine(result, std::hash{}(le.outputDataspace)); result = HashCombine(result, std::hash{}(le.undoPremultipliedAlpha)); - return HashCombine(result, std::hash{}(le.fakeInputDataspace)); + return HashCombine(result, std::hash{}(le.fakeOutputDataspace)); } }; diff --git a/libs/shaders/shaders.cpp b/libs/shaders/shaders.cpp index f80e93f6f876400d496f2b399cadf03b6cf57414..c85517a9761296efba45b938adfbdb1046984641 100644 --- a/libs/shaders/shaders.cpp +++ b/libs/shaders/shaders.cpp @@ -33,212 +33,111 @@ aidl::android::hardware::graphics::common::Dataspace toAidlDataspace(ui::Dataspa return static_cast(dataspace); } -void generateEOTF(ui::Dataspace dataspace, std::string& shader) { - switch (dataspace & HAL_DATASPACE_TRANSFER_MASK) { - case HAL_DATASPACE_TRANSFER_ST2084: - shader.append(R"( - - float3 EOTF(float3 color) { - float m1 = (2610.0 / 4096.0) / 4.0; - float m2 = (2523.0 / 4096.0) * 128.0; - float c1 = (3424.0 / 4096.0); - float c2 = (2413.0 / 4096.0) * 32.0; - float c3 = (2392.0 / 4096.0) * 32.0; - - float3 tmp = pow(clamp(color, 0.0, 1.0), 1.0 / float3(m2)); - tmp = max(tmp - c1, 0.0) / (c2 - c3 * tmp); - return pow(tmp, 1.0 / float3(m1)); - } - )"); - break; - case HAL_DATASPACE_TRANSFER_HLG: - shader.append(R"( - float EOTF_channel(float channel) { - const float a = 0.17883277; - const float b = 0.28466892; - const float c = 0.55991073; - return channel <= 0.5 ? channel * channel / 3.0 : - (exp((channel - c) / a) + b) / 12.0; - } - - float3 EOTF(float3 color) { - return float3(EOTF_channel(color.r), EOTF_channel(color.g), - EOTF_channel(color.b)); - } - )"); - break; - case HAL_DATASPACE_TRANSFER_LINEAR: - shader.append(R"( - float3 EOTF(float3 color) { - return color; - } - )"); - break; - case HAL_DATASPACE_TRANSFER_SMPTE_170M: - shader.append(R"( - - float EOTF_sRGB(float srgb) { - return srgb <= 0.08125 ? srgb / 4.50 : pow((srgb + 0.099) / 1.099, 1 / 0.45); - } - - float3 EOTF_sRGB(float3 srgb) { - return float3(EOTF_sRGB(srgb.r), EOTF_sRGB(srgb.g), EOTF_sRGB(srgb.b)); - } - - float3 EOTF(float3 srgb) { - return sign(srgb.rgb) * EOTF_sRGB(abs(srgb.rgb)); - } - )"); - break; - case HAL_DATASPACE_TRANSFER_GAMMA2_2: - shader.append(R"( +void generateXYZTransforms(std::string& shader) { + shader.append(R"( + uniform float3x3 in_rgbToXyz; + uniform float3x3 in_xyzToSrcRgb; + uniform float4x4 in_colorTransform; + float3 ToXYZ(float3 rgb) { + return in_rgbToXyz * rgb; + } - float EOTF_sRGB(float srgb) { - return pow(srgb, 2.2); - } + float3 ToSrcRGB(float3 xyz) { + return in_xyzToSrcRgb * xyz; + } - float3 EOTF_sRGB(float3 srgb) { - return float3(EOTF_sRGB(srgb.r), EOTF_sRGB(srgb.g), EOTF_sRGB(srgb.b)); - } + float3 ApplyColorTransform(float3 rgb) { + return (in_colorTransform * float4(rgb, 1.0)).rgb; + } + )"); +} - float3 EOTF(float3 srgb) { - return sign(srgb.rgb) * EOTF_sRGB(abs(srgb.rgb)); - } - )"); - break; - case HAL_DATASPACE_TRANSFER_GAMMA2_6: +// Conversion from relative light to absolute light +// Note that 1.0 == 203 nits. +void generateLuminanceScalesForOOTF(ui::Dataspace inputDataspace, std::string& shader) { + switch (inputDataspace & HAL_DATASPACE_TRANSFER_MASK) { + case HAL_DATASPACE_TRANSFER_HLG: + // BT. 2408 says that a signal level of 0.75 == 203 nits for HLG, but that's after + // applying OOTF. But we haven't applied OOTF yet, so we need to scale by a different + // constant instead. shader.append(R"( - - float EOTF_sRGB(float srgb) { - return pow(srgb, 2.6); - } - - float3 EOTF_sRGB(float3 srgb) { - return float3(EOTF_sRGB(srgb.r), EOTF_sRGB(srgb.g), EOTF_sRGB(srgb.b)); - } - - float3 EOTF(float3 srgb) { - return sign(srgb.rgb) * EOTF_sRGB(abs(srgb.rgb)); + float3 ScaleLuminance(float3 xyz) { + return xyz * 264.96; } )"); break; - case HAL_DATASPACE_TRANSFER_GAMMA2_8: - shader.append(R"( - - float EOTF_sRGB(float srgb) { - return pow(srgb, 2.8); - } - - float3 EOTF_sRGB(float3 srgb) { - return float3(EOTF_sRGB(srgb.r), EOTF_sRGB(srgb.g), EOTF_sRGB(srgb.b)); - } - - float3 EOTF(float3 srgb) { - return sign(srgb.rgb) * EOTF_sRGB(abs(srgb.rgb)); - } - )"); - break; - case HAL_DATASPACE_TRANSFER_SRGB: default: shader.append(R"( - - float EOTF_sRGB(float srgb) { - return srgb <= 0.04045 ? srgb / 12.92 : pow((srgb + 0.055) / 1.055, 2.4); - } - - float3 EOTF_sRGB(float3 srgb) { - return float3(EOTF_sRGB(srgb.r), EOTF_sRGB(srgb.g), EOTF_sRGB(srgb.b)); - } - - float3 EOTF(float3 srgb) { - return sign(srgb.rgb) * EOTF_sRGB(abs(srgb.rgb)); + float3 ScaleLuminance(float3 xyz) { + return xyz * 203.0; } )"); break; } } -void generateXYZTransforms(std::string& shader) { - shader.append(R"( - uniform float4x4 in_rgbToXyz; - uniform float4x4 in_xyzToRgb; - float3 ToXYZ(float3 rgb) { - return (in_rgbToXyz * float4(rgb, 1.0)).rgb; - } - - float3 ToRGB(float3 xyz) { - return clamp((in_xyzToRgb * float4(xyz, 1.0)).rgb, 0.0, 1.0); - } - )"); -} - -// Conversion from relative light to absolute light (maps from [0, 1] to [0, maxNits]) -void generateLuminanceScalesForOOTF(ui::Dataspace inputDataspace, ui::Dataspace outputDataspace, - std::string& shader) { - switch (inputDataspace & HAL_DATASPACE_TRANSFER_MASK) { +// Normalizes from absolute light back to relative light (maps from [0, maxNits] back to [0, 1]) +static void generateLuminanceNormalizationForOOTF(ui::Dataspace inputDataspace, + ui::Dataspace outputDataspace, + std::string& shader) { + switch (outputDataspace & HAL_DATASPACE_TRANSFER_MASK) { case HAL_DATASPACE_TRANSFER_ST2084: shader.append(R"( - float3 ScaleLuminance(float3 xyz) { - return xyz * 10000.0; - } - )"); + float3 NormalizeLuminance(float3 xyz) { + return xyz / 203.0; + } + )"); break; case HAL_DATASPACE_TRANSFER_HLG: - shader.append(R"( - float3 ScaleLuminance(float3 xyz) { - return xyz * 1000.0; - } - )"); - break; - default: - switch (outputDataspace & HAL_DATASPACE_TRANSFER_MASK) { - case HAL_DATASPACE_TRANSFER_ST2084: + switch (inputDataspace & HAL_DATASPACE_TRANSFER_MASK) { case HAL_DATASPACE_TRANSFER_HLG: - // SDR -> HDR tonemap shader.append(R"( - float3 ScaleLuminance(float3 xyz) { - return xyz * in_libtonemap_inputMaxLuminance; + float3 NormalizeLuminance(float3 xyz) { + return xyz / 264.96; } )"); break; default: - // Input and output are both SDR, so no tone-mapping is expected so - // no-op the luminance normalization. + // Transcoding to HLG requires applying the inverse OOTF + // with the expectation that the OOTF is then applied during + // tonemapping downstream. + // BT. 2100-2 operates on normalized luminances, so renormalize to the input to + // correctly adjust gamma. + // Note that following BT. 2408 for HLG OETF actually maps 0.75 == ~264.96 nits, + // rather than 203 nits, because 203 nits == OOTF(invOETF(0.75)), so even though + // we originally scaled by 203 nits we need to re-normalize to 264.96 nits when + // converting to the correct brightness range. shader.append(R"( - float3 ScaleLuminance(float3 xyz) { - return xyz * in_libtonemap_displayMaxLuminance; - } - )"); + float3 NormalizeLuminance(float3 xyz) { + float ootfGain = pow(xyz.y / 1000.0, -0.2 / 1.2); + return xyz * ootfGain / 264.96; + } + )"); break; } - } -} - -// Normalizes from absolute light back to relative light (maps from [0, maxNits] back to [0, 1]) -static void generateLuminanceNormalizationForOOTF(ui::Dataspace outputDataspace, - std::string& shader) { - switch (outputDataspace & HAL_DATASPACE_TRANSFER_MASK) { - case HAL_DATASPACE_TRANSFER_ST2084: - shader.append(R"( - float3 NormalizeLuminance(float3 xyz) { - return xyz / 10000.0; - } - )"); - break; - case HAL_DATASPACE_TRANSFER_HLG: - shader.append(R"( - float3 NormalizeLuminance(float3 xyz) { - return xyz / 1000.0; - } - )"); break; default: - shader.append(R"( - float3 NormalizeLuminance(float3 xyz) { - return xyz / in_libtonemap_displayMaxLuminance; - } - )"); - break; + switch (inputDataspace & HAL_DATASPACE_TRANSFER_MASK) { + case HAL_DATASPACE_TRANSFER_HLG: + case HAL_DATASPACE_TRANSFER_ST2084: + // libtonemap outputs a range [0, in_libtonemap_displayMaxLuminance], so + // normalize back to [0, 1] when the output is SDR. + shader.append(R"( + float3 NormalizeLuminance(float3 xyz) { + return xyz / in_libtonemap_displayMaxLuminance; + } + )"); + break; + default: + // Otherwise normalize back down to the range [0, 1] + // TODO: get this working for extended range outputs + shader.append(R"( + float3 NormalizeLuminance(float3 xyz) { + return xyz / 203.0; + } + )"); + break; + } } } @@ -249,159 +148,67 @@ void generateOOTF(ui::Dataspace inputDataspace, ui::Dataspace outputDataspace, toAidlDataspace(outputDataspace)) .c_str()); - generateLuminanceScalesForOOTF(inputDataspace, outputDataspace, shader); - generateLuminanceNormalizationForOOTF(outputDataspace, shader); + generateLuminanceScalesForOOTF(inputDataspace, shader); + generateLuminanceNormalizationForOOTF(inputDataspace, outputDataspace, shader); + // Some tonemappers operate on CIE luminance, other tonemappers operate on linear rgb + // luminance in the source gamut. shader.append(R"( - float3 OOTF(float3 linearRGB, float3 xyz) { + float3 OOTF(float3 linearRGB) { float3 scaledLinearRGB = ScaleLuminance(linearRGB); - float3 scaledXYZ = ScaleLuminance(xyz); + float3 scaledXYZ = ToXYZ(scaledLinearRGB); - float gain = libtonemap_LookupTonemapGain(scaledLinearRGB, scaledXYZ); + float gain = libtonemap_LookupTonemapGain(ToSrcRGB(scaledXYZ), scaledXYZ); return NormalizeLuminance(scaledXYZ * gain); } )"); } -void generateOETF(ui::Dataspace dataspace, std::string& shader) { - switch (dataspace & HAL_DATASPACE_TRANSFER_MASK) { - case HAL_DATASPACE_TRANSFER_ST2084: - shader.append(R"( - - float3 OETF(float3 xyz) { - float m1 = (2610.0 / 4096.0) / 4.0; - float m2 = (2523.0 / 4096.0) * 128.0; - float c1 = (3424.0 / 4096.0); - float c2 = (2413.0 / 4096.0) * 32.0; - float c3 = (2392.0 / 4096.0) * 32.0; - - float3 tmp = pow(xyz, float3(m1)); - tmp = (c1 + c2 * tmp) / (1.0 + c3 * tmp); - return pow(tmp, float3(m2)); - } - )"); - break; - case HAL_DATASPACE_TRANSFER_HLG: - shader.append(R"( - float OETF_channel(float channel) { - const float a = 0.17883277; - const float b = 0.28466892; - const float c = 0.55991073; - return channel <= 1.0 / 12.0 ? sqrt(3.0 * channel) : - a * log(12.0 * channel - b) + c; - } - - float3 OETF(float3 linear) { - return float3(OETF_channel(linear.r), OETF_channel(linear.g), - OETF_channel(linear.b)); - } - )"); - break; - case HAL_DATASPACE_TRANSFER_LINEAR: - shader.append(R"( - float3 OETF(float3 linear) { - return linear; - } - )"); - break; - case HAL_DATASPACE_TRANSFER_SMPTE_170M: - shader.append(R"( - float OETF_sRGB(float linear) { - return linear <= 0.018 ? - linear * 4.50 : (pow(linear, 0.45) * 1.099) - 0.099; - } - - float3 OETF_sRGB(float3 linear) { - return float3(OETF_sRGB(linear.r), OETF_sRGB(linear.g), OETF_sRGB(linear.b)); - } - - float3 OETF(float3 linear) { - return sign(linear.rgb) * OETF_sRGB(abs(linear.rgb)); - } - )"); - break; - case HAL_DATASPACE_TRANSFER_GAMMA2_2: - shader.append(R"( - float OETF_sRGB(float linear) { - return pow(linear, (1.0 / 2.2)); - } - - float3 OETF_sRGB(float3 linear) { - return float3(OETF_sRGB(linear.r), OETF_sRGB(linear.g), OETF_sRGB(linear.b)); - } - - float3 OETF(float3 linear) { - return sign(linear.rgb) * OETF_sRGB(abs(linear.rgb)); - } - )"); - break; - case HAL_DATASPACE_TRANSFER_GAMMA2_6: - shader.append(R"( - float OETF_sRGB(float linear) { - return pow(linear, (1.0 / 2.6)); - } - - float3 OETF_sRGB(float3 linear) { - return float3(OETF_sRGB(linear.r), OETF_sRGB(linear.g), OETF_sRGB(linear.b)); - } +void generateOETF(std::string& shader) { + // Only support gamma 2.2 for now + shader.append(R"( + float OETF(float3 linear) { + return sign(linear) * pow(abs(linear), (1.0 / 2.2)); + } + )"); +} - float3 OETF(float3 linear) { - return sign(linear.rgb) * OETF_sRGB(abs(linear.rgb)); - } - )"); - break; - case HAL_DATASPACE_TRANSFER_GAMMA2_8: +void generateEffectiveOOTF(bool undoPremultipliedAlpha, LinearEffect::SkSLType type, + bool needsCustomOETF, std::string& shader) { + switch (type) { + case LinearEffect::SkSLType::ColorFilter: shader.append(R"( - float OETF_sRGB(float linear) { - return pow(linear, (1.0 / 2.8)); - } - - float3 OETF_sRGB(float3 linear) { - return float3(OETF_sRGB(linear.r), OETF_sRGB(linear.g), OETF_sRGB(linear.b)); - } - - float3 OETF(float3 linear) { - return sign(linear.rgb) * OETF_sRGB(abs(linear.rgb)); - } + half4 main(half4 inputColor) { + float4 c = float4(inputColor); )"); break; - case HAL_DATASPACE_TRANSFER_SRGB: - default: + case LinearEffect::SkSLType::Shader: shader.append(R"( - float OETF_sRGB(float linear) { - return linear <= 0.0031308 ? - linear * 12.92 : (pow(linear, 1.0 / 2.4) * 1.055) - 0.055; - } - - float3 OETF_sRGB(float3 linear) { - return float3(OETF_sRGB(linear.r), OETF_sRGB(linear.g), OETF_sRGB(linear.b)); - } - - float3 OETF(float3 linear) { - return sign(linear.rgb) * OETF_sRGB(abs(linear.rgb)); - } + uniform shader child; + half4 main(float2 xy) { + float4 c = float4(child.eval(xy)); )"); break; } -} - -void generateEffectiveOOTF(bool undoPremultipliedAlpha, std::string& shader) { - shader.append(R"( - uniform shader child; - half4 main(float2 xy) { - float4 c = float4(child.eval(xy)); - )"); if (undoPremultipliedAlpha) { shader.append(R"( c.rgb = c.rgb / (c.a + 0.0019); )"); } + // We are using linear sRGB as a working space, with 1.0 == 203 nits shader.append(R"( - float3 linearRGB = EOTF(c.rgb); - float3 xyz = ToXYZ(linearRGB); - c.rgb = OETF(ToRGB(OOTF(linearRGB, xyz))); + c.rgb = ApplyColorTransform(OOTF(toLinearSrgb(c.rgb))); )"); + if (needsCustomOETF) { + shader.append(R"( + c.rgb = OETF(c.rgb); + )"); + } else { + shader.append(R"( + c.rgb = fromLinearSrgb(c.rgb); + )"); + } if (undoPremultipliedAlpha) { shader.append(R"( c.rgb = c.rgb * (c.a + 0.0019); @@ -413,7 +220,31 @@ void generateEffectiveOOTF(bool undoPremultipliedAlpha, std::string& shader) { )"); } -// please keep in sync with toSkColorSpace function in renderengine/skia/ColorSpaces.cpp +template ::value, bool> = true> +std::vector buildUniformValue(T value) { + std::vector result; + result.resize(sizeof(value)); + std::memcpy(result.data(), &value, sizeof(value)); + return result; +} + +} // namespace + +std::string buildLinearEffectSkSL(const LinearEffect& linearEffect) { + std::string shaderString; + generateXYZTransforms(shaderString); + generateOOTF(linearEffect.inputDataspace, linearEffect.outputDataspace, shaderString); + + const bool needsCustomOETF = (linearEffect.fakeOutputDataspace & HAL_DATASPACE_TRANSFER_MASK) == + HAL_DATASPACE_TRANSFER_GAMMA2_2; + if (needsCustomOETF) { + generateOETF(shaderString); + } + generateEffectiveOOTF(linearEffect.undoPremultipliedAlpha, linearEffect.type, needsCustomOETF, + shaderString); + return shaderString; +} + ColorSpace toColorSpace(ui::Dataspace dataspace) { switch (dataspace & HAL_DATASPACE_STANDARD_MASK) { case HAL_DATASPACE_STANDARD_BT709: @@ -425,14 +256,14 @@ ColorSpace toColorSpace(ui::Dataspace dataspace) { return ColorSpace::BT2020(); case HAL_DATASPACE_STANDARD_ADOBE_RGB: return ColorSpace::AdobeRGB(); - // TODO(b/208290320): BT601 format and variants return different primaries + // TODO(b/208290320): BT601 format and variants return different primaries case HAL_DATASPACE_STANDARD_BT601_625: case HAL_DATASPACE_STANDARD_BT601_625_UNADJUSTED: case HAL_DATASPACE_STANDARD_BT601_525: case HAL_DATASPACE_STANDARD_BT601_525_UNADJUSTED: - // TODO(b/208290329): BT407M format returns different primaries + // TODO(b/208290329): BT407M format returns different primaries case HAL_DATASPACE_STANDARD_BT470M: - // TODO(b/208290904): FILM format returns different primaries + // TODO(b/208290904): FILM format returns different primaries case HAL_DATASPACE_STANDARD_FILM: case HAL_DATASPACE_STANDARD_UNSPECIFIED: default: @@ -440,29 +271,6 @@ ColorSpace toColorSpace(ui::Dataspace dataspace) { } } -template ::value, bool> = true> -std::vector buildUniformValue(T value) { - std::vector result; - result.resize(sizeof(value)); - std::memcpy(result.data(), &value, sizeof(value)); - return result; -} - -} // namespace - -std::string buildLinearEffectSkSL(const LinearEffect& linearEffect) { - std::string shaderString; - generateEOTF(linearEffect.fakeInputDataspace == ui::Dataspace::UNKNOWN - ? linearEffect.inputDataspace - : linearEffect.fakeInputDataspace, - shaderString); - generateXYZTransforms(shaderString); - generateOOTF(linearEffect.inputDataspace, linearEffect.outputDataspace, shaderString); - generateOETF(linearEffect.outputDataspace, shaderString); - generateEffectiveOOTF(linearEffect.undoPremultipliedAlpha, shaderString); - return shaderString; -} - // Generates a list of uniforms to set on the LinearEffect shader above. std::vector buildLinearEffectUniforms( const LinearEffect& linearEffect, const mat4& colorTransform, float maxDisplayLuminance, @@ -470,29 +278,29 @@ std::vector buildLinearEffectUniforms( aidl::android::hardware::graphics::composer3::RenderIntent renderIntent) { std::vector uniforms; - const ui::Dataspace inputDataspace = linearEffect.fakeInputDataspace == ui::Dataspace::UNKNOWN - ? linearEffect.inputDataspace - : linearEffect.fakeInputDataspace; - - if (inputDataspace == linearEffect.outputDataspace) { - uniforms.push_back({.name = "in_rgbToXyz", .value = buildUniformValue(mat4())}); - uniforms.push_back( - {.name = "in_xyzToRgb", .value = buildUniformValue(colorTransform)}); - } else { - ColorSpace inputColorSpace = toColorSpace(inputDataspace); - ColorSpace outputColorSpace = toColorSpace(linearEffect.outputDataspace); - uniforms.push_back({.name = "in_rgbToXyz", - .value = buildUniformValue(mat4(inputColorSpace.getRGBtoXYZ()))}); - uniforms.push_back({.name = "in_xyzToRgb", - .value = buildUniformValue( - colorTransform * mat4(outputColorSpace.getXYZtoRGB()))}); - } + auto inputColorSpace = toColorSpace(linearEffect.inputDataspace); + auto outputColorSpace = toColorSpace(linearEffect.outputDataspace); + + uniforms.push_back( + {.name = "in_rgbToXyz", + .value = buildUniformValue(ColorSpace::linearExtendedSRGB().getRGBtoXYZ())}); + uniforms.push_back({.name = "in_xyzToSrcRgb", + .value = buildUniformValue(inputColorSpace.getXYZtoRGB())}); + // Transforms xyz colors to linear source colors, then applies the color transform, then + // transforms to linear extended RGB for skia to color manage. + uniforms.push_back({.name = "in_colorTransform", + .value = buildUniformValue( + mat4(ColorSpace::linearExtendedSRGB().getXYZtoRGB()) * + // TODO: the color transform ideally should be applied + // in the source colorspace, but doing that breaks + // renderengine tests + mat4(outputColorSpace.getRGBtoXYZ()) * colorTransform * + mat4(outputColorSpace.getXYZtoRGB()))}); tonemap::Metadata metadata{.displayMaxLuminance = maxDisplayLuminance, // If the input luminance is unknown, use display luminance (aka, - // no-op any luminance changes) - // This will be the case for eg screenshots in addition to - // uncalibrated displays + // no-op any luminance changes). + // This is expected to only be meaningful for PQ content .contentMaxLuminance = maxLuminance > 0 ? maxLuminance : maxDisplayLuminance, .currentDisplayLuminance = currentDisplayLuminanceNits > 0 diff --git a/libs/shaders/tests/Android.bp b/libs/shaders/tests/Android.bp index 718d37bc38e5e3f1fc45a561883daaa6d1b4d755..1e4f45ac4517b163a57c4d0bb96a7871c871e5c2 100644 --- a/libs/shaders/tests/Android.bp +++ b/libs/shaders/tests/Android.bp @@ -23,6 +23,10 @@ package { cc_test { name: "libshaders_test", + defaults: [ + "android.hardware.graphics.common-ndk_shared", + "android.hardware.graphics.composer3-ndk_shared", + ], test_suites: ["device-tests"], srcs: [ "shaders_test.cpp", @@ -31,8 +35,6 @@ cc_test { "libtonemap_headers", ], shared_libs: [ - "android.hardware.graphics.common-V4-ndk", - "android.hardware.graphics.composer3-V1-ndk", "android.hardware.graphics.common@1.2", "libnativewindow", ], diff --git a/libs/shaders/tests/shaders_test.cpp b/libs/shaders/tests/shaders_test.cpp index d45fb246c79359cf0495fdcf852479e0abe9398e..ba8bed23e3de647bb569845b69df89a525c6b45a 100644 --- a/libs/shaders/tests/shaders_test.cpp +++ b/libs/shaders/tests/shaders_test.cpp @@ -35,6 +35,10 @@ MATCHER_P2(UniformEq, name, value, "") { return arg.name == name && arg.value == value; } +MATCHER_P(UniformNameEq, name, "") { + return arg.name == name; +} + template ::value, bool> = true> std::vector buildUniformValue(T value) { std::vector result; @@ -49,50 +53,44 @@ TEST_F(ShadersTest, buildLinearEffectUniforms_selectsNoOpGamutMatrices) { shaders::LinearEffect effect = shaders::LinearEffect{.inputDataspace = ui::Dataspace::V0_SRGB_LINEAR, .outputDataspace = ui::Dataspace::V0_SRGB_LINEAR, - .fakeInputDataspace = ui::Dataspace::UNKNOWN}; + .fakeOutputDataspace = ui::Dataspace::UNKNOWN}; mat4 colorTransform = mat4::scale(vec4(.9, .9, .9, 1.)); auto uniforms = shaders::buildLinearEffectUniforms(effect, colorTransform, 1.f, 1.f, 1.f, nullptr, aidl::android::hardware::graphics::composer3:: RenderIntent::COLORIMETRIC); - EXPECT_THAT(uniforms, Contains(UniformEq("in_rgbToXyz", buildUniformValue(mat4())))); EXPECT_THAT(uniforms, - Contains(UniformEq("in_xyzToRgb", buildUniformValue(colorTransform)))); + Contains(UniformEq("in_rgbToXyz", + buildUniformValue( + ColorSpace::linearExtendedSRGB().getRGBtoXYZ())))); + EXPECT_THAT(uniforms, + Contains(UniformEq("in_xyzToSrcRgb", + buildUniformValue( + ColorSpace::linearSRGB().getXYZtoRGB())))); + // color transforms are already tested in renderengine's tests + EXPECT_THAT(uniforms, Contains(UniformNameEq("in_colorTransform"))); } TEST_F(ShadersTest, buildLinearEffectUniforms_selectsGamutTransformMatrices) { shaders::LinearEffect effect = shaders::LinearEffect{.inputDataspace = ui::Dataspace::V0_SRGB, .outputDataspace = ui::Dataspace::DISPLAY_P3, - .fakeInputDataspace = ui::Dataspace::UNKNOWN}; + .fakeOutputDataspace = ui::Dataspace::UNKNOWN}; ColorSpace inputColorSpace = ColorSpace::sRGB(); - ColorSpace outputColorSpace = ColorSpace::DisplayP3(); auto uniforms = shaders::buildLinearEffectUniforms(effect, mat4(), 1.f, 1.f, 1.f, nullptr, aidl::android::hardware::graphics::composer3:: RenderIntent::COLORIMETRIC); EXPECT_THAT(uniforms, Contains(UniformEq("in_rgbToXyz", - buildUniformValue(mat4(inputColorSpace.getRGBtoXYZ()))))); + buildUniformValue( + ColorSpace::linearExtendedSRGB().getRGBtoXYZ())))); EXPECT_THAT(uniforms, - Contains(UniformEq("in_xyzToRgb", - buildUniformValue(mat4(outputColorSpace.getXYZtoRGB()))))); -} - -TEST_F(ShadersTest, buildLinearEffectUniforms_respectsFakeInputDataspace) { - shaders::LinearEffect effect = - shaders::LinearEffect{.inputDataspace = ui::Dataspace::V0_SRGB, - .outputDataspace = ui::Dataspace::DISPLAY_P3, - .fakeInputDataspace = ui::Dataspace::DISPLAY_P3}; - - auto uniforms = - shaders::buildLinearEffectUniforms(effect, mat4(), 1.f, 1.f, 1.f, nullptr, - aidl::android::hardware::graphics::composer3:: - RenderIntent::COLORIMETRIC); - EXPECT_THAT(uniforms, Contains(UniformEq("in_rgbToXyz", buildUniformValue(mat4())))); - EXPECT_THAT(uniforms, Contains(UniformEq("in_xyzToRgb", buildUniformValue(mat4())))); + Contains(UniformEq("in_xyzToSrcRgb", + buildUniformValue(inputColorSpace.getXYZtoRGB())))); + EXPECT_THAT(uniforms, Contains(UniformNameEq("in_colorTransform"))); } } // namespace android diff --git a/libs/tonemap/Android.bp b/libs/tonemap/Android.bp index eca051d21508dd97669ce11c1862d6fb18c01527..8c8815d0e43b81f9825e78d7d568b5c1225c4835 100644 --- a/libs/tonemap/Android.bp +++ b/libs/tonemap/Android.bp @@ -23,13 +23,15 @@ package { cc_library_static { name: "libtonemap", + defaults: [ + "android.hardware.graphics.common-ndk_shared", + "android.hardware.graphics.composer3-ndk_shared", + ], vendor_available: true, local_include_dirs: ["include"], shared_libs: [ - "android.hardware.graphics.common-V4-ndk", - "android.hardware.graphics.composer3-V1-ndk", "liblog", "libnativewindow", ], diff --git a/libs/tonemap/tests/Android.bp b/libs/tonemap/tests/Android.bp index 0002d3a935146b598d1bd6af60dbb45716e3f7b9..2abf51563c638e6835f5aa117ad1e84da403eb1d 100644 --- a/libs/tonemap/tests/Android.bp +++ b/libs/tonemap/tests/Android.bp @@ -23,6 +23,10 @@ package { cc_test { name: "libtonemap_test", + defaults: [ + "android.hardware.graphics.common-ndk_shared", + "android.hardware.graphics.composer3-ndk_shared", + ], test_suites: ["device-tests"], srcs: [ "tonemap_test.cpp", @@ -31,8 +35,6 @@ cc_test { "libtonemap_headers", ], shared_libs: [ - "android.hardware.graphics.common-V4-ndk", - "android.hardware.graphics.composer3-V1-ndk", "libnativewindow", ], static_libs: [ diff --git a/libs/ui/Android.bp b/libs/ui/Android.bp index 98d9b943229053747e18460e04176e09eb0d293a..ec0ab4e46414f1bf4185ac5db5f91d7b428b51b2 100644 --- a/libs/ui/Android.bp +++ b/libs/ui/Android.bp @@ -48,7 +48,6 @@ cc_defaults { integer_overflow: true, misc_undefined: ["bounds"], }, - } cc_library_static { @@ -127,7 +126,6 @@ cc_library_shared { "DebugUtils.cpp", "DeviceProductInfo.cpp", "DisplayIdentification.cpp", - "DisplayMode.cpp", "DynamicDisplayInfo.cpp", "Fence.cpp", "FenceTime.cpp", @@ -136,14 +134,13 @@ cc_library_shared { "Gralloc2.cpp", "Gralloc3.cpp", "Gralloc4.cpp", + "Gralloc5.cpp", "GraphicBuffer.cpp", "GraphicBufferAllocator.cpp", "GraphicBufferMapper.cpp", - "HdrCapabilities.cpp", "PixelFormat.cpp", "PublicFormat.cpp", "StaticAsserts.cpp", - "StaticDisplayInfo.cpp", ], include_dirs: [ @@ -179,6 +176,7 @@ cc_library_shared { "libsync", "libutils", "liblog", + "libvndksupport", ], export_shared_lib_headers: [ @@ -217,6 +215,8 @@ cc_library_shared { "libnativewindow_headers", "libhardware_headers", "libui_headers", + "libimapper_stablec", + "libimapper_providerutils", ], export_static_lib_headers: [ @@ -240,10 +240,6 @@ cc_library_shared { ], afdo: true, - - header_abi_checker: { - diff_flags: ["-allow-adding-removing-weak-symbols"], - }, } cc_library_headers { diff --git a/libs/ui/DeviceProductInfo.cpp b/libs/ui/DeviceProductInfo.cpp index 04d9d3c989ef95fd0ca1996e56697e467deb463e..6ae27dee1de026b271e8a00d75cddb983508e536 100644 --- a/libs/ui/DeviceProductInfo.cpp +++ b/libs/ui/DeviceProductInfo.cpp @@ -14,74 +14,43 @@ * limitations under the License. */ +#include #include #include -#include -#include - -#define RETURN_IF_ERROR(op) \ - if (const status_t status = (op); status != OK) return status; namespace android { -using base::StringAppendF; - -size_t DeviceProductInfo::getFlattenedSize() const { - return FlattenableHelpers::getFlattenedSize(name) + - FlattenableHelpers::getFlattenedSize(manufacturerPnpId) + - FlattenableHelpers::getFlattenedSize(productId) + - FlattenableHelpers::getFlattenedSize(manufactureOrModelDate) + - FlattenableHelpers::getFlattenedSize(relativeAddress); -} - -status_t DeviceProductInfo::flatten(void* buffer, size_t size) const { - if (size < getFlattenedSize()) { - return NO_MEMORY; - } - RETURN_IF_ERROR(FlattenableHelpers::flatten(&buffer, &size, name)); - RETURN_IF_ERROR(FlattenableHelpers::flatten(&buffer, &size, manufacturerPnpId)); - RETURN_IF_ERROR(FlattenableHelpers::flatten(&buffer, &size, productId)); - RETURN_IF_ERROR(FlattenableHelpers::flatten(&buffer, &size, manufactureOrModelDate)); - RETURN_IF_ERROR(FlattenableHelpers::flatten(&buffer, &size, relativeAddress)); - return OK; -} - -status_t DeviceProductInfo::unflatten(void const* buffer, size_t size) { - RETURN_IF_ERROR(FlattenableHelpers::unflatten(&buffer, &size, &name)); - RETURN_IF_ERROR(FlattenableHelpers::unflatten(&buffer, &size, &manufacturerPnpId)); - RETURN_IF_ERROR(FlattenableHelpers::unflatten(&buffer, &size, &productId)); - RETURN_IF_ERROR(FlattenableHelpers::unflatten(&buffer, &size, &manufactureOrModelDate)); - RETURN_IF_ERROR(FlattenableHelpers::unflatten(&buffer, &size, &relativeAddress)); - return OK; -} - -void DeviceProductInfo::dump(std::string& result) const { - StringAppendF(&result, "{name=\"%s\", ", name.c_str()); - StringAppendF(&result, "manufacturerPnpId=%s, ", manufacturerPnpId.data()); - StringAppendF(&result, "productId=%s, ", productId.c_str()); - - if (const auto* model = std::get_if(&manufactureOrModelDate)) { - StringAppendF(&result, "modelYear=%u, ", model->year); - } else if (const auto* manufactureWeekAndYear = - std::get_if(&manufactureOrModelDate)) { - StringAppendF(&result, "manufactureWeek=%u, ", manufactureWeekAndYear->week); - StringAppendF(&result, "manufactureYear=%d, ", manufactureWeekAndYear->year); - } else if (const auto* manufactureYear = - std::get_if(&manufactureOrModelDate)) { - StringAppendF(&result, "manufactureYear=%d, ", manufactureYear->year); - } else { - ALOGE("Unknown alternative for variant DeviceProductInfo::ManufactureOrModelDate"); - } +std::string to_string(const DeviceProductInfo& info) { + using base::StringAppendF; + + std::string result; + StringAppendF(&result, "{name=\"%s\", ", info.name.c_str()); + StringAppendF(&result, "manufacturerPnpId=%s, ", info.manufacturerPnpId.data()); + StringAppendF(&result, "productId=%s, ", info.productId.c_str()); + + ftl::match( + info.manufactureOrModelDate, + [&](DeviceProductInfo::ModelYear model) { + StringAppendF(&result, "modelYear=%u, ", model.year); + }, + [&](DeviceProductInfo::ManufactureWeekAndYear manufacture) { + StringAppendF(&result, "manufactureWeek=%u, ", manufacture.week); + StringAppendF(&result, "manufactureYear=%d, ", manufacture.year); + }, + [&](DeviceProductInfo::ManufactureYear manufacture) { + StringAppendF(&result, "manufactureYear=%d, ", manufacture.year); + }); result.append("relativeAddress=["); - for (size_t i = 0; i < relativeAddress.size(); i++) { + for (size_t i = 0; i < info.relativeAddress.size(); i++) { if (i != 0) { result.append(", "); } - StringAppendF(&result, "%u", relativeAddress[i]); + StringAppendF(&result, "%u", info.relativeAddress[i]); } result.append("]}"); + return result; } } // namespace android diff --git a/libs/ui/DisplayMode.cpp b/libs/ui/DisplayMode.cpp deleted file mode 100644 index cf05dbfb0550b1c6f6de0028f71a16c6eafcde1b..0000000000000000000000000000000000000000 --- a/libs/ui/DisplayMode.cpp +++ /dev/null @@ -1,69 +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. - */ - -#include - -#include - -#include - -#define RETURN_IF_ERROR(op) \ - if (const status_t status = (op); status != OK) return status; - -namespace android::ui { - -size_t DisplayMode::getFlattenedSize() const { - return FlattenableHelpers::getFlattenedSize(id) + - FlattenableHelpers::getFlattenedSize(resolution) + - FlattenableHelpers::getFlattenedSize(xDpi) + - FlattenableHelpers::getFlattenedSize(yDpi) + - FlattenableHelpers::getFlattenedSize(refreshRate) + - FlattenableHelpers::getFlattenedSize(appVsyncOffset) + - FlattenableHelpers::getFlattenedSize(sfVsyncOffset) + - FlattenableHelpers::getFlattenedSize(presentationDeadline) + - FlattenableHelpers::getFlattenedSize(group); -} - -status_t DisplayMode::flatten(void* buffer, size_t size) const { - if (size < getFlattenedSize()) { - return NO_MEMORY; - } - RETURN_IF_ERROR(FlattenableHelpers::flatten(&buffer, &size, id)); - RETURN_IF_ERROR(FlattenableHelpers::flatten(&buffer, &size, resolution)); - RETURN_IF_ERROR(FlattenableHelpers::flatten(&buffer, &size, xDpi)); - RETURN_IF_ERROR(FlattenableHelpers::flatten(&buffer, &size, yDpi)); - RETURN_IF_ERROR(FlattenableHelpers::flatten(&buffer, &size, refreshRate)); - RETURN_IF_ERROR(FlattenableHelpers::flatten(&buffer, &size, appVsyncOffset)); - RETURN_IF_ERROR(FlattenableHelpers::flatten(&buffer, &size, sfVsyncOffset)); - RETURN_IF_ERROR(FlattenableHelpers::flatten(&buffer, &size, presentationDeadline)); - RETURN_IF_ERROR(FlattenableHelpers::flatten(&buffer, &size, group)); - return OK; -} - -status_t DisplayMode::unflatten(const void* buffer, size_t size) { - RETURN_IF_ERROR(FlattenableHelpers::unflatten(&buffer, &size, &id)); - RETURN_IF_ERROR(FlattenableHelpers::unflatten(&buffer, &size, &resolution)); - RETURN_IF_ERROR(FlattenableHelpers::unflatten(&buffer, &size, &xDpi)); - RETURN_IF_ERROR(FlattenableHelpers::unflatten(&buffer, &size, &yDpi)); - RETURN_IF_ERROR(FlattenableHelpers::unflatten(&buffer, &size, &refreshRate)); - RETURN_IF_ERROR(FlattenableHelpers::unflatten(&buffer, &size, &appVsyncOffset)); - RETURN_IF_ERROR(FlattenableHelpers::unflatten(&buffer, &size, &sfVsyncOffset)); - RETURN_IF_ERROR(FlattenableHelpers::unflatten(&buffer, &size, &presentationDeadline)); - RETURN_IF_ERROR(FlattenableHelpers::unflatten(&buffer, &size, &group)); - return OK; -} - -} // namespace android::ui diff --git a/libs/ui/DynamicDisplayInfo.cpp b/libs/ui/DynamicDisplayInfo.cpp index 78ba9965b6415a31b9e8daf9c228074029fe34c7..f5feea925d417c5f63c508dc23593102dd2984be 100644 --- a/libs/ui/DynamicDisplayInfo.cpp +++ b/libs/ui/DynamicDisplayInfo.cpp @@ -18,11 +18,6 @@ #include -#include - -#define RETURN_IF_ERROR(op) \ - if (const status_t status = (op); status != OK) return status; - namespace android::ui { std::optional DynamicDisplayInfo::getActiveDisplayMode() const { @@ -34,42 +29,4 @@ std::optional DynamicDisplayInfo::getActiveDisplayMode() const return {}; } -size_t DynamicDisplayInfo::getFlattenedSize() const { - return FlattenableHelpers::getFlattenedSize(supportedDisplayModes) + - FlattenableHelpers::getFlattenedSize(activeDisplayModeId) + - FlattenableHelpers::getFlattenedSize(supportedColorModes) + - FlattenableHelpers::getFlattenedSize(activeColorMode) + - FlattenableHelpers::getFlattenedSize(hdrCapabilities) + - FlattenableHelpers::getFlattenedSize(autoLowLatencyModeSupported) + - FlattenableHelpers::getFlattenedSize(gameContentTypeSupported) + - FlattenableHelpers::getFlattenedSize(preferredBootDisplayMode); -} - -status_t DynamicDisplayInfo::flatten(void* buffer, size_t size) const { - if (size < getFlattenedSize()) { - return NO_MEMORY; - } - RETURN_IF_ERROR(FlattenableHelpers::flatten(&buffer, &size, supportedDisplayModes)); - RETURN_IF_ERROR(FlattenableHelpers::flatten(&buffer, &size, activeDisplayModeId)); - RETURN_IF_ERROR(FlattenableHelpers::flatten(&buffer, &size, supportedColorModes)); - RETURN_IF_ERROR(FlattenableHelpers::flatten(&buffer, &size, activeColorMode)); - RETURN_IF_ERROR(FlattenableHelpers::flatten(&buffer, &size, hdrCapabilities)); - RETURN_IF_ERROR(FlattenableHelpers::flatten(&buffer, &size, autoLowLatencyModeSupported)); - RETURN_IF_ERROR(FlattenableHelpers::flatten(&buffer, &size, gameContentTypeSupported)); - RETURN_IF_ERROR(FlattenableHelpers::flatten(&buffer, &size, preferredBootDisplayMode)); - return OK; -} - -status_t DynamicDisplayInfo::unflatten(const void* buffer, size_t size) { - RETURN_IF_ERROR(FlattenableHelpers::unflatten(&buffer, &size, &supportedDisplayModes)); - RETURN_IF_ERROR(FlattenableHelpers::unflatten(&buffer, &size, &activeDisplayModeId)); - RETURN_IF_ERROR(FlattenableHelpers::unflatten(&buffer, &size, &supportedColorModes)); - RETURN_IF_ERROR(FlattenableHelpers::unflatten(&buffer, &size, &activeColorMode)); - RETURN_IF_ERROR(FlattenableHelpers::unflatten(&buffer, &size, &hdrCapabilities)); - RETURN_IF_ERROR(FlattenableHelpers::unflatten(&buffer, &size, &autoLowLatencyModeSupported)); - RETURN_IF_ERROR(FlattenableHelpers::unflatten(&buffer, &size, &gameContentTypeSupported)); - RETURN_IF_ERROR(FlattenableHelpers::unflatten(&buffer, &size, &preferredBootDisplayMode)); - return OK; -} - } // namespace android::ui diff --git a/libs/ui/Gralloc2.cpp b/libs/ui/Gralloc2.cpp index f23f10a1a96a8b2a1531e335bd543329ae4ee508..e9b5decee871a71579067bd790bfae02f421f336 100644 --- a/libs/ui/Gralloc2.cpp +++ b/libs/ui/Gralloc2.cpp @@ -161,7 +161,7 @@ status_t Gralloc2Mapper::createDescriptor(void* bufferDescriptorInfo, return static_cast((ret.isOk()) ? error : kTransactionError); } -status_t Gralloc2Mapper::importBuffer(const hardware::hidl_handle& rawHandle, +status_t Gralloc2Mapper::importBuffer(const native_handle_t* rawHandle, buffer_handle_t* outBufferHandle) const { Error error; auto ret = mMapper->importBuffer(rawHandle, diff --git a/libs/ui/Gralloc3.cpp b/libs/ui/Gralloc3.cpp index 15c60bcadfed4bcd0429e9c76e731746d95800e8..474d381dbb75b021c72db293ce6ceb1d284adc8f 100644 --- a/libs/ui/Gralloc3.cpp +++ b/libs/ui/Gralloc3.cpp @@ -138,7 +138,7 @@ status_t Gralloc3Mapper::createDescriptor(void* bufferDescriptorInfo, return static_cast((ret.isOk()) ? error : kTransactionError); } -status_t Gralloc3Mapper::importBuffer(const hardware::hidl_handle& rawHandle, +status_t Gralloc3Mapper::importBuffer(const native_handle_t* rawHandle, buffer_handle_t* outBufferHandle) const { Error error; auto ret = mMapper->importBuffer(rawHandle, [&](const auto& tmpError, const auto& tmpBuffer) { diff --git a/libs/ui/Gralloc4.cpp b/libs/ui/Gralloc4.cpp index 53372c986628af817d682d1061a65e2514ede499..b6274ab9c05cb1d0dd5a3c48cbac135bcea874a3 100644 --- a/libs/ui/Gralloc4.cpp +++ b/libs/ui/Gralloc4.cpp @@ -198,7 +198,7 @@ status_t Gralloc4Mapper::createDescriptor(void* bufferDescriptorInfo, return static_cast((ret.isOk()) ? error : kTransactionError); } -status_t Gralloc4Mapper::importBuffer(const hardware::hidl_handle& rawHandle, +status_t Gralloc4Mapper::importBuffer(const native_handle_t* rawHandle, buffer_handle_t* outBufferHandle) const { Error error; auto ret = mMapper->importBuffer(rawHandle, [&](const auto& tmpError, const auto& tmpBuffer) { @@ -766,162 +766,6 @@ status_t Gralloc4Mapper::setSmpte2094_10(buffer_handle_t bufferHandle, gralloc4::encodeSmpte2094_10); } -template -status_t Gralloc4Mapper::getDefault(uint32_t width, uint32_t height, PixelFormat format, - uint32_t layerCount, uint64_t usage, - const MetadataType& metadataType, - DecodeFunction decodeFunction, T* outMetadata) const { - if (!outMetadata) { - return BAD_VALUE; - } - - IMapper::BufferDescriptorInfo descriptorInfo; - if (auto error = sBufferDescriptorInfo("getDefault", width, height, format, layerCount, usage, - &descriptorInfo) != OK) { - return error; - } - - hidl_vec vec; - Error error; - auto ret = mMapper->getFromBufferDescriptorInfo(descriptorInfo, metadataType, - [&](const auto& tmpError, - const hidl_vec& tmpVec) { - error = tmpError; - vec = tmpVec; - }); - - if (!ret.isOk()) { - error = kTransactionError; - } - - if (error != Error::NONE) { - ALOGE("getDefault(%s, %" PRIu64 ", ...) failed with %d", metadataType.name.c_str(), - metadataType.value, error); - return static_cast(error); - } - - return decodeFunction(vec, outMetadata); -} - -status_t Gralloc4Mapper::getDefaultPixelFormatFourCC(uint32_t width, uint32_t height, - PixelFormat format, uint32_t layerCount, - uint64_t usage, - uint32_t* outPixelFormatFourCC) const { - return getDefault(width, height, format, layerCount, usage, - gralloc4::MetadataType_PixelFormatFourCC, gralloc4::decodePixelFormatFourCC, - outPixelFormatFourCC); -} - -status_t Gralloc4Mapper::getDefaultPixelFormatModifier(uint32_t width, uint32_t height, - PixelFormat format, uint32_t layerCount, - uint64_t usage, - uint64_t* outPixelFormatModifier) const { - return getDefault(width, height, format, layerCount, usage, - gralloc4::MetadataType_PixelFormatModifier, - gralloc4::decodePixelFormatModifier, outPixelFormatModifier); -} - -status_t Gralloc4Mapper::getDefaultAllocationSize(uint32_t width, uint32_t height, - PixelFormat format, uint32_t layerCount, - uint64_t usage, - uint64_t* outAllocationSize) const { - return getDefault(width, height, format, layerCount, usage, - gralloc4::MetadataType_AllocationSize, gralloc4::decodeAllocationSize, - outAllocationSize); -} - -status_t Gralloc4Mapper::getDefaultProtectedContent(uint32_t width, uint32_t height, - PixelFormat format, uint32_t layerCount, - uint64_t usage, - uint64_t* outProtectedContent) const { - return getDefault(width, height, format, layerCount, usage, - gralloc4::MetadataType_ProtectedContent, gralloc4::decodeProtectedContent, - outProtectedContent); -} - -status_t Gralloc4Mapper::getDefaultCompression(uint32_t width, uint32_t height, PixelFormat format, - uint32_t layerCount, uint64_t usage, - ExtendableType* outCompression) const { - return getDefault(width, height, format, layerCount, usage, gralloc4::MetadataType_Compression, - gralloc4::decodeCompression, outCompression); -} - -status_t Gralloc4Mapper::getDefaultCompression(uint32_t width, uint32_t height, PixelFormat format, - uint32_t layerCount, uint64_t usage, - ui::Compression* outCompression) const { - if (!outCompression) { - return BAD_VALUE; - } - ExtendableType compression; - status_t error = getDefaultCompression(width, height, format, layerCount, usage, &compression); - if (error) { - return error; - } - if (!gralloc4::isStandardCompression(compression)) { - return BAD_TYPE; - } - *outCompression = gralloc4::getStandardCompressionValue(compression); - return NO_ERROR; -} - -status_t Gralloc4Mapper::getDefaultInterlaced(uint32_t width, uint32_t height, PixelFormat format, - uint32_t layerCount, uint64_t usage, - ExtendableType* outInterlaced) const { - return getDefault(width, height, format, layerCount, usage, gralloc4::MetadataType_Interlaced, - gralloc4::decodeInterlaced, outInterlaced); -} - -status_t Gralloc4Mapper::getDefaultInterlaced(uint32_t width, uint32_t height, PixelFormat format, - uint32_t layerCount, uint64_t usage, - ui::Interlaced* outInterlaced) const { - if (!outInterlaced) { - return BAD_VALUE; - } - ExtendableType interlaced; - status_t error = getDefaultInterlaced(width, height, format, layerCount, usage, &interlaced); - if (error) { - return error; - } - if (!gralloc4::isStandardInterlaced(interlaced)) { - return BAD_TYPE; - } - *outInterlaced = gralloc4::getStandardInterlacedValue(interlaced); - return NO_ERROR; -} - -status_t Gralloc4Mapper::getDefaultChromaSiting(uint32_t width, uint32_t height, PixelFormat format, - uint32_t layerCount, uint64_t usage, - ExtendableType* outChromaSiting) const { - return getDefault(width, height, format, layerCount, usage, gralloc4::MetadataType_ChromaSiting, - gralloc4::decodeChromaSiting, outChromaSiting); -} - -status_t Gralloc4Mapper::getDefaultChromaSiting(uint32_t width, uint32_t height, PixelFormat format, - uint32_t layerCount, uint64_t usage, - ui::ChromaSiting* outChromaSiting) const { - if (!outChromaSiting) { - return BAD_VALUE; - } - ExtendableType chromaSiting; - status_t error = - getDefaultChromaSiting(width, height, format, layerCount, usage, &chromaSiting); - if (error) { - return error; - } - if (!gralloc4::isStandardChromaSiting(chromaSiting)) { - return BAD_TYPE; - } - *outChromaSiting = gralloc4::getStandardChromaSitingValue(chromaSiting); - return NO_ERROR; -} - -status_t Gralloc4Mapper::getDefaultPlaneLayouts( - uint32_t width, uint32_t height, PixelFormat format, uint32_t layerCount, uint64_t usage, - std::vector* outPlaneLayouts) const { - return getDefault(width, height, format, layerCount, usage, gralloc4::MetadataType_PlaneLayouts, - gralloc4::decodePlaneLayouts, outPlaneLayouts); -} - std::vector Gralloc4Mapper::listSupportedMetadataTypes() const { hidl_vec descriptions; Error error; @@ -1242,7 +1086,10 @@ status_t Gralloc4Allocator::allocate(std::string requestorName, uint32_t width, if (mAidlAllocator) { AllocationResult result; +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wdeprecated-declarations" auto status = mAidlAllocator->allocate(descriptor, bufferCount, &result); +#pragma clang diagnostic pop // deprecation if (!status.isOk()) { error = status.getExceptionCode(); if (error == EX_SERVICE_SPECIFIC) { diff --git a/libs/ui/Gralloc5.cpp b/libs/ui/Gralloc5.cpp new file mode 100644 index 0000000000000000000000000000000000000000..c3b2d3d808b722d5a84350a6d1c9c3bd8ae57417 --- /dev/null +++ b/libs/ui/Gralloc5.cpp @@ -0,0 +1,835 @@ +/* + * 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. + */ + +#define LOG_TAG "Gralloc5" +#define ATRACE_TAG ATRACE_TAG_GRAPHICS + +#include + +#include +#include +#include +#include +#include +#include +#include + +using namespace aidl::android::hardware::graphics::allocator; +using namespace aidl::android::hardware::graphics::common; +using namespace ::android::hardware::graphics::mapper; + +namespace android { + +static const auto kIAllocatorServiceName = IAllocator::descriptor + std::string("/default"); +static const auto kIAllocatorMinimumVersion = 2; + +// TODO(b/72323293, b/72703005): Remove these invalid bits from callers +static constexpr uint64_t kRemovedUsageBits = static_cast((1 << 10) | (1 << 13)); + +typedef AIMapper_Error (*AIMapper_loadIMapperFn)(AIMapper *_Nullable *_Nonnull outImplementation); + +struct Gralloc5 { + std::shared_ptr allocator; + AIMapper *mapper = nullptr; +}; + +static std::shared_ptr waitForAllocator() { + if (__builtin_available(android 31, *)) { + if (!AServiceManager_isDeclared(kIAllocatorServiceName.c_str())) { + return nullptr; + } + auto allocator = IAllocator::fromBinder( + ndk::SpAIBinder(AServiceManager_waitForService(kIAllocatorServiceName.c_str()))); + if (!allocator) { + ALOGE("AIDL IAllocator declared but failed to get service"); + return nullptr; + } + + int32_t version = 0; + if (!allocator->getInterfaceVersion(&version).isOk()) { + ALOGE("Failed to query interface version"); + return nullptr; + } + if (version < kIAllocatorMinimumVersion) { + return nullptr; + } + return allocator; + } else { + // TODO: LOG_ALWAYS_FATAL("libui is not backwards compatible"); + return nullptr; + } +} + +static void *loadIMapperLibrary() { + static void *imapperLibrary = []() -> void * { + auto allocator = waitForAllocator(); + std::string mapperSuffix; + auto status = allocator->getIMapperLibrarySuffix(&mapperSuffix); + if (!status.isOk()) { + ALOGE("Failed to get IMapper library suffix"); + return nullptr; + } + + std::string lib_name = "mapper." + mapperSuffix + ".so"; + void *so = android_load_sphal_library(lib_name.c_str(), RTLD_LOCAL | RTLD_NOW); + if (!so) { + ALOGE("Failed to load %s", lib_name.c_str()); + } + return so; + }(); + return imapperLibrary; +} + +static const Gralloc5 &getInstance() { + static Gralloc5 instance = []() { + auto allocator = waitForAllocator(); + if (!allocator) { + return Gralloc5{}; + } + void *so = loadIMapperLibrary(); + if (!so) { + return Gralloc5{}; + } + auto loadIMapper = (AIMapper_loadIMapperFn)dlsym(so, "AIMapper_loadIMapper"); + AIMapper *mapper = nullptr; + AIMapper_Error error = loadIMapper(&mapper); + if (error != AIMAPPER_ERROR_NONE) { + ALOGE("AIMapper_loadIMapper failed %d", error); + return Gralloc5{}; + } + return Gralloc5{std::move(allocator), mapper}; + }(); + return instance; +} + +template +static auto getStandardMetadata(AIMapper *mapper, buffer_handle_t bufferHandle) + -> decltype(StandardMetadata::value::decode(nullptr, 0)) { + using Value = typename StandardMetadata::value; + // TODO: Tune for common-case better + FatVector buffer; + int32_t sizeRequired = mapper->v5.getStandardMetadata(bufferHandle, static_cast(T), + buffer.data(), buffer.size()); + if (sizeRequired < 0) { + ALOGW_IF(-AIMAPPER_ERROR_UNSUPPORTED != sizeRequired, + "Unexpected error %d from valid getStandardMetadata call", -sizeRequired); + return std::nullopt; + } + if ((size_t)sizeRequired > buffer.size()) { + buffer.resize(sizeRequired); + sizeRequired = mapper->v5.getStandardMetadata(bufferHandle, static_cast(T), + buffer.data(), buffer.size()); + } + if (sizeRequired < 0 || (size_t)sizeRequired > buffer.size()) { + ALOGW("getStandardMetadata failed, received %d with buffer size %zd", sizeRequired, + buffer.size()); + // Generate a fail type + return std::nullopt; + } + return Value::decode(buffer.data(), sizeRequired); +} + +template +static AIMapper_Error setStandardMetadata(AIMapper *mapper, buffer_handle_t bufferHandle, + const typename StandardMetadata::value_type &value) { + using Value = typename StandardMetadata::value; + int32_t sizeRequired = Value::encode(value, nullptr, 0); + if (sizeRequired < 0) { + ALOGW("Failed to calculate required size"); + return static_cast(-sizeRequired); + } + FatVector buffer; + buffer.resize(sizeRequired); + sizeRequired = Value::encode(value, buffer.data(), buffer.size()); + if (sizeRequired < 0 || (size_t)sizeRequired > buffer.size()) { + ALOGW("Failed to encode with calculated size %d; buffer size %zd", sizeRequired, + buffer.size()); + return static_cast(-sizeRequired); + } + return mapper->v5.setStandardMetadata(bufferHandle, static_cast(T), buffer.data(), + sizeRequired); +} + +Gralloc5Allocator::Gralloc5Allocator(const Gralloc5Mapper &mapper) : mMapper(mapper) { + mAllocator = getInstance().allocator; +} + +bool Gralloc5Allocator::isLoaded() const { + return mAllocator != nullptr; +} + +static uint64_t getValidUsageBits() { + static const uint64_t validUsageBits = []() -> uint64_t { + uint64_t bits = 0; + for (const auto bit : ndk::enum_range{}) { + bits |= static_cast(bit); + } + return bits; + }(); + return validUsageBits | kRemovedUsageBits; +} + +static std::optional makeDescriptor(std::string requestorName, uint32_t width, + uint32_t height, PixelFormat format, + uint32_t layerCount, uint64_t usage) { + uint64_t validUsageBits = getValidUsageBits(); + if (usage & ~validUsageBits) { + ALOGE("buffer descriptor contains invalid usage bits 0x%" PRIx64, usage & ~validUsageBits); + return std::nullopt; + } + + BufferDescriptorInfo descriptorInfo{ + .width = static_cast(width), + .height = static_cast(height), + .layerCount = static_cast(layerCount), + .format = static_cast<::aidl::android::hardware::graphics::common::PixelFormat>(format), + .usage = static_cast(usage), + }; + auto nameLength = std::min(requestorName.length(), descriptorInfo.name.size() - 1); + memcpy(descriptorInfo.name.data(), requestorName.data(), nameLength); + requestorName.data()[nameLength] = 0; + return descriptorInfo; +} + +std::string Gralloc5Allocator::dumpDebugInfo(bool less) const { + return mMapper.dumpBuffers(less); +} + +status_t Gralloc5Allocator::allocate(std::string requestorName, uint32_t width, uint32_t height, + android::PixelFormat format, uint32_t layerCount, + uint64_t usage, uint32_t bufferCount, uint32_t *outStride, + buffer_handle_t *outBufferHandles, bool importBuffers) const { + auto descriptorInfo = makeDescriptor(requestorName, width, height, format, layerCount, usage); + if (!descriptorInfo) { + return BAD_VALUE; + } + + AllocationResult result; + auto status = mAllocator->allocate2(*descriptorInfo, bufferCount, &result); + if (!status.isOk()) { + auto error = status.getExceptionCode(); + if (error == EX_SERVICE_SPECIFIC) { + error = status.getServiceSpecificError(); + } + if (error == OK) { + error = UNKNOWN_ERROR; + } + return error; + } + + if (importBuffers) { + for (uint32_t i = 0; i < bufferCount; i++) { + auto handle = makeFromAidl(result.buffers[i]); + auto error = mMapper.importBuffer(handle, &outBufferHandles[i]); + native_handle_delete(handle); + if (error != NO_ERROR) { + for (uint32_t j = 0; j < i; j++) { + mMapper.freeBuffer(outBufferHandles[j]); + outBufferHandles[j] = nullptr; + } + return error; + } + } + } else { + for (uint32_t i = 0; i < bufferCount; i++) { + outBufferHandles[i] = dupFromAidl(result.buffers[i]); + if (!outBufferHandles[i]) { + for (uint32_t j = 0; j < i; j++) { + auto buffer = const_cast(outBufferHandles[j]); + native_handle_close(buffer); + native_handle_delete(buffer); + outBufferHandles[j] = nullptr; + } + return NO_MEMORY; + } + } + } + + *outStride = result.stride; + + // Release all the resources held by AllocationResult (specifically any remaining FDs) + result = {}; + // make sure the kernel driver sees BC_FREE_BUFFER and closes the fds now + // TODO: Re-enable this at some point if it's necessary. We can't do it now because libui + // is marked apex_available (b/214400477) and libbinder isn't (which of course is correct) + // IPCThreadState::self()->flushCommands(); + + return OK; +} + +void Gralloc5Mapper::preload() { + // TODO(b/261858155): Implement. We can't bounce off of IAllocator for this because zygote can't + // use binder. So when an alternate strategy of retrieving the library prefix is available, + // use that here. +} + +Gralloc5Mapper::Gralloc5Mapper() { + mMapper = getInstance().mapper; +} + +bool Gralloc5Mapper::isLoaded() const { + return mMapper != nullptr && mMapper->version >= AIMAPPER_VERSION_5; +} + +std::string Gralloc5Mapper::dumpBuffer(buffer_handle_t bufferHandle, bool less) const { + // TODO(b/261858392): Implement + (void)bufferHandle; + (void)less; + return {}; +} + +std::string Gralloc5Mapper::dumpBuffers(bool less) const { + // TODO(b/261858392): Implement + (void)less; + return {}; +} + +status_t Gralloc5Mapper::importBuffer(const native_handle_t *rawHandle, + buffer_handle_t *outBufferHandle) const { + return mMapper->v5.importBuffer(rawHandle, outBufferHandle); +} + +void Gralloc5Mapper::freeBuffer(buffer_handle_t bufferHandle) const { + mMapper->v5.freeBuffer(bufferHandle); +} + +status_t Gralloc5Mapper::validateBufferSize(buffer_handle_t bufferHandle, uint32_t width, + uint32_t height, PixelFormat format, + uint32_t layerCount, uint64_t usage, + uint32_t stride) const { + { + auto value = getStandardMetadata(mMapper, bufferHandle); + if (width != value) { + ALOGW("Width didn't match, expected %d got %" PRId64, width, value.value_or(-1)); + return BAD_VALUE; + } + } + { + auto value = getStandardMetadata(mMapper, bufferHandle); + if (height != value) { + ALOGW("Height didn't match, expected %d got %" PRId64, height, value.value_or(-1)); + return BAD_VALUE; + } + } + { + auto value = + getStandardMetadata(mMapper, + bufferHandle); + if (static_cast<::aidl::android::hardware::graphics::common::PixelFormat>(format) != + value) { + ALOGW("Format didn't match, expected %d got %s", format, + value.has_value() ? toString(*value).c_str() : ""); + return BAD_VALUE; + } + } + { + auto value = getStandardMetadata(mMapper, bufferHandle); + if (layerCount != value) { + ALOGW("Layer count didn't match, expected %d got %" PRId64, layerCount, + value.value_or(-1)); + return BAD_VALUE; + } + } + // TODO: This can false-positive fail if the allocator adjusted the USAGE bits internally + // Investigate further & re-enable or remove, but for now ignoring usage should be OK + (void)usage; + // { + // auto value = getStandardMetadata(mMapper, bufferHandle); + // if (static_cast(usage) != value) { + // ALOGW("Usage didn't match, expected %" PRIu64 " got %" PRId64, usage, + // static_cast(value.value_or(BufferUsage::CPU_READ_NEVER))); + // return BAD_VALUE; + // } + // } + { + auto value = getStandardMetadata(mMapper, bufferHandle); + if (stride != value) { + ALOGW("Stride didn't match, expected %" PRIu32 " got %" PRId32, stride, + value.value_or(-1)); + return BAD_VALUE; + } + } + return OK; +} + +void Gralloc5Mapper::getTransportSize(buffer_handle_t bufferHandle, uint32_t *outNumFds, + uint32_t *outNumInts) const { + mMapper->v5.getTransportSize(bufferHandle, outNumFds, outNumInts); +} + +status_t Gralloc5Mapper::lock(buffer_handle_t bufferHandle, uint64_t usage, const Rect &bounds, + int acquireFence, void **outData, int32_t *outBytesPerPixel, + int32_t *outBytesPerStride) const { + std::vector planeLayouts; + status_t err = getPlaneLayouts(bufferHandle, &planeLayouts); + + if (err == NO_ERROR && !planeLayouts.empty()) { + if (outBytesPerPixel) { + int32_t bitsPerPixel = planeLayouts.front().sampleIncrementInBits; + for (const auto &planeLayout : planeLayouts) { + if (bitsPerPixel != planeLayout.sampleIncrementInBits) { + bitsPerPixel = -1; + } + } + if (bitsPerPixel >= 0 && bitsPerPixel % 8 == 0) { + *outBytesPerPixel = bitsPerPixel / 8; + } else { + *outBytesPerPixel = -1; + } + } + if (outBytesPerStride) { + int32_t bytesPerStride = planeLayouts.front().strideInBytes; + for (const auto &planeLayout : planeLayouts) { + if (bytesPerStride != planeLayout.strideInBytes) { + bytesPerStride = -1; + } + } + if (bytesPerStride >= 0) { + *outBytesPerStride = bytesPerStride; + } else { + *outBytesPerStride = -1; + } + } + } + + auto status = mMapper->v5.lock(bufferHandle, usage, bounds, acquireFence, outData); + + ALOGW_IF(status != AIMAPPER_ERROR_NONE, "lock(%p, ...) failed: %d", bufferHandle, status); + return static_cast(status); +} + +status_t Gralloc5Mapper::lock(buffer_handle_t bufferHandle, uint64_t usage, const Rect &bounds, + int acquireFence, android_ycbcr *outYcbcr) const { + if (!outYcbcr) { + return BAD_VALUE; + } + + // TODO(b/262279301): Change the return type of ::unlock to unique_fd instead of int so that + // ignoring the return value "just works" instead + auto unlock = [this](buffer_handle_t bufferHandle) { + int fence = this->unlock(bufferHandle); + if (fence != -1) { + ::close(fence); + } + }; + + std::vector planeLayouts; + status_t error = getPlaneLayouts(bufferHandle, &planeLayouts); + if (error != NO_ERROR) { + return error; + } + + void *data = nullptr; + error = lock(bufferHandle, usage, bounds, acquireFence, &data, nullptr, nullptr); + if (error != NO_ERROR) { + return error; + } + + android_ycbcr ycbcr; + + ycbcr.y = nullptr; + ycbcr.cb = nullptr; + ycbcr.cr = nullptr; + ycbcr.ystride = 0; + ycbcr.cstride = 0; + ycbcr.chroma_step = 0; + + for (const auto &planeLayout : planeLayouts) { + for (const auto &planeLayoutComponent : planeLayout.components) { + if (!gralloc4::isStandardPlaneLayoutComponentType(planeLayoutComponent.type)) { + continue; + } + + uint8_t *tmpData = static_cast(data) + planeLayout.offsetInBytes; + + // Note that `offsetInBits` may not be a multiple of 8 for packed formats (e.g. P010) + // but we still want to point to the start of the first byte. + tmpData += (planeLayoutComponent.offsetInBits / 8); + + uint64_t sampleIncrementInBytes; + + auto type = static_cast(planeLayoutComponent.type.value); + switch (type) { + case PlaneLayoutComponentType::Y: + if ((ycbcr.y != nullptr) || (planeLayout.sampleIncrementInBits % 8 != 0)) { + unlock(bufferHandle); + return BAD_VALUE; + } + ycbcr.y = tmpData; + ycbcr.ystride = planeLayout.strideInBytes; + break; + + case PlaneLayoutComponentType::CB: + case PlaneLayoutComponentType::CR: + if (planeLayout.sampleIncrementInBits % 8 != 0) { + unlock(bufferHandle); + return BAD_VALUE; + } + + sampleIncrementInBytes = planeLayout.sampleIncrementInBits / 8; + if ((sampleIncrementInBytes != 1) && (sampleIncrementInBytes != 2) && + (sampleIncrementInBytes != 4)) { + unlock(bufferHandle); + return BAD_VALUE; + } + + if (ycbcr.cstride == 0 && ycbcr.chroma_step == 0) { + ycbcr.cstride = planeLayout.strideInBytes; + ycbcr.chroma_step = sampleIncrementInBytes; + } else { + if ((static_cast(ycbcr.cstride) != planeLayout.strideInBytes) || + (ycbcr.chroma_step != sampleIncrementInBytes)) { + unlock(bufferHandle); + return BAD_VALUE; + } + } + + if (type == PlaneLayoutComponentType::CB) { + if (ycbcr.cb != nullptr) { + unlock(bufferHandle); + return BAD_VALUE; + } + ycbcr.cb = tmpData; + } else { + if (ycbcr.cr != nullptr) { + unlock(bufferHandle); + return BAD_VALUE; + } + ycbcr.cr = tmpData; + } + break; + default: + break; + }; + } + } + + *outYcbcr = ycbcr; + return OK; +} + +int Gralloc5Mapper::unlock(buffer_handle_t bufferHandle) const { + int fence = -1; + AIMapper_Error error = mMapper->v5.unlock(bufferHandle, &fence); + if (error != AIMAPPER_ERROR_NONE) { + ALOGW("unlock failed with error %d", error); + } + return fence; +} + +status_t Gralloc5Mapper::isSupported(uint32_t width, uint32_t height, PixelFormat format, + uint32_t layerCount, uint64_t usage, + bool *outSupported) const { + auto descriptorInfo = makeDescriptor("", width, height, format, layerCount, usage); + if (!descriptorInfo) { + *outSupported = false; + return OK; + } + auto status = getInstance().allocator->isSupported(*descriptorInfo, outSupported); + if (!status.isOk()) { + ALOGW("IAllocator::isSupported error %d (%s)", status.getStatus(), status.getMessage()); + *outSupported = false; + } + return OK; +} + +status_t Gralloc5Mapper::getBufferId(buffer_handle_t bufferHandle, uint64_t *outBufferId) const { + auto value = getStandardMetadata(mMapper, bufferHandle); + if (value.has_value()) { + *outBufferId = *value; + return OK; + } + return UNKNOWN_TRANSACTION; +} + +status_t Gralloc5Mapper::getName(buffer_handle_t bufferHandle, std::string *outName) const { + auto value = getStandardMetadata(mMapper, bufferHandle); + if (value.has_value()) { + *outName = *value; + return OK; + } + return UNKNOWN_TRANSACTION; +} + +status_t Gralloc5Mapper::getWidth(buffer_handle_t bufferHandle, uint64_t *outWidth) const { + auto value = getStandardMetadata(mMapper, bufferHandle); + if (value.has_value()) { + *outWidth = *value; + return OK; + } + return UNKNOWN_TRANSACTION; +} + +status_t Gralloc5Mapper::getHeight(buffer_handle_t bufferHandle, uint64_t *outHeight) const { + auto value = getStandardMetadata(mMapper, bufferHandle); + if (value.has_value()) { + *outHeight = *value; + return OK; + } + return UNKNOWN_TRANSACTION; +} + +status_t Gralloc5Mapper::getLayerCount(buffer_handle_t bufferHandle, + uint64_t *outLayerCount) const { + auto value = getStandardMetadata(mMapper, bufferHandle); + if (value.has_value()) { + *outLayerCount = *value; + return OK; + } + return UNKNOWN_TRANSACTION; +} + +status_t Gralloc5Mapper::getPixelFormatRequested(buffer_handle_t bufferHandle, + ui::PixelFormat *outPixelFormatRequested) const { + auto value = getStandardMetadata(mMapper, + bufferHandle); + if (value.has_value()) { + *outPixelFormatRequested = static_cast(*value); + return OK; + } + return UNKNOWN_TRANSACTION; +} + +status_t Gralloc5Mapper::getPixelFormatFourCC(buffer_handle_t bufferHandle, + uint32_t *outPixelFormatFourCC) const { + auto value = + getStandardMetadata(mMapper, bufferHandle); + if (value.has_value()) { + *outPixelFormatFourCC = *value; + return OK; + } + return UNKNOWN_TRANSACTION; +} + +status_t Gralloc5Mapper::getPixelFormatModifier(buffer_handle_t bufferHandle, + uint64_t *outPixelFormatModifier) const { + auto value = + getStandardMetadata(mMapper, bufferHandle); + if (value.has_value()) { + *outPixelFormatModifier = *value; + return OK; + } + return UNKNOWN_TRANSACTION; +} + +status_t Gralloc5Mapper::getUsage(buffer_handle_t bufferHandle, uint64_t *outUsage) const { + auto value = getStandardMetadata(mMapper, bufferHandle); + if (value.has_value()) { + *outUsage = static_cast(*value); + return OK; + } + return UNKNOWN_TRANSACTION; +} + +status_t Gralloc5Mapper::getAllocationSize(buffer_handle_t bufferHandle, + uint64_t *outAllocationSize) const { + auto value = getStandardMetadata(mMapper, bufferHandle); + if (value.has_value()) { + *outAllocationSize = *value; + return OK; + } + return UNKNOWN_TRANSACTION; +} + +status_t Gralloc5Mapper::getProtectedContent(buffer_handle_t bufferHandle, + uint64_t *outProtectedContent) const { + auto value = + getStandardMetadata(mMapper, bufferHandle); + if (value.has_value()) { + *outProtectedContent = *value; + return OK; + } + return UNKNOWN_TRANSACTION; +} + +status_t Gralloc5Mapper::getCompression( + buffer_handle_t bufferHandle, + aidl::android::hardware::graphics::common::ExtendableType *outCompression) const { + auto value = getStandardMetadata(mMapper, bufferHandle); + if (value.has_value()) { + *outCompression = *value; + return OK; + } + return UNKNOWN_TRANSACTION; +} + +status_t Gralloc5Mapper::getCompression(buffer_handle_t bufferHandle, + ui::Compression *outCompression) const { + auto value = getStandardMetadata(mMapper, bufferHandle); + if (!value.has_value()) { + return UNKNOWN_TRANSACTION; + } + if (!gralloc4::isStandardCompression(*value)) { + return BAD_TYPE; + } + *outCompression = gralloc4::getStandardCompressionValue(*value); + return OK; +} + +status_t Gralloc5Mapper::getInterlaced( + buffer_handle_t bufferHandle, + aidl::android::hardware::graphics::common::ExtendableType *outInterlaced) const { + auto value = getStandardMetadata(mMapper, bufferHandle); + if (value.has_value()) { + *outInterlaced = *value; + return OK; + } + return UNKNOWN_TRANSACTION; +} + +status_t Gralloc5Mapper::getInterlaced(buffer_handle_t bufferHandle, + ui::Interlaced *outInterlaced) const { + if (!outInterlaced) { + return BAD_VALUE; + } + ExtendableType interlaced; + status_t error = getInterlaced(bufferHandle, &interlaced); + if (error) { + return error; + } + if (!gralloc4::isStandardInterlaced(interlaced)) { + return BAD_TYPE; + } + *outInterlaced = gralloc4::getStandardInterlacedValue(interlaced); + return NO_ERROR; +} + +status_t Gralloc5Mapper::getChromaSiting( + buffer_handle_t bufferHandle, + aidl::android::hardware::graphics::common::ExtendableType *outChromaSiting) const { + auto value = getStandardMetadata(mMapper, bufferHandle); + if (value.has_value()) { + *outChromaSiting = *value; + return OK; + } + return UNKNOWN_TRANSACTION; +} + +status_t Gralloc5Mapper::getChromaSiting(buffer_handle_t bufferHandle, + ui::ChromaSiting *outChromaSiting) const { + if (!outChromaSiting) { + return BAD_VALUE; + } + ExtendableType chromaSiting; + status_t error = getChromaSiting(bufferHandle, &chromaSiting); + if (error) { + return error; + } + if (!gralloc4::isStandardChromaSiting(chromaSiting)) { + return BAD_TYPE; + } + *outChromaSiting = gralloc4::getStandardChromaSitingValue(chromaSiting); + return NO_ERROR; +} + +status_t Gralloc5Mapper::getPlaneLayouts(buffer_handle_t bufferHandle, + std::vector *outPlaneLayouts) const { + auto value = getStandardMetadata(mMapper, bufferHandle); + if (value.has_value()) { + *outPlaneLayouts = *value; + return OK; + } + return UNKNOWN_TRANSACTION; +} + +status_t Gralloc5Mapper::getDataspace(buffer_handle_t bufferHandle, + ui::Dataspace *outDataspace) const { + auto value = getStandardMetadata(mMapper, bufferHandle); + if (value.has_value()) { + *outDataspace = static_cast(*value); + return OK; + } + return UNKNOWN_TRANSACTION; +} + +status_t Gralloc5Mapper::setDataspace(buffer_handle_t bufferHandle, ui::Dataspace dataspace) const { + return setStandardMetadata(mMapper, bufferHandle, + static_cast(dataspace)); +} + +status_t Gralloc5Mapper::getBlendMode(buffer_handle_t bufferHandle, + ui::BlendMode *outBlendMode) const { + auto value = getStandardMetadata(mMapper, bufferHandle); + if (value.has_value()) { + *outBlendMode = static_cast(*value); + return OK; + } + return UNKNOWN_TRANSACTION; +} + +status_t Gralloc5Mapper::getSmpte2086(buffer_handle_t bufferHandle, + std::optional *outSmpte2086) const { + auto value = getStandardMetadata(mMapper, bufferHandle); + if (value.has_value()) { + *outSmpte2086 = *value; + return OK; + } + return UNKNOWN_TRANSACTION; +} + +status_t Gralloc5Mapper::setSmpte2086(buffer_handle_t bufferHandle, + std::optional smpte2086) const { + return setStandardMetadata(mMapper, bufferHandle, smpte2086); +} + +status_t Gralloc5Mapper::getCta861_3(buffer_handle_t bufferHandle, + std::optional *outCta861_3) const { + auto value = getStandardMetadata(mMapper, bufferHandle); + if (value.has_value()) { + *outCta861_3 = *value; + return OK; + } + return UNKNOWN_TRANSACTION; +} + +status_t Gralloc5Mapper::setCta861_3(buffer_handle_t bufferHandle, + std::optional cta861_3) const { + return setStandardMetadata(mMapper, bufferHandle, cta861_3); +} + +status_t Gralloc5Mapper::getSmpte2094_40( + buffer_handle_t bufferHandle, std::optional> *outSmpte2094_40) const { + auto value = getStandardMetadata(mMapper, bufferHandle); + if (value.has_value()) { + *outSmpte2094_40 = std::move(*value); + return OK; + } + return UNKNOWN_TRANSACTION; +} + +status_t Gralloc5Mapper::setSmpte2094_40(buffer_handle_t bufferHandle, + std::optional> smpte2094_40) const { + return setStandardMetadata(mMapper, bufferHandle, + smpte2094_40); +} + +status_t Gralloc5Mapper::getSmpte2094_10( + buffer_handle_t bufferHandle, std::optional> *outSmpte2094_10) const { + auto value = getStandardMetadata(mMapper, bufferHandle); + if (value.has_value()) { + *outSmpte2094_10 = std::move(*value); + return OK; + } + return UNKNOWN_TRANSACTION; +} + +status_t Gralloc5Mapper::setSmpte2094_10(buffer_handle_t bufferHandle, + std::optional> smpte2094_10) const { + return setStandardMetadata(mMapper, bufferHandle, + smpte2094_10); +} + +} // namespace android \ No newline at end of file diff --git a/libs/ui/GraphicBufferAllocator.cpp b/libs/ui/GraphicBufferAllocator.cpp index 3f958ba68f52c4c4ffd97170402c49885ab862ba..c0abec23e031d2fcca682bd5439e7269e36a5fbb 100644 --- a/libs/ui/GraphicBufferAllocator.cpp +++ b/libs/ui/GraphicBufferAllocator.cpp @@ -34,6 +34,7 @@ #include #include #include +#include #include namespace android { @@ -48,23 +49,27 @@ KeyedVector GraphicBufferAllocator::sAllocList; GraphicBufferAllocator::GraphicBufferAllocator() : mMapper(GraphicBufferMapper::getInstance()) { - mAllocator = std::make_unique( - reinterpret_cast(mMapper.getGrallocMapper())); - if (mAllocator->isLoaded()) { - return; + switch (mMapper.getMapperVersion()) { + case GraphicBufferMapper::GRALLOC_5: + mAllocator = std::make_unique( + reinterpret_cast(mMapper.getGrallocMapper())); + break; + case GraphicBufferMapper::GRALLOC_4: + mAllocator = std::make_unique( + reinterpret_cast(mMapper.getGrallocMapper())); + break; + case GraphicBufferMapper::GRALLOC_3: + mAllocator = std::make_unique( + reinterpret_cast(mMapper.getGrallocMapper())); + break; + case GraphicBufferMapper::GRALLOC_2: + mAllocator = std::make_unique( + reinterpret_cast(mMapper.getGrallocMapper())); + break; } - mAllocator = std::make_unique( - reinterpret_cast(mMapper.getGrallocMapper())); - if (mAllocator->isLoaded()) { - return; - } - mAllocator = std::make_unique( - reinterpret_cast(mMapper.getGrallocMapper())); - if (mAllocator->isLoaded()) { - return; - } - - LOG_ALWAYS_FATAL("gralloc-allocator is missing"); + LOG_ALWAYS_FATAL_IF(!mAllocator->isLoaded(), + "Failed to load matching allocator for mapper version %d", + mMapper.getMapperVersion()); } GraphicBufferAllocator::~GraphicBufferAllocator() {} diff --git a/libs/ui/GraphicBufferMapper.cpp b/libs/ui/GraphicBufferMapper.cpp index f5824233e92656b584fdaca839202958828734fb..7086e0470cbec2af94b468f74c82a005671b17da 100644 --- a/libs/ui/GraphicBufferMapper.cpp +++ b/libs/ui/GraphicBufferMapper.cpp @@ -36,6 +36,7 @@ #include #include #include +#include #include #include @@ -49,9 +50,15 @@ void GraphicBufferMapper::preloadHal() { Gralloc2Mapper::preload(); Gralloc3Mapper::preload(); Gralloc4Mapper::preload(); + Gralloc5Mapper::preload(); } GraphicBufferMapper::GraphicBufferMapper() { + mMapper = std::make_unique(); + if (mMapper->isLoaded()) { + mMapperVersion = Version::GRALLOC_5; + return; + } mMapper = std::make_unique(); if (mMapper->isLoaded()) { mMapperVersion = Version::GRALLOC_4; @@ -82,15 +89,14 @@ void GraphicBufferMapper::dumpBufferToSystemLog(buffer_handle_t bufferHandle, bo ALOGD("%s", s.c_str()); } -status_t GraphicBufferMapper::importBuffer(buffer_handle_t rawHandle, - uint32_t width, uint32_t height, uint32_t layerCount, - PixelFormat format, uint64_t usage, uint32_t stride, - buffer_handle_t* outHandle) -{ +status_t GraphicBufferMapper::importBuffer(const native_handle_t* rawHandle, uint32_t width, + uint32_t height, uint32_t layerCount, PixelFormat format, + uint64_t usage, uint32_t stride, + buffer_handle_t* outHandle) { ATRACE_CALL(); buffer_handle_t bufferHandle; - status_t error = mMapper->importBuffer(hardware::hidl_handle(rawHandle), &bufferHandle); + status_t error = mMapper->importBuffer(rawHandle, &bufferHandle); if (error != NO_ERROR) { ALOGW("importBuffer(%p) failed: %d", rawHandle, error); return error; @@ -335,84 +341,5 @@ status_t GraphicBufferMapper::setSmpte2094_10(buffer_handle_t bufferHandle, return mMapper->setSmpte2094_10(bufferHandle, smpte2094_10); } -status_t GraphicBufferMapper::getDefaultPixelFormatFourCC(uint32_t width, uint32_t height, - PixelFormat format, uint32_t layerCount, - uint64_t usage, - uint32_t* outPixelFormatFourCC) { - return mMapper->getDefaultPixelFormatFourCC(width, height, format, layerCount, usage, - outPixelFormatFourCC); -} - -status_t GraphicBufferMapper::getDefaultPixelFormatModifier(uint32_t width, uint32_t height, - PixelFormat format, uint32_t layerCount, - uint64_t usage, - uint64_t* outPixelFormatModifier) { - return mMapper->getDefaultPixelFormatModifier(width, height, format, layerCount, usage, - outPixelFormatModifier); -} - -status_t GraphicBufferMapper::getDefaultAllocationSize(uint32_t width, uint32_t height, - PixelFormat format, uint32_t layerCount, - uint64_t usage, - uint64_t* outAllocationSize) { - return mMapper->getDefaultAllocationSize(width, height, format, layerCount, usage, - outAllocationSize); -} - -status_t GraphicBufferMapper::getDefaultProtectedContent(uint32_t width, uint32_t height, - PixelFormat format, uint32_t layerCount, - uint64_t usage, - uint64_t* outProtectedContent) { - return mMapper->getDefaultProtectedContent(width, height, format, layerCount, usage, - outProtectedContent); -} - -status_t GraphicBufferMapper::getDefaultCompression( - uint32_t width, uint32_t height, PixelFormat format, uint32_t layerCount, uint64_t usage, - aidl::android::hardware::graphics::common::ExtendableType* outCompression) { - return mMapper->getDefaultCompression(width, height, format, layerCount, usage, outCompression); -} - -status_t GraphicBufferMapper::getDefaultCompression(uint32_t width, uint32_t height, - PixelFormat format, uint32_t layerCount, - uint64_t usage, - ui::Compression* outCompression) { - return mMapper->getDefaultCompression(width, height, format, layerCount, usage, outCompression); -} - -status_t GraphicBufferMapper::getDefaultInterlaced( - uint32_t width, uint32_t height, PixelFormat format, uint32_t layerCount, uint64_t usage, - aidl::android::hardware::graphics::common::ExtendableType* outInterlaced) { - return mMapper->getDefaultInterlaced(width, height, format, layerCount, usage, outInterlaced); -} - -status_t GraphicBufferMapper::getDefaultInterlaced(uint32_t width, uint32_t height, - PixelFormat format, uint32_t layerCount, - uint64_t usage, ui::Interlaced* outInterlaced) { - return mMapper->getDefaultInterlaced(width, height, format, layerCount, usage, outInterlaced); -} - -status_t GraphicBufferMapper::getDefaultChromaSiting( - uint32_t width, uint32_t height, PixelFormat format, uint32_t layerCount, uint64_t usage, - aidl::android::hardware::graphics::common::ExtendableType* outChromaSiting) { - return mMapper->getDefaultChromaSiting(width, height, format, layerCount, usage, - outChromaSiting); -} - -status_t GraphicBufferMapper::getDefaultChromaSiting(uint32_t width, uint32_t height, - PixelFormat format, uint32_t layerCount, - uint64_t usage, - ui::ChromaSiting* outChromaSiting) { - return mMapper->getDefaultChromaSiting(width, height, format, layerCount, usage, - outChromaSiting); -} - -status_t GraphicBufferMapper::getDefaultPlaneLayouts( - uint32_t width, uint32_t height, PixelFormat format, uint32_t layerCount, uint64_t usage, - std::vector* outPlaneLayouts) { - return mMapper->getDefaultPlaneLayouts(width, height, format, layerCount, usage, - outPlaneLayouts); -} - // --------------------------------------------------------------------------- }; // namespace android diff --git a/libs/ui/HdrCapabilities.cpp b/libs/ui/HdrCapabilities.cpp deleted file mode 100644 index aec2fac78070ca9aa34464f2d509662651d17c2f..0000000000000000000000000000000000000000 --- a/libs/ui/HdrCapabilities.cpp +++ /dev/null @@ -1,86 +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. - */ - -#include - -namespace android { - -#if defined(__clang__) -#pragma clang diagnostic push -#pragma clang diagnostic ignored "-Wundefined-reinterpret-cast" -#endif - -size_t HdrCapabilities::getFlattenedSize() const { - return sizeof(mMaxLuminance) + - sizeof(mMaxAverageLuminance) + - sizeof(mMinLuminance) + - sizeof(int32_t) + - mSupportedHdrTypes.size() * sizeof(ui::Hdr); -} - -status_t HdrCapabilities::flatten(void* buffer, size_t size) const { - - if (size < getFlattenedSize()) { - return NO_MEMORY; - } - - int32_t* const buf = static_cast(buffer); - reinterpret_cast(buf[0]) = mMaxLuminance; - reinterpret_cast(buf[1]) = mMaxAverageLuminance; - reinterpret_cast(buf[2]) = mMinLuminance; - buf[3] = static_cast(mSupportedHdrTypes.size()); - for (size_t i = 0, c = mSupportedHdrTypes.size(); i < c; ++i) { - buf[4 + i] = static_cast(mSupportedHdrTypes[i]); - } - return NO_ERROR; -} - -status_t HdrCapabilities::unflatten(void const* buffer, size_t size) { - - size_t minSize = sizeof(mMaxLuminance) + - sizeof(mMaxAverageLuminance) + - sizeof(mMinLuminance) + - sizeof(int32_t); - - if (size < minSize) { - return NO_MEMORY; - } - - int32_t const * const buf = static_cast(buffer); - const size_t itemCount = size_t(buf[3]); - - // check the buffer is large enough - if (size < minSize + itemCount * sizeof(int32_t)) { - return BAD_VALUE; - } - - mMaxLuminance = reinterpret_cast(buf[0]); - mMaxAverageLuminance = reinterpret_cast(buf[1]); - mMinLuminance = reinterpret_cast(buf[2]); - if (itemCount) { - mSupportedHdrTypes.resize(itemCount); - for (size_t i = 0; i < itemCount; ++i) { - mSupportedHdrTypes[i] = static_cast(buf[4 + i]); - } - } - return NO_ERROR; -} - -#if defined(__clang__) -#pragma clang diagnostic pop -#endif - -} // namespace android diff --git a/libs/ui/PublicFormat.cpp b/libs/ui/PublicFormat.cpp index 78e82dab39791a3a8ae4eb7d665af6deecf19411..c9663edd7aabad0c047b8e7d019380e661e6d143 100644 --- a/libs/ui/PublicFormat.cpp +++ b/libs/ui/PublicFormat.cpp @@ -14,14 +14,15 @@ * limitations under the License. */ -#include // ui::Dataspace +#include "aidl/android/hardware/graphics/common/Dataspace.h" #include + // ---------------------------------------------------------------------------- namespace android { // ---------------------------------------------------------------------------- -using ui::Dataspace; +using ::aidl::android::hardware::graphics::common::Dataspace; int mapPublicFormatToHalFormat(PublicFormat f) { switch (f) { @@ -29,6 +30,7 @@ int mapPublicFormatToHalFormat(PublicFormat f) { case PublicFormat::DEPTH_POINT_CLOUD: case PublicFormat::DEPTH_JPEG: case PublicFormat::HEIC: + case PublicFormat::JPEG_R: return HAL_PIXEL_FORMAT_BLOB; case PublicFormat::DEPTH16: return HAL_PIXEL_FORMAT_Y16; @@ -47,7 +49,7 @@ android_dataspace mapPublicFormatToHalDataspace(PublicFormat f) { Dataspace dataspace; switch (f) { case PublicFormat::JPEG: - dataspace = Dataspace::V0_JFIF; + dataspace = Dataspace::JFIF; break; case PublicFormat::DEPTH_POINT_CLOUD: case PublicFormat::DEPTH16: @@ -64,7 +66,7 @@ android_dataspace mapPublicFormatToHalDataspace(PublicFormat f) { case PublicFormat::YUV_420_888: case PublicFormat::NV21: case PublicFormat::YV12: - dataspace = Dataspace::V0_JFIF; + dataspace = Dataspace::JFIF; break; case PublicFormat::DEPTH_JPEG: dataspace = Dataspace::DYNAMIC_DEPTH; @@ -72,6 +74,9 @@ android_dataspace mapPublicFormatToHalDataspace(PublicFormat f) { case PublicFormat::HEIC: dataspace = Dataspace::HEIF; break; + case PublicFormat::JPEG_R: + dataspace = Dataspace::JPEG_R; + break; default: // Most formats map to UNKNOWN dataspace = Dataspace::UNKNOWN; @@ -139,14 +144,16 @@ PublicFormat mapHalFormatDataspaceToPublicFormat(int format, android_dataspace d switch (ds) { case Dataspace::DEPTH: return PublicFormat::DEPTH_POINT_CLOUD; - case Dataspace::V0_JFIF: + case Dataspace::JFIF: return PublicFormat::JPEG; case Dataspace::HEIF: return PublicFormat::HEIC; default: if (dataSpace == static_cast(HAL_DATASPACE_DYNAMIC_DEPTH)) { return PublicFormat::DEPTH_JPEG; - } else { + } else if (dataSpace == static_cast(Dataspace::JPEG_R)) { + return PublicFormat::JPEG_R; + }else { // Assume otherwise-marked blobs are also JPEG return PublicFormat::JPEG; } diff --git a/libs/ui/StaticDisplayInfo.cpp b/libs/ui/StaticDisplayInfo.cpp deleted file mode 100644 index 03d15e46948075613fad1bf32bd1c63f0de122da..0000000000000000000000000000000000000000 --- a/libs/ui/StaticDisplayInfo.cpp +++ /dev/null @@ -1,57 +0,0 @@ -/* - * Copyright 2020 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#include - -#include - -#include - -#define RETURN_IF_ERROR(op) \ - if (const status_t status = (op); status != OK) return status; - -namespace android::ui { - -size_t StaticDisplayInfo::getFlattenedSize() const { - return FlattenableHelpers::getFlattenedSize(connectionType) + - FlattenableHelpers::getFlattenedSize(density) + - FlattenableHelpers::getFlattenedSize(secure) + - FlattenableHelpers::getFlattenedSize(deviceProductInfo) + - FlattenableHelpers::getFlattenedSize(installOrientation); -} - -status_t StaticDisplayInfo::flatten(void* buffer, size_t size) const { - if (size < getFlattenedSize()) { - return NO_MEMORY; - } - RETURN_IF_ERROR(FlattenableHelpers::flatten(&buffer, &size, connectionType)); - RETURN_IF_ERROR(FlattenableHelpers::flatten(&buffer, &size, density)); - RETURN_IF_ERROR(FlattenableHelpers::flatten(&buffer, &size, secure)); - RETURN_IF_ERROR(FlattenableHelpers::flatten(&buffer, &size, deviceProductInfo)); - RETURN_IF_ERROR(FlattenableHelpers::flatten(&buffer, &size, installOrientation)); - return OK; -} - -status_t StaticDisplayInfo::unflatten(void const* buffer, size_t size) { - RETURN_IF_ERROR(FlattenableHelpers::unflatten(&buffer, &size, &connectionType)); - RETURN_IF_ERROR(FlattenableHelpers::unflatten(&buffer, &size, &density)); - RETURN_IF_ERROR(FlattenableHelpers::unflatten(&buffer, &size, &secure)); - RETURN_IF_ERROR(FlattenableHelpers::unflatten(&buffer, &size, &deviceProductInfo)); - RETURN_IF_ERROR(FlattenableHelpers::unflatten(&buffer, &size, &installOrientation)); - return OK; -} - -} // namespace android::ui diff --git a/libs/ui/include/ui/ColorMode.h b/libs/ui/include/ui/ColorMode.h new file mode 100644 index 0000000000000000000000000000000000000000..a47eaed171f3f94a2f83e6871610508db16402ea --- /dev/null +++ b/libs/ui/include/ui/ColorMode.h @@ -0,0 +1,60 @@ +/* + * 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::ui { + +using ColorModes = std::vector; + +inline bool isWideColorMode(ColorMode colorMode) { + switch (colorMode) { + case ColorMode::DISPLAY_P3: + case ColorMode::ADOBE_RGB: + case ColorMode::DCI_P3: + case ColorMode::BT2020: + case ColorMode::DISPLAY_BT2020: + case ColorMode::BT2100_PQ: + case ColorMode::BT2100_HLG: + return true; + case ColorMode::NATIVE: + case ColorMode::STANDARD_BT601_625: + case ColorMode::STANDARD_BT601_625_UNADJUSTED: + case ColorMode::STANDARD_BT601_525: + case ColorMode::STANDARD_BT601_525_UNADJUSTED: + case ColorMode::STANDARD_BT709: + case ColorMode::SRGB: + return false; + } +} + +inline Dataspace pickDataspaceFor(ColorMode colorMode) { + switch (colorMode) { + case ColorMode::DISPLAY_P3: + case ColorMode::BT2100_PQ: + case ColorMode::BT2100_HLG: + case ColorMode::DISPLAY_BT2020: + return Dataspace::DISPLAY_P3; + default: + return Dataspace::V0_SRGB; + } +} + +} // namespace android::ui diff --git a/libs/ui/include/ui/DeviceProductInfo.h b/libs/ui/include/ui/DeviceProductInfo.h index 807a5d96a3cf71a2137f148b8cca41bda11aaf8d..4229cf15079dd36db90f74e6a7357f6402d6c6bd 100644 --- a/libs/ui/include/ui/DeviceProductInfo.h +++ b/libs/ui/include/ui/DeviceProductInfo.h @@ -24,8 +24,6 @@ #include #include -#include - namespace android { // NUL-terminated plug and play ID. @@ -34,7 +32,7 @@ using PnpId = std::array; // Product-specific information about the display or the directly connected device on the // display chain. For example, if the display is transitively connected, this field may contain // product information about the intermediate device. -struct DeviceProductInfo : LightFlattenable { +struct DeviceProductInfo { struct ModelYear { uint32_t year; }; @@ -63,13 +61,8 @@ struct DeviceProductInfo : LightFlattenable { // address is unavailable. // For example, for HDMI connected device this will be the physical address. std::vector relativeAddress; - - bool isFixedSize() const { return false; } - size_t getFlattenedSize() const; - status_t flatten(void* buffer, size_t size) const; - status_t unflatten(void const* buffer, size_t size); - - void dump(std::string& result) const; }; +std::string to_string(const DeviceProductInfo&); + } // namespace android diff --git a/libs/ui/include/ui/DisplayId.h b/libs/ui/include/ui/DisplayId.h index 9120972a42be88270902f9633cfdb15e63f841be..3a31fa084878920116a4ff47d47925dda800d0d5 100644 --- a/libs/ui/include/ui/DisplayId.h +++ b/libs/ui/include/ui/DisplayId.h @@ -17,9 +17,11 @@ #pragma once #include -#include +#include #include +#include + namespace android { // ID of a physical or a virtual display. This class acts as a type safe wrapper around uint64_t. @@ -66,9 +68,14 @@ inline std::string to_string(DisplayId displayId) { return std::to_string(displayId.value); } +// For tests. +inline std::ostream& operator<<(std::ostream& stream, DisplayId displayId) { + return stream << "DisplayId{" << displayId.value << '}'; +} + // DisplayId of a physical display, such as the internal display or externally connected display. struct PhysicalDisplayId : DisplayId { - static constexpr std::optional tryCast(DisplayId id) { + static constexpr ftl::Optional tryCast(DisplayId id) { if (id.value & FLAG_VIRTUAL) { return std::nullopt; } diff --git a/libs/ui/include/ui/DisplayMode.h b/libs/ui/include/ui/DisplayMode.h index 56f68e7bb2eae759a4ad1a26032a09ea59eef13a..65a8769c985279608dbfb77244f2ff7250dc59f9 100644 --- a/libs/ui/include/ui/DisplayMode.h +++ b/libs/ui/include/ui/DisplayMode.h @@ -19,6 +19,7 @@ #include #include +#include #include #include #include @@ -29,22 +30,18 @@ namespace android::ui { using DisplayModeId = int32_t; // Mode supported by physical display. -struct DisplayMode : LightFlattenable { +struct DisplayMode { DisplayModeId id; ui::Size resolution; float xDpi = 0; float yDpi = 0; + std::vector supportedHdrTypes; float refreshRate = 0; nsecs_t appVsyncOffset = 0; nsecs_t sfVsyncOffset = 0; nsecs_t presentationDeadline = 0; int32_t group = -1; - - bool isFixedSize() const { return false; } - size_t getFlattenedSize() const; - status_t flatten(void* buffer, size_t size) const; - status_t unflatten(const void* buffer, size_t size); }; } // namespace android::ui diff --git a/libs/ui/include/ui/DynamicDisplayInfo.h b/libs/ui/include/ui/DynamicDisplayInfo.h index ce75a652140d2c5aba0636c281d7ddd73a545928..0b77754455a03a4fc1e76a4b2fa28bda5f770e18 100644 --- a/libs/ui/include/ui/DynamicDisplayInfo.h +++ b/libs/ui/include/ui/DynamicDisplayInfo.h @@ -24,18 +24,18 @@ #include #include -#include namespace android::ui { // Information about a physical display which may change on hotplug reconnect. -struct DynamicDisplayInfo : LightFlattenable { +struct DynamicDisplayInfo { std::vector supportedDisplayModes; // This struct is going to be serialized over binder, so // we can't use size_t because it may have different width // in the client process. ui::DisplayModeId activeDisplayModeId; + float renderFrameRate; std::vector supportedColorModes; ui::ColorMode activeColorMode; @@ -53,11 +53,6 @@ struct DynamicDisplayInfo : LightFlattenable { ui::DisplayModeId preferredBootDisplayMode; std::optional getActiveDisplayMode() const; - - bool isFixedSize() const { return false; } - size_t getFlattenedSize() const; - status_t flatten(void* buffer, size_t size) const; - status_t unflatten(const void* buffer, size_t size); }; } // namespace android::ui diff --git a/services/surfaceflinger/CompositionEngine/include/compositionengine/FenceResult.h b/libs/ui/include/ui/FenceResult.h similarity index 60% rename from services/surfaceflinger/CompositionEngine/include/compositionengine/FenceResult.h rename to libs/ui/include/ui/FenceResult.h index 0ce263b930760dbeed597515d528266f2e662c16..6d63fc99730b12e841f82ce8cb937a501a96af43 100644 --- a/services/surfaceflinger/CompositionEngine/include/compositionengine/FenceResult.h +++ b/libs/ui/include/ui/FenceResult.h @@ -20,30 +20,14 @@ #include #include -// TODO(b/232535621): Pull this file to so that RenderEngine::drawLayers returns -// FenceResult rather than RenderEngineResult. -#pragma clang diagnostic push -#pragma clang diagnostic ignored "-Wconversion" -#include -#pragma clang diagnostic pop - namespace android { class Fence; using FenceResult = base::expected, status_t>; -// TODO(b/232535621): Prevent base::unexpected(NO_ERROR) from being a valid FenceResult. inline status_t fenceStatus(const FenceResult& fenceResult) { return fenceResult.ok() ? NO_ERROR : fenceResult.error(); } -inline FenceResult toFenceResult(renderengine::RenderEngineResult&& result) { - if (auto [status, fence] = std::move(result); fence.ok()) { - return sp::make(std::move(fence)); - } else { - return base::unexpected(status); - } -} - } // namespace android diff --git a/libs/ui/include/ui/Gralloc.h b/libs/ui/include/ui/Gralloc.h index 6101d4b43bfa33ea4735ed07e6bd1b9f84bd120c..496ba57789367e5f04a668f16407ad1a58b83fbf 100644 --- a/libs/ui/include/ui/Gralloc.h +++ b/libs/ui/include/ui/Gralloc.h @@ -39,14 +39,11 @@ public: return ""; } - virtual status_t createDescriptor(void* bufferDescriptorInfo, - void* outBufferDescriptor) const = 0; - // Import a buffer that is from another HAL, another process, or is // cloned. // // The returned handle must be freed with freeBuffer. - virtual status_t importBuffer(const hardware::hidl_handle& rawHandle, + virtual status_t importBuffer(const native_handle_t* rawHandle, buffer_handle_t* outBufferHandle) const = 0; virtual void freeBuffer(buffer_handle_t bufferHandle) const = 0; @@ -203,77 +200,6 @@ public: std::optional> /*smpte2094_10*/) const { return INVALID_OPERATION; } - virtual status_t getDefaultPixelFormatFourCC(uint32_t /*width*/, uint32_t /*height*/, - PixelFormat /*format*/, uint32_t /*layerCount*/, - uint64_t /*usage*/, - uint32_t* /*outPixelFormatFourCC*/) const { - return INVALID_OPERATION; - } - virtual status_t getDefaultPixelFormatModifier(uint32_t /*width*/, uint32_t /*height*/, - PixelFormat /*format*/, uint32_t /*layerCount*/, - uint64_t /*usage*/, - uint64_t* /*outPixelFormatModifier*/) const { - return INVALID_OPERATION; - } - virtual status_t getDefaultAllocationSize(uint32_t /*width*/, uint32_t /*height*/, - PixelFormat /*format*/, uint32_t /*layerCount*/, - uint64_t /*usage*/, - uint64_t* /*outAllocationSize*/) const { - return INVALID_OPERATION; - } - virtual status_t getDefaultProtectedContent(uint32_t /*width*/, uint32_t /*height*/, - PixelFormat /*format*/, uint32_t /*layerCount*/, - uint64_t /*usage*/, - uint64_t* /*outProtectedContent*/) const { - return INVALID_OPERATION; - } - virtual status_t getDefaultCompression( - uint32_t /*width*/, uint32_t /*height*/, PixelFormat /*format*/, - uint32_t /*layerCount*/, uint64_t /*usage*/, - aidl::android::hardware::graphics::common::ExtendableType* /*outCompression*/) const { - return INVALID_OPERATION; - } - virtual status_t getDefaultCompression(uint32_t /*width*/, uint32_t /*height*/, - PixelFormat /*format*/, uint32_t /*layerCount*/, - uint64_t /*usage*/, - ui::Compression* /*outCompression*/) const { - return INVALID_OPERATION; - } - virtual status_t getDefaultInterlaced( - uint32_t /*width*/, uint32_t /*height*/, PixelFormat /*format*/, - uint32_t /*layerCount*/, uint64_t /*usage*/, - aidl::android::hardware::graphics::common::ExtendableType* /*outInterlaced*/) const { - return INVALID_OPERATION; - } - virtual status_t getDefaultInterlaced(uint32_t /*width*/, uint32_t /*height*/, - PixelFormat /*format*/, uint32_t /*layerCount*/, - uint64_t /*usage*/, - ui::Interlaced* /*outInterlaced*/) const { - return INVALID_OPERATION; - } - virtual status_t getDefaultChromaSiting( - uint32_t /*width*/, uint32_t /*height*/, PixelFormat /*format*/, - uint32_t /*layerCount*/, uint64_t /*usage*/, - aidl::android::hardware::graphics::common::ExtendableType* /*outChromaSiting*/) const { - return INVALID_OPERATION; - } - virtual status_t getDefaultChromaSiting(uint32_t /*width*/, uint32_t /*height*/, - PixelFormat /*format*/, uint32_t /*layerCount*/, - uint64_t /*usage*/, - ui::ChromaSiting* /*outChromaSiting*/) const { - return INVALID_OPERATION; - } - virtual status_t getDefaultPlaneLayouts( - uint32_t /*width*/, uint32_t /*height*/, PixelFormat /*format*/, - uint32_t /*layerCount*/, uint64_t /*usage*/, - std::vector* /*outPlaneLayouts*/) const { - return INVALID_OPERATION; - } - - virtual std::vector - listSupportedMetadataTypes() const { - return {}; - } }; // A wrapper to IAllocator diff --git a/libs/ui/include/ui/Gralloc2.h b/libs/ui/include/ui/Gralloc2.h index f570c428b68176a3b12aa4bc0d490d3855fd5758..a7b6f492061c8fea695a15e6e93925ec1407c428 100644 --- a/libs/ui/include/ui/Gralloc2.h +++ b/libs/ui/include/ui/Gralloc2.h @@ -38,9 +38,9 @@ public: bool isLoaded() const override; - status_t createDescriptor(void* bufferDescriptorInfo, void* outBufferDescriptor) const override; + status_t createDescriptor(void* bufferDescriptorInfo, void* outBufferDescriptor) const; - status_t importBuffer(const hardware::hidl_handle& rawHandle, + status_t importBuffer(const native_handle_t* rawHandle, buffer_handle_t* outBufferHandle) const override; void freeBuffer(buffer_handle_t bufferHandle) const override; diff --git a/libs/ui/include/ui/Gralloc3.h b/libs/ui/include/ui/Gralloc3.h index 93a5077313ba1de40260ce62ab9847ec82418083..7367549964aec0a4629f2120e3cc1c9ede7997bf 100644 --- a/libs/ui/include/ui/Gralloc3.h +++ b/libs/ui/include/ui/Gralloc3.h @@ -37,9 +37,9 @@ public: bool isLoaded() const override; - status_t createDescriptor(void* bufferDescriptorInfo, void* outBufferDescriptor) const override; + status_t createDescriptor(void* bufferDescriptorInfo, void* outBufferDescriptor) const; - status_t importBuffer(const hardware::hidl_handle& rawHandle, + status_t importBuffer(const native_handle_t* rawHandle, buffer_handle_t* outBufferHandle) const override; void freeBuffer(buffer_handle_t bufferHandle) const override; diff --git a/libs/ui/include/ui/Gralloc4.h b/libs/ui/include/ui/Gralloc4.h index cf023c9bcc41238c2770f1fdb98728a3c81685b4..df43be87cd1e33ec5787b9f1c48ab7bacbbcf44e 100644 --- a/libs/ui/include/ui/Gralloc4.h +++ b/libs/ui/include/ui/Gralloc4.h @@ -42,9 +42,9 @@ public: std::string dumpBuffer(buffer_handle_t bufferHandle, bool less = true) const override; std::string dumpBuffers(bool less = true) const; - status_t createDescriptor(void* bufferDescriptorInfo, void* outBufferDescriptor) const override; + status_t createDescriptor(void* bufferDescriptorInfo, void* outBufferDescriptor) const; - status_t importBuffer(const hardware::hidl_handle& rawHandle, + status_t importBuffer(const native_handle_t* rawHandle, buffer_handle_t* outBufferHandle) const override; void freeBuffer(buffer_handle_t bufferHandle) const override; @@ -120,42 +120,6 @@ public: std::optional>* outSmpte2094_10) const override; status_t setSmpte2094_10(buffer_handle_t bufferHandle, std::optional> smpte2094_10) const override; - status_t getDefaultPixelFormatFourCC(uint32_t width, uint32_t height, PixelFormat format, - uint32_t layerCount, uint64_t usage, - uint32_t* outPixelFormatFourCC) const override; - status_t getDefaultPixelFormatModifier(uint32_t width, uint32_t height, PixelFormat format, - uint32_t layerCount, uint64_t usage, - uint64_t* outPixelFormatModifier) const override; - status_t getDefaultAllocationSize(uint32_t width, uint32_t height, PixelFormat format, - uint32_t layerCount, uint64_t usage, - uint64_t* outAllocationSize) const override; - status_t getDefaultProtectedContent(uint32_t width, uint32_t height, PixelFormat format, - uint32_t layerCount, uint64_t usage, - uint64_t* outProtectedContent) const override; - status_t getDefaultCompression(uint32_t width, uint32_t height, PixelFormat format, - uint32_t layerCount, uint64_t usage, - aidl::android::hardware::graphics::common::ExtendableType* - outCompression) const override; - status_t getDefaultCompression(uint32_t width, uint32_t height, PixelFormat format, - uint32_t layerCount, uint64_t usage, - ui::Compression* outCompression) const override; - status_t getDefaultInterlaced(uint32_t width, uint32_t height, PixelFormat format, - uint32_t layerCount, uint64_t usage, - aidl::android::hardware::graphics::common::ExtendableType* - outInterlaced) const override; - status_t getDefaultInterlaced(uint32_t width, uint32_t height, PixelFormat format, - uint32_t layerCount, uint64_t usage, - ui::Interlaced* outInterlaced) const override; - status_t getDefaultChromaSiting(uint32_t width, uint32_t height, PixelFormat format, - uint32_t layerCount, uint64_t usage, - aidl::android::hardware::graphics::common::ExtendableType* - outChromaSiting) const override; - status_t getDefaultChromaSiting(uint32_t width, uint32_t height, PixelFormat format, - uint32_t layerCount, uint64_t usage, - ui::ChromaSiting* outChromaSiting) const override; - status_t getDefaultPlaneLayouts(uint32_t width, uint32_t height, PixelFormat format, - uint32_t layerCount, uint64_t usage, - std::vector* outPlaneLayouts) const override; std::vector listSupportedMetadataTypes() const; diff --git a/libs/ui/include/ui/Gralloc5.h b/libs/ui/include/ui/Gralloc5.h new file mode 100644 index 0000000000000000000000000000000000000000..44b97d1a6f68268c697b88aa8f179fd60ff8d7f8 --- /dev/null +++ b/libs/ui/include/ui/Gralloc5.h @@ -0,0 +1,184 @@ +/* + * 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. + */ + +#pragma once + +#include +#include +#include + +namespace android { + +class Gralloc5Mapper : public GrallocMapper { +public: +public: + static void preload(); + + Gralloc5Mapper(); + + [[nodiscard]] bool isLoaded() const override; + + [[nodiscard]] std::string dumpBuffer(buffer_handle_t bufferHandle, bool less) const override; + + [[nodiscard]] std::string dumpBuffers(bool less = true) const; + + [[nodiscard]] status_t importBuffer(const native_handle_t *rawHandle, + buffer_handle_t *outBufferHandle) const override; + + void freeBuffer(buffer_handle_t bufferHandle) const override; + + [[nodiscard]] status_t validateBufferSize(buffer_handle_t bufferHandle, uint32_t width, + uint32_t height, PixelFormat format, + uint32_t layerCount, uint64_t usage, + uint32_t stride) const override; + + void getTransportSize(buffer_handle_t bufferHandle, uint32_t *outNumFds, + uint32_t *outNumInts) const override; + + [[nodiscard]] status_t lock(buffer_handle_t bufferHandle, uint64_t usage, const Rect &bounds, + int acquireFence, void **outData, int32_t *outBytesPerPixel, + int32_t *outBytesPerStride) const override; + + [[nodiscard]] status_t lock(buffer_handle_t bufferHandle, uint64_t usage, const Rect &bounds, + int acquireFence, android_ycbcr *ycbcr) const override; + + [[nodiscard]] int unlock(buffer_handle_t bufferHandle) const override; + + [[nodiscard]] status_t isSupported(uint32_t width, uint32_t height, PixelFormat format, + uint32_t layerCount, uint64_t usage, + bool *outSupported) const override; + + [[nodiscard]] status_t getBufferId(buffer_handle_t bufferHandle, + uint64_t *outBufferId) const override; + + [[nodiscard]] status_t getName(buffer_handle_t bufferHandle, + std::string *outName) const override; + + [[nodiscard]] status_t getWidth(buffer_handle_t bufferHandle, + uint64_t *outWidth) const override; + + [[nodiscard]] status_t getHeight(buffer_handle_t bufferHandle, + uint64_t *outHeight) const override; + + [[nodiscard]] status_t getLayerCount(buffer_handle_t bufferHandle, + uint64_t *outLayerCount) const override; + + [[nodiscard]] status_t getPixelFormatRequested( + buffer_handle_t bufferHandle, ui::PixelFormat *outPixelFormatRequested) const override; + + [[nodiscard]] status_t getPixelFormatFourCC(buffer_handle_t bufferHandle, + uint32_t *outPixelFormatFourCC) const override; + + [[nodiscard]] status_t getPixelFormatModifier(buffer_handle_t bufferHandle, + uint64_t *outPixelFormatModifier) const override; + + [[nodiscard]] status_t getUsage(buffer_handle_t bufferHandle, + uint64_t *outUsage) const override; + + [[nodiscard]] status_t getAllocationSize(buffer_handle_t bufferHandle, + uint64_t *outAllocationSize) const override; + + [[nodiscard]] status_t getProtectedContent(buffer_handle_t bufferHandle, + uint64_t *outProtectedContent) const override; + + [[nodiscard]] status_t getCompression(buffer_handle_t bufferHandle, + aidl::android::hardware::graphics::common::ExtendableType + *outCompression) const override; + + [[nodiscard]] status_t getCompression(buffer_handle_t bufferHandle, + ui::Compression *outCompression) const override; + + [[nodiscard]] status_t getInterlaced(buffer_handle_t bufferHandle, + aidl::android::hardware::graphics::common::ExtendableType + *outInterlaced) const override; + + [[nodiscard]] status_t getInterlaced(buffer_handle_t bufferHandle, + ui::Interlaced *outInterlaced) const override; + + [[nodiscard]] status_t getChromaSiting(buffer_handle_t bufferHandle, + aidl::android::hardware::graphics::common::ExtendableType + *outChromaSiting) const override; + + [[nodiscard]] status_t getChromaSiting(buffer_handle_t bufferHandle, + ui::ChromaSiting *outChromaSiting) const override; + + [[nodiscard]] status_t getPlaneLayouts( + buffer_handle_t bufferHandle, + std::vector *outPlaneLayouts) const override; + + [[nodiscard]] status_t getDataspace(buffer_handle_t bufferHandle, + ui::Dataspace *outDataspace) const override; + + [[nodiscard]] status_t setDataspace(buffer_handle_t bufferHandle, + ui::Dataspace dataspace) const override; + + [[nodiscard]] status_t getBlendMode(buffer_handle_t bufferHandle, + ui::BlendMode *outBlendMode) const override; + + [[nodiscard]] status_t getSmpte2086(buffer_handle_t bufferHandle, + std::optional *outSmpte2086) const override; + + [[nodiscard]] status_t setSmpte2086(buffer_handle_t bufferHandle, + std::optional smpte2086) const override; + + [[nodiscard]] status_t getCta861_3(buffer_handle_t bufferHandle, + std::optional *outCta861_3) const override; + + [[nodiscard]] status_t setCta861_3(buffer_handle_t bufferHandle, + std::optional cta861_3) const override; + + [[nodiscard]] status_t getSmpte2094_40( + buffer_handle_t bufferHandle, + std::optional> *outSmpte2094_40) const override; + + [[nodiscard]] status_t setSmpte2094_40( + buffer_handle_t bufferHandle, + std::optional> smpte2094_40) const override; + + [[nodiscard]] status_t getSmpte2094_10( + buffer_handle_t bufferHandle, + std::optional> *outSmpte2094_10) const override; + + [[nodiscard]] status_t setSmpte2094_10( + buffer_handle_t bufferHandle, + std::optional> smpte2094_10) const override; + +private: + void unlockBlocking(buffer_handle_t bufferHandle) const; + + AIMapper *mMapper = nullptr; +}; + +class Gralloc5Allocator : public GrallocAllocator { +public: + Gralloc5Allocator(const Gralloc5Mapper &mapper); + + [[nodiscard]] bool isLoaded() const override; + + [[nodiscard]] std::string dumpDebugInfo(bool less) const override; + + [[nodiscard]] status_t allocate(std::string requestorName, uint32_t width, uint32_t height, + PixelFormat format, uint32_t layerCount, uint64_t usage, + uint32_t bufferCount, uint32_t *outStride, + buffer_handle_t *outBufferHandles, + bool importBuffers) const override; + +private: + const Gralloc5Mapper &mMapper; + std::shared_ptr mAllocator; +}; + +} // namespace android diff --git a/libs/ui/include/ui/GraphicBuffer.h b/libs/ui/include/ui/GraphicBuffer.h index 57be686592ddc121498953dddcaa1124c7f2795e..dbe475b805db384150dd948ca03ab87853552f73 100644 --- a/libs/ui/include/ui/GraphicBuffer.h +++ b/libs/ui/include/ui/GraphicBuffer.h @@ -270,7 +270,7 @@ private: // Send a callback when a GraphicBuffer dies. // - // This is used for BufferStateLayer caching. GraphicBuffers are refcounted per process. When + // This is used for layer caching. GraphicBuffers are refcounted per process. When // A GraphicBuffer doesn't have any more sp<> in a process, it is destroyed. This causes // problems when trying to implicitcly cache across process boundaries. Ideally, both sides // of the cache would hold onto wp<> references. When an app dropped its sp<>, the GraphicBuffer diff --git a/libs/ui/include/ui/GraphicBufferMapper.h b/libs/ui/include/ui/GraphicBufferMapper.h index 37a2e1cc9f8360badca2f5d40c97ac72604c3b15..3a5167ab255da0c727317c036e071431a3b2a9f8 100644 --- a/libs/ui/include/ui/GraphicBufferMapper.h +++ b/libs/ui/include/ui/GraphicBufferMapper.h @@ -42,9 +42,10 @@ class GraphicBufferMapper : public Singleton { public: enum Version { - GRALLOC_2, + GRALLOC_2 = 2, GRALLOC_3, GRALLOC_4, + GRALLOC_5, }; static void preloadHal(); static inline GraphicBufferMapper& get() { return getInstance(); } @@ -54,10 +55,9 @@ public: // The imported outHandle must be freed with freeBuffer when no longer // needed. rawHandle is owned by the caller. - status_t importBuffer(buffer_handle_t rawHandle, - uint32_t width, uint32_t height, uint32_t layerCount, - PixelFormat format, uint64_t usage, uint32_t stride, - buffer_handle_t* outHandle); + status_t importBuffer(const native_handle_t* rawHandle, uint32_t width, uint32_t height, + uint32_t layerCount, PixelFormat format, uint64_t usage, uint32_t stride, + buffer_handle_t* outHandle); status_t importBufferNoValidate(const native_handle_t* rawHandle, buffer_handle_t* outHandle); @@ -138,48 +138,6 @@ public: status_t setSmpte2094_10(buffer_handle_t bufferHandle, std::optional> smpte2094_10); - /** - * Gets the default metadata for a gralloc buffer allocated with the given parameters. - * - * These functions are supported by gralloc 4.0+. - */ - status_t getDefaultPixelFormatFourCC(uint32_t width, uint32_t height, PixelFormat format, - uint32_t layerCount, uint64_t usage, - uint32_t* outPixelFormatFourCC); - status_t getDefaultPixelFormatModifier(uint32_t width, uint32_t height, PixelFormat format, - uint32_t layerCount, uint64_t usage, - uint64_t* outPixelFormatModifier); - status_t getDefaultAllocationSize(uint32_t width, uint32_t height, PixelFormat format, - uint32_t layerCount, uint64_t usage, - uint64_t* outAllocationSize); - status_t getDefaultProtectedContent(uint32_t width, uint32_t height, PixelFormat format, - uint32_t layerCount, uint64_t usage, - uint64_t* outProtectedContent); - status_t getDefaultCompression( - uint32_t width, uint32_t height, PixelFormat format, uint32_t layerCount, - uint64_t usage, - aidl::android::hardware::graphics::common::ExtendableType* outCompression); - status_t getDefaultCompression(uint32_t width, uint32_t height, PixelFormat format, - uint32_t layerCount, uint64_t usage, - ui::Compression* outCompression); - status_t getDefaultInterlaced( - uint32_t width, uint32_t height, PixelFormat format, uint32_t layerCount, - uint64_t usage, - aidl::android::hardware::graphics::common::ExtendableType* outInterlaced); - status_t getDefaultInterlaced(uint32_t width, uint32_t height, PixelFormat format, - uint32_t layerCount, uint64_t usage, - ui::Interlaced* outInterlaced); - status_t getDefaultChromaSiting( - uint32_t width, uint32_t height, PixelFormat format, uint32_t layerCount, - uint64_t usage, - aidl::android::hardware::graphics::common::ExtendableType* outChromaSiting); - status_t getDefaultChromaSiting(uint32_t width, uint32_t height, PixelFormat format, - uint32_t layerCount, uint64_t usage, - ui::ChromaSiting* outChromaSiting); - status_t getDefaultPlaneLayouts(uint32_t width, uint32_t height, PixelFormat format, - uint32_t layerCount, uint64_t usage, - std::vector* outPlaneLayouts); - const GrallocMapper& getGrallocMapper() const { return reinterpret_cast(*mMapper); } diff --git a/libs/ui/include/ui/GraphicTypes.h b/libs/ui/include/ui/GraphicTypes.h index 8661c366764f4cbf539a062afe633e78bd99cd84..1775d390f024d0da3c085f7a2bd65749b5ae047e 100644 --- a/libs/ui/include/ui/GraphicTypes.h +++ b/libs/ui/include/ui/GraphicTypes.h @@ -20,6 +20,7 @@ #include #include #include +#include #include #include #include @@ -42,7 +43,6 @@ namespace ui { using android::hardware::graphics::common::V1_1::RenderIntent; using android::hardware::graphics::common::V1_2::ColorMode; using android::hardware::graphics::common::V1_2::Dataspace; -using android::hardware::graphics::common::V1_2::Hdr; using android::hardware::graphics::common::V1_2::PixelFormat; /** @@ -50,6 +50,7 @@ using android::hardware::graphics::common::V1_2::PixelFormat; */ using aidl::android::hardware::graphics::common::BlendMode; using aidl::android::hardware::graphics::common::Cta861_3; +using aidl::android::hardware::graphics::common::Hdr; using aidl::android::hardware::graphics::common::PlaneLayout; using aidl::android::hardware::graphics::common::Smpte2086; diff --git a/libs/ui/include/ui/HdrCapabilities.h b/libs/ui/include/ui/HdrCapabilities.h index 813addeca612b7da3b8ed3477e403fa185e256ff..ae54223585c00e3530e432362ab493ed3a0c6ffc 100644 --- a/libs/ui/include/ui/HdrCapabilities.h +++ b/libs/ui/include/ui/HdrCapabilities.h @@ -22,12 +22,10 @@ #include #include -#include namespace android { -class HdrCapabilities : public LightFlattenable -{ +class HdrCapabilities { public: HdrCapabilities(const std::vector& types, float maxLuminance, float maxAverageLuminance, float minLuminance) @@ -49,12 +47,6 @@ public: float getDesiredMaxAverageLuminance() const { return mMaxAverageLuminance; } float getDesiredMinLuminance() const { return mMinLuminance; } - // Flattenable protocol - bool isFixedSize() const { return false; } - size_t getFlattenedSize() const; - status_t flatten(void* buffer, size_t size) const; - status_t unflatten(void const* buffer, size_t size); - private: std::vector mSupportedHdrTypes; float mMaxLuminance; diff --git a/libs/ui/include/ui/PublicFormat.h b/libs/ui/include/ui/PublicFormat.h index aa58805718c65c2d4d133c1a15bc04a041947d6d..2248ccab0cbd4b028497e7df228e9e90f208d585 100644 --- a/libs/ui/include/ui/PublicFormat.h +++ b/libs/ui/include/ui/PublicFormat.h @@ -57,6 +57,7 @@ enum class PublicFormat { YCBCR_P010 = 0x36, DEPTH16 = 0x44363159, DEPTH_JPEG = 0x69656963, + JPEG_R = 0x1005, HEIC = 0x48454946, }; diff --git a/libs/ui/include/ui/Rotation.h b/libs/ui/include/ui/Rotation.h index 83d431dea38fb3ffae6109448af8c972d9257cea..c1d60f4f6c5c12a9fb6e0aaecfb7d858aa456138 100644 --- a/libs/ui/include/ui/Rotation.h +++ b/libs/ui/include/ui/Rotation.h @@ -20,7 +20,14 @@ namespace android::ui { -enum class Rotation { Rotation0 = 0, Rotation90 = 1, Rotation180 = 2, Rotation270 = 3 }; +enum class Rotation { + Rotation0 = 0, + Rotation90 = 1, + Rotation180 = 2, + Rotation270 = 3, + + ftl_last = Rotation270 +}; // Equivalent to Surface.java constants. constexpr auto ROTATION_0 = Rotation::Rotation0; diff --git a/libs/ui/include/ui/StaticDisplayInfo.h b/libs/ui/include/ui/StaticDisplayInfo.h index cc7c869b3bc6da0ebedbd918c6459f2e18217c98..83da821f37f63c8617ab1b7633295d2e0a6f78d6 100644 --- a/libs/ui/include/ui/StaticDisplayInfo.h +++ b/libs/ui/include/ui/StaticDisplayInfo.h @@ -20,24 +20,18 @@ #include #include -#include namespace android::ui { -enum class DisplayConnectionType { Internal, External }; +enum class DisplayConnectionType { Internal, External, ftl_last = External }; // Immutable information about physical display. -struct StaticDisplayInfo : LightFlattenable { +struct StaticDisplayInfo { DisplayConnectionType connectionType = DisplayConnectionType::Internal; float density = 0.f; bool secure = false; std::optional deviceProductInfo; Rotation installOrientation = ROTATION_0; - - bool isFixedSize() const { return false; } - size_t getFlattenedSize() const; - status_t flatten(void* buffer, size_t size) const; - status_t unflatten(void const* buffer, size_t size); }; } // namespace android::ui diff --git a/libs/ultrahdr/Android.bp b/libs/ultrahdr/Android.bp new file mode 100644 index 0000000000000000000000000000000000000000..9deba01dc852496cade741b3e08bde3277ce415d --- /dev/null +++ b/libs/ultrahdr/Android.bp @@ -0,0 +1,82 @@ +// 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. + +package { + // See: http://go/android-license-faq + default_applicable_licenses: [ + "frameworks_native_license", + "adobe_hdr_gain_map_license", + ], +} + +cc_library { + name: "libultrahdr", + host_supported: true, + vendor_available: true, + export_include_dirs: ["include"], + local_include_dirs: ["include"], + + srcs: [ + "icc.cpp", + "jpegr.cpp", + "gainmapmath.cpp", + "jpegrutils.cpp", + "multipictureformat.cpp", + ], + + shared_libs: [ + "libimage_io", + "libjpeg", + "libjpegencoder", + "libjpegdecoder", + "liblog", + "libutils", + ], +} + +cc_library { + name: "libjpegencoder", + host_supported: true, + vendor_available: true, + + shared_libs: [ + "libjpeg", + "liblog", + "libutils", + ], + + export_include_dirs: ["include"], + + srcs: [ + "jpegencoderhelper.cpp", + ], +} + +cc_library { + name: "libjpegdecoder", + host_supported: true, + vendor_available: true, + + shared_libs: [ + "libjpeg", + "liblog", + "libutils", + ], + + export_include_dirs: ["include"], + + srcs: [ + "jpegdecoderhelper.cpp", + ], +} diff --git a/libs/ultrahdr/OWNERS b/libs/ultrahdr/OWNERS new file mode 100644 index 0000000000000000000000000000000000000000..6ace354d0b6d9d273cdd4912d3b508a590df7f12 --- /dev/null +++ b/libs/ultrahdr/OWNERS @@ -0,0 +1,3 @@ +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 new file mode 100644 index 0000000000000000000000000000000000000000..e999a8bd284e3698beda96675f2b2e9ccfee94ee --- /dev/null +++ b/libs/ultrahdr/adobe-hdr-gain-map-license/Android.bp @@ -0,0 +1,19 @@ +// 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", + 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 new file mode 100644 index 0000000000000000000000000000000000000000..3f6c5944c75c48ce4b5975ee1f9d391793d267c8 --- /dev/null +++ b/libs/ultrahdr/adobe-hdr-gain-map-license/NOTICE @@ -0,0 +1 @@ +This product includes Gain Map technology under license by Adobe. diff --git a/libs/ultrahdr/fuzzer/Android.bp b/libs/ultrahdr/fuzzer/Android.bp new file mode 100644 index 0000000000000000000000000000000000000000..6c0a2f577c2c5249703a76a8c1711049ee9c6bbd --- /dev/null +++ b/libs/ultrahdr/fuzzer/Android.bp @@ -0,0 +1,69 @@ +// 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. + +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_defaults { + name: "ultrahdr_fuzzer_defaults", + host_supported: true, + shared_libs: [ + "libimage_io", + "libjpeg", + ], + static_libs: [ + "libjpegdecoder", + "libjpegencoder", + "libultrahdr", + "libutils", + "liblog", + ], + target: { + darwin: { + enabled: false, + }, + }, + fuzz_config: { + cc: [ + "android-media-fuzzing-reports@google.com", + ], + description: "The fuzzers target the APIs of jpeg hdr", + service_privilege: "constrained", + users: "multi_user", + fuzzed_code_usage: "future_version", + vector: "local_no_privileges_required", + }, +} + +cc_fuzz { + name: "ultrahdr_enc_fuzzer", + defaults: ["ultrahdr_fuzzer_defaults"], + srcs: [ + "ultrahdr_enc_fuzzer.cpp", + ], +} + +cc_fuzz { + name: "ultrahdr_dec_fuzzer", + defaults: ["ultrahdr_fuzzer_defaults"], + srcs: [ + "ultrahdr_dec_fuzzer.cpp", + ], +} diff --git a/libs/ultrahdr/fuzzer/ultrahdr_dec_fuzzer.cpp b/libs/ultrahdr/fuzzer/ultrahdr_dec_fuzzer.cpp new file mode 100644 index 0000000000000000000000000000000000000000..ad1d57aaeecd17a4bac8e2e853c24114a732a010 --- /dev/null +++ b/libs/ultrahdr/fuzzer/ultrahdr_dec_fuzzer.cpp @@ -0,0 +1,73 @@ +/* + * 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_SDR) ? 4 : 8); + 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 new file mode 100644 index 0000000000000000000000000000000000000000..bbe58e0f2e874ab48aa7904dc1617ae7fc763f50 --- /dev/null +++ b/libs/ultrahdr/fuzzer/ultrahdr_enc_fuzzer.cpp @@ -0,0 +1,303 @@ +/* + * 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/jpegencoderhelper.h" +#include "utils/Log.h" + +using namespace android::ultrahdr; + +// constants +const int kMinWidth = 8; +const int kMaxWidth = 7680; + +const int kMinHeight = 8; +const int kMaxHeight = 4320; + +const int kScaleFactor = 4; + +const int kJpegBlock = 16; + +// 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 size); + +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); + } + for (int j = 0; j < height; j++) { + for (int i = 0; i < width; i += buffer.size()) { + memcpy(data + i, buffer.data(), std::min((int)buffer.size(), (width - i))); + std::shuffle(buffer.begin(), buffer.end(), + std::default_random_engine(std::random_device{}())); + } + tmp += stride; + } +} + +void UltraHdrEncFuzzer::fill420Buffer(uint8_t* data, int size) { + std::vector buffer(16); + mFdp.ConsumeData(buffer.data(), buffer.size()); + for (int i = 0; i < size; i += buffer.size()) { + memcpy(data + i, buffer.data(), std::min((int)buffer.size(), (size - i))); + std::shuffle(buffer.begin(), buffer.end(), + std::default_random_engine(std::random_device{}())); + } +} + +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 bufferY = nullptr; + std::unique_ptr bufferUV = nullptr; + std::unique_ptr yuv420ImgRaw = 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; + bufferY = std::make_unique(p010Size); + p010Img.data = bufferY.get(); + p010Img.chroma_data = nullptr; + p010Img.chroma_stride = 0; + fillP010Buffer(bufferY.get(), width, height, yStride); + fillP010Buffer(bufferY.get() + yStride * height, width, height / 2, yStride); + } else { + int uvStride = mFdp.ConsumeIntegralInRange(width, width + 128); + size_t p010YSize = yStride * height; + bufferY = std::make_unique(p010YSize); + p010Img.data = bufferY.get(); + fillP010Buffer(bufferY.get(), width, height, yStride); + size_t p010UVSize = uvStride * p010Img.height / 2; + bufferUV = std::make_unique(p010UVSize); + p010Img.chroma_data = bufferUV.get(); + p010Img.chroma_stride = uvStride; + fillP010Buffer(bufferUV.get(), width, height / 2, uvStride); + } + } else { + int map_width = width / kScaleFactor; + int map_height = height / kScaleFactor; + map_width = static_cast(floor((map_width + kJpegBlock - 1) / kJpegBlock)) * + kJpegBlock; + map_height = ((map_height + 1) >> 1) << 1; + // 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(), graySize); + grayImg.chroma_data = nullptr; + grayImg.luma_stride = 0; + grayImg.chroma_stride = 0; + } + + if (muxSwitch > 0) { + // init 420 image + yuv420Img.width = width; + yuv420Img.height = height; + yuv420Img.colorGamut = yuv420Cg; + + const size_t yuv420Size = (yuv420Img.width * yuv420Img.height * 3) / 2; + yuv420ImgRaw = std::make_unique(yuv420Size); + yuv420Img.data = yuv420ImgRaw.get(); + fill420Buffer(yuv420ImgRaw.get(), yuv420Size); + yuv420Img.chroma_data = nullptr; + yuv420Img.luma_stride = 0; + yuv420Img.chroma_stride = 0; + } + + // 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 << "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; + if (encoder.compressImage(yuv420Img.data, yuv420Img.width, yuv420Img.height, 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(grayImg.data, grayImg.width, grayImg.height, + quality, nullptr, 0, true)) { + jpegGainMap.length = gainMapEncoder.getCompressedImageSize(); + jpegGainMap.maxLength = jpegImg.length; + jpegGainMap.data = gainMapEncoder.getCompressedImagePtr(); + jpegGainMap.colorGamut = ULTRAHDR_COLORGAMUT_UNSPECIFIED; + ultrahdr_metadata_struct metadata; + metadata.version = "1.0"; + 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_SDR) ? 4 : 8); + 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 new file mode 100644 index 0000000000000000000000000000000000000000..ee15363b69daa8a22f2fb4e86174321b3ce94512 --- /dev/null +++ b/libs/ultrahdr/gainmapmath.cpp @@ -0,0 +1,781 @@ +/* + * 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); + //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); + //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); + //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); + //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); + //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->width; + size_t pixel_y2_idx = (x_chroma * 2 + 1) + y_chroma * 2 * image->width; + size_t pixel_y3_idx = x_chroma * 2 + (y_chroma * 2 + 1) * image->width; + size_t pixel_y4_idx = (x_chroma * 2 + 1) + (y_chroma * 2 + 1) * image->width; + + 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->width * image->height; + size_t pixel_uv_idx = x_chroma + y_chroma * (image->width / 2); + + uint8_t& u_uint = reinterpret_cast(image->data)[pixel_count + pixel_uv_idx]; + uint8_t& v_uint = reinterpret_cast(image->data)[pixel_count * 5 / 4 + pixel_uv_idx]; + + y1_uint = static_cast(floor(yuv1.y * 255.0f + 0.5f)); + y2_uint = static_cast(floor(yuv2.y * 255.0f + 0.5f)); + y3_uint = static_cast(floor(yuv3.y * 255.0f + 0.5f)); + y4_uint = static_cast(floor(yuv4.y * 255.0f + 0.5f)); + + u_uint = static_cast(floor(new_uv.u * 255.0f + 128.0f + 0.5f)); + v_uint = static_cast(floor(new_uv.v * 255.0f + 128.0f + 0.5f)); +} + +//////////////////////////////////////////////////////////////////////////////// +// 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) { + size_t pixel_count = image->width * image->height; + + size_t pixel_y_idx = x + y * image->width; + size_t pixel_uv_idx = x / 2 + (y / 2) * (image->width / 2); + + uint8_t y_uint = reinterpret_cast(image->data)[pixel_y_idx]; + uint8_t u_uint = reinterpret_cast(image->data)[pixel_count + pixel_uv_idx]; + uint8_t v_uint = reinterpret_cast(image->data)[pixel_count * 5 / 4 + pixel_uv_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) { + size_t luma_stride = image->luma_stride; + size_t chroma_stride = image->chroma_stride; + uint16_t* luma_data = reinterpret_cast(image->data); + uint16_t* chroma_data = reinterpret_cast(image->chroma_data); + + if (luma_stride == 0) { + luma_stride = image->width; + } + if (chroma_stride == 0) { + chroma_stride = luma_stride; + } + if (chroma_data == nullptr) { + chroma_data = &reinterpret_cast(image->data)[luma_stride * image->height]; + } + + 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 new file mode 100644 index 0000000000000000000000000000000000000000..1ab3c7c7932e7258c76feccf32d8e0401c8d8727 --- /dev/null +++ b/libs/ultrahdr/icc.cpp @@ -0,0 +1,677 @@ +/* + * 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 +#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_for_linear() { + int total_length = 16; + sp dataStruct = sp::make(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(1.0))); + + 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_for_linear(); + } + + // 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_for_linear(); + } + } + + 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_for_linear()); + tags.emplace_back(kTAG_gTRC, write_trc_tag_for_linear()); + tags.emplace_back(kTAG_bTRC, write_trc_tag_for_linear()); + } + } + + // 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 new file mode 100644 index 0000000000000000000000000000000000000000..edf152d8ed6e9e18c7cf6c72be734c48c08a99b8 --- /dev/null +++ b/libs/ultrahdr/include/ultrahdr/gainmapmath.h @@ -0,0 +1,488 @@ +/* + * 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); + +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)); + //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 = 10; +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 = 10; +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 = 10; +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 = 10; +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 new file mode 100644 index 0000000000000000000000000000000000000000..7f047f8f5bd4453bcd49fac3f92551632a9967aa --- /dev/null +++ b/libs/ultrahdr/include/ultrahdr/icc.h @@ -0,0 +1,255 @@ +/* + * 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 + +#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_for_linear(); + 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 new file mode 100644 index 0000000000000000000000000000000000000000..8b5499a2c0ef7d54ae04cd621a7fab162459511a --- /dev/null +++ b/libs/ultrahdr/include/ultrahdr/jpegdecoderhelper.h @@ -0,0 +1,127 @@ +/* + * 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 + +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(); + /* + * Returns the EXIF data from the image. + */ + void* getEXIFPtr(); + /* + * Returns the decompressed EXIF buffer size. This method must be called only after + * calling decompressImage() or getCompressedImageParameters(). + */ + size_t getEXIFSize(); + /* + * 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; +}; +} /* namespace android::ultrahdr */ + +#endif // ANDROID_ULTRAHDR_JPEGDECODERHELPER_H diff --git a/libs/ultrahdr/include/ultrahdr/jpegencoderhelper.h b/libs/ultrahdr/include/ultrahdr/jpegencoderhelper.h new file mode 100644 index 0000000000000000000000000000000000000000..2c6778e299afa6aa410753ebb80f5b2b122be4f7 --- /dev/null +++ b/libs/ultrahdr/include/ultrahdr/jpegencoderhelper.h @@ -0,0 +1,97 @@ +/* + * 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 + +extern "C" { +#include +#include +} + +#include +#include + +namespace android::ultrahdr { + +/* + * 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 void* image, int width, int height, int quality, + const void* iccBuffer, unsigned int iccSize, bool isSingleChannel = false); + + /* + * 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 void* inYuv, int width, int height, int jpegQuality, + const void* iccBuffer, unsigned int iccSize, bool isSingleChannel); + 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 compress(jpeg_compress_struct* cinfo, const uint8_t* image, bool isSingleChannel); + bool compressYuv(jpeg_compress_struct* cinfo, const uint8_t* yuv); + bool compressSingleChannel(jpeg_compress_struct* cinfo, const uint8_t* image); + + // 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 new file mode 100644 index 0000000000000000000000000000000000000000..a35fd30634c0703213faae9b47586b574fa0ba57 --- /dev/null +++ b/libs/ultrahdr/include/ultrahdr/jpegr.h @@ -0,0 +1,453 @@ +/* + * 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 "jpegencoderhelper.h" +#include "jpegrerrorcode.h" +#include "ultrahdr.h" + +#ifndef FLT_MAX +#define FLT_MAX 0x1.fffffep127f +#endif + +namespace android::ultrahdr { + +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 + // following after the luma plane. + // Note: currently this feature is only supported for P010 image (HDR input). + void* chroma_data = nullptr; + // Strides of Y plane in number of pixels, using 0 to present uninitialized, must be + // larger than or equal to luma width. + // Note: currently this feature is only supported for P010 image (HDR input). + int luma_stride = 0; + // Strides of UV plane in number of pixels, using 0 to present uninitialized, must be + // larger than or equal to chroma width. + // Note: currently this feature is only supported for P010 image (HDR input). + 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 uncompressed_p010_image 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 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 uncompressed_p010_image, + 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 uncompressed_p010_image uncompressed HDR image in P010 color format + * @param uncompressed_yuv_420_image 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 uncompressed_p010_image, + jr_uncompressed_ptr uncompressed_yuv_420_image, + 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 uncompressed_p010_image uncompressed HDR image in P010 color format + * @param uncompressed_yuv_420_image uncompressed SDR image in YUV_420 color format + * Note: the SDR image must be the decoded version of the JPEG + * input + * @param compressed_jpeg_image compressed 8-bit JPEG image + * @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 uncompressed_p010_image, + jr_uncompressed_ptr uncompressed_yuv_420_image, + jr_compressed_ptr compressed_jpeg_image, + 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 uncompressed_p010_image uncompressed HDR image in P010 color format + * @param compressed_jpeg_image compressed 8-bit JPEG image + * @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 uncompressed_p010_image, + jr_compressed_ptr compressed_jpeg_image, + 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 compressed_jpeg_image compressed 8-bit JPEG image + * @param compressed_gainmap compressed 8-bit JPEG single channel image + * @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 compressed_jpeg_image, + jr_compressed_ptr compressed_gainmap, + 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 compressed_jpegr_image 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 gain_map 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 compressed_jpegr_image, + 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 gain_map = 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 compressed_jpegr_image compressed JPEGR image + * @param jpegr_info pointer to output JPEGR info. 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 compressed_jpegr_image, + jr_info_ptr jpegr_info); +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 uncompressed_yuv_420_image uncompressed SDR image in YUV_420 color format + * @param uncompressed_p010_image uncompressed HDR image in P010 color format + * @param hdr_tf transfer function of the HDR image + * @param dest gain map; caller responsible for memory of data + * @param metadata max_content_boost is filled in + * @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 uncompressed_yuv_420_image, + jr_uncompressed_ptr uncompressed_p010_image, + 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 uncompressed_yuv_420_image uncompressed SDR image in YUV_420 color format + * @param uncompressed_gain_map uncompressed gain map + * @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 uncompressed_yuv_420_image, + jr_uncompressed_ptr uncompressed_gain_map, + 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 uncompressed_gain_map uncompressed gain map + * @param resource to compress gain map + * @return NO_ERROR if encoding succeeds, error code if error occurs. + */ + status_t compressGainMap(jr_uncompressed_ptr uncompressed_gain_map, + JpegEncoderHelper* jpeg_encoder); + + /* + * This methoud is called to separate primary image and gain map image from JPEGR + * + * @param compressed_jpegr_image compressed JPEGR image + * @param primary_image destination of primary image + * @param gain_map destination of compressed gain map + * @return NO_ERROR if calculation succeeds, error code if error occurs. + */ + status_t extractPrimaryImageAndGainMap(jr_compressed_ptr compressed_jpegr_image, + jr_compressed_ptr primary_image, + jr_compressed_ptr gain_map); + /* + * This method is called in the decoding pipeline. It will read XMP metadata to find the start + * position of the compressed gain map, and will extract the compressed gain map. + * + * @param compressed_jpegr_image compressed JPEGR image + * @param dest destination of compressed gain map + * @return NO_ERROR if calculation succeeds, error code if error occurs. + */ + status_t extractGainMap(jr_compressed_ptr compressed_jpegr_image, + jr_compressed_ptr dest); + + /* + * 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 EXIF package is only available for encoding API-0 and API-1. For encoding API-2 and + * API-3 this parameter is null, but the primary image in JPEG/R may still have EXIF as long as + * the input JPEG has EXIF. + * + * @param compressed_jpeg_image compressed 8-bit JPEG image + * @param compress_gain_map compressed recover map + * @param (nullable) exif EXIF package + * @param (nullable) icc 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 compressed_jpeg_image, + jr_compressed_ptr compressed_gain_map, + jr_exif_ptr exif, + void* icc, 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 (input) uncompressed P010 image + * @param dest (output) tone mapping result as a YUV_420 image + * @return NO_ERROR if calculation succeeds, error code if error occurs. + */ + 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 uncompressed_p010_image uncompressed HDR image in P010 color format + * @param uncompressed_yuv_420_image 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}. + * @return NO_ERROR if the input args are valid, error code is not valid. + */ + status_t areInputArgumentsValid(jr_uncompressed_ptr uncompressed_p010_image, + jr_uncompressed_ptr uncompressed_yuv_420_image, + ultrahdr_transfer_function hdr_tf, + jr_compressed_ptr dest); + + /* + * This method will check the validity of the input arguments. + * + * @param uncompressed_p010_image uncompressed HDR image in P010 color format + * @param uncompressed_yuv_420_image 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 + * @return NO_ERROR if the input args are valid, error code is not valid. + */ + status_t areInputArgumentsValid(jr_uncompressed_ptr uncompressed_p010_image, + jr_uncompressed_ptr uncompressed_yuv_420_image, + 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 new file mode 100644 index 0000000000000000000000000000000000000000..064123210fa9cfd7bf02b581331fe6368385cc7e --- /dev/null +++ b/libs/ultrahdr/include/ultrahdr/jpegrerrorcode.h @@ -0,0 +1,60 @@ +/* + * 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, + + 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 new file mode 100644 index 0000000000000000000000000000000000000000..4ab664e79880fd02eb8128ff4761d4393152b8b5 --- /dev/null +++ b/libs/ultrahdr/include/ultrahdr/jpegrutils.h @@ -0,0 +1,167 @@ +/* + * 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 new file mode 100644 index 0000000000000000000000000000000000000000..c5bd09d6f232aa0e50e241db2df9751f4a0b0213 --- /dev/null +++ b/libs/ultrahdr/include/ultrahdr/multipictureformat.h @@ -0,0 +1,64 @@ +/* + * 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 new file mode 100644 index 0000000000000000000000000000000000000000..17cc97173c8e2ed545a8b58e36be3f7b30a01459 --- /dev/null +++ b/libs/ultrahdr/include/ultrahdr/ultrahdr.h @@ -0,0 +1,79 @@ +/* + * 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 + +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 +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 new file mode 100644 index 0000000000000000000000000000000000000000..fef544452a5e9870de6e12f2f98137dd12be82eb --- /dev/null +++ b/libs/ultrahdr/jpegdecoderhelper.cpp @@ -0,0 +1,499 @@ +/* + * 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 + +const std::string kXmpNameSpace = "http://ns.adobe.com/xap/1.0/"; +const std::string kExifIdCode = "Exif"; +constexpr uint32_t kICCMarkerHeaderSize = 14; +constexpr uint8_t kICCSig[] = { + 'I', 'C', 'C', '_', 'P', 'R', 'O', 'F', 'I', 'L', 'E', '\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(); + if (!decode(image, length, decodeToRGBA)) { + return false; + } + + return true; +} + +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; +} + +bool JpegDecoderHelper::decode(const void* image, int length, bool decodeToRGBA) { + jpeg_decompress_struct cinfo; + jpegr_source_mgr mgr(static_cast(image), length); + jpegrerror_mgr myerr; + bool status = true; + + 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); + + cinfo.src = &mgr; + jpeg_read_header(&cinfo, TRUE); + + // 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; + for (jpeg_marker_struct* marker = cinfo.marker_list; + marker && !(exifAppears && xmpAppears && iccAppears); + marker = marker->next) { + + if (marker->marker != kAPP1Marker && marker->marker != kAPP2Marker) { + continue; + } + const unsigned int len = marker->data_length; + if (!xmpAppears && + len > kXmpNameSpace.size() && + !strncmp(reinterpret_cast(marker->data), + kXmpNameSpace.c_str(), + kXmpNameSpace.size())) { + mXMPBuffer.resize(len+1, 0); + memcpy(static_cast(mXMPBuffer.data()), marker->data, len); + xmpAppears = true; + } else if (!exifAppears && + len > kExifIdCode.size() && + !strncmp(reinterpret_cast(marker->data), + kExifIdCode.c_str(), + kExifIdCode.size())) { + mEXIFBuffer.resize(len, 0); + memcpy(static_cast(mEXIFBuffer.data()), marker->data, len); + exifAppears = true; + } 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; + } + } + + if (cinfo.image_width > kMaxWidth || cinfo.image_height > kMaxHeight) { + // constraint on max width and max height is only due to alloc constraints + // tune these values basing on the target device + status = false; + goto CleanUp; + } + + mWidth = cinfo.image_width; + mHeight = cinfo.image_height; + + if (decodeToRGBA) { + if (cinfo.jpeg_color_space == JCS_GRAYSCALE) { + // We don't intend to support decoding grayscale to RGBA + status = false; + ALOGE("%s: decoding grayscale to RGBA is unsupported", __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[1].h_samp_factor != 1 || + cinfo.comp_info[2].h_samp_factor != 1 || + cinfo.comp_info[0].v_samp_factor != 2 || + cinfo.comp_info[1].v_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); + } + cinfo.out_color_space = cinfo.jpeg_color_space; + cinfo.raw_data_out = TRUE; + } + + cinfo.dct_method = JDCT_IFAST; + + 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) { + if (isSingleChannel) { + return decompressSingleChannel(cinfo, dest); + } + if (cinfo->out_color_space == JCS_EXT_RGBA) + return decompressRGBA(cinfo, dest); + else + return 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; + 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, kAPP1Marker, 0xFFFF); + jpeg_save_markers(&cinfo, kAPP2Marker, 0xFFFF); + + 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 >= kExifIdCode.size() && + !strncmp(reinterpret_cast(marker->data), kExifIdCode.c_str(), + kExifIdCode.size())) { + 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* decodeDst = (JSAMPLE*) dest; + uint32_t lines = 0; + // TODO: use batches for more effectiveness + while (lines < cinfo->image_height) { + uint32_t ret = jpeg_read_scanlines(cinfo, &decodeDst, 1); + if (ret == 0) { + break; + } + decodeDst += cinfo->image_width * 4; + lines++; + } + return lines == cinfo->image_height; +} + +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 ultrahdr diff --git a/libs/ultrahdr/jpegencoderhelper.cpp b/libs/ultrahdr/jpegencoderhelper.cpp new file mode 100644 index 0000000000000000000000000000000000000000..a03547b5384240f41373392aa552f65828440cbb --- /dev/null +++ b/libs/ultrahdr/jpegencoderhelper.cpp @@ -0,0 +1,294 @@ +/* + * 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 { + +#define ALIGNM(x, m) ((((x) + ((m) - 1)) / (m)) * (m)) + +// The destination manager that can access |mResultBuffer| in JpegEncoderHelper. +struct destination_mgr { +public: + struct jpeg_destination_mgr mgr; + JpegEncoderHelper* encoder; +}; + +JpegEncoderHelper::JpegEncoderHelper() { +} + +JpegEncoderHelper::~JpegEncoderHelper() { +} + +bool JpegEncoderHelper::compressImage(const void* image, int width, int height, int quality, + const void* iccBuffer, unsigned int iccSize, + bool isSingleChannel) { + mResultBuffer.clear(); + if (!encode(image, width, height, quality, iccBuffer, iccSize, isSingleChannel)) { + 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 void* image, int width, int height, int jpegQuality, + const void* iccBuffer, unsigned int iccSize, bool isSingleChannel) { + jpeg_compress_struct cinfo; + jpeg_error_mgr jerr; + + cinfo.err = jpeg_std_error(&jerr); + // Override output_message() to print error log with ALOGE(). + cinfo.err->output_message = &outputErrorMessage; + jpeg_create_compress(&cinfo); + setJpegDestination(&cinfo); + + setJpegCompressStruct(width, height, jpegQuality, &cinfo, isSingleChannel); + jpeg_start_compress(&cinfo, TRUE); + + if (iccBuffer != nullptr && iccSize > 0) { + jpeg_write_marker(&cinfo, JPEG_APP0 + 2, static_cast(iccBuffer), iccSize); + } + + bool status = compress(&cinfo, static_cast(image), isSingleChannel); + 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; + if (isSingleChannel) { + cinfo->input_components = 1; + cinfo->in_color_space = JCS_GRAYSCALE; + } else { + cinfo->input_components = 3; + cinfo->in_color_space = JCS_YCbCr; + } + jpeg_set_defaults(cinfo); + + jpeg_set_quality(cinfo, quality, TRUE); + jpeg_set_colorspace(cinfo, isSingleChannel ? JCS_GRAYSCALE : JCS_YCbCr); + cinfo->raw_data_in = TRUE; + cinfo->dct_method = JDCT_IFAST; + + if (!isSingleChannel) { + // Configure sampling factors. The sampling factor is JPEG subsampling 420 because the + // source format is YUV420. + 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; + } +} + +bool JpegEncoderHelper::compress( + jpeg_compress_struct* cinfo, const uint8_t* image, bool isSingleChannel) { + if (isSingleChannel) { + return compressSingleChannel(cinfo, image); + } + return compressYuv(cinfo, image); +} + +bool JpegEncoderHelper::compressYuv(jpeg_compress_struct* cinfo, const uint8_t* yuv) { + 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(yuv); + uint8_t* u_plane = const_cast(yuv + y_plane_size); + uint8_t* v_plane = const_cast(yuv + 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); + const 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; + 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 * cinfo->image_width; + } else { + y[i] = empty.get(); + } + if (!is_width_aligned) { + 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 * (cinfo->image_width / 2); + cb[i] = u_plane + offset; + cr[i] = v_plane + offset; + } else { + cb[i] = cr[i] = empty.get(); + } + if (!is_width_aligned) { + 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, is_width_aligned ? planes : planes_intrm, + kCompressBatchSize); + if (processed != kCompressBatchSize) { + ALOGE("Number of processed lines does not equal input lines."); + return false; + } + } + return true; +} + +bool JpegEncoderHelper::compressSingleChannel(jpeg_compress_struct* cinfo, const uint8_t* image) { + JSAMPROW y[kCompressBatchSize]; + JSAMPARRAY planes[1] {y}; + + uint8_t* y_plane = const_cast(image); + 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; + JSAMPROW y_intrm[kCompressBatchSize]; + JSAMPARRAY planes_intrm[]{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; + 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 * cinfo->image_width; + } else { + y[i] = empty.get(); + } + if (!is_width_aligned) { + memcpy(y_intrm[i], y[i], cinfo->image_width); + } + } + int processed = jpeg_write_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; + } + } + return true; +} + +} // namespace ultrahdr diff --git a/libs/ultrahdr/jpegr.cpp b/libs/ultrahdr/jpegr.cpp new file mode 100644 index 0000000000000000000000000000000000000000..9c57f34c2a725d71b449e4fd6da71bce2e082f26 --- /dev/null +++ b/libs/ultrahdr/jpegr.cpp @@ -0,0 +1,1486 @@ +/* + * 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 +#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; \ + } \ + } + +// 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; + +// JPEG block size. +// JPEG encoding / decoding will require block based DCT transform 16 x 16 for luma, +// and 8 x 8 for chroma. +// Width must be 16 dividable for luma, and 8 dividable for chroma. +// If this criteria is not facilitated, we will pad zeros based to each line on the +// required block size. +static const size_t kJpegBlock = JpegEncoderHelper::kCompressBatchSize; +// 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; +} + +status_t JpegR::areInputArgumentsValid(jr_uncompressed_ptr uncompressed_p010_image, + jr_uncompressed_ptr uncompressed_yuv_420_image, + ultrahdr_transfer_function hdr_tf, + jr_compressed_ptr dest) { + if (uncompressed_p010_image == nullptr || uncompressed_p010_image->data == nullptr) { + ALOGE("received nullptr for uncompressed p010 image"); + return ERROR_JPEGR_INVALID_NULL_PTR; + } + + if (uncompressed_p010_image->width % 2 != 0 + || uncompressed_p010_image->height % 2 != 0) { + ALOGE("Image dimensions cannot be odd, image dimensions %dx%d", + uncompressed_p010_image->width, uncompressed_p010_image->height); + return ERROR_JPEGR_INVALID_INPUT_TYPE; + } + + if (uncompressed_p010_image->width < kMinWidth + || uncompressed_p010_image->height < kMinHeight) { + ALOGE("Image dimensions cannot be less than %dx%d, image dimensions %dx%d", + kMinWidth, kMinHeight, uncompressed_p010_image->width, uncompressed_p010_image->height); + return ERROR_JPEGR_INVALID_INPUT_TYPE; + } + + if (uncompressed_p010_image->width > kMaxWidth + || uncompressed_p010_image->height > kMaxHeight) { + ALOGE("Image dimensions cannot be larger than %dx%d, image dimensions %dx%d", + kMaxWidth, kMaxHeight, uncompressed_p010_image->width, uncompressed_p010_image->height); + return ERROR_JPEGR_INVALID_INPUT_TYPE; + } + + if (uncompressed_p010_image->colorGamut <= ULTRAHDR_COLORGAMUT_UNSPECIFIED + || uncompressed_p010_image->colorGamut > ULTRAHDR_COLORGAMUT_MAX) { + ALOGE("Unrecognized p010 color gamut %d", uncompressed_p010_image->colorGamut); + return ERROR_JPEGR_INVALID_INPUT_TYPE; + } + + if (uncompressed_p010_image->luma_stride != 0 + && uncompressed_p010_image->luma_stride < uncompressed_p010_image->width) { + ALOGE("Luma stride can not be smaller than width, stride=%d, width=%d", + uncompressed_p010_image->luma_stride, uncompressed_p010_image->width); + return ERROR_JPEGR_INVALID_INPUT_TYPE; + } + + if (uncompressed_p010_image->chroma_data != nullptr + && uncompressed_p010_image->chroma_stride < uncompressed_p010_image->width) { + ALOGE("Chroma stride can not be smaller than width, stride=%d, width=%d", + uncompressed_p010_image->chroma_stride, + uncompressed_p010_image->width); + return ERROR_JPEGR_INVALID_INPUT_TYPE; + } + + if (dest == nullptr || dest->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 (uncompressed_yuv_420_image == nullptr) { + return NO_ERROR; + } + + if (uncompressed_yuv_420_image->data == nullptr) { + ALOGE("received nullptr for uncompressed 420 image"); + return ERROR_JPEGR_INVALID_NULL_PTR; + } + + if (uncompressed_yuv_420_image->luma_stride != 0) { + ALOGE("Stride is not supported for YUV420 image"); + return ERROR_JPEGR_UNSUPPORTED_FEATURE; + } + + if (uncompressed_yuv_420_image->chroma_data != nullptr) { + ALOGE("Pointer to chroma plane is not supported for YUV420 image, chroma data must" + "be immediately after the luma data."); + return ERROR_JPEGR_UNSUPPORTED_FEATURE; + } + + if (uncompressed_p010_image->width != uncompressed_yuv_420_image->width + || uncompressed_p010_image->height != uncompressed_yuv_420_image->height) { + ALOGE("Image resolutions mismatch: P010: %dx%d, YUV420: %dx%d", + uncompressed_p010_image->width, + uncompressed_p010_image->height, + uncompressed_yuv_420_image->width, + uncompressed_yuv_420_image->height); + return ERROR_JPEGR_RESOLUTION_MISMATCH; + } + + if (uncompressed_yuv_420_image->colorGamut <= ULTRAHDR_COLORGAMUT_UNSPECIFIED + || uncompressed_yuv_420_image->colorGamut > ULTRAHDR_COLORGAMUT_MAX) { + ALOGE("Unrecognized 420 color gamut %d", uncompressed_yuv_420_image->colorGamut); + return ERROR_JPEGR_INVALID_INPUT_TYPE; + } + + return NO_ERROR; +} + +status_t JpegR::areInputArgumentsValid(jr_uncompressed_ptr uncompressed_p010_image, + jr_uncompressed_ptr uncompressed_yuv_420_image, + ultrahdr_transfer_function hdr_tf, + jr_compressed_ptr dest, + int quality) { + if (status_t ret = areInputArgumentsValid( + uncompressed_p010_image, uncompressed_yuv_420_image, hdr_tf, dest) != NO_ERROR) { + return ret; + } + + 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 NO_ERROR; +} + +/* Encode API-0 */ +status_t JpegR::encodeJPEGR(jr_uncompressed_ptr uncompressed_p010_image, + ultrahdr_transfer_function hdr_tf, + jr_compressed_ptr dest, + int quality, + jr_exif_ptr exif) { + if (status_t ret = areInputArgumentsValid( + uncompressed_p010_image, /* uncompressed_yuv_420_image */ nullptr, + hdr_tf, dest, quality) != NO_ERROR) { + return ret; + } + + if (exif != nullptr && exif->data == nullptr) { + ALOGE("received nullptr for exif metadata"); + return ERROR_JPEGR_INVALID_NULL_PTR; + } + + ultrahdr_metadata_struct metadata; + metadata.version = kJpegrVersion; + + jpegr_uncompressed_struct uncompressed_yuv_420_image; + unique_ptr uncompressed_yuv_420_image_data = make_unique( + uncompressed_p010_image->width * uncompressed_p010_image->height * 3 / 2); + uncompressed_yuv_420_image.data = uncompressed_yuv_420_image_data.get(); + JPEGR_CHECK(toneMap(uncompressed_p010_image, &uncompressed_yuv_420_image)); + + jpegr_uncompressed_struct map; + JPEGR_CHECK(generateGainMap( + &uncompressed_yuv_420_image, uncompressed_p010_image, hdr_tf, &metadata, &map)); + std::unique_ptr map_data; + map_data.reset(reinterpret_cast(map.data)); + + JpegEncoderHelper jpeg_encoder_gainmap; + JPEGR_CHECK(compressGainMap(&map, &jpeg_encoder_gainmap)); + jpegr_compressed_struct compressed_map; + compressed_map.maxLength = jpeg_encoder_gainmap.getCompressedImageSize(); + compressed_map.length = compressed_map.maxLength; + compressed_map.data = jpeg_encoder_gainmap.getCompressedImagePtr(); + compressed_map.colorGamut = ULTRAHDR_COLORGAMUT_UNSPECIFIED; + + sp icc = IccHelper::writeIccProfile(ULTRAHDR_TF_SRGB, + uncompressed_yuv_420_image.colorGamut); + + // Convert to Bt601 YUV encoding for JPEG encode + JPEGR_CHECK(convertYuv(&uncompressed_yuv_420_image, uncompressed_yuv_420_image.colorGamut, + ULTRAHDR_COLORGAMUT_P3)); + + JpegEncoderHelper jpeg_encoder; + if (!jpeg_encoder.compressImage(uncompressed_yuv_420_image.data, + uncompressed_yuv_420_image.width, + uncompressed_yuv_420_image.height, quality, + icc->getData(), icc->getLength())) { + return ERROR_JPEGR_ENCODE_ERROR; + } + jpegr_compressed_struct jpeg; + jpeg.data = jpeg_encoder.getCompressedImagePtr(); + jpeg.length = jpeg_encoder.getCompressedImageSize(); + + // 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 uncompressed_p010_image, + jr_uncompressed_ptr uncompressed_yuv_420_image, + ultrahdr_transfer_function hdr_tf, + jr_compressed_ptr dest, + int quality, + jr_exif_ptr exif) { + if (uncompressed_yuv_420_image == 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 (status_t ret = areInputArgumentsValid( + uncompressed_p010_image, uncompressed_yuv_420_image, hdr_tf, + dest, quality) != NO_ERROR) { + return ret; + } + + ultrahdr_metadata_struct metadata; + metadata.version = kJpegrVersion; + + jpegr_uncompressed_struct map; + JPEGR_CHECK(generateGainMap( + uncompressed_yuv_420_image, uncompressed_p010_image, hdr_tf, &metadata, &map)); + std::unique_ptr map_data; + map_data.reset(reinterpret_cast(map.data)); + + JpegEncoderHelper jpeg_encoder_gainmap; + JPEGR_CHECK(compressGainMap(&map, &jpeg_encoder_gainmap)); + jpegr_compressed_struct compressed_map; + compressed_map.maxLength = jpeg_encoder_gainmap.getCompressedImageSize(); + compressed_map.length = compressed_map.maxLength; + compressed_map.data = jpeg_encoder_gainmap.getCompressedImagePtr(); + compressed_map.colorGamut = ULTRAHDR_COLORGAMUT_UNSPECIFIED; + + sp icc = IccHelper::writeIccProfile(ULTRAHDR_TF_SRGB, + uncompressed_yuv_420_image->colorGamut); + + // Convert to Bt601 YUV encoding for JPEG encode; make a copy so as to no clobber client data + unique_ptr yuv_420_bt601_data = make_unique( + uncompressed_yuv_420_image->width * uncompressed_yuv_420_image->height * 3 / 2); + memcpy(yuv_420_bt601_data.get(), uncompressed_yuv_420_image->data, + uncompressed_yuv_420_image->width * uncompressed_yuv_420_image->height * 3 / 2); + + jpegr_uncompressed_struct yuv_420_bt601_image = { + yuv_420_bt601_data.get(), uncompressed_yuv_420_image->width, uncompressed_yuv_420_image->height, + uncompressed_yuv_420_image->colorGamut }; + JPEGR_CHECK(convertYuv(&yuv_420_bt601_image, yuv_420_bt601_image.colorGamut, + ULTRAHDR_COLORGAMUT_P3)); + + JpegEncoderHelper jpeg_encoder; + if (!jpeg_encoder.compressImage(yuv_420_bt601_image.data, + yuv_420_bt601_image.width, + yuv_420_bt601_image.height, quality, + icc->getData(), icc->getLength())) { + return ERROR_JPEGR_ENCODE_ERROR; + } + jpegr_compressed_struct jpeg; + jpeg.data = jpeg_encoder.getCompressedImagePtr(); + jpeg.length = jpeg_encoder.getCompressedImageSize(); + + // 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 uncompressed_p010_image, + jr_uncompressed_ptr uncompressed_yuv_420_image, + jr_compressed_ptr compressed_jpeg_image, + ultrahdr_transfer_function hdr_tf, + jr_compressed_ptr dest) { + if (uncompressed_yuv_420_image == nullptr) { + ALOGE("received nullptr for uncompressed 420 image"); + return ERROR_JPEGR_INVALID_NULL_PTR; + } + + if (compressed_jpeg_image == nullptr || compressed_jpeg_image->data == nullptr) { + ALOGE("received nullptr for compressed jpeg image"); + return ERROR_JPEGR_INVALID_NULL_PTR; + } + + if (status_t ret = areInputArgumentsValid( + uncompressed_p010_image, uncompressed_yuv_420_image, hdr_tf, dest) != NO_ERROR) { + return ret; + } + + ultrahdr_metadata_struct metadata; + metadata.version = kJpegrVersion; + + jpegr_uncompressed_struct map; + JPEGR_CHECK(generateGainMap( + uncompressed_yuv_420_image, uncompressed_p010_image, hdr_tf, &metadata, &map)); + std::unique_ptr map_data; + map_data.reset(reinterpret_cast(map.data)); + + JpegEncoderHelper jpeg_encoder_gainmap; + JPEGR_CHECK(compressGainMap(&map, &jpeg_encoder_gainmap)); + jpegr_compressed_struct compressed_map; + compressed_map.maxLength = jpeg_encoder_gainmap.getCompressedImageSize(); + compressed_map.length = compressed_map.maxLength; + compressed_map.data = jpeg_encoder_gainmap.getCompressedImagePtr(); + compressed_map.colorGamut = ULTRAHDR_COLORGAMUT_UNSPECIFIED; + + // 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(compressed_jpeg_image->data, compressed_jpeg_image->length, + /* pWidth */ nullptr, /* pHeight */ nullptr, + &icc, /* exifData */ nullptr); + + // Add ICC if not already present. + if (icc.size() > 0) { + JPEGR_CHECK(appendGainMap(compressed_jpeg_image, &compressed_map, /* exif */ nullptr, + /* icc */ nullptr, /* icc size */ 0, &metadata, dest)); + } else { + sp newIcc = IccHelper::writeIccProfile(ULTRAHDR_TF_SRGB, + uncompressed_yuv_420_image->colorGamut); + JPEGR_CHECK(appendGainMap(compressed_jpeg_image, &compressed_map, /* exif */ nullptr, + newIcc->getData(), newIcc->getLength(), &metadata, dest)); + } + + return NO_ERROR; +} + +/* Encode API-3 */ +status_t JpegR::encodeJPEGR(jr_uncompressed_ptr uncompressed_p010_image, + jr_compressed_ptr compressed_jpeg_image, + ultrahdr_transfer_function hdr_tf, + jr_compressed_ptr dest) { + if (compressed_jpeg_image == nullptr || compressed_jpeg_image->data == nullptr) { + ALOGE("received nullptr for compressed jpeg image"); + return ERROR_JPEGR_INVALID_NULL_PTR; + } + + if (status_t ret = areInputArgumentsValid( + uncompressed_p010_image, /* uncompressed_yuv_420_image */ nullptr, + hdr_tf, dest) != NO_ERROR) { + return ret; + } + + // Note: output is Bt.601 YUV encoded regardless of gamut, due to jpeg decode. + JpegDecoderHelper jpeg_decoder; + if (!jpeg_decoder.decompressImage(compressed_jpeg_image->data, compressed_jpeg_image->length)) { + return ERROR_JPEGR_DECODE_ERROR; + } + jpegr_uncompressed_struct uncompressed_yuv_420_image; + uncompressed_yuv_420_image.data = jpeg_decoder.getDecompressedImagePtr(); + uncompressed_yuv_420_image.width = jpeg_decoder.getDecompressedImageWidth(); + uncompressed_yuv_420_image.height = jpeg_decoder.getDecompressedImageHeight(); + uncompressed_yuv_420_image.colorGamut = compressed_jpeg_image->colorGamut; + + if (uncompressed_p010_image->width != uncompressed_yuv_420_image.width + || uncompressed_p010_image->height != uncompressed_yuv_420_image.height) { + return ERROR_JPEGR_RESOLUTION_MISMATCH; + } + + ultrahdr_metadata_struct metadata; + metadata.version = kJpegrVersion; + + jpegr_uncompressed_struct map; + // Indicate that the SDR image is Bt.601 YUV encoded. + JPEGR_CHECK(generateGainMap( + &uncompressed_yuv_420_image, uncompressed_p010_image, hdr_tf, &metadata, &map, + true /* sdr_is_601 */ )); + std::unique_ptr map_data; + map_data.reset(reinterpret_cast(map.data)); + + JpegEncoderHelper jpeg_encoder_gainmap; + JPEGR_CHECK(compressGainMap(&map, &jpeg_encoder_gainmap)); + jpegr_compressed_struct compressed_map; + compressed_map.maxLength = jpeg_encoder_gainmap.getCompressedImageSize(); + compressed_map.length = compressed_map.maxLength; + compressed_map.data = jpeg_encoder_gainmap.getCompressedImagePtr(); + compressed_map.colorGamut = ULTRAHDR_COLORGAMUT_UNSPECIFIED; + + // 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(compressed_jpeg_image->data, compressed_jpeg_image->length, + /* pWidth */ nullptr, /* pHeight */ nullptr, + &icc, /* exifData */ nullptr); + + // Add ICC if not already present. + if (icc.size() > 0) { + JPEGR_CHECK(appendGainMap(compressed_jpeg_image, &compressed_map, /* exif */ nullptr, + /* icc */ nullptr, /* icc size */ 0, &metadata, dest)); + } else { + sp newIcc = IccHelper::writeIccProfile(ULTRAHDR_TF_SRGB, + uncompressed_yuv_420_image.colorGamut); + JPEGR_CHECK(appendGainMap(compressed_jpeg_image, &compressed_map, /* exif */ nullptr, + newIcc->getData(), newIcc->getLength(), &metadata, dest)); + } + + return NO_ERROR; +} + +/* Encode API-4 */ +status_t JpegR::encodeJPEGR(jr_compressed_ptr compressed_jpeg_image, + jr_compressed_ptr compressed_gainmap, + ultrahdr_metadata_ptr metadata, + jr_compressed_ptr dest) { + if (compressed_jpeg_image == nullptr || compressed_jpeg_image->data == nullptr) { + ALOGE("received nullptr for compressed jpeg image"); + return ERROR_JPEGR_INVALID_NULL_PTR; + } + + if (compressed_gainmap == nullptr || compressed_gainmap->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(compressed_jpeg_image->data, compressed_jpeg_image->length, + /* pWidth */ nullptr, /* pHeight */ nullptr, + &icc, /* exifData */ nullptr); + + // Add ICC if not already present. + if (icc.size() > 0) { + JPEGR_CHECK(appendGainMap(compressed_jpeg_image, compressed_gainmap, /* exif */ nullptr, + /* icc */ nullptr, /* icc size */ 0, metadata, dest)); + } else { + sp newIcc = IccHelper::writeIccProfile(ULTRAHDR_TF_SRGB, + compressed_jpeg_image->colorGamut); + JPEGR_CHECK(appendGainMap(compressed_jpeg_image, compressed_gainmap, /* exif */ nullptr, + newIcc->getData(), newIcc->getLength(), metadata, dest)); + } + + return NO_ERROR; +} + +status_t JpegR::getJPEGRInfo(jr_compressed_ptr compressed_jpegr_image, jr_info_ptr jpegr_info) { + if (compressed_jpegr_image == nullptr || compressed_jpegr_image->data == nullptr) { + ALOGE("received nullptr for compressed jpegr image"); + return ERROR_JPEGR_INVALID_NULL_PTR; + } + + if (jpegr_info == nullptr) { + ALOGE("received nullptr for compressed jpegr info struct"); + return ERROR_JPEGR_INVALID_NULL_PTR; + } + + jpegr_compressed_struct primary_image, gain_map; + JPEGR_CHECK(extractPrimaryImageAndGainMap(compressed_jpegr_image, + &primary_image, &gain_map)); + + JpegDecoderHelper jpeg_decoder; + if (!jpeg_decoder.getCompressedImageParameters(primary_image.data, primary_image.length, + &jpegr_info->width, &jpegr_info->height, + jpegr_info->iccData, jpegr_info->exifData)) { + return ERROR_JPEGR_DECODE_ERROR; + } + + return NO_ERROR; +} + +/* Decode API */ +status_t JpegR::decodeJPEGR(jr_compressed_ptr compressed_jpegr_image, + jr_uncompressed_ptr dest, + float max_display_boost, + jr_exif_ptr exif, + ultrahdr_output_format output_format, + jr_uncompressed_ptr gain_map, + ultrahdr_metadata_ptr metadata) { + if (compressed_jpegr_image == nullptr || compressed_jpegr_image->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; + } + + if (output_format == ULTRAHDR_OUTPUT_SDR) { + JpegDecoderHelper jpeg_decoder; + if (!jpeg_decoder.decompressImage(compressed_jpegr_image->data, compressed_jpegr_image->length, + true)) { + return ERROR_JPEGR_DECODE_ERROR; + } + jpegr_uncompressed_struct uncompressed_rgba_image; + uncompressed_rgba_image.data = jpeg_decoder.getDecompressedImagePtr(); + uncompressed_rgba_image.width = jpeg_decoder.getDecompressedImageWidth(); + uncompressed_rgba_image.height = jpeg_decoder.getDecompressedImageHeight(); + memcpy(dest->data, uncompressed_rgba_image.data, + uncompressed_rgba_image.width * uncompressed_rgba_image.height * 4); + dest->width = uncompressed_rgba_image.width; + dest->height = uncompressed_rgba_image.height; + + if (gain_map == nullptr && exif == nullptr) { + return NO_ERROR; + } + + if (exif != nullptr) { + if (exif->data == nullptr) { + return ERROR_JPEGR_INVALID_NULL_PTR; + } + if (exif->length < jpeg_decoder.getEXIFSize()) { + return ERROR_JPEGR_BUFFER_TOO_SMALL; + } + memcpy(exif->data, jpeg_decoder.getEXIFPtr(), jpeg_decoder.getEXIFSize()); + exif->length = jpeg_decoder.getEXIFSize(); + } + if (gain_map == nullptr) { + return NO_ERROR; + } + } + + jpegr_compressed_struct compressed_map; + JPEGR_CHECK(extractGainMap(compressed_jpegr_image, &compressed_map)); + + JpegDecoderHelper gain_map_decoder; + if (!gain_map_decoder.decompressImage(compressed_map.data, compressed_map.length)) { + return ERROR_JPEGR_DECODE_ERROR; + } + if ((gain_map_decoder.getDecompressedImageWidth() * + gain_map_decoder.getDecompressedImageHeight()) > + gain_map_decoder.getDecompressedImageSize()) { + return ERROR_JPEGR_CALCULATION_ERROR; + } + + if (gain_map != nullptr) { + gain_map->width = gain_map_decoder.getDecompressedImageWidth(); + gain_map->height = gain_map_decoder.getDecompressedImageHeight(); + int size = gain_map->width * gain_map->height; + gain_map->data = malloc(size); + memcpy(gain_map->data, gain_map_decoder.getDecompressedImagePtr(), size); + } + + ultrahdr_metadata_struct uhdr_metadata; + if (!getMetadataFromXMP(static_cast(gain_map_decoder.getXMPPtr()), + gain_map_decoder.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; + } + + if (output_format == ULTRAHDR_OUTPUT_SDR) { + return NO_ERROR; + } + + JpegDecoderHelper jpeg_decoder; + if (!jpeg_decoder.decompressImage(compressed_jpegr_image->data, compressed_jpegr_image->length)) { + return ERROR_JPEGR_DECODE_ERROR; + } + if ((jpeg_decoder.getDecompressedImageWidth() * + jpeg_decoder.getDecompressedImageHeight() * 3 / 2) > + jpeg_decoder.getDecompressedImageSize()) { + return ERROR_JPEGR_CALCULATION_ERROR; + } + + if (exif != nullptr) { + if (exif->data == nullptr) { + return ERROR_JPEGR_INVALID_NULL_PTR; + } + if (exif->length < jpeg_decoder.getEXIFSize()) { + return ERROR_JPEGR_BUFFER_TOO_SMALL; + } + memcpy(exif->data, jpeg_decoder.getEXIFPtr(), jpeg_decoder.getEXIFSize()); + exif->length = jpeg_decoder.getEXIFSize(); + } + + jpegr_uncompressed_struct map; + map.data = gain_map_decoder.getDecompressedImagePtr(); + map.width = gain_map_decoder.getDecompressedImageWidth(); + map.height = gain_map_decoder.getDecompressedImageHeight(); + + jpegr_uncompressed_struct uncompressed_yuv_420_image; + uncompressed_yuv_420_image.data = jpeg_decoder.getDecompressedImagePtr(); + uncompressed_yuv_420_image.width = jpeg_decoder.getDecompressedImageWidth(); + uncompressed_yuv_420_image.height = jpeg_decoder.getDecompressedImageHeight(); + uncompressed_yuv_420_image.colorGamut = IccHelper::readIccColorGamut( + jpeg_decoder.getICCPtr(), jpeg_decoder.getICCSize()); + + JPEGR_CHECK(applyGainMap(&uncompressed_yuv_420_image, &map, &uhdr_metadata, output_format, + max_display_boost, dest)); + return NO_ERROR; +} + +status_t JpegR::compressGainMap(jr_uncompressed_ptr uncompressed_gain_map, + JpegEncoderHelper* jpeg_encoder) { + if (uncompressed_gain_map == nullptr || jpeg_encoder == nullptr) { + return ERROR_JPEGR_INVALID_NULL_PTR; + } + + // Don't need to convert YUV to Bt601 since single channel + if (!jpeg_encoder->compressImage(uncompressed_gain_map->data, + uncompressed_gain_map->width, + uncompressed_gain_map->height, + kMapCompressQuality, + nullptr, + 0, + true /* isSingleChannel */)) { + 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 uncompressed_yuv_420_image, + jr_uncompressed_ptr uncompressed_p010_image, + ultrahdr_transfer_function hdr_tf, + ultrahdr_metadata_ptr metadata, + jr_uncompressed_ptr dest, + bool sdr_is_601) { + if (uncompressed_yuv_420_image == nullptr + || uncompressed_p010_image == nullptr + || metadata == nullptr + || dest == nullptr) { + return ERROR_JPEGR_INVALID_NULL_PTR; + } + + if (uncompressed_yuv_420_image->width != uncompressed_p010_image->width + || uncompressed_yuv_420_image->height != uncompressed_p010_image->height) { + return ERROR_JPEGR_RESOLUTION_MISMATCH; + } + + if (uncompressed_yuv_420_image->colorGamut == ULTRAHDR_COLORGAMUT_UNSPECIFIED + || uncompressed_p010_image->colorGamut == ULTRAHDR_COLORGAMUT_UNSPECIFIED) { + return ERROR_JPEGR_INVALID_COLORGAMUT; + } + + size_t image_width = uncompressed_yuv_420_image->width; + size_t image_height = uncompressed_yuv_420_image->height; + size_t map_width = image_width / kMapDimensionScaleFactor; + size_t map_height = image_height / kMapDimensionScaleFactor; + size_t map_stride = static_cast( + floor((map_width + kJpegBlock - 1) / kJpegBlock)) * kJpegBlock; + size_t map_height_aligned = ((map_height + 1) >> 1) << 1; + + dest->width = map_stride; + dest->height = map_height_aligned; + dest->colorGamut = ULTRAHDR_COLORGAMUT_UNSPECIFIED; + dest->data = new uint8_t[map_stride * map_height_aligned]; + std::unique_ptr map_data; + map_data.reset(reinterpret_cast(dest->data)); + + ColorTransformFn hdrInvOetf = nullptr; + float hdr_white_nits = kSdrWhiteNits; + switch (hdr_tf) { + case ULTRAHDR_TF_LINEAR: + hdrInvOetf = identityConversion; + 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( + uncompressed_yuv_420_image->colorGamut, uncompressed_p010_image->colorGamut); + + ColorCalculationFn luminanceFn = nullptr; + ColorTransformFn sdrYuvToRgbFn = nullptr; + switch (uncompressed_yuv_420_image->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 (uncompressed_p010_image->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 = [uncompressed_yuv_420_image, uncompressed_p010_image, + metadata, dest, hdrInvOetf, hdrGamutConversionFn, + luminanceFn, sdrYuvToRgbFn, hdrYuvToRgbFn, hdr_white_nits, + log2MinBoost, log2MaxBoost, &jobQueue]() -> void { + size_t rowStart, rowEnd; + size_t dest_map_width = uncompressed_yuv_420_image->width / kMapDimensionScaleFactor; + size_t dest_map_stride = dest->width; + while (jobQueue.dequeueJob(rowStart, rowEnd)) { + for (size_t y = rowStart; y < rowEnd; ++y) { + for (size_t x = 0; x < dest_map_width; ++x) { + Color sdr_yuv_gamma = + sampleYuv420(uncompressed_yuv_420_image, 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(uncompressed_p010_image, 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_map_stride; + 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 uncompressed_yuv_420_image, + jr_uncompressed_ptr uncompressed_gain_map, + ultrahdr_metadata_ptr metadata, + ultrahdr_output_format output_format, + float max_display_boost, + jr_uncompressed_ptr dest) { + if (uncompressed_yuv_420_image == nullptr + || uncompressed_gain_map == nullptr + || metadata == nullptr + || dest == nullptr) { + return ERROR_JPEGR_INVALID_NULL_PTR; + } + + if (metadata->version.compare("1.0")) { + 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 = uncompressed_yuv_420_image->width; + size_t image_height = uncompressed_yuv_420_image->height; + size_t map_width = image_width / kMapDimensionScaleFactor; + size_t map_height = image_height / kMapDimensionScaleFactor; + map_width = static_cast( + floor((map_width + kJpegBlock - 1) / kJpegBlock)) * kJpegBlock; + map_height = ((map_height + 1) >> 1) << 1; + if (map_width != uncompressed_gain_map->width + || map_height != uncompressed_gain_map->height) { + ALOGE("gain map dimensions and primary image dimensions are not to scale"); + return ERROR_JPEGR_INVALID_INPUT_TYPE; + } + + dest->width = uncompressed_yuv_420_image->width; + dest->height = uncompressed_yuv_420_image->height; + ShepardsIDW idwTable(kMapDimensionScaleFactor); + float display_boost = std::min(max_display_boost, metadata->maxContentBoost); + GainLUT gainLUT(metadata, display_boost); + + JobQueue jobQueue; + std::function applyRecMap = [uncompressed_yuv_420_image, uncompressed_gain_map, + metadata, dest, &jobQueue, &idwTable, output_format, + &gainLUT, display_boost]() -> void { + size_t width = uncompressed_yuv_420_image->width; + size_t height = uncompressed_yuv_420_image->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(uncompressed_yuv_420_image, 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(uncompressed_gain_map, map_scale_factor, x, y); + } else { + gain = sampleMap(uncompressed_gain_map, 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_HLG_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 ? uncompressed_yuv_420_image->height : kJobSzInRows; + for (int rowStart = 0; rowStart < uncompressed_yuv_420_image->height;) { + int rowEnd = std::min(rowStart + rowStep, uncompressed_yuv_420_image->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 compressed_jpegr_image, + jr_compressed_ptr primary_image, + jr_compressed_ptr gain_map) { + if (compressed_jpegr_image == nullptr) { + return ERROR_JPEGR_INVALID_NULL_PTR; + } + + MessageHandler msg_handler; + std::shared_ptr seg = + DataSegment::Create(DataRange(0, compressed_jpegr_image->length), + static_cast(compressed_jpegr_image->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 (image_ranges.size() != 2) { + // Must be 2 JPEG Images + return ERROR_JPEGR_INVALID_INPUT_TYPE; + } + + if (primary_image != nullptr) { + primary_image->data = static_cast(compressed_jpegr_image->data) + + image_ranges[0].GetBegin(); + primary_image->length = image_ranges[0].GetLength(); + } + + if (gain_map != nullptr) { + gain_map->data = static_cast(compressed_jpegr_image->data) + + image_ranges[1].GetBegin(); + gain_map->length = image_ranges[1].GetLength(); + } + + return NO_ERROR; +} + + +status_t JpegR::extractGainMap(jr_compressed_ptr compressed_jpegr_image, + jr_compressed_ptr dest) { + if (compressed_jpegr_image == nullptr || dest == nullptr) { + return ERROR_JPEGR_INVALID_NULL_PTR; + } + + return extractPrimaryImageAndGainMap(compressed_jpegr_image, nullptr, dest); +} + +// JPEG/R structure: +// SOI (ff d8) +// +// (Optional, only if EXIF package is from outside) +// 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), 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 compressed_jpeg_image, + jr_compressed_ptr compressed_gain_map, + jr_exif_ptr exif, + void* icc, size_t icc_size, + ultrahdr_metadata_ptr metadata, + jr_compressed_ptr dest) { + if (compressed_jpeg_image == nullptr + || compressed_gain_map == 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 + + compressed_gain_map->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(); + + 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 (exif != nullptr) { + const int length = 2 + exif->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, exif->data, exif->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 (icc != 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, icc, 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 + compressed_jpeg_image->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*)compressed_jpeg_image->data + 2, compressed_jpeg_image->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*)compressed_gain_map->data + 2, compressed_gain_map->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; + } + + uint16_t* src_luma_data = reinterpret_cast(src->data); + size_t src_luma_stride = src->luma_stride == 0 ? src->width : src->luma_stride; + + uint16_t* src_chroma_data; + size_t src_chroma_stride; + if (src->chroma_data == nullptr) { + src_chroma_stride = src_luma_stride; + src_chroma_data = &reinterpret_cast(src->data)[src_luma_stride * src->height]; + } else { + src_chroma_stride = src->chroma_stride; + src_chroma_data = reinterpret_cast(src->chroma_data); + } + dest->width = src->width; + dest->height = src->height; + + size_t dest_luma_pixel_count = dest->width * dest->height; + + for (size_t y = 0; y < src->height; ++y) { + for (size_t x = 0; x < src->width; ++x) { + size_t src_y_idx = y * src_luma_stride + x; + size_t src_u_idx = (y >> 1) * src_chroma_stride + (x & ~0x1); + size_t src_v_idx = src_u_idx + 1; + + uint16_t y_uint = src_luma_data[src_y_idx] >> 6; + uint16_t u_uint = src_chroma_data[src_u_idx] >> 6; + uint16_t v_uint = src_chroma_data[src_v_idx] >> 6; + + size_t dest_y_idx = x + y * dest->width; + size_t dest_uv_idx = x / 2 + (y / 2) * (dest->width / 2); + + uint8_t* y = &reinterpret_cast(dest->data)[dest_y_idx]; + uint8_t* u = &reinterpret_cast(dest->data)[dest_luma_pixel_count + dest_uv_idx]; + uint8_t* v = &reinterpret_cast( + dest->data)[dest_luma_pixel_count * 5 / 4 + dest_uv_idx]; + + *y = static_cast((y_uint >> 2) & 0xff); + *u = static_cast((u_uint >> 2) & 0xff); + *v = static_cast((v_uint >> 2) & 0xff); + } + } + + 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 new file mode 100644 index 0000000000000000000000000000000000000000..c434eb6459e246ce657fa30d8200c4bfc2b4da9c --- /dev/null +++ b/libs/ultrahdr/jpegrutils.cpp @@ -0,0 +1,600 @@ +/* + * 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 new file mode 100644 index 0000000000000000000000000000000000000000..f1679ef1b3bc10132211522828f9120c34b1124f --- /dev/null +++ b/libs/ultrahdr/multipictureformat.cpp @@ -0,0 +1,94 @@ +/* + * 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/Android.bp b/libs/ultrahdr/tests/Android.bp new file mode 100644 index 0000000000000000000000000000000000000000..594413018cbe36410dadb10f6f03ba75fff481b5 --- /dev/null +++ b/libs/ultrahdr/tests/Android.bp @@ -0,0 +1,79 @@ +// 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. + +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: "libultrahdr_test", + test_suites: ["device-tests"], + srcs: [ + "gainmapmath_test.cpp", + "icchelper_test.cpp", + "jpegr_test.cpp", + ], + shared_libs: [ + "libimage_io", + "libjpeg", + "liblog", + ], + static_libs: [ + "libgmock", + "libgtest", + "libjpegdecoder", + "libjpegencoder", + "libultrahdr", + "libutils", + ], +} + +cc_test { + name: "libjpegencoderhelper_test", + test_suites: ["device-tests"], + srcs: [ + "jpegencoderhelper_test.cpp", + ], + shared_libs: [ + "libjpeg", + "liblog", + ], + static_libs: [ + "libgtest", + "libjpegencoder", + ], +} + +cc_test { + name: "libjpegdecoderhelper_test", + test_suites: ["device-tests"], + srcs: [ + "jpegdecoderhelper_test.cpp", + ], + shared_libs: [ + "libjpeg", + "liblog", + ], + static_libs: [ + "libgtest", + "libjpegdecoder", + "libultrahdr", + "libutils", + ], +} diff --git a/libs/ultrahdr/tests/data/jpeg_image.jpg b/libs/ultrahdr/tests/data/jpeg_image.jpg new file mode 100644 index 0000000000000000000000000000000000000000..e2857425e7734496457a483e4f11ea284c5a8773 Binary files /dev/null and b/libs/ultrahdr/tests/data/jpeg_image.jpg differ diff --git a/libs/ultrahdr/tests/data/minnie-318x240.yu12 b/libs/ultrahdr/tests/data/minnie-318x240.yu12 new file mode 100644 index 0000000000000000000000000000000000000000..7b2fc71bc0bb94b9d6ef932e637cac70fd5b7e4d --- /dev/null +++ b/libs/ultrahdr/tests/data/minnie-318x240.yu12 @@ -0,0 +1,1930 @@ +ØÖÖÓÑÑÏËÈÈÈÅÅÃÃÂÅÂÁÀÁÀ½¹¶³°«¬±´³²±´¯–‘ž°¯¯±´³³´µ¹»½½½»¸¸·¹»¼½¿ÁÃÆÇÉÌÎÏÐÎÍÌËÊÊÉÊÍÌËÊÆÇÈËÊÌÌÉÈÇÆÅÄÄÅÆÇÆÆÇÇÆÅÃÄÂÃÁÁÁÁÁ¿¾¼¼¼¼½»»¸·¹¸·¶µ³²²²²²±°¯¯®®®ª©¦£¤¥¤“}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 new file mode 100644 index 0000000000000000000000000000000000000000..20b5a2c0df5209ebe821d4220055012ac6d94993 Binary files /dev/null and b/libs/ultrahdr/tests/data/minnie-320x240-y.jpg differ diff --git a/libs/ultrahdr/tests/data/minnie-320x240-yuv-icc.jpg b/libs/ultrahdr/tests/data/minnie-320x240-yuv-icc.jpg new file mode 100644 index 0000000000000000000000000000000000000000..c7f45385346d7d6af5ca648ecf9813e176a6dc6d Binary files /dev/null and b/libs/ultrahdr/tests/data/minnie-320x240-yuv-icc.jpg differ diff --git a/libs/ultrahdr/tests/data/minnie-320x240-yuv.jpg b/libs/ultrahdr/tests/data/minnie-320x240-yuv.jpg new file mode 100644 index 0000000000000000000000000000000000000000..41300f47f1e17313a0bff0afd240a563e96955b5 Binary files /dev/null and b/libs/ultrahdr/tests/data/minnie-320x240-yuv.jpg differ diff --git a/libs/ultrahdr/tests/data/minnie-320x240.y b/libs/ultrahdr/tests/data/minnie-320x240.y new file mode 100644 index 0000000000000000000000000000000000000000..f9d8371c1889302d25e05476abdc7b17657cdf25 --- /dev/null +++ b/libs/ultrahdr/tests/data/minnie-320x240.y @@ -0,0 +1,1930 @@ +ØÖÖÓÑÑÏËÈÈÈÅÅÃÃÂÅÂÁÀÁÀ½¹¶³°«¬±´³²±´¯–‘ž°¯¯±´³³´µ¹»½½½»¸¸·¹»¼½¿ÁÃÆÇÉÌÎÏÐÎÍÌËÊÊÉÊÍÌËÊÆÇÈËÊÌÌÉÈÇÆÅÄÄÅÆÇÆÆÇÇÆÅÃÄÂÃÁÁÁÁÁ¿¾¼¼¼¼½»»¸·¹¸·¶µ³²²²²²±°¯¯®®®ª©¦£¤¥¤“}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 new file mode 100644 index 0000000000000000000000000000000000000000..0d66f530298e5659eaa609651ef7b1e630a83cc1 --- /dev/null +++ b/libs/ultrahdr/tests/data/minnie-320x240.yu12 @@ -0,0 +1,1930 @@ +ØÖÖÓÑÑÏËÈÈÈÅÅÃÃÂÅÂÁÀÁÀ½¹¶³°«¬±´³²±´¯–‘ž°¯¯±´³³´µ¹»½½½»¸¸·¹»¼½¿ÁÃÆÇÉÌÎÏÐÎÍÌËÊÊÉÊÍÌËÊÆÇÈËÊÌÌÉÈÇÆÅÄÄÅÆÇÆÆÇÇÆÅÃÄÂÃÁÁÁÁÁ¿¾¼¼¼¼½»»¸·¹¸·¶µ³²²²²²±°¯¯®®®ª©¦£¤¥¤“}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 new file mode 100644 index 0000000000000000000000000000000000000000..01673bf6d5bbfd11737acccd484b981ac6f8fbf8 Binary files /dev/null and b/libs/ultrahdr/tests/data/raw_p010_image.p010 differ diff --git a/libs/ultrahdr/tests/data/raw_p010_image_with_stride.p010 b/libs/ultrahdr/tests/data/raw_p010_image_with_stride.p010 new file mode 100644 index 0000000000000000000000000000000000000000..e7a5dc84dc7ab98f35303753e7e7191a8d158993 Binary files /dev/null and b/libs/ultrahdr/tests/data/raw_p010_image_with_stride.p010 differ diff --git a/libs/ultrahdr/tests/data/raw_yuv420_image.yuv420 b/libs/ultrahdr/tests/data/raw_yuv420_image.yuv420 new file mode 100644 index 0000000000000000000000000000000000000000..c043da642395133c801cfc3984ac11f5b2b81dc9 --- /dev/null +++ b/libs/ultrahdr/tests/data/raw_yuv420_image.yuv420 @@ -0,0 +1 @@ +ûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòôíöÖØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÏÌÑßåáàäââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòôíöÖØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÏÌÑßåáàäââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòôíöÖØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÏÌÑßåáàäââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòôíöÖØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÏÌÑßåáàäââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòôíöÖØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÏÌÑßåáàäââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòôíöÖØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÏÌÑßåáàäââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòôíöÖØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÏÌÑßåáàäââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòôíöÖØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÏÌÑßåáàäââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòôíöÖØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÏÌÑßåáàäââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòôíöÖØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÏÌÑßåáàäââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòôíöÖØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÏÌÑßåáàäââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòôíöÖØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÏÌÑßåáàäââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòôíöÖØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÏÌÑßåáàäââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòôíöÖØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÏÌÑßåáàäââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòôíöÖØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÏÌÑßåáàäââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòôíöÖØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÏÌÑßåáàäââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòôíöÖØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÏÌÑßåáàäââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòôíöÖØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÏÌÑßåáàäââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòôíöÖØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÏÌÑßåáàäââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòôíöÖØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÏÌÑßåáàäââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòôíöÖØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÏÌÑßåáàäââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòôíöÖØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÏÌÑßåáàäââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòôíöÖØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÏÌÑßåáàäââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòôíöÖØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÏÌÑßåáàäââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòôíöÖØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÏÌÑßåáàäââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòôíöÖØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÏÌÑßåáàäââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòôíöÖØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÏÌÑßåáàäââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòôíöÖØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÏÌÑßåáàäââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòôíöÖØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÏÌÑßåáàäââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòôíöÖØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÏÌÑßåáàäââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòôíöÖØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÏÌÑßåáàäââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòôíöÖØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÏÌÑßåáàäââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòñîçßÙØØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÑËÓÊÐÎÌÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÏÌÑßåáàäââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÎÏÑÒÔ×ÙÙØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÉÅÑÔÕ±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 new file mode 100644 index 0000000000000000000000000000000000000000..af90365e56dfcfebd5aef8f861e2e336f23ebf80 --- /dev/null +++ b/libs/ultrahdr/tests/gainmapmath_test.cpp @@ -0,0 +1,1356 @@ +/* + * 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 }; + } + + 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 }; + } + + 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(); + + 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 new file mode 100644 index 0000000000000000000000000000000000000000..ff61c0857414d64c449026754d5ed1a349f51d32 --- /dev/null +++ b/libs/ultrahdr/tests/icchelper_test.cpp @@ -0,0 +1,77 @@ +/* + * 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 new file mode 100644 index 0000000000000000000000000000000000000000..e2da01c373a35ce6ca9a56fc8a5f9e5753817e66 --- /dev/null +++ b/libs/ultrahdr/tests/jpegdecoderhelper_test.cpp @@ -0,0 +1,156 @@ +/* + * 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 "/sdcard/Documents/minnie-320x240-yuv.jpg" +#define YUV_IMAGE_SIZE 20193 +// Has ICC and EXIF +#define YUV_ICC_IMAGE "/sdcard/Documents/minnie-320x240-yuv-icc.jpg" +#define YUV_ICC_IMAGE_SIZE 34266 +// No ICC or EXIF +#define GREY_IMAGE "/sdcard/Documents/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 new file mode 100644 index 0000000000000000000000000000000000000000..f0e1fa4968e4c75c4e0513b40a4a5245f0802453 --- /dev/null +++ b/libs/ultrahdr/tests/jpegencoderhelper_test.cpp @@ -0,0 +1,126 @@ +/* + * 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 "/sdcard/Documents/minnie-320x240.yu12" +#define ALIGNED_IMAGE_WIDTH 320 +#define ALIGNED_IMAGE_HEIGHT 240 +#define SINGLE_CHANNEL_IMAGE "/sdcard/Documents/minnie-320x240.y" +#define SINGLE_CHANNEL_IMAGE_WIDTH ALIGNED_IMAGE_WIDTH +#define SINGLE_CHANNEL_IMAGE_HEIGHT ALIGNED_IMAGE_HEIGHT +#define UNALIGNED_IMAGE "/sdcard/Documents/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.width, + mAlignedImage.height, 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.width, + mUnalignedImage.height, JPEG_QUALITY, NULL, 0)); + ASSERT_GT(encoder.getCompressedImageSize(), static_cast(0)); +} + +TEST_F(JpegEncoderHelperTest, encodeSingleChannelImage) { + JpegEncoderHelper encoder; + EXPECT_TRUE(encoder.compressImage(mSingleChannelImage.buffer.get(), mSingleChannelImage.width, + mSingleChannelImage.height, JPEG_QUALITY, NULL, 0, true)); + 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 new file mode 100644 index 0000000000000000000000000000000000000000..41d55ec4979e5fd6122d9d3ccf02d4deccc92efb --- /dev/null +++ b/libs/ultrahdr/tests/jpegr_test.cpp @@ -0,0 +1,1375 @@ +/* + * 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 RAW_P010_IMAGE "/sdcard/Documents/raw_p010_image.p010" +#define RAW_P010_IMAGE_WITH_STRIDE "/sdcard/Documents/raw_p010_image_with_stride.p010" +#define RAW_YUV420_IMAGE "/sdcard/Documents/raw_yuv420_image.yuv420" +#define JPEG_IMAGE "/sdcard/Documents/jpeg_image.jpg" +#define TEST_IMAGE_WIDTH 1280 +#define TEST_IMAGE_HEIGHT 720 +#define TEST_IMAGE_STRIDE 1288 +#define DEFAULT_JPEG_QUALITY 90 + +#define SAVE_ENCODING_RESULT true +#define SAVE_DECODING_RESULT true +#define SAVE_INPUT_RGBA true + +namespace android::ultrahdr { + +struct Timer { + struct timeval StartingTime; + struct timeval EndingTime; + struct timeval ElapsedMicroseconds; +}; + +void timerStart(Timer *t) { + gettimeofday(&t->StartingTime, nullptr); +} + +void timerStop(Timer *t) { + gettimeofday(&t->EndingTime, nullptr); +} + +int64_t elapsedTime(Timer *t) { + t->ElapsedMicroseconds.tv_sec = t->EndingTime.tv_sec - t->StartingTime.tv_sec; + t->ElapsedMicroseconds.tv_usec = t->EndingTime.tv_usec - t->StartingTime.tv_usec; + return t->ElapsedMicroseconds.tv_sec * 1000000 + t->ElapsedMicroseconds.tv_usec; +} + +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[], void*& result, int* fileLength) { + int fd = open(filename, O_CLOEXEC); + if (fd < 0) { + return false; + } + int length = getFileSize(fd); + if (length == 0) { + close(fd); + return false; + } + if (fileLength != nullptr) { + *fileLength = length; + } + result = malloc(length); + if (read(fd, result, length) != static_cast(length)) { + close(fd); + return false; + } + close(fd); + return true; +} + +static bool loadP010Image(const char *filename, jr_uncompressed_ptr img, + bool isUVContiguous) { + int fd = open(filename, O_CLOEXEC); + if (fd < 0) { + return false; + } + const int bpp = 2; + int lumaStride = img->luma_stride == 0 ? img->width : img->luma_stride; + int lumaSize = bpp * lumaStride * img->height; + int chromaSize = bpp * (img->height / 2) * + (isUVContiguous ? lumaStride : img->chroma_stride); + img->data = malloc(lumaSize + (isUVContiguous ? chromaSize : 0)); + if (img->data == nullptr) { + ALOGE("loadP010Image(): failed to allocate memory for luma data."); + return false; + } + uint8_t *mem = static_cast(img->data); + for (int i = 0; i < img->height; i++) { + if (read(fd, mem, img->width * bpp) != img->width * bpp) { + close(fd); + return false; + } + mem += lumaStride * bpp; + } + int chromaStride = lumaStride; + if (!isUVContiguous) { + img->chroma_data = malloc(chromaSize); + if (img->chroma_data == nullptr) { + ALOGE("loadP010Image(): failed to allocate memory for chroma data."); + return false; + } + mem = static_cast(img->chroma_data); + chromaStride = img->chroma_stride; + } + for (int i = 0; i < img->height / 2; i++) { + if (read(fd, mem, img->width * bpp) != img->width * bpp) { + close(fd); + return false; + } + mem += chromaStride * bpp; + } + close(fd); + return true; +} + +class JpegRTest : public testing::Test { +public: + JpegRTest(); + ~JpegRTest(); + +protected: + virtual void SetUp(); + virtual void TearDown(); + + struct jpegr_uncompressed_struct mRawP010Image{}; + struct jpegr_uncompressed_struct mRawP010ImageWithStride{}; + struct jpegr_uncompressed_struct mRawP010ImageWithChromaData{}; + struct jpegr_uncompressed_struct mRawYuv420Image{}; + struct jpegr_compressed_struct mJpegImage{}; +}; + +JpegRTest::JpegRTest() {} +JpegRTest::~JpegRTest() {} + +void JpegRTest::SetUp() {} +void JpegRTest::TearDown() { + free(mRawP010Image.data); + free(mRawP010Image.chroma_data); + free(mRawP010ImageWithStride.data); + free(mRawP010ImageWithStride.chroma_data); + free(mRawP010ImageWithChromaData.data); + free(mRawP010ImageWithChromaData.chroma_data); + free(mRawYuv420Image.data); + free(mJpegImage.data); +} + +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); + + Timer genRecMapTime; + + timerStart(&genRecMapTime); + 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); + } + timerStop(&genRecMapTime); + + ALOGE("Generate Gain Map:- Res = %i x %i, time = %f ms", + yuv420Image->width, yuv420Image->height, + elapsedTime(&genRecMapTime) / (kProfileCount * 1000.f)); + +} + +void JpegRBenchmark::BenchmarkApplyGainMap(jr_uncompressed_ptr yuv420Image, + jr_uncompressed_ptr map, + ultrahdr_metadata_ptr metadata, + jr_uncompressed_ptr dest) { + Timer applyRecMapTime; + + timerStart(&applyRecMapTime); + for (auto i = 0; i < kProfileCount; i++) { + ASSERT_EQ(OK, applyGainMap(yuv420Image, map, metadata, ULTRAHDR_OUTPUT_HDR_HLG, + metadata->maxContentBoost /* displayBoost */, dest)); + } + timerStop(&applyRecMapTime); + + ALOGE("Apply Gain Map:- Res = %i x %i, time = %f ms", + yuv420Image->width, yuv420Image->height, + elapsedTime(&applyRecMapTime) / (kProfileCount * 1000.f)); +} + +TEST_F(JpegRTest, build) { + // Force all of the gain map lib to be linked by calling all public functions. + JpegR jpegRCodec; + jpegRCodec.encodeJPEGR(nullptr, static_cast(0), nullptr, 0, nullptr); + jpegRCodec.encodeJPEGR(nullptr, nullptr, static_cast(0), + nullptr, 0, nullptr); + jpegRCodec.encodeJPEGR(nullptr, nullptr, nullptr, static_cast(0), + nullptr); + jpegRCodec.encodeJPEGR(nullptr, nullptr, static_cast(0), nullptr); + jpegRCodec.decodeJPEGR(nullptr, nullptr); +} + +/* Test Encode API-0 invalid arguments */ +TEST_F(JpegRTest, encodeAPI0ForInvalidArgs) { + int ret; + + // we are not really compressing anything so lets keep allocs to a minimum + jpegr_compressed_struct jpegR; + jpegR.maxLength = 16 * sizeof(uint8_t); + jpegR.data = malloc(jpegR.maxLength); + + JpegR jpegRCodec; + + // we are not really compressing anything so lets keep allocs to a minimum + mRawP010ImageWithStride.data = malloc(16); + mRawP010ImageWithStride.width = TEST_IMAGE_WIDTH; + mRawP010ImageWithStride.height = TEST_IMAGE_HEIGHT; + mRawP010ImageWithStride.luma_stride = TEST_IMAGE_STRIDE; + mRawP010ImageWithStride.colorGamut = ultrahdr_color_gamut::ULTRAHDR_COLORGAMUT_BT2100; + + // test quality factor + EXPECT_NE(OK, jpegRCodec.encodeJPEGR( + &mRawP010ImageWithStride, ultrahdr_transfer_function::ULTRAHDR_TF_HLG, &jpegR, + -1, nullptr)) << "fail, API allows bad jpeg quality factor"; + + EXPECT_NE(OK, jpegRCodec.encodeJPEGR( + &mRawP010ImageWithStride, ultrahdr_transfer_function::ULTRAHDR_TF_HLG, &jpegR, + 101, nullptr)) << "fail, API allows bad jpeg quality factor"; + + // test hdr transfer function + EXPECT_NE(OK, jpegRCodec.encodeJPEGR( + &mRawP010ImageWithStride, ultrahdr_transfer_function::ULTRAHDR_TF_UNSPECIFIED, &jpegR, + DEFAULT_JPEG_QUALITY, nullptr)) << "fail, API allows bad hdr transfer function"; + + EXPECT_NE(OK, jpegRCodec.encodeJPEGR( + &mRawP010ImageWithStride, + static_cast(ultrahdr_transfer_function::ULTRAHDR_TF_MAX + 1), + &jpegR, DEFAULT_JPEG_QUALITY, nullptr)) << "fail, API allows bad hdr transfer function"; + + EXPECT_NE(OK, jpegRCodec.encodeJPEGR( + &mRawP010ImageWithStride, + static_cast(-10), + &jpegR, DEFAULT_JPEG_QUALITY, nullptr)) << "fail, API allows bad hdr transfer function"; + + // test dest + EXPECT_NE(OK, jpegRCodec.encodeJPEGR( + &mRawP010ImageWithStride, ultrahdr_transfer_function::ULTRAHDR_TF_HLG, nullptr, + DEFAULT_JPEG_QUALITY, nullptr)) << "fail, API allows nullptr dest"; + + // test p010 input + EXPECT_NE(OK, jpegRCodec.encodeJPEGR( + nullptr, ultrahdr_transfer_function::ULTRAHDR_TF_HLG, &jpegR, + DEFAULT_JPEG_QUALITY, nullptr)) << "fail, API allows nullptr p010 image"; + + mRawP010ImageWithStride.width = TEST_IMAGE_WIDTH; + mRawP010ImageWithStride.height = TEST_IMAGE_HEIGHT; + mRawP010ImageWithStride.colorGamut = ultrahdr_color_gamut::ULTRAHDR_COLORGAMUT_UNSPECIFIED; + EXPECT_NE(OK, jpegRCodec.encodeJPEGR( + &mRawP010ImageWithStride, ultrahdr_transfer_function::ULTRAHDR_TF_HLG, &jpegR, + DEFAULT_JPEG_QUALITY, nullptr)) << "fail, API allows bad p010 color gamut"; + + mRawP010ImageWithStride.width = TEST_IMAGE_WIDTH; + mRawP010ImageWithStride.height = TEST_IMAGE_HEIGHT; + mRawP010ImageWithStride.colorGamut = static_cast( + ultrahdr_color_gamut::ULTRAHDR_COLORGAMUT_MAX + 1); + EXPECT_NE(OK, jpegRCodec.encodeJPEGR( + &mRawP010ImageWithStride, ultrahdr_transfer_function::ULTRAHDR_TF_HLG, &jpegR, + DEFAULT_JPEG_QUALITY, nullptr)) << "fail, API allows bad p010 color gamut"; + + mRawP010ImageWithStride.width = TEST_IMAGE_WIDTH - 1; + mRawP010ImageWithStride.height = TEST_IMAGE_HEIGHT; + mRawP010ImageWithStride.colorGamut = ultrahdr_color_gamut::ULTRAHDR_COLORGAMUT_BT2100; + EXPECT_NE(OK, jpegRCodec.encodeJPEGR( + &mRawP010ImageWithStride, ultrahdr_transfer_function::ULTRAHDR_TF_HLG, &jpegR, + DEFAULT_JPEG_QUALITY, nullptr)) << "fail, API allows bad image width"; + + mRawP010ImageWithStride.width = TEST_IMAGE_WIDTH; + mRawP010ImageWithStride.height = TEST_IMAGE_HEIGHT - 1; + EXPECT_NE(OK, jpegRCodec.encodeJPEGR( + &mRawP010ImageWithStride, ultrahdr_transfer_function::ULTRAHDR_TF_HLG, &jpegR, + DEFAULT_JPEG_QUALITY, nullptr)) << "fail, API allows bad image height"; + + mRawP010ImageWithStride.width = 0; + mRawP010ImageWithStride.height = TEST_IMAGE_HEIGHT; + EXPECT_NE(OK, jpegRCodec.encodeJPEGR( + &mRawP010ImageWithStride, ultrahdr_transfer_function::ULTRAHDR_TF_HLG, &jpegR, + DEFAULT_JPEG_QUALITY, nullptr)) << "fail, API allows bad image width"; + + mRawP010ImageWithStride.width = TEST_IMAGE_WIDTH; + mRawP010ImageWithStride.height = 0; + EXPECT_NE(OK, jpegRCodec.encodeJPEGR( + &mRawP010ImageWithStride, ultrahdr_transfer_function::ULTRAHDR_TF_HLG, &jpegR, + DEFAULT_JPEG_QUALITY, nullptr)) << "fail, API allows bad image height"; + + mRawP010ImageWithStride.width = TEST_IMAGE_WIDTH; + mRawP010ImageWithStride.height = TEST_IMAGE_HEIGHT; + mRawP010ImageWithStride.luma_stride = TEST_IMAGE_WIDTH - 2; + EXPECT_NE(OK, jpegRCodec.encodeJPEGR( + &mRawP010ImageWithStride, ultrahdr_transfer_function::ULTRAHDR_TF_HLG, &jpegR, + DEFAULT_JPEG_QUALITY, nullptr)) << "fail, API allows bad luma stride"; + + mRawP010ImageWithStride.width = TEST_IMAGE_WIDTH; + mRawP010ImageWithStride.height = TEST_IMAGE_HEIGHT; + mRawP010ImageWithStride.luma_stride = TEST_IMAGE_STRIDE; + mRawP010ImageWithStride.chroma_data = mRawP010ImageWithStride.data; + mRawP010ImageWithStride.chroma_stride = TEST_IMAGE_WIDTH - 2; + EXPECT_NE(OK, jpegRCodec.encodeJPEGR( + &mRawP010ImageWithStride, ultrahdr_transfer_function::ULTRAHDR_TF_HLG, &jpegR, + DEFAULT_JPEG_QUALITY, nullptr)) << "fail, API allows bad chroma stride"; + + mRawP010ImageWithStride.chroma_data = nullptr; + + free(jpegR.data); +} + +/* Test Encode API-1 invalid arguments */ +TEST_F(JpegRTest, encodeAPI1ForInvalidArgs) { + int ret; + + // we are not really compressing anything so lets keep allocs to a minimum + jpegr_compressed_struct jpegR; + jpegR.maxLength = 16 * sizeof(uint8_t); + jpegR.data = malloc(jpegR.maxLength); + + JpegR jpegRCodec; + + // we are not really compressing anything so lets keep allocs to a minimum + mRawP010ImageWithStride.data = malloc(16); + mRawP010ImageWithStride.width = TEST_IMAGE_WIDTH; + mRawP010ImageWithStride.height = TEST_IMAGE_HEIGHT; + mRawP010ImageWithStride.luma_stride = TEST_IMAGE_STRIDE; + mRawP010ImageWithStride.colorGamut = ultrahdr_color_gamut::ULTRAHDR_COLORGAMUT_BT2100; + + // we are not really compressing anything so lets keep allocs to a minimum + mRawYuv420Image.data = malloc(16); + mRawYuv420Image.width = TEST_IMAGE_WIDTH; + mRawYuv420Image.height = TEST_IMAGE_HEIGHT; + mRawYuv420Image.colorGamut = ultrahdr_color_gamut::ULTRAHDR_COLORGAMUT_BT709; + + // test quality factor + EXPECT_NE(OK, jpegRCodec.encodeJPEGR( + &mRawP010ImageWithStride, &mRawYuv420Image, ultrahdr_transfer_function::ULTRAHDR_TF_HLG, + &jpegR, -1, nullptr)) << "fail, API allows bad jpeg quality factor"; + + EXPECT_NE(OK, jpegRCodec.encodeJPEGR( + &mRawP010ImageWithStride, &mRawYuv420Image, ultrahdr_transfer_function::ULTRAHDR_TF_HLG, + &jpegR, 101, nullptr)) << "fail, API allows bad jpeg quality factor"; + + // test hdr transfer function + EXPECT_NE(OK, jpegRCodec.encodeJPEGR( + &mRawP010ImageWithStride, &mRawYuv420Image, + ultrahdr_transfer_function::ULTRAHDR_TF_UNSPECIFIED, &jpegR, DEFAULT_JPEG_QUALITY, + nullptr)) << "fail, API allows bad hdr transfer function"; + + EXPECT_NE(OK, jpegRCodec.encodeJPEGR( + &mRawP010ImageWithStride, &mRawYuv420Image, + static_cast(ultrahdr_transfer_function::ULTRAHDR_TF_MAX + 1), + &jpegR, DEFAULT_JPEG_QUALITY, nullptr)) << "fail, API allows bad hdr transfer function"; + + EXPECT_NE(OK, jpegRCodec.encodeJPEGR( + &mRawP010ImageWithStride, &mRawYuv420Image, + static_cast(-10), + &jpegR, DEFAULT_JPEG_QUALITY, nullptr)) << "fail, API allows bad hdr transfer function"; + + // test dest + EXPECT_NE(OK, jpegRCodec.encodeJPEGR( + &mRawP010ImageWithStride, &mRawYuv420Image, ultrahdr_transfer_function::ULTRAHDR_TF_HLG, + nullptr, DEFAULT_JPEG_QUALITY, nullptr)) << "fail, API allows nullptr dest"; + + // test p010 input + EXPECT_NE(OK, jpegRCodec.encodeJPEGR( + nullptr, &mRawYuv420Image, ultrahdr_transfer_function::ULTRAHDR_TF_HLG, &jpegR, + DEFAULT_JPEG_QUALITY, nullptr)) << "fail, API allows nullptr p010 image"; + + mRawP010ImageWithStride.width = TEST_IMAGE_WIDTH; + mRawP010ImageWithStride.height = TEST_IMAGE_HEIGHT; + mRawP010ImageWithStride.colorGamut = ultrahdr_color_gamut::ULTRAHDR_COLORGAMUT_UNSPECIFIED; + EXPECT_NE(OK, jpegRCodec.encodeJPEGR( + &mRawP010ImageWithStride, &mRawYuv420Image, ultrahdr_transfer_function::ULTRAHDR_TF_HLG, + &jpegR, DEFAULT_JPEG_QUALITY, nullptr)) << "fail, API allows bad p010 color gamut"; + + mRawP010ImageWithStride.width = TEST_IMAGE_WIDTH; + mRawP010ImageWithStride.height = TEST_IMAGE_HEIGHT; + mRawP010ImageWithStride.colorGamut = static_cast( + ultrahdr_color_gamut::ULTRAHDR_COLORGAMUT_MAX + 1); + EXPECT_NE(OK, jpegRCodec.encodeJPEGR( + &mRawP010ImageWithStride, &mRawYuv420Image, ultrahdr_transfer_function::ULTRAHDR_TF_HLG, + &jpegR, DEFAULT_JPEG_QUALITY, nullptr)) << "fail, API allows bad p010 color gamut"; + + mRawP010ImageWithStride.width = TEST_IMAGE_WIDTH - 1; + mRawP010ImageWithStride.height = TEST_IMAGE_HEIGHT; + mRawP010ImageWithStride.colorGamut = ultrahdr_color_gamut::ULTRAHDR_COLORGAMUT_BT2100; + EXPECT_NE(OK, jpegRCodec.encodeJPEGR( + &mRawP010ImageWithStride, &mRawYuv420Image, ultrahdr_transfer_function::ULTRAHDR_TF_HLG, + &jpegR, DEFAULT_JPEG_QUALITY, nullptr)) << "fail, API allows bad image width"; + + mRawP010ImageWithStride.width = TEST_IMAGE_WIDTH; + mRawP010ImageWithStride.height = TEST_IMAGE_HEIGHT - 1; + EXPECT_NE(OK, jpegRCodec.encodeJPEGR( + &mRawP010ImageWithStride, &mRawYuv420Image, ultrahdr_transfer_function::ULTRAHDR_TF_HLG, + &jpegR, DEFAULT_JPEG_QUALITY, nullptr)) << "fail, API allows bad image height"; + + mRawP010ImageWithStride.width = 0; + mRawP010ImageWithStride.height = TEST_IMAGE_HEIGHT; + EXPECT_NE(OK, jpegRCodec.encodeJPEGR( + &mRawP010ImageWithStride, &mRawYuv420Image, ultrahdr_transfer_function::ULTRAHDR_TF_HLG, + &jpegR, DEFAULT_JPEG_QUALITY, nullptr)) << "fail, API allows bad image width"; + + mRawP010ImageWithStride.width = TEST_IMAGE_WIDTH; + mRawP010ImageWithStride.height = 0; + EXPECT_NE(OK, jpegRCodec.encodeJPEGR( + &mRawP010ImageWithStride, &mRawYuv420Image, ultrahdr_transfer_function::ULTRAHDR_TF_HLG, + &jpegR, DEFAULT_JPEG_QUALITY, nullptr)) << "fail, API allows bad image height"; + + mRawP010ImageWithStride.width = TEST_IMAGE_WIDTH; + mRawP010ImageWithStride.height = TEST_IMAGE_HEIGHT; + mRawP010ImageWithStride.luma_stride = TEST_IMAGE_WIDTH - 2; + EXPECT_NE(OK, jpegRCodec.encodeJPEGR( + &mRawP010ImageWithStride, &mRawYuv420Image, ultrahdr_transfer_function::ULTRAHDR_TF_HLG, + &jpegR, DEFAULT_JPEG_QUALITY, nullptr)) << "fail, API allows bad luma stride"; + + mRawP010ImageWithStride.width = TEST_IMAGE_WIDTH; + mRawP010ImageWithStride.height = TEST_IMAGE_HEIGHT; + mRawP010ImageWithStride.luma_stride = TEST_IMAGE_STRIDE; + mRawP010ImageWithStride.chroma_data = mRawP010ImageWithStride.data; + mRawP010ImageWithStride.chroma_stride = TEST_IMAGE_WIDTH - 2; + EXPECT_NE(OK, jpegRCodec.encodeJPEGR( + &mRawP010ImageWithStride, &mRawYuv420Image, ultrahdr_transfer_function::ULTRAHDR_TF_HLG, + &jpegR, DEFAULT_JPEG_QUALITY, nullptr)) << "fail, API allows bad chroma stride"; + + // test 420 input + mRawP010ImageWithStride.width = TEST_IMAGE_WIDTH; + mRawP010ImageWithStride.height = TEST_IMAGE_HEIGHT; + mRawP010ImageWithStride.luma_stride = TEST_IMAGE_STRIDE; + mRawP010ImageWithStride.chroma_data = nullptr; + mRawP010ImageWithStride.chroma_stride = 0; + EXPECT_NE(OK, jpegRCodec.encodeJPEGR( + &mRawP010ImageWithStride, nullptr, ultrahdr_transfer_function::ULTRAHDR_TF_HLG, &jpegR, + DEFAULT_JPEG_QUALITY, nullptr)) << "fail, API allows nullptr for 420 image"; + + mRawYuv420Image.width = TEST_IMAGE_WIDTH; + mRawYuv420Image.height = TEST_IMAGE_HEIGHT - 2; + EXPECT_NE(OK, jpegRCodec.encodeJPEGR( + &mRawP010ImageWithStride, &mRawYuv420Image, ultrahdr_transfer_function::ULTRAHDR_TF_HLG, + &jpegR, DEFAULT_JPEG_QUALITY, nullptr)) << "fail, API allows bad 420 image width"; + + mRawYuv420Image.width = TEST_IMAGE_WIDTH - 2; + mRawYuv420Image.height = TEST_IMAGE_HEIGHT; + EXPECT_NE(OK, jpegRCodec.encodeJPEGR( + &mRawP010ImageWithStride, &mRawYuv420Image, ultrahdr_transfer_function::ULTRAHDR_TF_HLG, + &jpegR, DEFAULT_JPEG_QUALITY, nullptr)) << "fail, API allows bad 420 image height"; + + mRawYuv420Image.width = TEST_IMAGE_WIDTH; + mRawYuv420Image.height = TEST_IMAGE_HEIGHT; + mRawYuv420Image.luma_stride = TEST_IMAGE_STRIDE; + EXPECT_NE(OK, jpegRCodec.encodeJPEGR( + &mRawP010ImageWithStride, &mRawYuv420Image, ultrahdr_transfer_function::ULTRAHDR_TF_HLG, + &jpegR, DEFAULT_JPEG_QUALITY, nullptr)) << "fail, API allows bad luma stride for 420"; + + mRawYuv420Image.width = TEST_IMAGE_WIDTH; + mRawYuv420Image.height = TEST_IMAGE_HEIGHT; + mRawYuv420Image.luma_stride = 0; + mRawYuv420Image.chroma_data = mRawYuv420Image.data; + EXPECT_NE(OK, jpegRCodec.encodeJPEGR( + &mRawP010ImageWithStride, &mRawYuv420Image, ultrahdr_transfer_function::ULTRAHDR_TF_HLG, + &jpegR, DEFAULT_JPEG_QUALITY, nullptr)) << "fail, API allows chroma pointer for 420"; + + mRawYuv420Image.width = TEST_IMAGE_WIDTH; + mRawYuv420Image.height = TEST_IMAGE_HEIGHT; + mRawYuv420Image.luma_stride = 0; + mRawYuv420Image.chroma_data = nullptr; + mRawYuv420Image.colorGamut = ultrahdr_color_gamut::ULTRAHDR_COLORGAMUT_UNSPECIFIED; + EXPECT_NE(OK, jpegRCodec.encodeJPEGR( + &mRawP010ImageWithStride, &mRawYuv420Image, ultrahdr_transfer_function::ULTRAHDR_TF_HLG, + &jpegR, DEFAULT_JPEG_QUALITY, nullptr)) << "fail, API allows bad 420 color gamut"; + + mRawYuv420Image.colorGamut = static_cast( + ultrahdr_color_gamut::ULTRAHDR_COLORGAMUT_MAX + 1); + EXPECT_NE(OK, jpegRCodec.encodeJPEGR( + &mRawP010ImageWithStride, &mRawYuv420Image, ultrahdr_transfer_function::ULTRAHDR_TF_HLG, + &jpegR, DEFAULT_JPEG_QUALITY, nullptr)) << "fail, API allows bad 420 color gamut"; + + free(jpegR.data); +} + +/* Test Encode API-2 invalid arguments */ +TEST_F(JpegRTest, encodeAPI2ForInvalidArgs) { + int ret; + + // we are not really compressing anything so lets keep allocs to a minimum + jpegr_compressed_struct jpegR; + jpegR.maxLength = 16 * sizeof(uint8_t); + jpegR.data = malloc(jpegR.maxLength); + + JpegR jpegRCodec; + + // we are not really compressing anything so lets keep allocs to a minimum + mRawP010ImageWithStride.data = malloc(16); + mRawP010ImageWithStride.width = TEST_IMAGE_WIDTH; + mRawP010ImageWithStride.height = TEST_IMAGE_HEIGHT; + mRawP010ImageWithStride.luma_stride = TEST_IMAGE_STRIDE; + mRawP010ImageWithStride.colorGamut = ultrahdr_color_gamut::ULTRAHDR_COLORGAMUT_BT2100; + + // we are not really compressing anything so lets keep allocs to a minimum + mRawYuv420Image.data = malloc(16); + mRawYuv420Image.width = TEST_IMAGE_WIDTH; + mRawYuv420Image.height = TEST_IMAGE_HEIGHT; + mRawYuv420Image.colorGamut = ultrahdr_color_gamut::ULTRAHDR_COLORGAMUT_BT709; + + // test hdr transfer function + EXPECT_NE(OK, jpegRCodec.encodeJPEGR( + &mRawP010ImageWithStride, &mRawYuv420Image, &jpegR, + ultrahdr_transfer_function::ULTRAHDR_TF_UNSPECIFIED, + &jpegR)) << "fail, API allows bad hdr transfer function"; + + EXPECT_NE(OK, jpegRCodec.encodeJPEGR( + &mRawP010ImageWithStride, &mRawYuv420Image, &jpegR, + static_cast(ultrahdr_transfer_function::ULTRAHDR_TF_MAX + 1), + &jpegR)) << "fail, API allows bad hdr transfer function"; + + EXPECT_NE(OK, jpegRCodec.encodeJPEGR( + &mRawP010ImageWithStride, &mRawYuv420Image, &jpegR, + static_cast(-10), + &jpegR)) << "fail, API allows bad hdr transfer function"; + + // test dest + EXPECT_NE(OK, jpegRCodec.encodeJPEGR( + &mRawP010ImageWithStride, &mRawYuv420Image, &jpegR, + ultrahdr_transfer_function::ULTRAHDR_TF_HLG, nullptr)) << "fail, API allows nullptr dest"; + + // test p010 input + EXPECT_NE(OK, jpegRCodec.encodeJPEGR( + nullptr, &mRawYuv420Image, &jpegR, ultrahdr_transfer_function::ULTRAHDR_TF_HLG, + &jpegR)) << "fail, API allows nullptr p010 image"; + + mRawP010ImageWithStride.width = TEST_IMAGE_WIDTH; + mRawP010ImageWithStride.height = TEST_IMAGE_HEIGHT; + mRawP010ImageWithStride.colorGamut = ultrahdr_color_gamut::ULTRAHDR_COLORGAMUT_UNSPECIFIED; + EXPECT_NE(OK, jpegRCodec.encodeJPEGR( + &mRawP010ImageWithStride, &mRawYuv420Image, &jpegR, + ultrahdr_transfer_function::ULTRAHDR_TF_HLG, + &jpegR)) << "fail, API allows bad p010 color gamut"; + + mRawP010ImageWithStride.width = TEST_IMAGE_WIDTH; + mRawP010ImageWithStride.height = TEST_IMAGE_HEIGHT; + mRawP010ImageWithStride.colorGamut = static_cast( + ultrahdr_color_gamut::ULTRAHDR_COLORGAMUT_MAX + 1); + EXPECT_NE(OK, jpegRCodec.encodeJPEGR( + &mRawP010ImageWithStride, &mRawYuv420Image, &jpegR, + ultrahdr_transfer_function::ULTRAHDR_TF_HLG, + &jpegR)) << "fail, API allows bad p010 color gamut"; + + mRawP010ImageWithStride.width = TEST_IMAGE_WIDTH - 1; + mRawP010ImageWithStride.height = TEST_IMAGE_HEIGHT; + mRawP010ImageWithStride.colorGamut = ultrahdr_color_gamut::ULTRAHDR_COLORGAMUT_BT2100; + EXPECT_NE(OK, jpegRCodec.encodeJPEGR( + &mRawP010ImageWithStride, &mRawYuv420Image, &jpegR, + ultrahdr_transfer_function::ULTRAHDR_TF_HLG, &jpegR)) << "fail, API allows bad image width"; + + mRawP010ImageWithStride.width = TEST_IMAGE_WIDTH; + mRawP010ImageWithStride.height = TEST_IMAGE_HEIGHT - 1; + EXPECT_NE(OK, jpegRCodec.encodeJPEGR( + &mRawP010ImageWithStride, &mRawYuv420Image, &jpegR, + ultrahdr_transfer_function::ULTRAHDR_TF_HLG, &jpegR)) << "fail, API allows bad image height"; + + mRawP010ImageWithStride.width = 0; + mRawP010ImageWithStride.height = TEST_IMAGE_HEIGHT; + EXPECT_NE(OK, jpegRCodec.encodeJPEGR( + &mRawP010ImageWithStride, &mRawYuv420Image, &jpegR, + ultrahdr_transfer_function::ULTRAHDR_TF_HLG, &jpegR)) << "fail, API allows bad image width"; + + mRawP010ImageWithStride.width = TEST_IMAGE_WIDTH; + mRawP010ImageWithStride.height = 0; + EXPECT_NE(OK, jpegRCodec.encodeJPEGR( + &mRawP010ImageWithStride, &mRawYuv420Image, &jpegR, + ultrahdr_transfer_function::ULTRAHDR_TF_HLG, &jpegR)) << "fail, API allows bad image height"; + + mRawP010ImageWithStride.width = TEST_IMAGE_WIDTH; + mRawP010ImageWithStride.height = TEST_IMAGE_HEIGHT; + mRawP010ImageWithStride.luma_stride = TEST_IMAGE_WIDTH - 2; + EXPECT_NE(OK, jpegRCodec.encodeJPEGR( + &mRawP010ImageWithStride, &mRawYuv420Image, &jpegR, + ultrahdr_transfer_function::ULTRAHDR_TF_HLG, &jpegR)) << "fail, API allows bad luma stride"; + + mRawP010ImageWithStride.width = TEST_IMAGE_WIDTH; + mRawP010ImageWithStride.height = TEST_IMAGE_HEIGHT; + mRawP010ImageWithStride.luma_stride = TEST_IMAGE_STRIDE; + mRawP010ImageWithStride.chroma_data = mRawP010ImageWithStride.data; + mRawP010ImageWithStride.chroma_stride = TEST_IMAGE_WIDTH - 2; + EXPECT_NE(OK, jpegRCodec.encodeJPEGR( + &mRawP010ImageWithStride, &mRawYuv420Image, &jpegR, + ultrahdr_transfer_function::ULTRAHDR_TF_HLG, + &jpegR)) << "fail, API allows bad chroma stride"; + + // test 420 input + mRawP010ImageWithStride.width = TEST_IMAGE_WIDTH; + mRawP010ImageWithStride.height = TEST_IMAGE_HEIGHT; + mRawP010ImageWithStride.luma_stride = TEST_IMAGE_STRIDE; + mRawP010ImageWithStride.chroma_data = nullptr; + mRawP010ImageWithStride.chroma_stride = 0; + EXPECT_NE(OK, jpegRCodec.encodeJPEGR( + &mRawP010ImageWithStride, nullptr, &jpegR, + ultrahdr_transfer_function::ULTRAHDR_TF_HLG, + &jpegR)) << "fail, API allows nullptr for 420 image"; + + mRawYuv420Image.width = TEST_IMAGE_WIDTH; + mRawYuv420Image.height = TEST_IMAGE_HEIGHT - 2; + EXPECT_NE(OK, jpegRCodec.encodeJPEGR( + &mRawP010ImageWithStride, &mRawYuv420Image, &jpegR, + ultrahdr_transfer_function::ULTRAHDR_TF_HLG, + &jpegR)) << "fail, API allows bad 420 image width"; + + mRawYuv420Image.width = TEST_IMAGE_WIDTH - 2; + mRawYuv420Image.height = TEST_IMAGE_HEIGHT; + EXPECT_NE(OK, jpegRCodec.encodeJPEGR( + &mRawP010ImageWithStride, &mRawYuv420Image, &jpegR, + ultrahdr_transfer_function::ULTRAHDR_TF_HLG, + &jpegR)) << "fail, API allows bad 420 image height"; + + mRawYuv420Image.width = TEST_IMAGE_WIDTH; + mRawYuv420Image.height = TEST_IMAGE_HEIGHT; + mRawYuv420Image.luma_stride = TEST_IMAGE_STRIDE; + EXPECT_NE(OK, jpegRCodec.encodeJPEGR( + &mRawP010ImageWithStride, &mRawYuv420Image, &jpegR, + ultrahdr_transfer_function::ULTRAHDR_TF_HLG, + &jpegR)) << "fail, API allows bad luma stride for 420"; + + mRawYuv420Image.width = TEST_IMAGE_WIDTH; + mRawYuv420Image.height = TEST_IMAGE_HEIGHT; + mRawYuv420Image.luma_stride = 0; + mRawYuv420Image.chroma_data = mRawYuv420Image.data; + EXPECT_NE(OK, jpegRCodec.encodeJPEGR( + &mRawP010ImageWithStride, &mRawYuv420Image, &jpegR, + ultrahdr_transfer_function::ULTRAHDR_TF_HLG, + &jpegR)) << "fail, API allows chroma pointer for 420"; + + mRawYuv420Image.width = TEST_IMAGE_WIDTH; + mRawYuv420Image.height = TEST_IMAGE_HEIGHT; + mRawYuv420Image.luma_stride = 0; + mRawYuv420Image.chroma_data = nullptr; + mRawYuv420Image.colorGamut = ultrahdr_color_gamut::ULTRAHDR_COLORGAMUT_UNSPECIFIED; + EXPECT_NE(OK, jpegRCodec.encodeJPEGR( + &mRawP010ImageWithStride, &mRawYuv420Image, &jpegR, + ultrahdr_transfer_function::ULTRAHDR_TF_HLG, + &jpegR)) << "fail, API allows bad 420 color gamut"; + + mRawYuv420Image.colorGamut = static_cast( + ultrahdr_color_gamut::ULTRAHDR_COLORGAMUT_MAX + 1); + EXPECT_NE(OK, jpegRCodec.encodeJPEGR( + &mRawP010ImageWithStride, &mRawYuv420Image, &jpegR, + ultrahdr_transfer_function::ULTRAHDR_TF_HLG, + &jpegR)) << "fail, API allows bad 420 color gamut"; + + // bad compressed image + mRawYuv420Image.colorGamut = ultrahdr_color_gamut::ULTRAHDR_COLORGAMUT_BT709; + EXPECT_NE(OK, jpegRCodec.encodeJPEGR( + &mRawP010ImageWithStride, &mRawYuv420Image, nullptr, + ultrahdr_transfer_function::ULTRAHDR_TF_HLG, + &jpegR)) << "fail, API allows bad 420 color gamut"; + + free(jpegR.data); +} + +/* Test Encode API-3 invalid arguments */ +TEST_F(JpegRTest, encodeAPI3ForInvalidArgs) { + int ret; + + // we are not really compressing anything so lets keep allocs to a minimum + jpegr_compressed_struct jpegR; + jpegR.maxLength = 16 * sizeof(uint8_t); + jpegR.data = malloc(jpegR.maxLength); + + JpegR jpegRCodec; + + // we are not really compressing anything so lets keep allocs to a minimum + mRawP010ImageWithStride.data = malloc(16); + mRawP010ImageWithStride.width = TEST_IMAGE_WIDTH; + mRawP010ImageWithStride.height = TEST_IMAGE_HEIGHT; + mRawP010ImageWithStride.luma_stride = TEST_IMAGE_STRIDE; + mRawP010ImageWithStride.colorGamut = ultrahdr_color_gamut::ULTRAHDR_COLORGAMUT_BT2100; + + // test hdr transfer function + EXPECT_NE(OK, jpegRCodec.encodeJPEGR( + &mRawP010ImageWithStride, &jpegR, ultrahdr_transfer_function::ULTRAHDR_TF_UNSPECIFIED, + &jpegR)) << "fail, API allows bad hdr transfer function"; + + EXPECT_NE(OK, jpegRCodec.encodeJPEGR( + &mRawP010ImageWithStride, &jpegR, + static_cast(ultrahdr_transfer_function::ULTRAHDR_TF_MAX + 1), + &jpegR)) << "fail, API allows bad hdr transfer function"; + + EXPECT_NE(OK, jpegRCodec.encodeJPEGR( + &mRawP010ImageWithStride, &jpegR, static_cast(-10), + &jpegR)) << "fail, API allows bad hdr transfer function"; + + // test dest + EXPECT_NE(OK, jpegRCodec.encodeJPEGR( + &mRawP010ImageWithStride, &jpegR, ultrahdr_transfer_function::ULTRAHDR_TF_HLG, + nullptr)) << "fail, API allows nullptr dest"; + + // test p010 input + EXPECT_NE(OK, jpegRCodec.encodeJPEGR( + nullptr, &jpegR, ultrahdr_transfer_function::ULTRAHDR_TF_HLG, + &jpegR)) << "fail, API allows nullptr p010 image"; + + mRawP010ImageWithStride.width = TEST_IMAGE_WIDTH; + mRawP010ImageWithStride.height = TEST_IMAGE_HEIGHT; + mRawP010ImageWithStride.colorGamut = ultrahdr_color_gamut::ULTRAHDR_COLORGAMUT_UNSPECIFIED; + EXPECT_NE(OK, jpegRCodec.encodeJPEGR( + &mRawP010ImageWithStride, &jpegR, ultrahdr_transfer_function::ULTRAHDR_TF_HLG, + &jpegR)) << "fail, API allows bad p010 color gamut"; + + mRawP010ImageWithStride.width = TEST_IMAGE_WIDTH; + mRawP010ImageWithStride.height = TEST_IMAGE_HEIGHT; + mRawP010ImageWithStride.colorGamut = static_cast( + ultrahdr_color_gamut::ULTRAHDR_COLORGAMUT_MAX + 1); + EXPECT_NE(OK, jpegRCodec.encodeJPEGR( + &mRawP010ImageWithStride, &jpegR, ultrahdr_transfer_function::ULTRAHDR_TF_HLG, + &jpegR)) << "fail, API allows bad p010 color gamut"; + + mRawP010ImageWithStride.width = TEST_IMAGE_WIDTH - 1; + mRawP010ImageWithStride.height = TEST_IMAGE_HEIGHT; + mRawP010ImageWithStride.colorGamut = ultrahdr_color_gamut::ULTRAHDR_COLORGAMUT_BT2100; + EXPECT_NE(OK, jpegRCodec.encodeJPEGR( + &mRawP010ImageWithStride, &jpegR, ultrahdr_transfer_function::ULTRAHDR_TF_HLG, + &jpegR)) << "fail, API allows bad image width"; + + mRawP010ImageWithStride.width = TEST_IMAGE_WIDTH; + mRawP010ImageWithStride.height = TEST_IMAGE_HEIGHT - 1; + EXPECT_NE(OK, jpegRCodec.encodeJPEGR( + &mRawP010ImageWithStride, &jpegR, ultrahdr_transfer_function::ULTRAHDR_TF_HLG, + &jpegR)) << "fail, API allows bad image height"; + + mRawP010ImageWithStride.width = 0; + mRawP010ImageWithStride.height = TEST_IMAGE_HEIGHT; + EXPECT_NE(OK, jpegRCodec.encodeJPEGR( + &mRawP010ImageWithStride, &jpegR, ultrahdr_transfer_function::ULTRAHDR_TF_HLG, + &jpegR)) << "fail, API allows bad image width"; + + mRawP010ImageWithStride.width = TEST_IMAGE_WIDTH; + mRawP010ImageWithStride.height = 0; + EXPECT_NE(OK, jpegRCodec.encodeJPEGR( + &mRawP010ImageWithStride, &jpegR, ultrahdr_transfer_function::ULTRAHDR_TF_HLG, + &jpegR)) << "fail, API allows bad image height"; + + mRawP010ImageWithStride.width = TEST_IMAGE_WIDTH; + mRawP010ImageWithStride.height = TEST_IMAGE_HEIGHT; + mRawP010ImageWithStride.luma_stride = TEST_IMAGE_WIDTH - 2; + EXPECT_NE(OK, jpegRCodec.encodeJPEGR( + &mRawP010ImageWithStride, &jpegR, ultrahdr_transfer_function::ULTRAHDR_TF_HLG, + &jpegR)) << "fail, API allows bad luma stride"; + + mRawP010ImageWithStride.width = TEST_IMAGE_WIDTH; + mRawP010ImageWithStride.height = TEST_IMAGE_HEIGHT; + mRawP010ImageWithStride.luma_stride = TEST_IMAGE_STRIDE; + mRawP010ImageWithStride.chroma_data = mRawP010ImageWithStride.data; + mRawP010ImageWithStride.chroma_stride = TEST_IMAGE_WIDTH - 2; + EXPECT_NE(OK, jpegRCodec.encodeJPEGR( + &mRawP010ImageWithStride, &jpegR, ultrahdr_transfer_function::ULTRAHDR_TF_HLG, + &jpegR)) << "fail, API allows bad chroma stride"; + mRawP010ImageWithStride.chroma_data = nullptr; + + // bad compressed image + EXPECT_NE(OK, jpegRCodec.encodeJPEGR( + &mRawP010ImageWithStride, nullptr, ultrahdr_transfer_function::ULTRAHDR_TF_HLG, + &jpegR)) << "fail, API allows bad 420 color gamut"; + + free(jpegR.data); +} + +/* Test Encode API-4 invalid arguments */ +TEST_F(JpegRTest, encodeAPI4ForInvalidArgs) { + int ret; + + // we are not really compressing anything so lets keep allocs to a minimum + jpegr_compressed_struct jpegR; + jpegR.maxLength = 16 * sizeof(uint8_t); + jpegR.data = malloc(jpegR.maxLength); + + JpegR jpegRCodec; + + // test dest + EXPECT_NE(OK, jpegRCodec.encodeJPEGR( + &jpegR, &jpegR, nullptr, nullptr)) << "fail, API allows nullptr dest"; + + // test primary image + EXPECT_NE(OK, jpegRCodec.encodeJPEGR( + nullptr, &jpegR, nullptr, &jpegR)) << "fail, API allows nullptr primary image"; + + // test gain map + EXPECT_NE(OK, jpegRCodec.encodeJPEGR( + &jpegR, nullptr, nullptr, &jpegR)) << "fail, API allows nullptr gainmap 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"; + EXPECT_NE(OK, jpegRCodec.encodeJPEGR( + &jpegR, nullptr, &metadata, &jpegR)) << "fail, API allows bad metadata version"; + + metadata = good_metadata; + metadata.minContentBoost = 3.0f; + EXPECT_NE(OK, jpegRCodec.encodeJPEGR( + &jpegR, nullptr, &metadata, &jpegR)) << "fail, API allows bad metadata content boost"; + + metadata = good_metadata; + metadata.gamma = -0.1f; + EXPECT_NE(OK, jpegRCodec.encodeJPEGR( + &jpegR, nullptr, &metadata, &jpegR)) << "fail, API allows bad metadata gamma"; + + metadata = good_metadata; + metadata.offsetSdr = -0.1f; + EXPECT_NE(OK, jpegRCodec.encodeJPEGR( + &jpegR, nullptr, &metadata, &jpegR)) << "fail, API allows bad metadata offset sdr"; + + metadata = good_metadata; + metadata.offsetHdr = -0.1f; + EXPECT_NE(OK, jpegRCodec.encodeJPEGR( + &jpegR, nullptr, &metadata, &jpegR)) << "fail, API allows bad metadata offset hdr"; + + metadata = good_metadata; + metadata.hdrCapacityMax = 0.5f; + EXPECT_NE(OK, jpegRCodec.encodeJPEGR( + &jpegR, nullptr, &metadata, &jpegR)) << "fail, API allows bad metadata hdr capacity max"; + + metadata = good_metadata; + metadata.hdrCapacityMin = 0.5f; + EXPECT_NE(OK, jpegRCodec.encodeJPEGR( + &jpegR, nullptr, &metadata, &jpegR)) << "fail, API allows bad metadata hdr capacity min"; + + free(jpegR.data); +} + +/* Test Decode API invalid arguments */ +TEST_F(JpegRTest, decodeAPIForInvalidArgs) { + int ret; + + // we are not really compressing anything so lets keep allocs to a minimum + jpegr_compressed_struct jpegR; + jpegR.maxLength = 16 * sizeof(uint8_t); + jpegR.data = malloc(jpegR.maxLength); + + // we are not really decoding anything so lets keep allocs to a minimum + mRawP010Image.data = malloc(16); + + JpegR jpegRCodec; + + // test jpegr image + EXPECT_NE(OK, jpegRCodec.decodeJPEGR( + nullptr, &mRawP010Image)) << "fail, API allows nullptr for jpegr img"; + + // test dest image + EXPECT_NE(OK, jpegRCodec.decodeJPEGR( + &jpegR, nullptr)) << "fail, API allows nullptr for dest"; + + // test max display boost + EXPECT_NE(OK, jpegRCodec.decodeJPEGR( + &jpegR, &mRawP010Image, 0.5)) << "fail, API allows invalid max display boost"; + + // test output format + EXPECT_NE(OK, jpegRCodec.decodeJPEGR( + &jpegR, &mRawP010Image, 0.5, nullptr, + static_cast(-1))) << "fail, API allows invalid output format"; + + EXPECT_NE(OK, jpegRCodec.decodeJPEGR( + &jpegR, &mRawP010Image, 0.5, nullptr, + static_cast(ULTRAHDR_OUTPUT_MAX + 1))) + << "fail, API allows invalid output format"; + + free(jpegR.data); +} + +TEST_F(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); +} + +/* Test Encode API-0 */ +TEST_F(JpegRTest, encodeFromP010) { + int ret; + + mRawP010Image.width = TEST_IMAGE_WIDTH; + mRawP010Image.height = TEST_IMAGE_HEIGHT; + mRawP010Image.colorGamut = ultrahdr_color_gamut::ULTRAHDR_COLORGAMUT_BT2100; + // Load input files. + if (!loadP010Image(RAW_P010_IMAGE, &mRawP010Image, true)) { + FAIL() << "Load file " << RAW_P010_IMAGE << " failed"; + } + + JpegR jpegRCodec; + + jpegr_compressed_struct jpegR; + jpegR.maxLength = TEST_IMAGE_WIDTH * TEST_IMAGE_HEIGHT * sizeof(uint8_t); + jpegR.data = malloc(jpegR.maxLength); + ret = jpegRCodec.encodeJPEGR( + &mRawP010Image, ultrahdr_transfer_function::ULTRAHDR_TF_HLG, &jpegR, DEFAULT_JPEG_QUALITY, + nullptr); + if (ret != OK) { + FAIL() << "Error code is " << ret; + } + + mRawP010ImageWithStride.width = TEST_IMAGE_WIDTH; + mRawP010ImageWithStride.height = TEST_IMAGE_HEIGHT; + mRawP010ImageWithStride.luma_stride = TEST_IMAGE_WIDTH + 128; + mRawP010ImageWithStride.colorGamut = ultrahdr_color_gamut::ULTRAHDR_COLORGAMUT_BT2100; + // Load input files. + if (!loadP010Image(RAW_P010_IMAGE, &mRawP010ImageWithStride, true)) { + FAIL() << "Load file " << RAW_P010_IMAGE << " failed"; + } + + jpegr_compressed_struct jpegRWithStride; + jpegRWithStride.maxLength = jpegR.length; + jpegRWithStride.data = malloc(jpegRWithStride.maxLength); + ret = jpegRCodec.encodeJPEGR( + &mRawP010ImageWithStride, ultrahdr_transfer_function::ULTRAHDR_TF_HLG, &jpegRWithStride, + DEFAULT_JPEG_QUALITY, nullptr); + if (ret != OK) { + FAIL() << "Error code is " << ret; + } + ASSERT_EQ(jpegR.length, jpegRWithStride.length) + << "Same input is yielding different output"; + ASSERT_EQ(0, memcmp(jpegR.data, jpegRWithStride.data, jpegR.length)) + << "Same input is yielding different output"; + + mRawP010ImageWithChromaData.width = TEST_IMAGE_WIDTH; + mRawP010ImageWithChromaData.height = TEST_IMAGE_HEIGHT; + mRawP010ImageWithChromaData.luma_stride = TEST_IMAGE_WIDTH + 64; + mRawP010ImageWithChromaData.chroma_stride = TEST_IMAGE_WIDTH + 256; + mRawP010ImageWithChromaData.colorGamut = ultrahdr_color_gamut::ULTRAHDR_COLORGAMUT_BT2100; + // Load input files. + if (!loadP010Image(RAW_P010_IMAGE, &mRawP010ImageWithChromaData, false)) { + FAIL() << "Load file " << RAW_P010_IMAGE << " failed"; + } + jpegr_compressed_struct jpegRWithChromaData; + jpegRWithChromaData.maxLength = jpegR.length; + jpegRWithChromaData.data = malloc(jpegRWithChromaData.maxLength); + ret = jpegRCodec.encodeJPEGR( + &mRawP010ImageWithChromaData, ultrahdr_transfer_function::ULTRAHDR_TF_HLG, + &jpegRWithChromaData, DEFAULT_JPEG_QUALITY, nullptr); + if (ret != OK) { + FAIL() << "Error code is " << ret; + } + ASSERT_EQ(jpegR.length, jpegRWithChromaData.length) + << "Same input is yielding different output"; + ASSERT_EQ(0, memcmp(jpegR.data, jpegRWithChromaData.data, jpegR.length)) + << "Same input is yielding different output"; + + free(jpegR.data); + free(jpegRWithStride.data); + free(jpegRWithChromaData.data); +} + +/* Test Encode API-0 and decode */ +TEST_F(JpegRTest, encodeFromP010ThenDecode) { + int ret; + + // Load input files. + if (!loadFile(RAW_P010_IMAGE, mRawP010Image.data, nullptr)) { + FAIL() << "Load file " << RAW_P010_IMAGE << " failed"; + } + mRawP010Image.width = TEST_IMAGE_WIDTH; + mRawP010Image.height = TEST_IMAGE_HEIGHT; + mRawP010Image.colorGamut = ultrahdr_color_gamut::ULTRAHDR_COLORGAMUT_BT2100; + + JpegR jpegRCodec; + + jpegr_compressed_struct jpegR; + jpegR.maxLength = TEST_IMAGE_WIDTH * TEST_IMAGE_HEIGHT * sizeof(uint8_t); + jpegR.data = malloc(jpegR.maxLength); + ret = jpegRCodec.encodeJPEGR( + &mRawP010Image, ultrahdr_transfer_function::ULTRAHDR_TF_HLG, &jpegR, DEFAULT_JPEG_QUALITY, + nullptr); + if (ret != OK) { + FAIL() << "Error code is " << ret; + } + if (SAVE_ENCODING_RESULT) { + // Output image data to file + std::string filePath = "/sdcard/Documents/encoded_from_p010_input.jpgr"; + std::ofstream imageFile(filePath.c_str(), std::ofstream::binary); + if (!imageFile.is_open()) { + ALOGE("%s: Unable to create file %s", __FUNCTION__, filePath.c_str()); + } + imageFile.write((const char*)jpegR.data, jpegR.length); + } + + jpegr_uncompressed_struct decodedJpegR; + int decodedJpegRSize = TEST_IMAGE_WIDTH * TEST_IMAGE_HEIGHT * 8; + decodedJpegR.data = malloc(decodedJpegRSize); + ret = jpegRCodec.decodeJPEGR(&jpegR, &decodedJpegR); + if (ret != OK) { + FAIL() << "Error code is " << ret; + } + if (SAVE_DECODING_RESULT) { + // Output image data to file + std::string filePath = "/sdcard/Documents/decoded_from_p010_input.rgb"; + std::ofstream imageFile(filePath.c_str(), std::ofstream::binary); + if (!imageFile.is_open()) { + ALOGE("%s: Unable to create file %s", __FUNCTION__, filePath.c_str()); + } + imageFile.write((const char*)decodedJpegR.data, decodedJpegRSize); + } + + free(jpegR.data); + free(decodedJpegR.data); +} + +/* Test Encode API-0 (with stride) and decode */ +TEST_F(JpegRTest, encodeFromP010WithStrideThenDecode) { + int ret; + + // Load input files. + if (!loadFile(RAW_P010_IMAGE_WITH_STRIDE, mRawP010ImageWithStride.data, nullptr)) { + FAIL() << "Load file " << RAW_P010_IMAGE_WITH_STRIDE << " failed"; + } + mRawP010ImageWithStride.width = TEST_IMAGE_WIDTH; + mRawP010ImageWithStride.height = TEST_IMAGE_HEIGHT; + mRawP010ImageWithStride.luma_stride = TEST_IMAGE_STRIDE; + mRawP010ImageWithStride.colorGamut = ultrahdr_color_gamut::ULTRAHDR_COLORGAMUT_BT2100; + + JpegR jpegRCodec; + + jpegr_compressed_struct jpegR; + jpegR.maxLength = TEST_IMAGE_WIDTH * TEST_IMAGE_HEIGHT * sizeof(uint8_t); + jpegR.data = malloc(jpegR.maxLength); + ret = jpegRCodec.encodeJPEGR( + &mRawP010ImageWithStride, ultrahdr_transfer_function::ULTRAHDR_TF_HLG, &jpegR, + DEFAULT_JPEG_QUALITY, nullptr); + if (ret != OK) { + FAIL() << "Error code is " << ret; + } + if (SAVE_ENCODING_RESULT) { + // Output image data to file + std::string filePath = "/sdcard/Documents/encoded_from_p010_input.jpgr"; + std::ofstream imageFile(filePath.c_str(), std::ofstream::binary); + if (!imageFile.is_open()) { + ALOGE("%s: Unable to create file %s", __FUNCTION__, filePath.c_str()); + } + imageFile.write((const char*)jpegR.data, jpegR.length); + } + + jpegr_uncompressed_struct decodedJpegR; + int decodedJpegRSize = TEST_IMAGE_WIDTH * TEST_IMAGE_HEIGHT * 8; + decodedJpegR.data = malloc(decodedJpegRSize); + ret = jpegRCodec.decodeJPEGR(&jpegR, &decodedJpegR); + if (ret != OK) { + FAIL() << "Error code is " << ret; + } + if (SAVE_DECODING_RESULT) { + // Output image data to file + std::string filePath = "/sdcard/Documents/decoded_from_p010_input.rgb"; + std::ofstream imageFile(filePath.c_str(), std::ofstream::binary); + if (!imageFile.is_open()) { + ALOGE("%s: Unable to create file %s", __FUNCTION__, filePath.c_str()); + } + imageFile.write((const char*)decodedJpegR.data, decodedJpegRSize); + } + + free(jpegR.data); + free(decodedJpegR.data); +} + +/* Test Encode API-1 and decode */ +TEST_F(JpegRTest, encodeFromRawHdrAndSdrThenDecode) { + int ret; + + // Load input files. + if (!loadFile(RAW_P010_IMAGE, mRawP010Image.data, nullptr)) { + FAIL() << "Load file " << RAW_P010_IMAGE << " failed"; + } + mRawP010Image.width = TEST_IMAGE_WIDTH; + mRawP010Image.height = TEST_IMAGE_HEIGHT; + mRawP010Image.colorGamut = ultrahdr_color_gamut::ULTRAHDR_COLORGAMUT_BT2100; + + if (!loadFile(RAW_YUV420_IMAGE, mRawYuv420Image.data, nullptr)) { + FAIL() << "Load file " << RAW_P010_IMAGE << " failed"; + } + mRawYuv420Image.width = TEST_IMAGE_WIDTH; + mRawYuv420Image.height = TEST_IMAGE_HEIGHT; + mRawYuv420Image.colorGamut = ultrahdr_color_gamut::ULTRAHDR_COLORGAMUT_BT709; + + JpegR jpegRCodec; + + jpegr_compressed_struct jpegR; + jpegR.maxLength = TEST_IMAGE_WIDTH * TEST_IMAGE_HEIGHT * sizeof(uint8_t); + jpegR.data = malloc(jpegR.maxLength); + ret = jpegRCodec.encodeJPEGR( + &mRawP010Image, &mRawYuv420Image, ultrahdr_transfer_function::ULTRAHDR_TF_HLG, &jpegR, + DEFAULT_JPEG_QUALITY, nullptr); + if (ret != OK) { + FAIL() << "Error code is " << ret; + } + if (SAVE_ENCODING_RESULT) { + // Output image data to file + std::string filePath = "/sdcard/Documents/encoded_from_p010_yuv420p_input.jpgr"; + std::ofstream imageFile(filePath.c_str(), std::ofstream::binary); + if (!imageFile.is_open()) { + ALOGE("%s: Unable to create file %s", __FUNCTION__, filePath.c_str()); + } + imageFile.write((const char*)jpegR.data, jpegR.length); + } + + jpegr_uncompressed_struct decodedJpegR; + int decodedJpegRSize = TEST_IMAGE_WIDTH * TEST_IMAGE_HEIGHT * 8; + decodedJpegR.data = malloc(decodedJpegRSize); + ret = jpegRCodec.decodeJPEGR(&jpegR, &decodedJpegR); + if (ret != OK) { + FAIL() << "Error code is " << ret; + } + if (SAVE_DECODING_RESULT) { + // Output image data to file + std::string filePath = "/sdcard/Documents/decoded_from_p010_yuv420p_input.rgb"; + std::ofstream imageFile(filePath.c_str(), std::ofstream::binary); + if (!imageFile.is_open()) { + ALOGE("%s: Unable to create file %s", __FUNCTION__, filePath.c_str()); + } + imageFile.write((const char*)decodedJpegR.data, decodedJpegRSize); + } + + free(jpegR.data); + free(decodedJpegR.data); +} + +/* Test Encode API-2 and decode */ +TEST_F(JpegRTest, encodeFromRawHdrAndSdrAndJpegThenDecode) { + int ret; + + // Load input files. + if (!loadFile(RAW_P010_IMAGE, mRawP010Image.data, nullptr)) { + FAIL() << "Load file " << RAW_P010_IMAGE << " failed"; + } + mRawP010Image.width = TEST_IMAGE_WIDTH; + mRawP010Image.height = TEST_IMAGE_HEIGHT; + mRawP010Image.colorGamut = ultrahdr_color_gamut::ULTRAHDR_COLORGAMUT_BT2100; + + if (!loadFile(RAW_YUV420_IMAGE, mRawYuv420Image.data, nullptr)) { + FAIL() << "Load file " << RAW_P010_IMAGE << " failed"; + } + mRawYuv420Image.width = TEST_IMAGE_WIDTH; + mRawYuv420Image.height = TEST_IMAGE_HEIGHT; + mRawYuv420Image.colorGamut = ultrahdr_color_gamut::ULTRAHDR_COLORGAMUT_BT709; + + if (!loadFile(JPEG_IMAGE, mJpegImage.data, &mJpegImage.length)) { + FAIL() << "Load file " << JPEG_IMAGE << " failed"; + } + mJpegImage.colorGamut = ultrahdr_color_gamut::ULTRAHDR_COLORGAMUT_BT709; + + JpegR jpegRCodec; + + jpegr_compressed_struct jpegR; + jpegR.maxLength = TEST_IMAGE_WIDTH * TEST_IMAGE_HEIGHT * sizeof(uint8_t); + jpegR.data = malloc(jpegR.maxLength); + ret = jpegRCodec.encodeJPEGR( + &mRawP010Image, &mRawYuv420Image, &mJpegImage, ultrahdr_transfer_function::ULTRAHDR_TF_HLG, + &jpegR); + if (ret != OK) { + FAIL() << "Error code is " << ret; + } + if (SAVE_ENCODING_RESULT) { + // Output image data to file + std::string filePath = "/sdcard/Documents/encoded_from_p010_yuv420p_jpeg_input.jpgr"; + std::ofstream imageFile(filePath.c_str(), std::ofstream::binary); + if (!imageFile.is_open()) { + ALOGE("%s: Unable to create file %s", __FUNCTION__, filePath.c_str()); + } + imageFile.write((const char*)jpegR.data, jpegR.length); + } + + jpegr_uncompressed_struct decodedJpegR; + int decodedJpegRSize = TEST_IMAGE_WIDTH * TEST_IMAGE_HEIGHT * 8; + decodedJpegR.data = malloc(decodedJpegRSize); + ret = jpegRCodec.decodeJPEGR(&jpegR, &decodedJpegR); + if (ret != OK) { + FAIL() << "Error code is " << ret; + } + if (SAVE_DECODING_RESULT) { + // Output image data to file + std::string filePath = "/sdcard/Documents/decoded_from_p010_yuv420p_jpeg_input.rgb"; + std::ofstream imageFile(filePath.c_str(), std::ofstream::binary); + if (!imageFile.is_open()) { + ALOGE("%s: Unable to create file %s", __FUNCTION__, filePath.c_str()); + } + imageFile.write((const char*)decodedJpegR.data, decodedJpegRSize); + } + + free(jpegR.data); + free(decodedJpegR.data); +} + +/* Test Encode API-3 and decode */ +TEST_F(JpegRTest, encodeFromJpegThenDecode) { + int ret; + + // Load input files. + if (!loadFile(RAW_P010_IMAGE, mRawP010Image.data, nullptr)) { + FAIL() << "Load file " << RAW_P010_IMAGE << " failed"; + } + mRawP010Image.width = TEST_IMAGE_WIDTH; + mRawP010Image.height = TEST_IMAGE_HEIGHT; + mRawP010Image.colorGamut = ultrahdr_color_gamut::ULTRAHDR_COLORGAMUT_BT2100; + + if (SAVE_INPUT_RGBA) { + size_t rgbaSize = mRawP010Image.width * mRawP010Image.height * sizeof(uint32_t); + uint32_t *data = (uint32_t *)malloc(rgbaSize); + + for (size_t y = 0; y < mRawP010Image.height; ++y) { + for (size_t x = 0; x < mRawP010Image.width; ++x) { + Color hdr_yuv_gamma = getP010Pixel(&mRawP010Image, x, y); + Color hdr_rgb_gamma = bt2100YuvToRgb(hdr_yuv_gamma); + uint32_t rgba1010102 = colorToRgba1010102(hdr_rgb_gamma); + size_t pixel_idx = x + y * mRawP010Image.width; + reinterpret_cast(data)[pixel_idx] = rgba1010102; + } + } + + // Output image data to file + std::string filePath = "/sdcard/Documents/input_from_p010.rgb10"; + std::ofstream imageFile(filePath.c_str(), std::ofstream::binary); + if (!imageFile.is_open()) { + ALOGE("%s: Unable to create file %s", __FUNCTION__, filePath.c_str()); + } + imageFile.write((const char*)data, rgbaSize); + free(data); + } + if (!loadFile(JPEG_IMAGE, mJpegImage.data, &mJpegImage.length)) { + FAIL() << "Load file " << JPEG_IMAGE << " failed"; + } + mJpegImage.colorGamut = ultrahdr_color_gamut::ULTRAHDR_COLORGAMUT_BT709; + + JpegR jpegRCodec; + + jpegr_compressed_struct jpegR; + jpegR.maxLength = TEST_IMAGE_WIDTH * TEST_IMAGE_HEIGHT * sizeof(uint8_t); + jpegR.data = malloc(jpegR.maxLength); + ret = jpegRCodec.encodeJPEGR( + &mRawP010Image, &mJpegImage, ultrahdr_transfer_function::ULTRAHDR_TF_HLG, &jpegR); + if (ret != OK) { + FAIL() << "Error code is " << ret; + } + if (SAVE_ENCODING_RESULT) { + // Output image data to file + std::string filePath = "/sdcard/Documents/encoded_from_p010_jpeg_input.jpgr"; + std::ofstream imageFile(filePath.c_str(), std::ofstream::binary); + if (!imageFile.is_open()) { + ALOGE("%s: Unable to create file %s", __FUNCTION__, filePath.c_str()); + } + imageFile.write((const char*)jpegR.data, jpegR.length); + } + + jpegr_uncompressed_struct decodedJpegR; + int decodedJpegRSize = TEST_IMAGE_WIDTH * TEST_IMAGE_HEIGHT * 8; + decodedJpegR.data = malloc(decodedJpegRSize); + ret = jpegRCodec.decodeJPEGR(&jpegR, &decodedJpegR); + if (ret != OK) { + FAIL() << "Error code is " << ret; + } + if (SAVE_DECODING_RESULT) { + // Output image data to file + std::string filePath = "/sdcard/Documents/decoded_from_p010_jpeg_input.rgb"; + std::ofstream imageFile(filePath.c_str(), std::ofstream::binary); + if (!imageFile.is_open()) { + ALOGE("%s: Unable to create file %s", __FUNCTION__, filePath.c_str()); + } + imageFile.write((const char*)decodedJpegR.data, decodedJpegRSize); + } + + free(jpegR.data); + free(decodedJpegR.data); +} + +TEST_F(JpegRTest, ProfileGainMapFuncs) { + const size_t kWidth = TEST_IMAGE_WIDTH; + const size_t kHeight = TEST_IMAGE_HEIGHT; + + // Load input files. + if (!loadFile(RAW_P010_IMAGE, mRawP010Image.data, nullptr)) { + FAIL() << "Load file " << RAW_P010_IMAGE << " failed"; + } + mRawP010Image.width = kWidth; + mRawP010Image.height = kHeight; + mRawP010Image.colorGamut = ultrahdr_color_gamut::ULTRAHDR_COLORGAMUT_BT2100; + + if (!loadFile(RAW_YUV420_IMAGE, mRawYuv420Image.data, nullptr)) { + FAIL() << "Load file " << RAW_P010_IMAGE << " failed"; + } + mRawYuv420Image.width = kWidth; + mRawYuv420Image.height = kHeight; + mRawYuv420Image.colorGamut = ultrahdr_color_gamut::ULTRAHDR_COLORGAMUT_BT709; + + JpegRBenchmark benchmark; + + ultrahdr_metadata_struct metadata = { .version = "1.0" }; + + jpegr_uncompressed_struct map = { .data = NULL, + .width = 0, + .height = 0, + .colorGamut = ULTRAHDR_COLORGAMUT_UNSPECIFIED }; + + benchmark.BenchmarkGenerateGainMap(&mRawYuv420Image, &mRawP010Image, &metadata, &map); + + const int dstSize = mRawYuv420Image.width * mRawYuv420Image.height * 4; + auto bufferDst = std::make_unique(dstSize); + jpegr_uncompressed_struct dest = { .data = bufferDst.get(), + .width = 0, + .height = 0, + .colorGamut = ULTRAHDR_COLORGAMUT_UNSPECIFIED }; + + benchmark.BenchmarkApplyGainMap(&mRawYuv420Image, &map, &metadata, &dest); +} + +} // namespace android::ultrahdr diff --git a/libs/vibrator/ExternalVibration.cpp b/libs/vibrator/ExternalVibration.cpp index ec906458a386817b36b61e5360cc264104456430..80e911c65d5d11495ffbe88b5490152e8aaa8fc1 100644 --- a/libs/vibrator/ExternalVibration.cpp +++ b/libs/vibrator/ExternalVibration.cpp @@ -22,15 +22,6 @@ #include #include - -// To guarantee if HapticScale enum has the same value as IExternalVibratorService -static_assert(static_cast(android::os::HapticScale::MUTE) == static_cast(android::os::IExternalVibratorService::SCALE_MUTE)); -static_assert(static_cast(android::os::HapticScale::VERY_LOW) == static_cast(android::os::IExternalVibratorService::SCALE_VERY_LOW)); -static_assert(static_cast(android::os::HapticScale::LOW) == static_cast(android::os::IExternalVibratorService::SCALE_LOW)); -static_assert(static_cast(android::os::HapticScale::NONE) == static_cast(android::os::IExternalVibratorService::SCALE_NONE)); -static_assert(static_cast(android::os::HapticScale::HIGH) == static_cast(android::os::IExternalVibratorService::SCALE_HIGH)); -static_assert(static_cast(android::os::HapticScale::VERY_HIGH) == static_cast(android::os::IExternalVibratorService::SCALE_VERY_HIGH)); - void writeAudioAttributes(const audio_attributes_t& attrs, android::Parcel* out) { out->writeInt32(attrs.usage); out->writeInt32(attrs.content_type); @@ -74,5 +65,25 @@ inline bool ExternalVibration::operator==(const ExternalVibration& rhs) const { return mToken == rhs.mToken; } +os::HapticScale ExternalVibration::externalVibrationScaleToHapticScale(int externalVibrationScale) { + switch (externalVibrationScale) { + case IExternalVibratorService::SCALE_MUTE: + return os::HapticScale::MUTE; + case IExternalVibratorService::SCALE_VERY_LOW: + return os::HapticScale::VERY_LOW; + case IExternalVibratorService::SCALE_LOW: + return os::HapticScale::LOW; + case IExternalVibratorService::SCALE_NONE: + return os::HapticScale::NONE; + case IExternalVibratorService::SCALE_HIGH: + return os::HapticScale::HIGH; + case IExternalVibratorService::SCALE_VERY_HIGH: + return os::HapticScale::VERY_HIGH; + default: + ALOGE("Unknown ExternalVibrationScale %d, not applying scaling", externalVibrationScale); + return os::HapticScale::NONE; + } +} + } // namespace os } // namespace android diff --git a/libs/vibrator/include/vibrator/ExternalVibration.h b/libs/vibrator/include/vibrator/ExternalVibration.h index 760dbce149ecdd89cd335927d6411d08598d2584..00cd3cd256c30579a568366c6455dd88006ba866 100644 --- a/libs/vibrator/include/vibrator/ExternalVibration.h +++ b/libs/vibrator/include/vibrator/ExternalVibration.h @@ -23,6 +23,7 @@ #include #include #include +#include namespace android { namespace os { @@ -44,6 +45,10 @@ public : audio_attributes_t getAudioAttributes() const { return mAttrs; } sp getController() { return mController; } + /* Converts the scale from non-public ExternalVibrationService into the HapticScale + * used by the utils. + */ + static os::HapticScale externalVibrationScaleToHapticScale(int externalVibrationScale); private: int32_t mUid; @@ -53,7 +58,7 @@ private: sp mToken = new BBinder(); }; -} // namespace android } // namespace os +} // namespace android #endif // ANDROID_EXTERNAL_VIBRATION_H diff --git a/libs/vibrator/include/vibrator/ExternalVibrationUtils.h b/libs/vibrator/include/vibrator/ExternalVibrationUtils.h index c588bfdedd10b99c87316138bc1692efda291184..ca219d3cbfc9b9b0d57164c1bd739ea024ca4cbb 100644 --- a/libs/vibrator/include/vibrator/ExternalVibrationUtils.h +++ b/libs/vibrator/include/vibrator/ExternalVibrationUtils.h @@ -19,8 +19,6 @@ namespace android::os { -// Copied from frameworks/base/core/java/android/os/IExternalVibratorService.aidl -// The values are checked in ExternalVibration.cpp enum class HapticScale { MUTE = -100, VERY_LOW = -2, diff --git a/opengl/TEST_MAPPING b/opengl/TEST_MAPPING index d391dce2de6c058fa5b10964516b56b53684c203..7c50a945fa70b0cebcafa6635a7f0a2d57262be9 100644 --- a/opengl/TEST_MAPPING +++ b/opengl/TEST_MAPPING @@ -2,6 +2,9 @@ "presubmit": [ { "name": "CtsGpuToolsHostTestCases" + }, + { + "name": "EGL_test" } ] } diff --git a/opengl/include/EGL/eglext.h b/opengl/include/EGL/eglext.h index 501bf58531c52e05195acd9e3151d1425b95f26b..c787fc9717fb11475655c587b01630040af43eb9 100644 --- a/opengl/include/EGL/eglext.h +++ b/opengl/include/EGL/eglext.h @@ -697,6 +697,11 @@ EGLAPI EGLBoolean EGLAPIENTRY eglQueryDisplayAttribEXT (EGLDisplay dpy, EGLint a #define EGL_EXT_device_query 1 #endif /* EGL_EXT_device_query */ +#ifndef EGL_EXT_gl_colorspace_bt2020_hlg +#define EGL_EXT_gl_colorspace_bt2020_hlg 1 +#define EGL_GL_COLORSPACE_BT2020_HLG_EXT 0x3540 +#endif /* EGL_EXT_gl_colorspace_bt2020_hlg */ + #ifndef EGL_EXT_gl_colorspace_bt2020_linear #define EGL_EXT_gl_colorspace_bt2020_linear 1 #define EGL_GL_COLORSPACE_BT2020_LINEAR_EXT 0x333F diff --git a/opengl/libs/Android.bp b/opengl/libs/Android.bp index 62cf2555ca323e30a11f7b9041529022649c8f96..16de3908f809a39e4f74b2a4beea443cf4859520 100644 --- a/opengl/libs/Android.bp +++ b/opengl/libs/Android.bp @@ -144,6 +144,7 @@ cc_library_static { srcs: [ "EGL/BlobCache.cpp", "EGL/FileBlobCache.cpp", + "EGL/MultifileBlobCache.cpp", ], export_include_dirs: ["EGL"], } @@ -204,6 +205,12 @@ cc_test { srcs: [ "EGL/BlobCache.cpp", "EGL/BlobCache_test.cpp", + "EGL/FileBlobCache.cpp", + "EGL/MultifileBlobCache.cpp", + "EGL/MultifileBlobCache_test.cpp", + ], + shared_libs: [ + "libutils", ], } diff --git a/opengl/libs/EGL/BlobCache.cpp b/opengl/libs/EGL/BlobCache.cpp index aecfc6b077f479cce7f742c8cd7f5f89e7a92be7..b3a4bc19fd4120373926ad84af3ddaf9ecd1645a 100644 --- a/opengl/libs/EGL/BlobCache.cpp +++ b/opengl/libs/EGL/BlobCache.cpp @@ -15,6 +15,7 @@ */ //#define LOG_NDEBUG 0 +#define ATRACE_TAG ATRACE_TAG_GRAPHICS #include "BlobCache.h" @@ -22,6 +23,7 @@ #include #include #include +#include #include @@ -230,6 +232,8 @@ int BlobCache::flatten(void* buffer, size_t size) const { } int BlobCache::unflatten(void const* buffer, size_t size) { + ATRACE_NAME("BlobCache::unflatten"); + // All errors should result in the BlobCache being in an empty state. clear(); @@ -293,6 +297,8 @@ long int BlobCache::blob_random() { } void BlobCache::clean() { + ATRACE_NAME("BlobCache::clean"); + // Remove a random cache entry until the total cache size gets below half // the maximum total cache size. while (mTotalSize > mMaxTotalSize / 2) { diff --git a/opengl/libs/EGL/FileBlobCache.cpp b/opengl/libs/EGL/FileBlobCache.cpp index 751f3be81a7ea6a005e5472ce6d7ddff416794d8..4a0fac4ce55c7bf7fceb1e10ce28074ad372f5b6 100644 --- a/opengl/libs/EGL/FileBlobCache.cpp +++ b/opengl/libs/EGL/FileBlobCache.cpp @@ -14,6 +14,8 @@ ** limitations under the License. */ +#define ATRACE_TAG ATRACE_TAG_GRAPHICS + #include "FileBlobCache.h" #include @@ -24,6 +26,7 @@ #include #include +#include // Cache file header static const char* cacheFileMagic = "EGL$"; @@ -31,7 +34,7 @@ static const size_t cacheFileHeaderSize = 8; namespace android { -static uint32_t crc32c(const uint8_t* buf, size_t len) { +uint32_t crc32c(const uint8_t* buf, size_t len) { const uint32_t polyBits = 0x82F63B78; uint32_t r = 0; for (size_t i = 0; i < len; i++) { @@ -51,6 +54,8 @@ FileBlobCache::FileBlobCache(size_t maxKeySize, size_t maxValueSize, size_t maxT const std::string& filename) : BlobCache(maxKeySize, maxValueSize, maxTotalSize) , mFilename(filename) { + ATRACE_CALL(); + if (mFilename.length() > 0) { size_t headerSize = cacheFileHeaderSize; @@ -117,6 +122,8 @@ FileBlobCache::FileBlobCache(size_t maxKeySize, size_t maxValueSize, size_t maxT } void FileBlobCache::writeToFile() { + ATRACE_CALL(); + if (mFilename.length() > 0) { size_t cacheSize = getFlattenedSize(); size_t headerSize = cacheFileHeaderSize; @@ -185,4 +192,10 @@ void FileBlobCache::writeToFile() { } } +size_t FileBlobCache::getSize() { + if (mFilename.length() > 0) { + return getFlattenedSize() + cacheFileHeaderSize; + } + return 0; +} } diff --git a/opengl/libs/EGL/FileBlobCache.h b/opengl/libs/EGL/FileBlobCache.h index 393703f23488550ed1d120936080e1abb932c04d..f083b0d6cad705f519e62a51dff9d9a9ce9e0ad8 100644 --- a/opengl/libs/EGL/FileBlobCache.h +++ b/opengl/libs/EGL/FileBlobCache.h @@ -22,6 +22,8 @@ namespace android { +uint32_t crc32c(const uint8_t* buf, size_t len); + class FileBlobCache : public BlobCache { public: // FileBlobCache attempts to load the saved cache contents from disk into @@ -33,6 +35,9 @@ public: // disk. void writeToFile(); + // Return the total size of the cache + size_t getSize(); + private: // mFilename is the name of the file for storing cache contents. std::string mFilename; diff --git a/opengl/libs/EGL/Loader.cpp b/opengl/libs/EGL/Loader.cpp index 6ea400721d1df0357bba01f79bb64c30f723ca9d..2c3ce16f665111f1c8cda0c34778047f989b2a1c 100644 --- a/opengl/libs/EGL/Loader.cpp +++ b/opengl/libs/EGL/Loader.cpp @@ -139,9 +139,10 @@ static void* load_wrapper(const char* path) { static const char* DRIVER_SUFFIX_PROPERTY = "ro.hardware.egl"; -static const char* HAL_SUBNAME_KEY_PROPERTIES[2] = { - DRIVER_SUFFIX_PROPERTY, - "ro.board.platform", +static const char* HAL_SUBNAME_KEY_PROPERTIES[3] = { + "persist.graphics.egl", + DRIVER_SUFFIX_PROPERTY, + "ro.board.platform", }; static bool should_unload_system_driver(egl_connection_t* cnx) { @@ -208,8 +209,7 @@ void* Loader::open(egl_connection_t* cnx) ATRACE_CALL(); const nsecs_t openTime = systemTime(); - if (!android::GraphicsEnv::getInstance().angleIsSystemDriver() && - should_unload_system_driver(cnx)) { + if (should_unload_system_driver(cnx)) { unload_system_driver(cnx); } @@ -218,12 +218,8 @@ void* Loader::open(egl_connection_t* cnx) return cnx->dso; } - // Firstly, try to load ANGLE driver, unless we know that we shouldn't. - bool shouldForceLegacyDriver = android::GraphicsEnv::getInstance().shouldForceLegacyDriver(); - driver_t* hnd = nullptr; - if (!shouldForceLegacyDriver) { - hnd = attempt_to_load_angle(cnx); - } + // Firstly, try to load ANGLE driver. + driver_t* hnd = attempt_to_load_angle(cnx); if (!hnd) { // Secondly, try to load from driver apk. @@ -285,8 +281,10 @@ void* Loader::open(egl_connection_t* cnx) } LOG_ALWAYS_FATAL_IF(!hnd, - "couldn't find an OpenGL ES implementation, make sure you set %s or %s", - HAL_SUBNAME_KEY_PROPERTIES[0], HAL_SUBNAME_KEY_PROPERTIES[1]); + "couldn't find an OpenGL ES implementation, make sure one of %s, %s and %s " + "is set", + HAL_SUBNAME_KEY_PROPERTIES[0], HAL_SUBNAME_KEY_PROPERTIES[1], + HAL_SUBNAME_KEY_PROPERTIES[2]); if (!cnx->libEgl) { cnx->libEgl = load_wrapper(EGL_WRAPPER_DIR "/libEGL.so"); @@ -499,7 +497,6 @@ static void* load_angle(const char* kind, android_namespace_t* ns) { void* so = do_android_dlopen_ext(name.c_str(), RTLD_LOCAL | RTLD_NOW, &dlextinfo); if (so) { - ALOGD("dlopen_ext from APK (%s) success at %p", name.c_str(), so); return so; } else { ALOGE("dlopen_ext(\"%s\") failed: %s", name.c_str(), dlerror()); diff --git a/opengl/libs/EGL/MultifileBlobCache.cpp b/opengl/libs/EGL/MultifileBlobCache.cpp new file mode 100644 index 0000000000000000000000000000000000000000..7ffdac7ea78e4fd22fd6954e693b21f32e5faa7e --- /dev/null +++ b/opengl/libs/EGL/MultifileBlobCache.cpp @@ -0,0 +1,735 @@ +/* + ** 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. + */ + +// #define LOG_NDEBUG 0 + +#include "MultifileBlobCache.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#include + +using namespace std::literals; + +constexpr uint32_t kMultifileMagic = 'MFB$'; +constexpr uint32_t kCrcPlaceholder = 0; + +namespace { + +// Helper function to close entries or free them +void freeHotCacheEntry(android::MultifileHotCache& entry) { + if (entry.entryFd != -1) { + // If we have an fd, then this entry was added to hot cache via INIT or GET + // We need to unmap and close the entry + munmap(entry.entryBuffer, entry.entrySize); + close(entry.entryFd); + } else { + // Otherwise, this was added to hot cache during SET, so it was never mapped + // and fd was only on the deferred thread. + delete[] entry.entryBuffer; + } +} + +} // namespace + +namespace android { + +MultifileBlobCache::MultifileBlobCache(size_t maxKeySize, size_t maxValueSize, size_t maxTotalSize, + const std::string& baseDir) + : mInitialized(false), + mMaxKeySize(maxKeySize), + mMaxValueSize(maxValueSize), + mMaxTotalSize(maxTotalSize), + mTotalCacheSize(0), + mHotCacheLimit(0), + mHotCacheSize(0), + mWorkerThreadIdle(true) { + if (baseDir.empty()) { + ALOGV("INIT: no baseDir provided in MultifileBlobCache constructor, returning early."); + return; + } + + // Establish the name of our multifile directory + mMultifileDirName = baseDir + ".multifile"; + + // Set the hotcache limit to be large enough to contain one max entry + // This ensure the hot cache is always large enough for single entry + mHotCacheLimit = mMaxKeySize + mMaxValueSize + sizeof(MultifileHeader); + + ALOGV("INIT: Initializing multifile blobcache with maxKeySize=%zu and maxValueSize=%zu", + mMaxKeySize, mMaxValueSize); + + // Initialize our cache with the contents of the directory + mTotalCacheSize = 0; + + // Create the worker thread + mTaskThread = std::thread(&MultifileBlobCache::processTasks, this); + + // See if the dir exists, and initialize using its contents + struct stat st; + if (stat(mMultifileDirName.c_str(), &st) == 0) { + // Read all the files and gather details, then preload their contents + DIR* dir; + struct dirent* entry; + if ((dir = opendir(mMultifileDirName.c_str())) != nullptr) { + while ((entry = readdir(dir)) != nullptr) { + if (entry->d_name == "."s || entry->d_name == ".."s) { + continue; + } + + std::string entryName = entry->d_name; + std::string fullPath = mMultifileDirName + "/" + entryName; + + // The filename is the same as the entryHash + uint32_t entryHash = static_cast(strtoul(entry->d_name, nullptr, 10)); + + ALOGV("INIT: Checking entry %u", entryHash); + + // Look up the details of the file + struct stat st; + if (stat(fullPath.c_str(), &st) != 0) { + ALOGE("Failed to stat %s", fullPath.c_str()); + return; + } + + // If the cache entry is damaged or no good, remove it + if (st.st_size <= 0 || st.st_atime <= 0) { + ALOGE("INIT: Entry %u has invalid stats! Removing.", entryHash); + if (remove(fullPath.c_str()) != 0) { + ALOGE("Error removing %s: %s", fullPath.c_str(), std::strerror(errno)); + } + continue; + } + + // Open the file so we can read its header + int fd = open(fullPath.c_str(), O_RDONLY); + if (fd == -1) { + ALOGE("Cache error - failed to open fullPath: %s, error: %s", fullPath.c_str(), + std::strerror(errno)); + return; + } + + // Read the beginning of the file to get header + MultifileHeader header; + size_t result = read(fd, static_cast(&header), sizeof(MultifileHeader)); + if (result != sizeof(MultifileHeader)) { + ALOGE("Error reading MultifileHeader from cache entry (%s): %s", + fullPath.c_str(), std::strerror(errno)); + return; + } + + // Verify header magic + if (header.magic != kMultifileMagic) { + ALOGE("INIT: Entry %u has bad magic (%u)! Removing.", entryHash, header.magic); + if (remove(fullPath.c_str()) != 0) { + ALOGE("Error removing %s: %s", fullPath.c_str(), std::strerror(errno)); + } + continue; + } + + // Note: Converting from off_t (signed) to size_t (unsigned) + size_t fileSize = static_cast(st.st_size); + + // Memory map the file + uint8_t* mappedEntry = reinterpret_cast( + mmap(nullptr, fileSize, PROT_READ, MAP_PRIVATE, fd, 0)); + if (mappedEntry == MAP_FAILED) { + ALOGE("Failed to mmap cacheEntry, error: %s", std::strerror(errno)); + return; + } + + // Ensure we have a good CRC + if (header.crc != + crc32c(mappedEntry + sizeof(MultifileHeader), + fileSize - sizeof(MultifileHeader))) { + ALOGE("INIT: Entry %u failed CRC check! Removing.", entryHash); + if (remove(fullPath.c_str()) != 0) { + ALOGE("Error removing %s: %s", fullPath.c_str(), std::strerror(errno)); + } + continue; + } + + // If the cache entry is damaged or no good, remove it + if (header.keySize <= 0 || header.valueSize <= 0) { + ALOGE("INIT: Entry %u has a bad header keySize (%lu) or valueSize (%lu), " + "removing.", + entryHash, header.keySize, header.valueSize); + if (remove(fullPath.c_str()) != 0) { + ALOGE("Error removing %s: %s", fullPath.c_str(), std::strerror(errno)); + } + continue; + } + + ALOGV("INIT: Entry %u is good, tracking it now.", entryHash); + + // Track details for rapid lookup later + trackEntry(entryHash, header.valueSize, fileSize, st.st_atime); + + // Track the total size + increaseTotalCacheSize(fileSize); + + // Preload the entry for fast retrieval + if ((mHotCacheSize + fileSize) < mHotCacheLimit) { + ALOGV("INIT: Populating hot cache with fd = %i, cacheEntry = %p for " + "entryHash %u", + fd, mappedEntry, entryHash); + + // Track the details of the preload so they can be retrieved later + if (!addToHotCache(entryHash, fd, mappedEntry, fileSize)) { + ALOGE("INIT Failed to add %u to hot cache", entryHash); + munmap(mappedEntry, fileSize); + close(fd); + return; + } + } else { + // If we're not keeping it in hot cache, unmap it now + munmap(mappedEntry, fileSize); + close(fd); + } + } + closedir(dir); + } else { + ALOGE("Unable to open filename: %s", mMultifileDirName.c_str()); + } + } else { + // If the multifile directory does not exist, create it and start from scratch + if (mkdir(mMultifileDirName.c_str(), 0755) != 0 && (errno != EEXIST)) { + ALOGE("Unable to create directory (%s), errno (%i)", mMultifileDirName.c_str(), errno); + } + } + + mInitialized = true; +} + +MultifileBlobCache::~MultifileBlobCache() { + if (!mInitialized) { + return; + } + + // Inform the worker thread we're done + ALOGV("DESCTRUCTOR: Shutting down worker thread"); + DeferredTask task(TaskCommand::Exit); + queueTask(std::move(task)); + + // Wait for it to complete + ALOGV("DESCTRUCTOR: Waiting for worker thread to complete"); + waitForWorkComplete(); + if (mTaskThread.joinable()) { + mTaskThread.join(); + } +} + +// Set will add the entry to hot cache and start a deferred process to write it to disk +void MultifileBlobCache::set(const void* key, EGLsizeiANDROID keySize, const void* value, + EGLsizeiANDROID valueSize) { + if (!mInitialized) { + return; + } + + // Ensure key and value are under their limits + if (keySize > mMaxKeySize || valueSize > mMaxValueSize) { + ALOGW("SET: keySize (%lu vs %zu) or valueSize (%lu vs %zu) too large", keySize, mMaxKeySize, + valueSize, mMaxValueSize); + return; + } + + // Generate a hash of the key and use it to track this entry + uint32_t entryHash = android::JenkinsHashMixBytes(0, static_cast(key), keySize); + + size_t fileSize = sizeof(MultifileHeader) + keySize + valueSize; + + // If we're going to be over the cache limit, kick off a trim to clear space + if (getTotalSize() + fileSize > mMaxTotalSize) { + ALOGV("SET: Cache is full, calling trimCache to clear space"); + trimCache(); + } + + ALOGV("SET: Add %u to cache", entryHash); + + uint8_t* buffer = new uint8_t[fileSize]; + + // Write placeholders for magic and CRC until deferred thread completes the write + android::MultifileHeader header = {kMultifileMagic, kCrcPlaceholder, keySize, valueSize}; + memcpy(static_cast(buffer), static_cast(&header), + sizeof(android::MultifileHeader)); + // Write the key and value after the header + memcpy(static_cast(buffer + sizeof(MultifileHeader)), static_cast(key), + keySize); + memcpy(static_cast(buffer + sizeof(MultifileHeader) + keySize), + static_cast(value), valueSize); + + std::string fullPath = mMultifileDirName + "/" + std::to_string(entryHash); + + // Track the size and access time for quick recall + trackEntry(entryHash, valueSize, fileSize, time(0)); + + // Update the overall cache size + increaseTotalCacheSize(fileSize); + + // Keep the entry in hot cache for quick retrieval + ALOGV("SET: Adding %u to hot cache.", entryHash); + + // Sending -1 as the fd indicates we don't have an fd for this + if (!addToHotCache(entryHash, -1, buffer, fileSize)) { + ALOGE("SET: Failed to add %u to hot cache", entryHash); + delete[] buffer; + return; + } + + // Track that we're creating a pending write for this entry + // Include the buffer to handle the case when multiple writes are pending for an entry + { + // Synchronize access to deferred write status + std::lock_guard lock(mDeferredWriteStatusMutex); + mDeferredWrites.insert(std::make_pair(entryHash, buffer)); + } + + // Create deferred task to write to storage + ALOGV("SET: Adding task to queue."); + DeferredTask task(TaskCommand::WriteToDisk); + task.initWriteToDisk(entryHash, fullPath, buffer, fileSize); + queueTask(std::move(task)); +} + +// Get will check the hot cache, then load it from disk if needed +EGLsizeiANDROID MultifileBlobCache::get(const void* key, EGLsizeiANDROID keySize, void* value, + EGLsizeiANDROID valueSize) { + if (!mInitialized) { + return 0; + } + + // Ensure key and value are under their limits + if (keySize > mMaxKeySize || valueSize > mMaxValueSize) { + ALOGW("GET: keySize (%lu vs %zu) or valueSize (%lu vs %zu) too large", keySize, mMaxKeySize, + valueSize, mMaxValueSize); + return 0; + } + + // Generate a hash of the key and use it to track this entry + uint32_t entryHash = android::JenkinsHashMixBytes(0, static_cast(key), keySize); + + // See if we have this file + if (!contains(entryHash)) { + ALOGV("GET: Cache MISS - cache does not contain entry: %u", entryHash); + return 0; + } + + // Look up the data for this entry + MultifileEntryStats entryStats = getEntryStats(entryHash); + + size_t cachedValueSize = entryStats.valueSize; + if (cachedValueSize > valueSize) { + ALOGV("GET: Cache MISS - valueSize not large enough (%lu) for entry %u, returning required" + "size (%zu)", + valueSize, entryHash, cachedValueSize); + return cachedValueSize; + } + + // We have the file and have enough room to write it out, return the entry + ALOGV("GET: Cache HIT - cache contains entry: %u", entryHash); + + // Look up the size of the file + size_t fileSize = entryStats.fileSize; + if (keySize > fileSize) { + ALOGW("keySize (%lu) is larger than entrySize (%zu). This is a hash collision or modified " + "file", + keySize, fileSize); + return 0; + } + + std::string fullPath = mMultifileDirName + "/" + std::to_string(entryHash); + + // Open the hashed filename path + uint8_t* cacheEntry = 0; + + // Check hot cache + if (mHotCache.find(entryHash) != mHotCache.end()) { + ALOGV("GET: HotCache HIT for entry %u", entryHash); + cacheEntry = mHotCache[entryHash].entryBuffer; + } else { + ALOGV("GET: HotCache MISS for entry: %u", entryHash); + + // Wait for writes to complete if there is an outstanding write for this entry + bool wait = false; + { + // Synchronize access to deferred write status + std::lock_guard lock(mDeferredWriteStatusMutex); + wait = mDeferredWrites.find(entryHash) != mDeferredWrites.end(); + } + + if (wait) { + ALOGV("GET: Waiting for write to complete for %u", entryHash); + waitForWorkComplete(); + } + + // Open the entry file + int fd = open(fullPath.c_str(), O_RDONLY); + if (fd == -1) { + ALOGE("Cache error - failed to open fullPath: %s, error: %s", fullPath.c_str(), + std::strerror(errno)); + return 0; + } + + // Memory map the file + cacheEntry = + reinterpret_cast(mmap(nullptr, fileSize, PROT_READ, MAP_PRIVATE, fd, 0)); + if (cacheEntry == MAP_FAILED) { + ALOGE("Failed to mmap cacheEntry, error: %s", std::strerror(errno)); + close(fd); + return 0; + } + + ALOGV("GET: Adding %u to hot cache", entryHash); + if (!addToHotCache(entryHash, fd, cacheEntry, fileSize)) { + ALOGE("GET: Failed to add %u to hot cache", entryHash); + return 0; + } + + cacheEntry = mHotCache[entryHash].entryBuffer; + } + + // Ensure the header matches + MultifileHeader* header = reinterpret_cast(cacheEntry); + if (header->keySize != keySize || header->valueSize != valueSize) { + ALOGW("Mismatch on keySize(%ld vs. cached %ld) or valueSize(%ld vs. cached %ld) compared " + "to cache header values for fullPath: %s", + keySize, header->keySize, valueSize, header->valueSize, fullPath.c_str()); + removeFromHotCache(entryHash); + return 0; + } + + // Compare the incoming key with our stored version (the beginning of the entry) + uint8_t* cachedKey = cacheEntry + sizeof(MultifileHeader); + int compare = memcmp(cachedKey, key, keySize); + if (compare != 0) { + ALOGW("Cached key and new key do not match! This is a hash collision or modified file"); + removeFromHotCache(entryHash); + return 0; + } + + // Remaining entry following the key is the value + uint8_t* cachedValue = cacheEntry + (keySize + sizeof(MultifileHeader)); + memcpy(value, cachedValue, cachedValueSize); + + return cachedValueSize; +} + +void MultifileBlobCache::finish() { + if (!mInitialized) { + return; + } + + // Wait for all deferred writes to complete + ALOGV("FINISH: Waiting for work to complete."); + waitForWorkComplete(); + + // Close all entries in the hot cache + for (auto hotCacheIter = mHotCache.begin(); hotCacheIter != mHotCache.end();) { + uint32_t entryHash = hotCacheIter->first; + MultifileHotCache entry = hotCacheIter->second; + + ALOGV("FINISH: Closing hot cache entry for %u", entryHash); + freeHotCacheEntry(entry); + + mHotCache.erase(hotCacheIter++); + } +} + +void MultifileBlobCache::trackEntry(uint32_t entryHash, EGLsizeiANDROID valueSize, size_t fileSize, + time_t accessTime) { + mEntries.insert(entryHash); + mEntryStats[entryHash] = {valueSize, fileSize, accessTime}; +} + +bool MultifileBlobCache::contains(uint32_t hashEntry) const { + return mEntries.find(hashEntry) != mEntries.end(); +} + +MultifileEntryStats MultifileBlobCache::getEntryStats(uint32_t entryHash) { + return mEntryStats[entryHash]; +} + +void MultifileBlobCache::increaseTotalCacheSize(size_t fileSize) { + mTotalCacheSize += fileSize; +} + +void MultifileBlobCache::decreaseTotalCacheSize(size_t fileSize) { + mTotalCacheSize -= fileSize; +} + +bool MultifileBlobCache::addToHotCache(uint32_t newEntryHash, int newFd, uint8_t* newEntryBuffer, + size_t newEntrySize) { + ALOGV("HOTCACHE(ADD): Adding %u to hot cache", newEntryHash); + + // Clear space if we need to + if ((mHotCacheSize + newEntrySize) > mHotCacheLimit) { + ALOGV("HOTCACHE(ADD): mHotCacheSize (%zu) + newEntrySize (%zu) is to big for " + "mHotCacheLimit " + "(%zu), freeing up space for %u", + mHotCacheSize, newEntrySize, mHotCacheLimit, newEntryHash); + + // Wait for all the files to complete writing so our hot cache is accurate + ALOGV("HOTCACHE(ADD): Waiting for work to complete for %u", newEntryHash); + waitForWorkComplete(); + + // Free up old entries until under the limit + for (auto hotCacheIter = mHotCache.begin(); hotCacheIter != mHotCache.end();) { + uint32_t oldEntryHash = hotCacheIter->first; + MultifileHotCache oldEntry = hotCacheIter->second; + + // Move our iterator before deleting the entry + hotCacheIter++; + if (!removeFromHotCache(oldEntryHash)) { + ALOGE("HOTCACHE(ADD): Unable to remove entry %u", oldEntryHash); + return false; + } + + // Clear at least half the hot cache + if ((mHotCacheSize + newEntrySize) <= mHotCacheLimit / 2) { + ALOGV("HOTCACHE(ADD): Freed enough space for %zu", mHotCacheSize); + break; + } + } + } + + // Track it + mHotCache[newEntryHash] = {newFd, newEntryBuffer, newEntrySize}; + mHotCacheSize += newEntrySize; + + ALOGV("HOTCACHE(ADD): New hot cache size: %zu", mHotCacheSize); + + return true; +} + +bool MultifileBlobCache::removeFromHotCache(uint32_t entryHash) { + if (mHotCache.find(entryHash) != mHotCache.end()) { + ALOGV("HOTCACHE(REMOVE): Removing %u from hot cache", entryHash); + + // Wait for all the files to complete writing so our hot cache is accurate + ALOGV("HOTCACHE(REMOVE): Waiting for work to complete for %u", entryHash); + waitForWorkComplete(); + + ALOGV("HOTCACHE(REMOVE): Closing hot cache entry for %u", entryHash); + MultifileHotCache entry = mHotCache[entryHash]; + freeHotCacheEntry(entry); + + // Delete the entry from our tracking + mHotCacheSize -= entry.entrySize; + mHotCache.erase(entryHash); + + return true; + } + + return false; +} + +bool MultifileBlobCache::applyLRU(size_t cacheLimit) { + // Walk through our map of sorted last access times and remove files until under the limit + for (auto cacheEntryIter = mEntryStats.begin(); cacheEntryIter != mEntryStats.end();) { + uint32_t entryHash = cacheEntryIter->first; + + ALOGV("LRU: Removing entryHash %u", entryHash); + + // Track the overall size + MultifileEntryStats entryStats = getEntryStats(entryHash); + decreaseTotalCacheSize(entryStats.fileSize); + + // Remove it from hot cache if present + removeFromHotCache(entryHash); + + // Remove it from the system + std::string entryPath = mMultifileDirName + "/" + std::to_string(entryHash); + if (remove(entryPath.c_str()) != 0) { + ALOGE("LRU: Error removing %s: %s", entryPath.c_str(), std::strerror(errno)); + return false; + } + + // Increment the iterator before clearing the entry + cacheEntryIter++; + + // Delete the entry from our tracking + size_t count = mEntryStats.erase(entryHash); + if (count != 1) { + ALOGE("LRU: Failed to remove entryHash (%u) from mEntryStats", entryHash); + return false; + } + + // See if it has been reduced enough + size_t totalCacheSize = getTotalSize(); + if (totalCacheSize <= cacheLimit) { + // Success + ALOGV("LRU: Reduced cache to %zu", totalCacheSize); + return true; + } + } + + ALOGV("LRU: Cache is empty"); + return false; +} + +// When removing files, what fraction of the overall limit should be reached when removing files +// A divisor of two will decrease the cache to 50%, four to 25% and so on +constexpr uint32_t kCacheLimitDivisor = 2; + +// Calculate the cache size and remove old entries until under the limit +void MultifileBlobCache::trimCache() { + // Wait for all deferred writes to complete + ALOGV("TRIM: Waiting for work to complete."); + waitForWorkComplete(); + + ALOGV("TRIM: Reducing multifile cache size to %zu", mMaxTotalSize / kCacheLimitDivisor); + if (!applyLRU(mMaxTotalSize / kCacheLimitDivisor)) { + ALOGE("Error when clearing multifile shader cache"); + return; + } +} + +// This function performs a task. It only knows how to write files to disk, +// but it could be expanded if needed. +void MultifileBlobCache::processTask(DeferredTask& task) { + switch (task.getTaskCommand()) { + case TaskCommand::Exit: { + ALOGV("DEFERRED: Shutting down"); + return; + } + case TaskCommand::WriteToDisk: { + uint32_t entryHash = task.getEntryHash(); + std::string& fullPath = task.getFullPath(); + uint8_t* buffer = task.getBuffer(); + size_t bufferSize = task.getBufferSize(); + + // Create the file or reset it if already present, read+write for user only + int fd = open(fullPath.c_str(), O_WRONLY | O_CREAT | O_TRUNC, S_IRUSR | S_IWUSR); + if (fd == -1) { + ALOGE("Cache error in SET - failed to open fullPath: %s, error: %s", + fullPath.c_str(), std::strerror(errno)); + return; + } + + ALOGV("DEFERRED: Opened fd %i from %s", fd, fullPath.c_str()); + + // Add CRC check to the header (always do this last!) + MultifileHeader* header = reinterpret_cast(buffer); + header->crc = + crc32c(buffer + sizeof(MultifileHeader), bufferSize - sizeof(MultifileHeader)); + + ssize_t result = write(fd, buffer, bufferSize); + if (result != bufferSize) { + ALOGE("Error writing fileSize to cache entry (%s): %s", fullPath.c_str(), + std::strerror(errno)); + return; + } + + ALOGV("DEFERRED: Completed write for: %s", fullPath.c_str()); + close(fd); + + // Erase the entry from mDeferredWrites + // Since there could be multiple outstanding writes for an entry, find the matching one + { + // Synchronize access to deferred write status + std::lock_guard lock(mDeferredWriteStatusMutex); + typedef std::multimap::iterator entryIter; + std::pair iterPair = mDeferredWrites.equal_range(entryHash); + for (entryIter it = iterPair.first; it != iterPair.second; ++it) { + if (it->second == buffer) { + ALOGV("DEFERRED: Marking write complete for %u at %p", it->first, + it->second); + mDeferredWrites.erase(it); + break; + } + } + } + + return; + } + default: { + ALOGE("DEFERRED: Unhandled task type"); + return; + } + } +} + +// This function will wait until tasks arrive, then execute them +// If the exit command is submitted, the loop will terminate +void MultifileBlobCache::processTasksImpl(bool* exitThread) { + while (true) { + std::unique_lock lock(mWorkerMutex); + if (mTasks.empty()) { + ALOGV("WORKER: No tasks available, waiting"); + mWorkerThreadIdle = true; + mWorkerIdleCondition.notify_all(); + // Only wake if notified and command queue is not empty + mWorkAvailableCondition.wait(lock, [this] { return !mTasks.empty(); }); + } + + ALOGV("WORKER: Task available, waking up."); + mWorkerThreadIdle = false; + DeferredTask task = std::move(mTasks.front()); + mTasks.pop(); + + if (task.getTaskCommand() == TaskCommand::Exit) { + ALOGV("WORKER: Exiting work loop."); + *exitThread = true; + mWorkerThreadIdle = true; + mWorkerIdleCondition.notify_one(); + return; + } + + lock.unlock(); + processTask(task); + } +} + +// Process tasks until the exit task is submitted +void MultifileBlobCache::processTasks() { + while (true) { + bool exitThread = false; + processTasksImpl(&exitThread); + if (exitThread) { + break; + } + } +} + +// Add a task to the queue to be processed by the worker thread +void MultifileBlobCache::queueTask(DeferredTask&& task) { + std::lock_guard queueLock(mWorkerMutex); + mTasks.emplace(std::move(task)); + mWorkAvailableCondition.notify_one(); +} + +// Wait until all tasks have been completed +void MultifileBlobCache::waitForWorkComplete() { + std::unique_lock lock(mWorkerMutex); + mWorkerIdleCondition.wait(lock, [this] { return (mTasks.empty() && mWorkerThreadIdle); }); +} + +}; // namespace android diff --git a/opengl/libs/EGL/MultifileBlobCache.h b/opengl/libs/EGL/MultifileBlobCache.h new file mode 100644 index 0000000000000000000000000000000000000000..5e527dcf355808a41c50bfc08549d2e738a729d4 --- /dev/null +++ b/opengl/libs/EGL/MultifileBlobCache.h @@ -0,0 +1,174 @@ +/* + ** 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_MULTIFILE_BLOB_CACHE_H +#define ANDROID_MULTIFILE_BLOB_CACHE_H + +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "FileBlobCache.h" + +namespace android { + +struct MultifileHeader { + uint32_t magic; + uint32_t crc; + EGLsizeiANDROID keySize; + EGLsizeiANDROID valueSize; +}; + +struct MultifileEntryStats { + EGLsizeiANDROID valueSize; + size_t fileSize; + time_t accessTime; +}; + +struct MultifileHotCache { + int entryFd; + uint8_t* entryBuffer; + size_t entrySize; +}; + +enum class TaskCommand { + Invalid = 0, + WriteToDisk, + Exit, +}; + +class DeferredTask { +public: + DeferredTask(TaskCommand command) + : mCommand(command), mEntryHash(0), mBuffer(nullptr), mBufferSize(0) {} + + TaskCommand getTaskCommand() { return mCommand; } + + void initWriteToDisk(uint32_t entryHash, std::string fullPath, uint8_t* buffer, + size_t bufferSize) { + mCommand = TaskCommand::WriteToDisk; + mEntryHash = entryHash; + mFullPath = std::move(fullPath); + mBuffer = buffer; + mBufferSize = bufferSize; + } + + uint32_t getEntryHash() { return mEntryHash; } + std::string& getFullPath() { return mFullPath; } + uint8_t* getBuffer() { return mBuffer; } + size_t getBufferSize() { return mBufferSize; }; + +private: + TaskCommand mCommand; + + // Parameters for WriteToDisk + uint32_t mEntryHash; + std::string mFullPath; + uint8_t* mBuffer; + size_t mBufferSize; +}; + +class MultifileBlobCache { +public: + MultifileBlobCache(size_t maxKeySize, size_t maxValueSize, size_t maxTotalSize, + const std::string& baseDir); + ~MultifileBlobCache(); + + void set(const void* key, EGLsizeiANDROID keySize, const void* value, + EGLsizeiANDROID valueSize); + EGLsizeiANDROID get(const void* key, EGLsizeiANDROID keySize, void* value, + EGLsizeiANDROID valueSize); + + void finish(); + + size_t getTotalSize() const { return mTotalCacheSize; } + +private: + void trackEntry(uint32_t entryHash, EGLsizeiANDROID valueSize, size_t fileSize, + time_t accessTime); + bool contains(uint32_t entryHash) const; + bool removeEntry(uint32_t entryHash); + MultifileEntryStats getEntryStats(uint32_t entryHash); + + size_t getFileSize(uint32_t entryHash); + size_t getValueSize(uint32_t entryHash); + + void increaseTotalCacheSize(size_t fileSize); + void decreaseTotalCacheSize(size_t fileSize); + + bool addToHotCache(uint32_t entryHash, int fd, uint8_t* entryBufer, size_t entrySize); + bool removeFromHotCache(uint32_t entryHash); + + void trimCache(); + bool applyLRU(size_t cacheLimit); + + bool mInitialized; + std::string mMultifileDirName; + + std::unordered_set mEntries; + std::unordered_map mEntryStats; + std::unordered_map mHotCache; + + size_t mMaxKeySize; + size_t mMaxValueSize; + size_t mMaxTotalSize; + size_t mTotalCacheSize; + size_t mHotCacheLimit; + size_t mHotCacheEntryLimit; + size_t mHotCacheSize; + + // Below are the components used for deferred writes + + // Track whether we have pending writes for an entry + std::mutex mDeferredWriteStatusMutex; + std::multimap mDeferredWrites GUARDED_BY(mDeferredWriteStatusMutex); + + // Functions to work through tasks in the queue + void processTasks(); + void processTasksImpl(bool* exitThread); + void processTask(DeferredTask& task); + + // Used by main thread to create work for the worker thread + void queueTask(DeferredTask&& task); + + // Used by main thread to wait for worker thread to complete all outstanding work. + void waitForWorkComplete(); + + std::thread mTaskThread; + std::queue mTasks; + std::mutex mWorkerMutex; + + // This condition will block the worker thread until a task is queued + std::condition_variable mWorkAvailableCondition; + + // This condition will block the main thread while the worker thread still has tasks + std::condition_variable mWorkerIdleCondition; + + // This bool will track whether all tasks have been completed + bool mWorkerThreadIdle; +}; + +}; // namespace android + +#endif // ANDROID_MULTIFILE_BLOB_CACHE_H diff --git a/opengl/libs/EGL/MultifileBlobCache_test.cpp b/opengl/libs/EGL/MultifileBlobCache_test.cpp new file mode 100644 index 0000000000000000000000000000000000000000..dbee13bb2ef47d31114d76dc84ca5019b49deab6 --- /dev/null +++ b/opengl/libs/EGL/MultifileBlobCache_test.cpp @@ -0,0 +1,219 @@ +/* + ** 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 "MultifileBlobCache.h" + +#include +#include +#include +#include + +#include + +namespace android { + +template +using sp = std::shared_ptr; + +constexpr size_t kMaxKeySize = 2 * 1024; +constexpr size_t kMaxValueSize = 6 * 1024; +constexpr size_t kMaxTotalSize = 32 * 1024; + +class MultifileBlobCacheTest : public ::testing::Test { +protected: + virtual void SetUp() { + mTempFile.reset(new TemporaryFile()); + mMBC.reset(new MultifileBlobCache(kMaxKeySize, kMaxValueSize, kMaxTotalSize, + &mTempFile->path[0])); + } + + virtual void TearDown() { mMBC.reset(); } + + std::unique_ptr mTempFile; + std::unique_ptr mMBC; +}; + +TEST_F(MultifileBlobCacheTest, CacheSingleValueSucceeds) { + unsigned char buf[4] = {0xee, 0xee, 0xee, 0xee}; + mMBC->set("abcd", 4, "efgh", 4); + ASSERT_EQ(size_t(4), mMBC->get("abcd", 4, buf, 4)); + ASSERT_EQ('e', buf[0]); + ASSERT_EQ('f', buf[1]); + ASSERT_EQ('g', buf[2]); + ASSERT_EQ('h', buf[3]); +} + +TEST_F(MultifileBlobCacheTest, CacheTwoValuesSucceeds) { + unsigned char buf[2] = {0xee, 0xee}; + mMBC->set("ab", 2, "cd", 2); + mMBC->set("ef", 2, "gh", 2); + ASSERT_EQ(size_t(2), mMBC->get("ab", 2, buf, 2)); + ASSERT_EQ('c', buf[0]); + ASSERT_EQ('d', buf[1]); + ASSERT_EQ(size_t(2), mMBC->get("ef", 2, buf, 2)); + ASSERT_EQ('g', buf[0]); + ASSERT_EQ('h', buf[1]); +} + +TEST_F(MultifileBlobCacheTest, GetSetTwiceSucceeds) { + unsigned char buf[2] = {0xee, 0xee}; + mMBC->set("ab", 2, "cd", 2); + ASSERT_EQ(size_t(2), mMBC->get("ab", 2, buf, 2)); + ASSERT_EQ('c', buf[0]); + ASSERT_EQ('d', buf[1]); + // Use the same key, but different value + mMBC->set("ab", 2, "ef", 2); + ASSERT_EQ(size_t(2), mMBC->get("ab", 2, buf, 2)); + ASSERT_EQ('e', buf[0]); + ASSERT_EQ('f', buf[1]); +} + +TEST_F(MultifileBlobCacheTest, GetOnlyWritesInsideBounds) { + unsigned char buf[6] = {0xee, 0xee, 0xee, 0xee, 0xee, 0xee}; + mMBC->set("abcd", 4, "efgh", 4); + ASSERT_EQ(size_t(4), mMBC->get("abcd", 4, buf + 1, 4)); + ASSERT_EQ(0xee, buf[0]); + ASSERT_EQ('e', buf[1]); + ASSERT_EQ('f', buf[2]); + ASSERT_EQ('g', buf[3]); + ASSERT_EQ('h', buf[4]); + ASSERT_EQ(0xee, buf[5]); +} + +TEST_F(MultifileBlobCacheTest, GetOnlyWritesIfBufferIsLargeEnough) { + unsigned char buf[3] = {0xee, 0xee, 0xee}; + mMBC->set("abcd", 4, "efgh", 4); + ASSERT_EQ(size_t(4), mMBC->get("abcd", 4, buf, 3)); + ASSERT_EQ(0xee, buf[0]); + ASSERT_EQ(0xee, buf[1]); + ASSERT_EQ(0xee, buf[2]); +} + +TEST_F(MultifileBlobCacheTest, GetDoesntAccessNullBuffer) { + mMBC->set("abcd", 4, "efgh", 4); + ASSERT_EQ(size_t(4), mMBC->get("abcd", 4, nullptr, 0)); +} + +TEST_F(MultifileBlobCacheTest, MultipleSetsCacheLatestValue) { + unsigned char buf[4] = {0xee, 0xee, 0xee, 0xee}; + mMBC->set("abcd", 4, "efgh", 4); + mMBC->set("abcd", 4, "ijkl", 4); + ASSERT_EQ(size_t(4), mMBC->get("abcd", 4, buf, 4)); + ASSERT_EQ('i', buf[0]); + ASSERT_EQ('j', buf[1]); + ASSERT_EQ('k', buf[2]); + ASSERT_EQ('l', buf[3]); +} + +TEST_F(MultifileBlobCacheTest, SecondSetKeepsFirstValueIfTooLarge) { + unsigned char buf[kMaxValueSize + 1] = {0xee, 0xee, 0xee, 0xee}; + mMBC->set("abcd", 4, "efgh", 4); + mMBC->set("abcd", 4, buf, kMaxValueSize + 1); + ASSERT_EQ(size_t(4), mMBC->get("abcd", 4, buf, 4)); + ASSERT_EQ('e', buf[0]); + ASSERT_EQ('f', buf[1]); + ASSERT_EQ('g', buf[2]); + ASSERT_EQ('h', buf[3]); +} + +TEST_F(MultifileBlobCacheTest, DoesntCacheIfKeyIsTooBig) { + char key[kMaxKeySize + 1]; + unsigned char buf[4] = {0xee, 0xee, 0xee, 0xee}; + for (int i = 0; i < kMaxKeySize + 1; i++) { + key[i] = 'a'; + } + mMBC->set(key, kMaxKeySize + 1, "bbbb", 4); + ASSERT_EQ(size_t(0), mMBC->get(key, kMaxKeySize + 1, buf, 4)); + ASSERT_EQ(0xee, buf[0]); + ASSERT_EQ(0xee, buf[1]); + ASSERT_EQ(0xee, buf[2]); + ASSERT_EQ(0xee, buf[3]); +} + +TEST_F(MultifileBlobCacheTest, DoesntCacheIfValueIsTooBig) { + char buf[kMaxValueSize + 1]; + for (int i = 0; i < kMaxValueSize + 1; i++) { + buf[i] = 'b'; + } + mMBC->set("abcd", 4, buf, kMaxValueSize + 1); + for (int i = 0; i < kMaxValueSize + 1; i++) { + buf[i] = 0xee; + } + ASSERT_EQ(size_t(0), mMBC->get("abcd", 4, buf, kMaxValueSize + 1)); + for (int i = 0; i < kMaxValueSize + 1; i++) { + SCOPED_TRACE(i); + ASSERT_EQ(0xee, buf[i]); + } +} + +TEST_F(MultifileBlobCacheTest, CacheMaxKeySizeSucceeds) { + char key[kMaxKeySize]; + unsigned char buf[4] = {0xee, 0xee, 0xee, 0xee}; + for (int i = 0; i < kMaxKeySize; i++) { + key[i] = 'a'; + } + mMBC->set(key, kMaxKeySize, "wxyz", 4); + ASSERT_EQ(size_t(4), mMBC->get(key, kMaxKeySize, buf, 4)); + ASSERT_EQ('w', buf[0]); + ASSERT_EQ('x', buf[1]); + ASSERT_EQ('y', buf[2]); + ASSERT_EQ('z', buf[3]); +} + +TEST_F(MultifileBlobCacheTest, CacheMaxValueSizeSucceeds) { + char buf[kMaxValueSize]; + for (int i = 0; i < kMaxValueSize; i++) { + buf[i] = 'b'; + } + mMBC->set("abcd", 4, buf, kMaxValueSize); + for (int i = 0; i < kMaxValueSize; i++) { + buf[i] = 0xee; + } + mMBC->get("abcd", 4, buf, kMaxValueSize); + for (int i = 0; i < kMaxValueSize; i++) { + SCOPED_TRACE(i); + ASSERT_EQ('b', buf[i]); + } +} + +TEST_F(MultifileBlobCacheTest, CacheMaxKeyAndValueSizeSucceeds) { + char key[kMaxKeySize]; + for (int i = 0; i < kMaxKeySize; i++) { + key[i] = 'a'; + } + char buf[kMaxValueSize]; + for (int i = 0; i < kMaxValueSize; i++) { + buf[i] = 'b'; + } + mMBC->set(key, kMaxKeySize, buf, kMaxValueSize); + for (int i = 0; i < kMaxValueSize; i++) { + buf[i] = 0xee; + } + mMBC->get(key, kMaxKeySize, buf, kMaxValueSize); + for (int i = 0; i < kMaxValueSize; i++) { + SCOPED_TRACE(i); + ASSERT_EQ('b', buf[i]); + } +} + +TEST_F(MultifileBlobCacheTest, CacheMinKeyAndValueSizeSucceeds) { + unsigned char buf[1] = {0xee}; + mMBC->set("x", 1, "y", 1); + ASSERT_EQ(size_t(1), mMBC->get("x", 1, buf, 1)); + ASSERT_EQ('y', buf[0]); +} + +} // namespace android diff --git a/opengl/libs/EGL/egl_angle_platform.cpp b/opengl/libs/EGL/egl_angle_platform.cpp index c0ead5096f8ba351e3a2cbbb0d6379d4ad76eca8..9a6bb7a61c82a5e1f95c139c6cf26df550e59692 100644 --- a/opengl/libs/EGL/egl_angle_platform.cpp +++ b/opengl/libs/EGL/egl_angle_platform.cpp @@ -68,9 +68,9 @@ static void logInfo(PlatformMethods* /*platform*/, const char* infoMessage) { static TraceEventHandle addTraceEvent( PlatformMethods* /**platform*/, char phase, const unsigned char* /*category_group_enabled*/, - const char* name, unsigned long long /*id*/, double /*timestamp*/, int /*num_args*/, - const char** /*arg_names*/, const unsigned char* /*arg_types*/, - const unsigned long long* /*arg_values*/, unsigned char /*flags*/) { + const char* name, unsigned long long /*id*/, double /*timestamp*/, int num_args, + const char** arg_names, const unsigned char* /*arg_types*/, + const unsigned long long* arg_values, unsigned char /*flags*/) { switch (phase) { case 'B': { ATRACE_BEGIN(name); @@ -84,6 +84,13 @@ static TraceEventHandle addTraceEvent( ATRACE_NAME(name); break; } + case 'C': { + for(int i=0; i +#include #include #include #include @@ -25,13 +29,18 @@ #include "../egl_impl.h" #include "egl_display.h" -// Cache size limits. -static const size_t maxKeySize = 12 * 1024; -static const size_t maxValueSize = 64 * 1024; -static const size_t maxTotalSize = 2 * 1024 * 1024; +// Monolithic cache size limits. +static const size_t kMaxMonolithicKeySize = 12 * 1024; +static const size_t kMaxMonolithicValueSize = 64 * 1024; +static const size_t kMaxMonolithicTotalSize = 2 * 1024 * 1024; + +// The time in seconds to wait before saving newly inserted monolithic cache entries. +static const unsigned int kDeferredMonolithicSaveDelay = 4; -// The time in seconds to wait before saving newly inserted cache entries. -static const unsigned int deferredSaveDelay = 4; +// Multifile cache size limits +constexpr uint32_t kMaxMultifileKeySize = 1 * 1024 * 1024; +constexpr uint32_t kMaxMultifileValueSize = 8 * 1024 * 1024; +constexpr uint32_t kMaxMultifileTotalSize = 32 * 1024 * 1024; namespace android { @@ -58,7 +67,8 @@ static EGLsizeiANDROID getBlob(const void* key, EGLsizeiANDROID keySize, void* v // // egl_cache_t definition // -egl_cache_t::egl_cache_t() : mInitialized(false) {} +egl_cache_t::egl_cache_t() + : mInitialized(false), mMultifileMode(false), mCacheByteLimit(kMaxMonolithicTotalSize) {} egl_cache_t::~egl_cache_t() {} @@ -72,7 +82,7 @@ void egl_cache_t::initialize(egl_display_t* display) { std::lock_guard lock(mMutex); egl_connection_t* const cnx = &gEGLImpl; - if (cnx->dso && cnx->major >= 0 && cnx->minor >= 0) { + if (display && cnx->dso && cnx->major >= 0 && cnx->minor >= 0) { const char* exts = display->disp.queryString.extensions; size_t bcExtLen = strlen(BC_EXT_STR); size_t extsLen = strlen(exts); @@ -110,6 +120,11 @@ void egl_cache_t::terminate() { mBlobCache->writeToFile(); } mBlobCache = nullptr; + if (mMultifileBlobCache) { + mMultifileBlobCache->finish(); + } + mMultifileBlobCache = nullptr; + mInitialized = false; } void egl_cache_t::setBlob(const void* key, EGLsizeiANDROID keySize, const void* value, @@ -121,21 +136,28 @@ void egl_cache_t::setBlob(const void* key, EGLsizeiANDROID keySize, const void* return; } + updateMode(); + if (mInitialized) { - BlobCache* bc = getBlobCacheLocked(); - bc->set(key, keySize, value, valueSize); - - if (!mSavePending) { - mSavePending = true; - std::thread deferredSaveThread([this]() { - sleep(deferredSaveDelay); - std::lock_guard lock(mMutex); - if (mInitialized && mBlobCache) { - mBlobCache->writeToFile(); - } - mSavePending = false; - }); - deferredSaveThread.detach(); + if (mMultifileMode) { + MultifileBlobCache* mbc = getMultifileBlobCacheLocked(); + mbc->set(key, keySize, value, valueSize); + } else { + BlobCache* bc = getBlobCacheLocked(); + bc->set(key, keySize, value, valueSize); + + if (!mSavePending) { + mSavePending = true; + std::thread deferredSaveThread([this]() { + sleep(kDeferredMonolithicSaveDelay); + std::lock_guard lock(mMutex); + if (mInitialized && mBlobCache) { + mBlobCache->writeToFile(); + } + mSavePending = false; + }); + deferredSaveThread.detach(); + } } } } @@ -145,27 +167,119 @@ EGLsizeiANDROID egl_cache_t::getBlob(const void* key, EGLsizeiANDROID keySize, v std::lock_guard lock(mMutex); if (keySize < 0 || valueSize < 0) { - ALOGW("EGL_ANDROID_blob_cache set: negative sizes are not allowed"); + ALOGW("EGL_ANDROID_blob_cache get: negative sizes are not allowed"); return 0; } + updateMode(); + if (mInitialized) { - BlobCache* bc = getBlobCacheLocked(); - return bc->get(key, keySize, value, valueSize); + if (mMultifileMode) { + MultifileBlobCache* mbc = getMultifileBlobCacheLocked(); + return mbc->get(key, keySize, value, valueSize); + } else { + BlobCache* bc = getBlobCacheLocked(); + return bc->get(key, keySize, value, valueSize); + } } + return 0; } +void egl_cache_t::setCacheMode(EGLCacheMode cacheMode) { + mMultifileMode = (cacheMode == EGLCacheMode::Multifile); +} + void egl_cache_t::setCacheFilename(const char* filename) { std::lock_guard lock(mMutex); mFilename = filename; } +void egl_cache_t::setCacheLimit(int64_t cacheByteLimit) { + std::lock_guard lock(mMutex); + + if (!mMultifileMode) { + // If we're not in multifile mode, ensure the cache limit is only being lowered, + // not increasing above the hard coded platform limit + if (cacheByteLimit > kMaxMonolithicTotalSize) { + return; + } + } + + mCacheByteLimit = cacheByteLimit; +} + +size_t egl_cache_t::getCacheSize() { + std::lock_guard lock(mMutex); + if (mMultifileBlobCache) { + return mMultifileBlobCache->getTotalSize(); + } + if (mBlobCache) { + return mBlobCache->getSize(); + } + return 0; +} + +void egl_cache_t::updateMode() { + // We don't set the mode in the constructor because these checks have + // a non-trivial cost, and not all processes that instantiate egl_cache_t + // will use it. + + // If we've already set the mode, skip these checks + static bool checked = false; + if (checked) { + return; + } + checked = true; + + // Check the device config to decide whether multifile should be used + if (base::GetBoolProperty("ro.egl.blobcache.multifile", false)) { + mMultifileMode = true; + ALOGV("Using multifile EGL blobcache"); + } + + // Allow forcing the mode for debug purposes + std::string mode = base::GetProperty("debug.egl.blobcache.multifile", ""); + if (mode == "true") { + ALOGV("Forcing multifile cache due to debug.egl.blobcache.multifile == %s", mode.c_str()); + mMultifileMode = true; + } else if (mode == "false") { + ALOGV("Forcing monolithic cache due to debug.egl.blobcache.multifile == %s", mode.c_str()); + mMultifileMode = false; + } + + if (mMultifileMode) { + mCacheByteLimit = static_cast( + base::GetUintProperty("ro.egl.blobcache.multifile_limit", + kMaxMultifileTotalSize)); + + // Check for a debug value + int debugCacheSize = base::GetIntProperty("debug.egl.blobcache.multifile_limit", -1); + if (debugCacheSize >= 0) { + ALOGV("Overriding cache limit %zu with %i from debug.egl.blobcache.multifile_limit", + mCacheByteLimit, debugCacheSize); + mCacheByteLimit = debugCacheSize; + } + + ALOGV("Using multifile EGL blobcache limit of %zu bytes", mCacheByteLimit); + } +} + BlobCache* egl_cache_t::getBlobCacheLocked() { if (mBlobCache == nullptr) { - mBlobCache.reset(new FileBlobCache(maxKeySize, maxValueSize, maxTotalSize, mFilename)); + mBlobCache.reset(new FileBlobCache(kMaxMonolithicKeySize, kMaxMonolithicValueSize, + mCacheByteLimit, mFilename)); } return mBlobCache.get(); } +MultifileBlobCache* egl_cache_t::getMultifileBlobCacheLocked() { + if (mMultifileBlobCache == nullptr) { + mMultifileBlobCache.reset(new MultifileBlobCache(kMaxMultifileKeySize, + kMaxMultifileValueSize, mCacheByteLimit, + mFilename)); + } + return mMultifileBlobCache.get(); +} + }; // namespace android diff --git a/opengl/libs/EGL/egl_cache.h b/opengl/libs/EGL/egl_cache.h index d10a6154b7d32cbc3e932ca1e87bb39a2ff8b9b4..ae6d38146e08cc565ef49943689f7f20c5855b13 100644 --- a/opengl/libs/EGL/egl_cache.h +++ b/opengl/libs/EGL/egl_cache.h @@ -25,6 +25,7 @@ #include #include "FileBlobCache.h" +#include "MultifileBlobCache.h" namespace android { @@ -32,6 +33,11 @@ class egl_display_t; class EGLAPI egl_cache_t { public: + enum class EGLCacheMode { + Monolithic, + Multifile, + }; + // get returns a pointer to the singleton egl_cache_t object. This // singleton object will never be destroyed. static egl_cache_t* get(); @@ -64,6 +70,15 @@ public: // cache contents from one program invocation to another. void setCacheFilename(const char* filename); + // Allow setting monolithic or multifile modes + void setCacheMode(EGLCacheMode cacheMode); + + // Allow the fixed cache limit to be overridden + void setCacheLimit(int64_t cacheByteLimit); + + // Return the byte total for cache file(s) + size_t getCacheSize(); + private: // Creation and (the lack of) destruction is handled internally. egl_cache_t(); @@ -73,12 +88,18 @@ private: egl_cache_t(const egl_cache_t&); // not implemented void operator=(const egl_cache_t&); // not implemented + // Check system properties to determine which blobcache mode should be used + void updateMode(); + // getBlobCacheLocked returns the BlobCache object being used to store the // key/value blob pairs. If the BlobCache object has not yet been created, // this will do so, loading the serialized cache contents from disk if // possible. BlobCache* getBlobCacheLocked(); + // Get or create the multifile blobcache + MultifileBlobCache* getMultifileBlobCacheLocked(); + // mInitialized indicates whether the egl_cache_t is in the initialized // state. It is initialized to false at construction time, and gets set to // true when initialize is called. It is set back to false when terminate @@ -92,6 +113,9 @@ private: // first time it's needed. std::unique_ptr mBlobCache; + // The multifile version of blobcache allowing larger contents to be stored + std::unique_ptr mMultifileBlobCache; + // mFilename is the name of the file for storing cache contents in between // program invocations. It is initialized to an empty string at // construction time, and can be set with the setCacheFilename method. An @@ -112,6 +136,12 @@ private: // sCache is the singleton egl_cache_t object. static egl_cache_t sCache; + + // Whether to use multiple files to store cache entries + bool mMultifileMode; + + // Cache limit + size_t mCacheByteLimit; }; }; // namespace android diff --git a/opengl/libs/EGL/egl_display.cpp b/opengl/libs/EGL/egl_display.cpp index 6593c1bb1601acc6e734dd87265781989b26bd42..525fed115d96ad88fc8ce6b94a326848f4aa3e43 100644 --- a/opengl/libs/EGL/egl_display.cpp +++ b/opengl/libs/EGL/egl_display.cpp @@ -353,8 +353,9 @@ EGLBoolean egl_display_t::initialize(EGLint* major, EGLint* minor) { // Typically that means there is an HDR capable display attached, but could be // support for attaching an HDR display. In either case, advertise support for // HDR color spaces. - mExtensionString.append( - "EGL_EXT_gl_colorspace_bt2020_linear EGL_EXT_gl_colorspace_bt2020_pq "); + mExtensionString.append("EGL_EXT_gl_colorspace_bt2020_hlg " + "EGL_EXT_gl_colorspace_bt2020_linear " + "EGL_EXT_gl_colorspace_bt2020_pq "); } char const* start = gExtensionString; diff --git a/opengl/libs/EGL/egl_platform_entries.cpp b/opengl/libs/EGL/egl_platform_entries.cpp index 4b637dc60bd164ca87e2ad4f00c6651ea3becd48..aefa1f0db536fb2c42a4db84a7f6674ae055e0d0 100644 --- a/opengl/libs/EGL/egl_platform_entries.cpp +++ b/opengl/libs/EGL/egl_platform_entries.cpp @@ -18,6 +18,7 @@ #include "egl_platform_entries.h" +#include #include #include #include @@ -29,7 +30,6 @@ #include #include #include -#include #include #include @@ -422,6 +422,8 @@ static android_dataspace dataSpaceFromEGLColorSpace(EGLint colorspace, PixelForm return HAL_DATASPACE_V0_SCRGB; } else if (colorspace == EGL_GL_COLORSPACE_SCRGB_LINEAR_EXT) { return HAL_DATASPACE_V0_SCRGB_LINEAR; + } else if (colorspace == EGL_GL_COLORSPACE_BT2020_HLG_EXT) { + return static_cast(HAL_DATASPACE_BT2020_HLG); } else if (colorspace == EGL_GL_COLORSPACE_BT2020_LINEAR_EXT) { if (pixelFormat == PixelFormat::RGBA_FP16) { return static_cast(HAL_DATASPACE_STANDARD_BT2020 | @@ -433,6 +435,7 @@ static android_dataspace dataSpaceFromEGLColorSpace(EGLint colorspace, PixelForm } else if (colorspace == EGL_GL_COLORSPACE_BT2020_PQ_EXT) { return HAL_DATASPACE_BT2020_PQ; } + return HAL_DATASPACE_UNKNOWN; } @@ -459,6 +462,9 @@ static std::vector getDriverColorSpaces(egl_display_t* dp) { if (findExtension(dp->disp.queryString.extensions, "EGL_EXT_gl_colorspace_scrgb_linear")) { colorSpaces.push_back(EGL_GL_COLORSPACE_SCRGB_LINEAR_EXT); } + if (findExtension(dp->disp.queryString.extensions, "EGL_EXT_gl_colorspace_bt2020_hlg")) { + colorSpaces.push_back(EGL_GL_COLORSPACE_BT2020_HLG_EXT); + } if (findExtension(dp->disp.queryString.extensions, "EGL_EXT_gl_colorspace_bt2020_linear")) { colorSpaces.push_back(EGL_GL_COLORSPACE_BT2020_LINEAR_EXT); } @@ -492,6 +498,7 @@ static EGLBoolean processAttributes(egl_display_t* dp, ANativeWindow* window, case EGL_GL_COLORSPACE_DISPLAY_P3_PASSTHROUGH_EXT: case EGL_GL_COLORSPACE_SCRGB_LINEAR_EXT: case EGL_GL_COLORSPACE_SCRGB_EXT: + case EGL_GL_COLORSPACE_BT2020_HLG_EXT: case EGL_GL_COLORSPACE_BT2020_LINEAR_EXT: case EGL_GL_COLORSPACE_BT2020_PQ_EXT: case EGL_GL_COLORSPACE_DISPLAY_P3_LINEAR_EXT: @@ -943,6 +950,8 @@ EGLContext eglCreateContextImpl(EGLDisplay dpy, EGLConfig config, EGLContext sha android::GraphicsEnv::getInstance().setTargetStats( android::GpuStatsInfo::Stats::GLES_1_IN_USE); } + android::GraphicsEnv::getInstance().setTargetStats( + android::GpuStatsInfo::Stats::CREATED_GLES_CONTEXT); egl_context_t* c = new egl_context_t(dpy, context, config, cnx, version); return c; } diff --git a/opengl/libs/EGL/fuzzer/Android.bp b/opengl/libs/EGL/fuzzer/Android.bp new file mode 100644 index 0000000000000000000000000000000000000000..022a2a3f06e672095c237d6a527e1c1bf5a10144 --- /dev/null +++ b/opengl/libs/EGL/fuzzer/Android.bp @@ -0,0 +1,42 @@ +// Copyright (C) 2023 The Android Open Source Project +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package { + // 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_fuzz { + name: "MultifileBlobCache_fuzzer", + + fuzz_config: { + cc: ["cnorthrop@google.com"], + libfuzzer_options: ["len_control=0"], + }, + + static_libs: [ + "libbase", + "libEGL_blobCache", + "liblog", + "libutils", + ], + + srcs: [ + "MultifileBlobCache_fuzzer.cpp", + ], +} diff --git a/opengl/libs/EGL/fuzzer/MultifileBlobCache_fuzzer.cpp b/opengl/libs/EGL/fuzzer/MultifileBlobCache_fuzzer.cpp new file mode 100644 index 0000000000000000000000000000000000000000..633cc9c51becd59971cb677999551f49defbddd4 --- /dev/null +++ b/opengl/libs/EGL/fuzzer/MultifileBlobCache_fuzzer.cpp @@ -0,0 +1,158 @@ +/* + ** 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 "MultifileBlobCache.h" + +#include +#include +#include +#include +#include +#include + +namespace android { + +constexpr size_t kMaxKeySize = 2 * 1024; +constexpr size_t kMaxValueSize = 6 * 1024; +constexpr size_t kMaxTotalSize = 32 * 1024; + +extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) { + // To fuzz this, we're going to create a key/value pair from data + // and use them with MultifileBlobCache in a random order + // - Use the first entry in data to determine keySize + // - Use the second entry in data to determine valueSize + // - Mod each of them against half the remaining size, ensuring both fit + // - Create key and value using sizes from data + // - Use remaining data to switch between GET and SET while + // tweaking the keys slightly + // - Ensure two cache cleaning scenarios are hit at the end + + // Ensure we have enough data to create interesting key/value pairs + size_t kMinInputLength = 128; + if (size < kMinInputLength) { + return 0; + } + + // Need non-zero sizes for interesting results + if (data[0] == 0 || data[1] == 0) { + return 0; + } + + // We need to divide the data up into buffers and sizes + FuzzedDataProvider fdp(data, size); + + // Pull two values from data for key and value size + EGLsizeiANDROID keySize = static_cast(fdp.ConsumeIntegral()); + EGLsizeiANDROID valueSize = static_cast(fdp.ConsumeIntegral()); + size -= 2 * sizeof(uint8_t); + + // Ensure key and value fit in the remaining space (cap them at half data size) + keySize = keySize % (size >> 1); + valueSize = valueSize % (size >> 1); + + // If either size ended up zero, just move on to save time + if (keySize == 0 || valueSize == 0) { + return 0; + } + + // Create key and value from remaining data + std::vector key; + std::vector value; + key = fdp.ConsumeBytes(keySize); + value = fdp.ConsumeBytes(valueSize); + + // Create a tempfile and a cache + std::unique_ptr tempFile; + std::unique_ptr mbc; + + tempFile.reset(new TemporaryFile()); + mbc.reset( + new MultifileBlobCache(kMaxKeySize, kMaxValueSize, kMaxTotalSize, &tempFile->path[0])); + // With remaining data, select different paths below + int loopCount = 1; + uint8_t bumpCount = 0; + while (fdp.remaining_bytes() > 0) { + // Bounce back and forth between gets and sets + if (fdp.ConsumeBool()) { + mbc->set(key.data(), keySize, value.data(), valueSize); + } else { + uint8_t* buffer = new uint8_t[valueSize]; + mbc->get(key.data(), keySize, buffer, valueSize); + delete[] buffer; + } + + // Bump the key and values periodically, causing different hits/misses + if (fdp.ConsumeBool()) { + key[0]++; + value[0]++; + bumpCount++; + } + + // Reset the key and value periodically to hit old entries + if (fdp.ConsumeBool()) { + key[0] -= bumpCount; + value[0] -= bumpCount; + bumpCount = 0; + } + + loopCount++; + } + mbc->finish(); + + // Fill 2 keys and 2 values to max size with unique values + std::vector maxKey1, maxKey2, maxValue1, maxValue2; + maxKey1.resize(kMaxKeySize, 0); + maxKey2.resize(kMaxKeySize, 0); + maxValue1.resize(kMaxValueSize, 0); + maxValue2.resize(kMaxValueSize, 0); + for (int i = 0; i < keySize && i < kMaxKeySize; ++i) { + maxKey1[i] = key[i]; + maxKey2[i] = key[i] - 1; + } + for (int i = 0; i < valueSize && i < kMaxValueSize; ++i) { + maxValue1[i] = value[i]; + maxValue2[i] = value[i] - 1; + } + + // Trigger hot cache trimming + // Place the maxKey/maxValue twice + // The first will fit, the second will trigger hot cache trimming + tempFile.reset(new TemporaryFile()); + mbc.reset( + new MultifileBlobCache(kMaxKeySize, kMaxValueSize, kMaxTotalSize, &tempFile->path[0])); + uint8_t* buffer = new uint8_t[kMaxValueSize]; + mbc->set(maxKey1.data(), kMaxKeySize, maxValue1.data(), kMaxValueSize); + mbc->set(maxKey2.data(), kMaxKeySize, maxValue2.data(), kMaxValueSize); + mbc->get(maxKey1.data(), kMaxKeySize, buffer, kMaxValueSize); + mbc->finish(); + + // Trigger cold cache trimming + // Create a total size small enough only one entry fits + // Since the cache will add a header, 2 * key + value will only hold one value, the second will + // overflow + tempFile.reset(new TemporaryFile()); + mbc.reset(new MultifileBlobCache(kMaxKeySize, kMaxValueSize, 2 * (kMaxKeySize + kMaxValueSize), + &tempFile->path[0])); + mbc->set(maxKey1.data(), kMaxKeySize, maxValue1.data(), kMaxValueSize); + mbc->set(maxKey2.data(), kMaxKeySize, maxValue2.data(), kMaxValueSize); + mbc->get(maxKey1.data(), kMaxKeySize, buffer, kMaxValueSize); + mbc->finish(); + + delete[] buffer; + return 0; +} + +} // namespace android diff --git a/opengl/tests/EGLTest/Android.bp b/opengl/tests/EGLTest/Android.bp index 51c937614fcd5cb8be3b4a9305e7f9c2a739bb35..d96a89564d534a91c15a78d68b4a8edba8128859 100644 --- a/opengl/tests/EGLTest/Android.bp +++ b/opengl/tests/EGLTest/Android.bp @@ -1,4 +1,3 @@ - package { // See: http://go/android-license-faq // A large-scale-change added 'default_applicable_licenses' to import @@ -11,6 +10,7 @@ package { cc_test { name: "EGL_test", + test_suites: ["general-tests"], srcs: [ "egl_cache_test.cpp", diff --git a/opengl/tests/EGLTest/EGL_test.cpp b/opengl/tests/EGLTest/EGL_test.cpp index bbd786d155f3cbe83f5206a399431477c6da9cb1..cbe4ef9c408af1d5d1290727e5b37b598bdf055b 100644 --- a/opengl/tests/EGLTest/EGL_test.cpp +++ b/opengl/tests/EGLTest/EGL_test.cpp @@ -343,6 +343,11 @@ TEST_F(EGLTest, EGLDisplayP3Passthrough) { } TEST_F(EGLTest, EGLDisplayP31010102) { + // This test has been failing since: + // libEGL: When driver doesn't understand P3, map sRGB-encoded P3 to sRGB + // https://android-review.git.corp.google.com/c/platform/frameworks/native/+/793504 + GTEST_SKIP() << "Skipping broken test. See b/120714942 and b/117104367"; + EGLint numConfigs; EGLConfig config; EGLBoolean success; @@ -866,6 +871,12 @@ TEST_F(EGLTest, EGLUnsupportedColorspaceFormatCombo) { EGLConfig config; EGLBoolean success; + if (!hasWideColorDisplay) { + // skip this test if device does not have wide-color display + RecordProperty("hasWideColorDisplay", false); + return; + } + const EGLint attrs[] = { // clang-format off EGL_SURFACE_TYPE, EGL_WINDOW_BIT, @@ -951,6 +962,12 @@ TEST_F(EGLTest, EGLCreateWindowFailAndSucceed) { TEST_F(EGLTest, EGLCreateWindowTwoColorspaces) { EGLConfig config; + if (!hasWideColorDisplay) { + // skip this test if device does not have wide-color display + RecordProperty("hasWideColorDisplay", false); + return; + } + ASSERT_NO_FATAL_FAILURE(get8BitConfig(config)); struct MockConsumer : public BnConsumerListener { diff --git a/opengl/tests/EGLTest/egl_cache_test.cpp b/opengl/tests/EGLTest/egl_cache_test.cpp index c974f63c13ae269430a3dc4a62f14a729b4364fc..f81c68f66e742e77ffc1152cf8456c60c50380e7 100644 --- a/opengl/tests/EGLTest/egl_cache_test.cpp +++ b/opengl/tests/EGLTest/egl_cache_test.cpp @@ -15,7 +15,7 @@ */ #define LOG_TAG "EGL_test" -//#define LOG_NDEBUG 0 +// #define LOG_NDEBUG 0 #include @@ -24,27 +24,42 @@ #include #include "egl_cache.h" +#include "MultifileBlobCache.h" #include "egl_display.h" +#include #include +using namespace std::literals; + namespace android { -class EGLCacheTest : public ::testing::Test { +class EGLCacheTest : public ::testing::TestWithParam { protected: virtual void SetUp() { - mCache = egl_cache_t::get(); + // Terminate to clean up any previous cache in this process + mCache->terminate(); + + mTempFile.reset(new TemporaryFile()); + mCache->setCacheFilename(&mTempFile->path[0]); + mCache->setCacheLimit(1024); + mCache->setCacheMode(mCacheMode); } virtual void TearDown() { - mCache->setCacheFilename(""); mCache->terminate(); + mCache->setCacheFilename(""); + mTempFile.reset(nullptr); } - egl_cache_t* mCache; + std::string getCachefileName(); + + egl_cache_t* mCache = egl_cache_t::get(); + std::unique_ptr mTempFile; + egl_cache_t::EGLCacheMode mCacheMode = GetParam(); }; -TEST_F(EGLCacheTest, UninitializedCacheAlwaysMisses) { +TEST_P(EGLCacheTest, UninitializedCacheAlwaysMisses) { uint8_t buf[4] = { 0xee, 0xee, 0xee, 0xee }; mCache->setBlob("abcd", 4, "efgh", 4); ASSERT_EQ(0, mCache->getBlob("abcd", 4, buf, 4)); @@ -54,7 +69,7 @@ TEST_F(EGLCacheTest, UninitializedCacheAlwaysMisses) { ASSERT_EQ(0xee, buf[3]); } -TEST_F(EGLCacheTest, InitializedCacheAlwaysHits) { +TEST_P(EGLCacheTest, InitializedCacheAlwaysHits) { uint8_t buf[4] = { 0xee, 0xee, 0xee, 0xee }; mCache->initialize(egl_display_t::get(EGL_DEFAULT_DISPLAY)); mCache->setBlob("abcd", 4, "efgh", 4); @@ -65,7 +80,7 @@ TEST_F(EGLCacheTest, InitializedCacheAlwaysHits) { ASSERT_EQ('h', buf[3]); } -TEST_F(EGLCacheTest, TerminatedCacheAlwaysMisses) { +TEST_P(EGLCacheTest, TerminatedCacheAlwaysMisses) { uint8_t buf[4] = { 0xee, 0xee, 0xee, 0xee }; mCache->initialize(egl_display_t::get(EGL_DEFAULT_DISPLAY)); mCache->setBlob("abcd", 4, "efgh", 4); @@ -77,35 +92,243 @@ TEST_F(EGLCacheTest, TerminatedCacheAlwaysMisses) { ASSERT_EQ(0xee, buf[3]); } -class EGLCacheSerializationTest : public EGLCacheTest { +TEST_P(EGLCacheTest, ReinitializedCacheContainsValues) { + uint8_t buf[4] = { 0xee, 0xee, 0xee, 0xee }; + mCache->initialize(egl_display_t::get(EGL_DEFAULT_DISPLAY)); + mCache->setBlob("abcd", 4, "efgh", 4); + mCache->terminate(); + mCache->initialize(egl_display_t::get(EGL_DEFAULT_DISPLAY)); + ASSERT_EQ(4, mCache->getBlob("abcd", 4, buf, 4)); + ASSERT_EQ('e', buf[0]); + ASSERT_EQ('f', buf[1]); + ASSERT_EQ('g', buf[2]); + ASSERT_EQ('h', buf[3]); +} + +std::string EGLCacheTest::getCachefileName() { + // Return the monolithic filename unless we find the multifile dir + std::string cachePath = &mTempFile->path[0]; + std::string multifileDirName = cachePath + ".multifile"; + std::string cachefileName = ""; -protected: + struct stat info; + if (stat(multifileDirName.c_str(), &info) == 0) { + // Ensure we only have one file to manage + int realFileCount = 0; - virtual void SetUp() { - EGLCacheTest::SetUp(); - mTempFile.reset(new TemporaryFile()); + // We have a multifile dir. Return the only real file in it. + DIR* dir; + struct dirent* entry; + if ((dir = opendir(multifileDirName.c_str())) != nullptr) { + while ((entry = readdir(dir)) != nullptr) { + if (entry->d_name == "."s || entry->d_name == ".."s) { + continue; + } + cachefileName = multifileDirName + "/" + entry->d_name; + realFileCount++; + } + } else { + printf("Unable to open %s, error: %s\n", + multifileDirName.c_str(), std::strerror(errno)); + } + + if (realFileCount != 1) { + // If there was more than one real file in the directory, this + // violates test assumptions + cachefileName = ""; + } + } else { + printf("Unable to stat %s, error: %s\n", + multifileDirName.c_str(), std::strerror(errno)); } - virtual void TearDown() { - mTempFile.reset(nullptr); - EGLCacheTest::TearDown(); + return cachefileName; +} + +TEST_P(EGLCacheTest, ModifiedCacheBeginMisses) { + // Skip if not in multifile mode + if (mCacheMode == egl_cache_t::EGLCacheMode::Monolithic) { + GTEST_SKIP() << "Skipping test designed for multifile"; } - std::unique_ptr mTempFile; -}; + uint8_t buf[4] = { 0xee, 0xee, 0xee, 0xee }; + mCache->initialize(egl_display_t::get(EGL_DEFAULT_DISPLAY)); + + mCache->setBlob("abcd", 4, "efgh", 4); + ASSERT_EQ(4, mCache->getBlob("abcd", 4, buf, 4)); + ASSERT_EQ('e', buf[0]); + ASSERT_EQ('f', buf[1]); + ASSERT_EQ('g', buf[2]); + ASSERT_EQ('h', buf[3]); + + // Ensure the cache file is written to disk + mCache->terminate(); + + // Depending on the cache mode, the file will be in different locations + std::string cachefileName = getCachefileName(); + ASSERT_TRUE(cachefileName.length() > 0); + + // Stomp on the beginning of the cache file, breaking the key match + const char* stomp = "BADF00D"; + std::fstream fs(cachefileName); + fs.seekp(0, std::ios_base::beg); + fs.write(stomp, strlen(stomp)); + fs.flush(); + fs.close(); + + // Ensure no cache hit + mCache->initialize(egl_display_t::get(EGL_DEFAULT_DISPLAY)); + uint8_t buf2[4] = { 0xee, 0xee, 0xee, 0xee }; + // getBlob may return junk for required size, but should not return a cache hit + mCache->getBlob("abcd", 4, buf2, 4); + ASSERT_EQ(0xee, buf2[0]); + ASSERT_EQ(0xee, buf2[1]); + ASSERT_EQ(0xee, buf2[2]); + ASSERT_EQ(0xee, buf2[3]); +} + +TEST_P(EGLCacheTest, ModifiedCacheEndMisses) { + // Skip if not in multifile mode + if (mCacheMode == egl_cache_t::EGLCacheMode::Monolithic) { + GTEST_SKIP() << "Skipping test designed for multifile"; + } + + uint8_t buf[16] = { 0xee, 0xee, 0xee, 0xee, + 0xee, 0xee, 0xee, 0xee, + 0xee, 0xee, 0xee, 0xee, + 0xee, 0xee, 0xee, 0xee }; + + mCache->initialize(egl_display_t::get(EGL_DEFAULT_DISPLAY)); + + mCache->setBlob("abcdefghij", 10, "klmnopqrstuvwxyz", 16); + ASSERT_EQ(16, mCache->getBlob("abcdefghij", 10, buf, 16)); + ASSERT_EQ('w', buf[12]); + ASSERT_EQ('x', buf[13]); + ASSERT_EQ('y', buf[14]); + ASSERT_EQ('z', buf[15]); + + // Ensure the cache file is written to disk + mCache->terminate(); + + // Depending on the cache mode, the file will be in different locations + std::string cachefileName = getCachefileName(); + ASSERT_TRUE(cachefileName.length() > 0); + + // Stomp on the END of the cache file, modifying its contents + const char* stomp = "BADF00D"; + std::fstream fs(cachefileName); + fs.seekp(-strlen(stomp), std::ios_base::end); + fs.write(stomp, strlen(stomp)); + fs.flush(); + fs.close(); + + // Ensure no cache hit + mCache->initialize(egl_display_t::get(EGL_DEFAULT_DISPLAY)); + uint8_t buf2[16] = { 0xee, 0xee, 0xee, 0xee, + 0xee, 0xee, 0xee, 0xee, + 0xee, 0xee, 0xee, 0xee, + 0xee, 0xee, 0xee, 0xee }; + + // getBlob may return junk for required size, but should not return a cache hit + mCache->getBlob("abcdefghij", 10, buf2, 16); + ASSERT_EQ(0xee, buf2[0]); + ASSERT_EQ(0xee, buf2[1]); + ASSERT_EQ(0xee, buf2[2]); + ASSERT_EQ(0xee, buf2[3]); +} + +TEST_P(EGLCacheTest, TerminatedCacheBelowCacheLimit) { + uint8_t buf[4] = { 0xee, 0xee, 0xee, 0xee }; + mCache->initialize(egl_display_t::get(EGL_DEFAULT_DISPLAY)); + + mCache->setBlob("abcd", 4, "efgh", 4); + ASSERT_EQ(4, mCache->getBlob("abcd", 4, buf, 4)); + ASSERT_EQ('e', buf[0]); + ASSERT_EQ('f', buf[1]); + ASSERT_EQ('g', buf[2]); + ASSERT_EQ('h', buf[3]); + + mCache->setBlob("ijkl", 4, "mnop", 4); + ASSERT_EQ(4, mCache->getBlob("ijkl", 4, buf, 4)); + ASSERT_EQ('m', buf[0]); + ASSERT_EQ('n', buf[1]); + ASSERT_EQ('o', buf[2]); + ASSERT_EQ('p', buf[3]); + + mCache->setBlob("qrst", 4, "uvwx", 4); + ASSERT_EQ(4, mCache->getBlob("qrst", 4, buf, 4)); + ASSERT_EQ('u', buf[0]); + ASSERT_EQ('v', buf[1]); + ASSERT_EQ('w', buf[2]); + ASSERT_EQ('x', buf[3]); + + // Cache should contain both the key and the value + // So 8 bytes per entry, at least 24 bytes + ASSERT_GE(mCache->getCacheSize(), 24); + + // Set the new limit and initialize cache + mCache->terminate(); + mCache->setCacheLimit(4); + mCache->initialize(egl_display_t::get(EGL_DEFAULT_DISPLAY)); + + // Ensure the new limit is respected + ASSERT_LE(mCache->getCacheSize(), 4); +} + +TEST_P(EGLCacheTest, TrimCacheOnOverflow) { + // Skip if not in multifile mode + if (mCacheMode == egl_cache_t::EGLCacheMode::Monolithic) { + GTEST_SKIP() << "Skipping test designed for multifile"; + } -TEST_F(EGLCacheSerializationTest, ReinitializedCacheContainsValues) { uint8_t buf[4] = { 0xee, 0xee, 0xee, 0xee }; - mCache->setCacheFilename(&mTempFile->path[0]); mCache->initialize(egl_display_t::get(EGL_DEFAULT_DISPLAY)); + + // Set one value in the cache mCache->setBlob("abcd", 4, "efgh", 4); + ASSERT_EQ(4, mCache->getBlob("abcd", 4, buf, 4)); + ASSERT_EQ('e', buf[0]); + ASSERT_EQ('f', buf[1]); + ASSERT_EQ('g', buf[2]); + ASSERT_EQ('h', buf[3]); + + // Get the size of cache with a single entry + size_t cacheEntrySize = mCache->getCacheSize(); + + // Now reinitialize the cache, using max size equal to a single entry mCache->terminate(); + mCache->setCacheLimit(cacheEntrySize); mCache->initialize(egl_display_t::get(EGL_DEFAULT_DISPLAY)); + + // Ensure our cache still has original value ASSERT_EQ(4, mCache->getBlob("abcd", 4, buf, 4)); ASSERT_EQ('e', buf[0]); ASSERT_EQ('f', buf[1]); ASSERT_EQ('g', buf[2]); ASSERT_EQ('h', buf[3]); + + // Set another value, which should overflow the cache and trim + mCache->setBlob("ijkl", 4, "mnop", 4); + ASSERT_EQ(4, mCache->getBlob("ijkl", 4, buf, 4)); + ASSERT_EQ('m', buf[0]); + ASSERT_EQ('n', buf[1]); + ASSERT_EQ('o', buf[2]); + ASSERT_EQ('p', buf[3]); + + // The cache should still be under the limit + ASSERT_TRUE(mCache->getCacheSize() == cacheEntrySize); + + // And no cache hit on trimmed entry + uint8_t buf2[4] = { 0xee, 0xee, 0xee, 0xee }; + mCache->getBlob("abcd", 4, buf2, 4); + ASSERT_EQ(0xee, buf2[0]); + ASSERT_EQ(0xee, buf2[1]); + ASSERT_EQ(0xee, buf2[2]); + ASSERT_EQ(0xee, buf2[3]); } +INSTANTIATE_TEST_CASE_P(MonolithicCacheTests, + EGLCacheTest, ::testing::Values(egl_cache_t::EGLCacheMode::Monolithic)); +INSTANTIATE_TEST_CASE_P(MultifileCacheTests, + EGLCacheTest, ::testing::Values(egl_cache_t::EGLCacheMode::Multifile)); } diff --git a/opengl/tests/lib/WindowSurface.cpp b/opengl/tests/lib/WindowSurface.cpp index fd4522e75739e309e5a34967b258acec1fd70547..e94b565f11619759bcfb0e5da0ab9f0098500828 100644 --- a/opengl/tests/lib/WindowSurface.cpp +++ b/opengl/tests/lib/WindowSurface.cpp @@ -36,7 +36,14 @@ WindowSurface::WindowSurface() { return; } - const auto displayToken = SurfaceComposerClient::getInternalDisplayToken(); + const auto ids = SurfaceComposerClient::getPhysicalDisplayIds(); + if (ids.empty()) { + fprintf(stderr, "Failed to get ID for any displays.\n"); + return; + } + + // display 0 is picked for now, can extend to support all displays if needed + const auto displayToken = SurfaceComposerClient::getPhysicalDisplayToken(ids.front()); if (displayToken == nullptr) { fprintf(stderr, "ERROR: no display\n"); return; diff --git a/services/audiomanager/IAudioManager.cpp b/services/audiomanager/IAudioManager.cpp index ae1bb1a0d0de68c3b41eb0949b0c673dc3b7ffc4..3ef5049230d968b8904f582151af0218a20dc1a6 100644 --- a/services/audiomanager/IAudioManager.cpp +++ b/services/audiomanager/IAudioManager.cpp @@ -87,12 +87,12 @@ public: } virtual status_t playerEvent(audio_unique_id_t piid, player_state_t event, - audio_port_handle_t deviceId) { + audio_port_handle_t eventId) { Parcel data, reply; data.writeInterfaceToken(IAudioManager::getInterfaceDescriptor()); data.writeInt32((int32_t) piid); data.writeInt32((int32_t) event); - data.writeInt32((int32_t) deviceId); + data.writeInt32((int32_t) eventId); return remote()->transact(PLAYER_EVENT, data, &reply, IBinder::FLAG_ONEWAY); } @@ -141,6 +141,17 @@ public: data.writeInt32((int32_t) sessionId); return remote()->transact(PLAYER_SESSION_ID, data, &reply, IBinder::FLAG_ONEWAY); } + + virtual status_t portEvent(audio_port_handle_t portId, player_state_t event, + const std::unique_ptr& extras) { + Parcel data, reply; + data.writeInterfaceToken(IAudioManager::getInterfaceDescriptor()); + data.writeInt32((int32_t) portId); + data.writeInt32((int32_t) event); + // TODO: replace PersistableBundle with own struct + data.writeNullableParcelable(extras); + return remote()->transact(PORT_EVENT, data, &reply, IBinder::FLAG_ONEWAY); + } }; IMPLEMENT_META_INTERFACE(AudioManager, "android.media.IAudioService"); diff --git a/services/batteryservice/Android.bp b/services/batteryservice/Android.bp index 1e3799185e2775b60d28e49889bef18eeb57b362..9b783916d33c4f6838ce9ad6c09571b050a61b3c 100644 --- a/services/batteryservice/Android.bp +++ b/services/batteryservice/Android.bp @@ -9,6 +9,7 @@ package { cc_library_headers { name: "libbatteryservice_headers", + host_supported: true, vendor_available: true, recovery_available: true, export_include_dirs: ["include"], diff --git a/services/gpuservice/Android.bp b/services/gpuservice/Android.bp index fba64c7569e94c72efc422d97685b6d04c4a0318..052efb6bbb8383a43f300c4c7f78de7b0a991758 100644 --- a/services/gpuservice/Android.bp +++ b/services/gpuservice/Android.bp @@ -71,6 +71,7 @@ filegroup { cc_library_shared { name: "libgpuservice", defaults: ["libgpuservice_production_defaults"], + export_include_dirs: ["include"], srcs: [ ":libgpuservice_sources", ], diff --git a/services/gpuservice/GpuService.cpp b/services/gpuservice/GpuService.cpp index 9171534ed6e15c2fcc51eb5125bec15daf09194b..48d793a4d4489c81d30dbeb6e697d283b45200dc 100644 --- a/services/gpuservice/GpuService.cpp +++ b/services/gpuservice/GpuService.cpp @@ -16,9 +16,10 @@ #define ATRACE_TAG ATRACE_TAG_GRAPHICS -#include "GpuService.h" +#include "gpuservice/GpuService.h" #include +#include #include #include #include @@ -34,6 +35,7 @@ #include #include +#include namespace android { @@ -46,6 +48,8 @@ void dumpGameDriverInfo(std::string* result); } // namespace const String16 sDump("android.permission.DUMP"); +const String16 sAccessGpuServicePermission("android.permission.ACCESS_GPU_SERVICE"); +const std::string sAngleGlesDriverSuffix = "angle"; const char* const GpuService::SERVICE_NAME = "gpu"; @@ -55,18 +59,21 @@ GpuService::GpuService() mGpuStats(std::make_unique()), mGpuMemTracer(std::make_unique()) { - std::thread gpuMemAsyncInitThread([this]() { + mGpuMemAsyncInitThread = std::make_unique([this] (){ mGpuMem->initialize(); mGpuMemTracer->initialize(mGpuMem); }); - gpuMemAsyncInitThread.detach(); - std::thread gpuWorkAsyncInitThread([this]() { + mGpuWorkAsyncInitThread = std::make_unique([this]() { mGpuWork->initialize(); }); - gpuWorkAsyncInitThread.detach(); }; +GpuService::~GpuService() { + mGpuWorkAsyncInitThread->join(); + mGpuMemAsyncInitThread->join(); +} + void GpuService::setGpuStats(const std::string& driverPackageName, const std::string& driverVersionName, uint64_t driverVersionCode, int64_t driverBuildTime, const std::string& appPackageName, @@ -82,6 +89,35 @@ void GpuService::setTargetStats(const std::string& appPackageName, const uint64_ mGpuStats->insertTargetStats(appPackageName, driverVersionCode, stats, value); } +void GpuService::setTargetStatsArray(const std::string& appPackageName, + const uint64_t driverVersionCode, const GpuStatsInfo::Stats stats, + const uint64_t* values, const uint32_t valueCount) { + mGpuStats->insertTargetStatsArray(appPackageName, driverVersionCode, stats, values, valueCount); +} + +void GpuService::toggleAngleAsSystemDriver(bool enabled) { + IPCThreadState* ipc = IPCThreadState::self(); + const int pid = ipc->getCallingPid(); + const int uid = ipc->getCallingUid(); + + // only system_server with the ACCESS_GPU_SERVICE permission is allowed to set + // persist.graphics.egl + if (uid != AID_SYSTEM || + !PermissionCache::checkPermission(sAccessGpuServicePermission, pid, uid)) { + ALOGE("Permission Denial: can't set persist.graphics.egl from setAngleAsSystemDriver() " + "pid=%d, uid=%d\n", pid, uid); + return; + } + + std::lock_guard lock(mLock); + if (enabled) { + android::base::SetProperty("persist.graphics.egl", sAngleGlesDriverSuffix); + } else { + android::base::SetProperty("persist.graphics.egl", ""); + } +} + + void GpuService::setUpdatableDriverPath(const std::string& driverPath) { IPCThreadState* ipc = IPCThreadState::self(); const int pid = ipc->getCallingPid(); diff --git a/services/gpuservice/gpustats/GpuStats.cpp b/services/gpuservice/gpustats/GpuStats.cpp index d033453dd748ef9b4b63c5f87aa0f71734b5b613..f06a0457d3fba06e4135b08e67f912ae07a6fee2 100644 --- a/services/gpuservice/gpustats/GpuStats.cpp +++ b/services/gpuservice/gpustats/GpuStats.cpp @@ -175,29 +175,83 @@ void GpuStats::insertDriverStats(const std::string& driverPackageName, void GpuStats::insertTargetStats(const std::string& appPackageName, const uint64_t driverVersionCode, const GpuStatsInfo::Stats stats, - const uint64_t /*value*/) { + const uint64_t value) { + return insertTargetStatsArray(appPackageName, driverVersionCode, stats, &value, 1); +} + +void GpuStats::insertTargetStatsArray(const std::string& appPackageName, + const uint64_t driverVersionCode, const GpuStatsInfo::Stats stats, + const uint64_t* values, const uint32_t valueCount) { ATRACE_CALL(); const std::string appStatsKey = appPackageName + std::to_string(driverVersionCode); std::lock_guard lock(mLock); registerStatsdCallbacksIfNeeded(); - if (!mAppStats.count(appStatsKey)) { + + const auto foundApp = mAppStats.find(appStatsKey); + if (foundApp == mAppStats.end()) { return; } - switch (stats) { - case GpuStatsInfo::Stats::CPU_VULKAN_IN_USE: - mAppStats[appStatsKey].cpuVulkanInUse = true; - break; - case GpuStatsInfo::Stats::FALSE_PREROTATION: - mAppStats[appStatsKey].falsePrerotation = true; - break; - case GpuStatsInfo::Stats::GLES_1_IN_USE: - mAppStats[appStatsKey].gles1InUse = true; - break; - default: - break; + GpuStatsAppInfo& targetAppStats = foundApp->second; + + if (stats == GpuStatsInfo::Stats::VULKAN_INSTANCE_EXTENSION + || stats == GpuStatsInfo::Stats::VULKAN_DEVICE_EXTENSION) { + // Handle extension arrays separately as we need to store a unique set of them + // in the stats vector. Storing in std::set<> is not efficient for serialization tasks. + std::vector& targetVec = + (stats == GpuStatsInfo::Stats::VULKAN_INSTANCE_EXTENSION) ? + targetAppStats.vulkanInstanceExtensions : + targetAppStats.vulkanDeviceExtensions; + const bool addAll = (targetVec.size() == 0); + targetVec.reserve(valueCount); + + // Add new extensions into the set + for(uint32_t i = 0; + (i < valueCount) && (targetVec.size() < GpuStatsAppInfo::MAX_NUM_EXTENSIONS); + i++) { + const int32_t extVal = int32_t(values[i] & 0xFFFFFFFF); + if (addAll + || std::find(targetVec.cbegin(), targetVec.cend(), extVal) == targetVec.cend()) { + targetVec.push_back(extVal); + } + } + } + else { + // Handle other type of stats info events + for(uint32_t i = 0; i < valueCount; i++) { + const uint64_t value = values[i]; + switch (stats) { + case GpuStatsInfo::Stats::CPU_VULKAN_IN_USE: + targetAppStats.cpuVulkanInUse = true; + break; + case GpuStatsInfo::Stats::FALSE_PREROTATION: + targetAppStats.falsePrerotation = true; + break; + case GpuStatsInfo::Stats::GLES_1_IN_USE: + targetAppStats.gles1InUse = true; + break; + case GpuStatsInfo::Stats::CREATED_GLES_CONTEXT: + targetAppStats.createdGlesContext = true; + break; + case GpuStatsInfo::Stats::CREATED_VULKAN_DEVICE: + targetAppStats.createdVulkanDevice = true; + break; + case GpuStatsInfo::Stats::CREATED_VULKAN_API_VERSION: + targetAppStats.vulkanApiVersion = uint32_t(value & 0xffffffff); + break; + case GpuStatsInfo::Stats::CREATED_VULKAN_SWAPCHAIN: + targetAppStats.createdVulkanSwapchain = true; + break; + case GpuStatsInfo::Stats::VULKAN_DEVICE_FEATURES_ENABLED: + // Merge all requested feature bits together for this app + targetAppStats.vulkanDeviceFeaturesEnabled |= value; + break; + default: + break; + } + } } } @@ -347,7 +401,14 @@ AStatsManager_PullAtomCallbackReturn GpuStats::pullAppInfoAtom(AStatsEventList* ele.second.cpuVulkanInUse, ele.second.falsePrerotation, ele.second.gles1InUse, - ele.second.angleInUse); + ele.second.angleInUse, + ele.second.createdGlesContext, + ele.second.createdVulkanDevice, + ele.second.createdVulkanSwapchain, + ele.second.vulkanApiVersion, + ele.second.vulkanDeviceFeaturesEnabled, + ele.second.vulkanInstanceExtensions, + ele.second.vulkanDeviceExtensions); } } diff --git a/services/gpuservice/gpustats/include/gpustats/GpuStats.h b/services/gpuservice/gpustats/include/gpustats/GpuStats.h index 2aba651af9ff2564450d34afa5b50a07927d62cd..22c64dbc02ee851fb359f5048c15fb8897029714 100644 --- a/services/gpuservice/gpustats/include/gpustats/GpuStats.h +++ b/services/gpuservice/gpustats/include/gpustats/GpuStats.h @@ -41,11 +41,14 @@ public: // Insert target stats into app stats or potentially global stats as well. void insertTargetStats(const std::string& appPackageName, const uint64_t driverVersionCode, const GpuStatsInfo::Stats stats, const uint64_t value); + void insertTargetStatsArray(const std::string& appPackageName, + const uint64_t driverVersionCode, const GpuStatsInfo::Stats stats, + const uint64_t* values, const uint32_t valueCount); // dumpsys interface void dump(const Vector& args, std::string* result); // This limits the worst case number of loading times tracked. - static const size_t MAX_NUM_LOADING_TIMES = 50; + static const size_t MAX_NUM_LOADING_TIMES = 16; // Below limits the memory usage of GpuStats to be less than 10KB. This is // the preferred number for statsd while maintaining nice data quality. static const size_t MAX_NUM_APP_RECORDS = 100; diff --git a/services/gpuservice/GpuService.h b/services/gpuservice/include/gpuservice/GpuService.h similarity index 86% rename from services/gpuservice/GpuService.h rename to services/gpuservice/include/gpuservice/GpuService.h index d7313d165e3544ba2cbb05e7a8f4febc06a75a8d..54f8f666bc7fad738c9df2fad3b2ffdc6aa6c230 100644 --- a/services/gpuservice/GpuService.h +++ b/services/gpuservice/include/gpuservice/GpuService.h @@ -24,6 +24,7 @@ #include #include +#include #include namespace android { @@ -41,6 +42,7 @@ public: static const char* const SERVICE_NAME ANDROID_API; GpuService() ANDROID_API; + ~GpuService(); protected: status_t shellCommand(int in, int out, int err, std::vector& args) override; @@ -56,8 +58,12 @@ private: int64_t driverLoadingTime) override; void setTargetStats(const std::string& appPackageName, const uint64_t driverVersionCode, const GpuStatsInfo::Stats stats, const uint64_t value) override; + void setTargetStatsArray(const std::string& appPackageName, + const uint64_t driverVersionCode, const GpuStatsInfo::Stats stats, + const uint64_t* values, const uint32_t valueCount) override; void setUpdatableDriverPath(const std::string& driverPath) override; std::string getUpdatableDriverPath() override; + void toggleAngleAsSystemDriver(bool enabled) override; /* * IBinder interface @@ -86,6 +92,8 @@ private: std::unique_ptr mGpuMemTracer; std::mutex mLock; std::string mDeveloperDriverPath; + std::unique_ptr mGpuMemAsyncInitThread; + std::unique_ptr mGpuWorkAsyncInitThread; }; } // namespace android diff --git a/services/gpuservice/main_gpuservice.cpp b/services/gpuservice/main_gpuservice.cpp index 64aafcab6a39fbe1e63531926de29f72c8a65af9..200237219e00b261f8f8bc81ff61c428a22ba882 100644 --- a/services/gpuservice/main_gpuservice.cpp +++ b/services/gpuservice/main_gpuservice.cpp @@ -18,7 +18,7 @@ #include #include #include -#include "GpuService.h" +#include "gpuservice/GpuService.h" using namespace android; diff --git a/services/gpuservice/tests/fuzzers/GpuServiceFuzzer.cpp b/services/gpuservice/tests/fuzzers/GpuServiceFuzzer.cpp index c2574a3fd35ef130b936fd7fa2dfe96593d085f7..241b8646e124e2274068d865bf974873ff531df1 100644 --- a/services/gpuservice/tests/fuzzers/GpuServiceFuzzer.cpp +++ b/services/gpuservice/tests/fuzzers/GpuServiceFuzzer.cpp @@ -16,7 +16,7 @@ #include -#include "GpuService.h" +#include "gpuservice/GpuService.h" using ::android::fuzzService; using ::android::GpuService; diff --git a/services/gpuservice/tests/unittests/Android.bp b/services/gpuservice/tests/unittests/Android.bp index 51642f94728e68cc7fe1648118a81cd2bba55550..c870b17b7967184e6c84176e84d42cf8f5ec5387 100644 --- a/services/gpuservice/tests/unittests/Android.bp +++ b/services/gpuservice/tests/unittests/Android.bp @@ -28,6 +28,7 @@ cc_test { "GpuMemTest.cpp", "GpuMemTracerTest.cpp", "GpuStatsTest.cpp", + "GpuServiceTest.cpp", ], header_libs: ["bpf_headers"], shared_libs: [ @@ -45,6 +46,7 @@ cc_test { "libstatslog", "libstatspull", "libutils", + "libgpuservice", ], static_libs: [ "libgmock", diff --git a/services/gpuservice/tests/unittests/GpuServiceTest.cpp b/services/gpuservice/tests/unittests/GpuServiceTest.cpp new file mode 100644 index 0000000000000000000000000000000000000000..62b3e53f5347dbff7d7bc5d2f64f6612247e46dd --- /dev/null +++ b/services/gpuservice/tests/unittests/GpuServiceTest.cpp @@ -0,0 +1,52 @@ +#undef LOG_TAG +#define LOG_TAG "gpuservice_unittest" + +#include "gpuservice/GpuService.h" + +#include +#include + +#include +#include + +namespace android { +namespace { + +class GpuServiceTest : public testing::Test { +public: + GpuServiceTest() { + const ::testing::TestInfo* const test_info = + ::testing::UnitTest::GetInstance()->current_test_info(); + ALOGD("**** Setting up for %s.%s\n", test_info->test_case_name(), test_info->name()); + } + + ~GpuServiceTest() { + const ::testing::TestInfo* const test_info = + ::testing::UnitTest::GetInstance()->current_test_info(); + ALOGD("**** Tearing down after %s.%s\n", test_info->test_case_name(), test_info->name()); + } + +}; + + +/* +* The behaviour before this test + fixes was UB caused by threads accessing deallocated memory. +* +* This test creates the service (which initializes the culprit threads), +* deallocates it immediately and sleeps. +* +* GpuService's destructor gets called and joins the threads. +* If we haven't crashed by the time the sleep time has elapsed, we're good +* Let the test pass. +*/ +TEST_F(GpuServiceTest, onInitializeShouldNotCauseUseAfterFree) { + sp service = new GpuService(); + service.clear(); + std::this_thread::sleep_for(std::chrono::seconds(3)); + + // If we haven't crashed yet due to threads accessing freed up memory, let the test pass + EXPECT_TRUE(true); +} + +} // namespace +} // namespace android diff --git a/services/gpuservice/tests/unittests/GpuStatsTest.cpp b/services/gpuservice/tests/unittests/GpuStatsTest.cpp index 7ea22888f85aacb742218fb810280565246fb30a..4ce533ff7c723ce6bf3195f4a2af18272755c06e 100644 --- a/services/gpuservice/tests/unittests/GpuStatsTest.cpp +++ b/services/gpuservice/tests/unittests/GpuStatsTest.cpp @@ -52,6 +52,13 @@ using testing::HasSubstr; #define DRIVER_LOADING_TIME_2 789 #define DRIVER_LOADING_TIME_3 891 +constexpr uint64_t VULKAN_FEATURES_MASK = 0x600D; +constexpr uint32_t VULKAN_API_VERSION = 0x400000; +constexpr int32_t VULKAN_INSTANCE_EXTENSION_1 = 0x1234; +constexpr int32_t VULKAN_INSTANCE_EXTENSION_2 = 0x8765; +constexpr int32_t VULKAN_DEVICE_EXTENSION_1 = 0x9012; +constexpr int32_t VULKAN_DEVICE_EXTENSION_2 = 0x3456; + enum InputCommand : int32_t { DUMP_ALL = 0, DUMP_GLOBAL = 1, @@ -218,6 +225,24 @@ TEST_F(GpuStatsTest, canNotInsertTargetStatsBeforeProperSetup) { GpuStatsInfo::Stats::FALSE_PREROTATION, 0); mGpuStats->insertTargetStats(APP_PKG_NAME_1, BUILTIN_DRIVER_VER_CODE, GpuStatsInfo::Stats::GLES_1_IN_USE, 0); + mGpuStats->insertTargetStats(APP_PKG_NAME_1, BUILTIN_DRIVER_VER_CODE, + GpuStatsInfo::Stats::CREATED_GLES_CONTEXT, 0); + mGpuStats->insertTargetStats(APP_PKG_NAME_1, BUILTIN_DRIVER_VER_CODE, + GpuStatsInfo::Stats::CREATED_VULKAN_API_VERSION, + VULKAN_API_VERSION); + mGpuStats->insertTargetStats(APP_PKG_NAME_1, BUILTIN_DRIVER_VER_CODE, + GpuStatsInfo::Stats::CREATED_VULKAN_DEVICE, 0); + mGpuStats->insertTargetStats(APP_PKG_NAME_1, BUILTIN_DRIVER_VER_CODE, + GpuStatsInfo::Stats::CREATED_VULKAN_SWAPCHAIN, 0); + mGpuStats->insertTargetStats(APP_PKG_NAME_1, BUILTIN_DRIVER_VER_CODE, + GpuStatsInfo::Stats::VULKAN_DEVICE_FEATURES_ENABLED, + VULKAN_FEATURES_MASK); + mGpuStats->insertTargetStats(APP_PKG_NAME_1, BUILTIN_DRIVER_VER_CODE, + GpuStatsInfo::Stats::VULKAN_INSTANCE_EXTENSION, + VULKAN_INSTANCE_EXTENSION_1); + mGpuStats->insertTargetStats(APP_PKG_NAME_1, BUILTIN_DRIVER_VER_CODE, + GpuStatsInfo::Stats::VULKAN_DEVICE_EXTENSION, + VULKAN_DEVICE_EXTENSION_1); EXPECT_TRUE(inputCommand(InputCommand::DUMP_APP).empty()); } @@ -233,10 +258,51 @@ TEST_F(GpuStatsTest, canInsertTargetStatsAfterProperSetup) { GpuStatsInfo::Stats::FALSE_PREROTATION, 0); mGpuStats->insertTargetStats(APP_PKG_NAME_1, BUILTIN_DRIVER_VER_CODE, GpuStatsInfo::Stats::GLES_1_IN_USE, 0); + mGpuStats->insertTargetStats(APP_PKG_NAME_1, BUILTIN_DRIVER_VER_CODE, + GpuStatsInfo::Stats::CREATED_GLES_CONTEXT, 0); + mGpuStats->insertTargetStats(APP_PKG_NAME_1, BUILTIN_DRIVER_VER_CODE, + GpuStatsInfo::Stats::CREATED_VULKAN_API_VERSION, + VULKAN_API_VERSION); + mGpuStats->insertTargetStats(APP_PKG_NAME_1, BUILTIN_DRIVER_VER_CODE, + GpuStatsInfo::Stats::CREATED_VULKAN_DEVICE, 0); + mGpuStats->insertTargetStats(APP_PKG_NAME_1, BUILTIN_DRIVER_VER_CODE, + GpuStatsInfo::Stats::CREATED_VULKAN_SWAPCHAIN, 0); + mGpuStats->insertTargetStats(APP_PKG_NAME_1, BUILTIN_DRIVER_VER_CODE, + GpuStatsInfo::Stats::VULKAN_DEVICE_FEATURES_ENABLED, + VULKAN_FEATURES_MASK); + mGpuStats->insertTargetStats(APP_PKG_NAME_1, BUILTIN_DRIVER_VER_CODE, + GpuStatsInfo::Stats::VULKAN_INSTANCE_EXTENSION, + VULKAN_INSTANCE_EXTENSION_1); + mGpuStats->insertTargetStats(APP_PKG_NAME_1, BUILTIN_DRIVER_VER_CODE, + GpuStatsInfo::Stats::VULKAN_INSTANCE_EXTENSION, + VULKAN_INSTANCE_EXTENSION_2); + mGpuStats->insertTargetStats(APP_PKG_NAME_1, BUILTIN_DRIVER_VER_CODE, + GpuStatsInfo::Stats::VULKAN_DEVICE_EXTENSION, + VULKAN_DEVICE_EXTENSION_1); + mGpuStats->insertTargetStats(APP_PKG_NAME_1, BUILTIN_DRIVER_VER_CODE, + GpuStatsInfo::Stats::VULKAN_DEVICE_EXTENSION, + VULKAN_DEVICE_EXTENSION_2); EXPECT_THAT(inputCommand(InputCommand::DUMP_APP), HasSubstr("cpuVulkanInUse = 1")); EXPECT_THAT(inputCommand(InputCommand::DUMP_APP), HasSubstr("falsePrerotation = 1")); EXPECT_THAT(inputCommand(InputCommand::DUMP_APP), HasSubstr("gles1InUse = 1")); + EXPECT_THAT(inputCommand(InputCommand::DUMP_APP), HasSubstr("createdGlesContext = 1")); + EXPECT_THAT(inputCommand(InputCommand::DUMP_APP), HasSubstr("createdVulkanDevice = 1")); + EXPECT_THAT(inputCommand(InputCommand::DUMP_APP), HasSubstr("createdVulkanSwapchain = 1")); + std::stringstream expectedResult; + expectedResult << "vulkanApiVersion = 0x" << std::hex << VULKAN_API_VERSION; + EXPECT_THAT(inputCommand(InputCommand::DUMP_APP), HasSubstr(expectedResult.str())); + expectedResult.str(""); + expectedResult << "vulkanDeviceFeaturesEnabled = 0x" << std::hex << VULKAN_FEATURES_MASK; + EXPECT_THAT(inputCommand(InputCommand::DUMP_APP), HasSubstr(expectedResult.str())); + expectedResult.str(""); + expectedResult << "vulkanInstanceExtensions: 0x" << std::hex << VULKAN_INSTANCE_EXTENSION_1 + << " 0x" << std::hex << VULKAN_INSTANCE_EXTENSION_2; + EXPECT_THAT(inputCommand(InputCommand::DUMP_APP), HasSubstr(expectedResult.str())); + expectedResult.str(""); + expectedResult << "vulkanDeviceExtensions: 0x" << std::hex << VULKAN_DEVICE_EXTENSION_1 + << " 0x" << std::hex << VULKAN_DEVICE_EXTENSION_2; + EXPECT_THAT(inputCommand(InputCommand::DUMP_APP), HasSubstr(expectedResult.str())); } // Verify we always have the most recently used apps in mAppStats, even when we fill it. @@ -260,11 +326,52 @@ TEST_F(GpuStatsTest, canInsertMoreThanMaxNumAppRecords) { GpuStatsInfo::Stats::FALSE_PREROTATION, 0); mGpuStats->insertTargetStats(fullPkgName, BUILTIN_DRIVER_VER_CODE, GpuStatsInfo::Stats::GLES_1_IN_USE, 0); + mGpuStats->insertTargetStats(fullPkgName, BUILTIN_DRIVER_VER_CODE, + GpuStatsInfo::Stats::CREATED_GLES_CONTEXT, 0); + mGpuStats->insertTargetStats(fullPkgName, BUILTIN_DRIVER_VER_CODE, + GpuStatsInfo::Stats::CREATED_VULKAN_API_VERSION, + VULKAN_API_VERSION); + mGpuStats->insertTargetStats(fullPkgName, BUILTIN_DRIVER_VER_CODE, + GpuStatsInfo::Stats::CREATED_VULKAN_DEVICE, 0); + mGpuStats->insertTargetStats(fullPkgName, BUILTIN_DRIVER_VER_CODE, + GpuStatsInfo::Stats::CREATED_VULKAN_SWAPCHAIN, 0); + mGpuStats->insertTargetStats(fullPkgName, BUILTIN_DRIVER_VER_CODE, + GpuStatsInfo::Stats::VULKAN_DEVICE_FEATURES_ENABLED, + VULKAN_FEATURES_MASK); + mGpuStats->insertTargetStats(fullPkgName, BUILTIN_DRIVER_VER_CODE, + GpuStatsInfo::Stats::VULKAN_INSTANCE_EXTENSION, + VULKAN_INSTANCE_EXTENSION_1); + mGpuStats->insertTargetStats(fullPkgName, BUILTIN_DRIVER_VER_CODE, + GpuStatsInfo::Stats::VULKAN_INSTANCE_EXTENSION, + VULKAN_INSTANCE_EXTENSION_2); + mGpuStats->insertTargetStats(fullPkgName, BUILTIN_DRIVER_VER_CODE, + GpuStatsInfo::Stats::VULKAN_DEVICE_EXTENSION, + VULKAN_DEVICE_EXTENSION_1); + mGpuStats->insertTargetStats(fullPkgName, BUILTIN_DRIVER_VER_CODE, + GpuStatsInfo::Stats::VULKAN_DEVICE_EXTENSION, + VULKAN_DEVICE_EXTENSION_2); EXPECT_THAT(inputCommand(InputCommand::DUMP_APP), HasSubstr(fullPkgName.c_str())); EXPECT_THAT(inputCommand(InputCommand::DUMP_APP), HasSubstr("cpuVulkanInUse = 1")); EXPECT_THAT(inputCommand(InputCommand::DUMP_APP), HasSubstr("falsePrerotation = 1")); EXPECT_THAT(inputCommand(InputCommand::DUMP_APP), HasSubstr("gles1InUse = 1")); + EXPECT_THAT(inputCommand(InputCommand::DUMP_APP), HasSubstr("createdGlesContext = 1")); + EXPECT_THAT(inputCommand(InputCommand::DUMP_APP), HasSubstr("createdVulkanDevice = 1")); + EXPECT_THAT(inputCommand(InputCommand::DUMP_APP), HasSubstr("createdVulkanSwapchain = 1")); + std::stringstream expectedResult; + expectedResult << "vulkanApiVersion = 0x" << std::hex << VULKAN_API_VERSION; + EXPECT_THAT(inputCommand(InputCommand::DUMP_APP), HasSubstr(expectedResult.str())); + expectedResult.str(""); + expectedResult << "vulkanDeviceFeaturesEnabled = 0x" << std::hex << VULKAN_FEATURES_MASK; + EXPECT_THAT(inputCommand(InputCommand::DUMP_APP), HasSubstr(expectedResult.str())); + expectedResult.str(""); + expectedResult << "vulkanInstanceExtensions: 0x" << std::hex << VULKAN_INSTANCE_EXTENSION_1 + << " 0x" << std::hex << VULKAN_INSTANCE_EXTENSION_2; + EXPECT_THAT(inputCommand(InputCommand::DUMP_APP), HasSubstr(expectedResult.str())); + expectedResult.str(""); + expectedResult << "vulkanDeviceExtensions: 0x" << std::hex << VULKAN_DEVICE_EXTENSION_1 + << " 0x" << std::hex << VULKAN_DEVICE_EXTENSION_2; + EXPECT_THAT(inputCommand(InputCommand::DUMP_APP), HasSubstr(expectedResult.str())); } // mAppStats purges GpuStats::APP_RECORD_HEADROOM apps removed everytime it's filled up. diff --git a/services/gpuservice/vts/Android.bp b/services/gpuservice/vts/Android.bp index b6362e2bc6df07fdad1280fba0167fd56d6a9dc3..a24822a7d19dc3e2d7b101c733ccc01d0ba5d0db 100644 --- a/services/gpuservice/vts/Android.bp +++ b/services/gpuservice/vts/Android.bp @@ -21,6 +21,7 @@ java_test_host { srcs: ["src/**/*.java"], libs: [ "tradefed", + "compatibility-host-util", ], test_suites: [ "general-tests", diff --git a/services/gpuservice/vts/src/com/android/tests/gpuservice/GpuWorkTracepointTest.java b/services/gpuservice/vts/src/com/android/tests/gpuservice/GpuWorkTracepointTest.java index 9fa901675d8c848fccac9b4357beac37b28cd7df..6c1633535997fa08c313b4ea0338388876ce2c6c 100644 --- a/services/gpuservice/vts/src/com/android/tests/gpuservice/GpuWorkTracepointTest.java +++ b/services/gpuservice/vts/src/com/android/tests/gpuservice/GpuWorkTracepointTest.java @@ -19,11 +19,16 @@ import static org.junit.Assert.assertEquals; import static org.junit.Assert.fail; import static org.junit.Assume.assumeTrue; +import android.platform.test.annotations.RestrictedBuildTest; + import com.android.tradefed.testtype.DeviceJUnit4ClassRunner; import com.android.tradefed.testtype.junit4.BaseHostJUnit4Test; import com.android.tradefed.util.CommandResult; import com.android.tradefed.util.CommandStatus; +import com.android.compatibility.common.util.PropertyUtil; +import com.android.compatibility.common.util.VsrTest; + import org.junit.Test; import org.junit.runner.RunWith; @@ -57,15 +62,23 @@ public class GpuWorkTracepointTest extends BaseHostJUnit4Test { commandResult.getStatus(), CommandStatus.SUCCESS); } + @VsrTest(requirements={"VSR-3.3-004"}) + @RestrictedBuildTest @Test public void testGpuWorkPeriodTracepointFormat() throws Exception { CommandResult commandResult = getDevice().executeShellV2Command( String.format("cat %s", GPU_WORK_PERIOD_TRACEPOINT_FORMAT_PATH)); // If we failed to cat the tracepoint format then the test ends here. - assumeTrue(String.format("Failed to cat the gpu_work_period tracepoint format at %s", - GPU_WORK_PERIOD_TRACEPOINT_FORMAT_PATH), - commandResult.getStatus().equals(CommandStatus.SUCCESS)); + if (!commandResult.getStatus().equals(CommandStatus.SUCCESS)) { + String message = String.format( + "Failed to cat the gpu_work_period tracepoint format at %s\n", + GPU_WORK_PERIOD_TRACEPOINT_FORMAT_PATH); + + // Tracepoint MUST exist on devices released with Android 14 or later + assumeTrue(message, PropertyUtil.getVsrApiLevel(getDevice()) >= 34); + fail(message); + } // Otherwise, we check that the fields match the expected fields. String actualFields = Arrays.stream( diff --git a/services/inputflinger/Android.bp b/services/inputflinger/Android.bp index 18d670ae382ec08ea606eb9abdc8456b8a502f71..69df45bc3eb4bb599b49cc885d146e7c38876031 100644 --- a/services/inputflinger/Android.bp +++ b/services/inputflinger/Android.bp @@ -38,9 +38,12 @@ cc_defaults { "-Wshadow", "-Wshadow-field-in-constructor-modified", "-Wshadow-uncaptured-local", + "-DANDROID_UTILS_REF_BASE_DISABLE_IMPLICIT_CONSTRUCTION", ], sanitize: { - misc_undefined: ["bounds"], + misc_undefined: [ + "bounds", + ], }, tidy: true, tidy_checks: [ @@ -56,11 +59,10 @@ cc_defaults { filegroup { name: "libinputflinger_sources", srcs: [ - "InputClassifier.cpp", "InputCommonConverter.cpp", + "InputProcessor.cpp", "PreferStylusOverTouchBlocker.cpp", "UnwantedInteractionBlocker.cpp", - "InputManager.cpp", ], } @@ -76,21 +78,35 @@ cc_defaults { "libcrypto", "libcutils", "libhidlbase", - "libinput", "libkll", "liblog", "libprotobuf-cpp-lite", "libstatslog", - "libstatspull", - "libstatssocket", "libutils", - "libui", "server_configurable_flags", ], static_libs: [ "libattestation", "libpalmrejection", + "libui-types", ], + target: { + android: { + shared_libs: [ + "libgui", + "libinput", + "libstatspull", + "libstatssocket", + ], + }, + host: { + static_libs: [ + "libinput", + "libstatspull", + "libstatssocket", + ], + }, + }, } cc_library_shared { @@ -99,6 +115,10 @@ cc_library_shared { "inputflinger_defaults", "libinputflinger_defaults", ], + srcs: [ + "InputManager.cpp", + // other sources are added via "defaults" + ], cflags: [ // TODO(b/23084678): Move inputflinger to its own process and mark it hidden //-fvisibility=hidden @@ -107,9 +127,8 @@ cc_library_shared { // This should consist only of dependencies from inputflinger. Other dependencies should be // in cc_defaults so that they are included in the tests. "libinputflinger_base", - "libinputreporter", "libinputreader", - "libgui", + "libinputreporter", ], static_libs: [ "libinputdispatcher", @@ -129,6 +148,7 @@ cc_library_shared { cc_library_headers { name: "libinputflinger_headers", + host_supported: true, export_include_dirs: ["include"], } @@ -138,6 +158,7 @@ filegroup { "InputListener.cpp", "InputReaderBase.cpp", "InputThread.cpp", + "NotifyArgs.cpp", "VibrationElement.cpp", ], } @@ -149,18 +170,30 @@ cc_defaults { "libbase", "libbinder", "libcutils", - "libinput", "liblog", - "libui", "libutils", ], header_libs: [ "libinputflinger_headers", ], + target: { + android: { + shared_libs: [ + "libinput", + ], + }, + host: { + static_libs: [ + "libinput", + "libui-types", + ], + }, + }, } cc_library_shared { name: "libinputflinger_base", + host_supported: true, defaults: [ "inputflinger_defaults", "libinputflinger_base_defaults", @@ -169,3 +202,52 @@ cc_library_shared { "libinputflinger_headers", ], } + +// This target will build everything 'input-related'. This could be useful for +// large refactorings of the input code. This is similar to 'm checkbuild', but +// just for input code. +// Use 'm checkinput' to build, and then (optionally) use 'm installclean' to +// remove any of the installed artifacts that you may not want on your actual +// build. +phony { + name: "checkinput", + required: [ + // native targets + "libgui_test", + "libinput", + "libinputflinger", + "inputflinger_tests", + "inputflinger_benchmarks", + "libinput_tests", + "libpalmrejection_test", + "libandroid_runtime", + "libinputservice_test", + "Bug-115739809", + "StructLayout_test", + // currently unused, but still must build correctly + "inputflinger", + "libinputflingerhost", + + // native fuzzers + "inputflinger_latencytracker_fuzzer", + "inputflinger_cursor_input_fuzzer", + "inputflinger_keyboard_input_fuzzer", + "inputflinger_multitouch_input_fuzzer", + "inputflinger_switch_input_fuzzer", + "inputflinger_input_reader_fuzzer", + "inputflinger_blocking_queue_fuzzer", + "inputflinger_input_classifier_fuzzer", + + // Java/Kotlin targets + "CtsWindowManagerDeviceTestCases", + "InputTests", + "CtsHardwareTestCases", + "CtsInputTestCases", + "CtsViewTestCases", + "CtsWidgetTestCases", + "FrameworksCoreTests", + "FrameworksServicesTests", + "CtsSecurityTestCases", + "CtsSecurityBulletinHostTestCases", + ], +} diff --git a/services/inputflinger/BlockingQueue.h b/services/inputflinger/BlockingQueue.h index 032cb6dea3ab7c710e5015835916d44124f4d54c..c435bd2ebe1f3f57a736e0435fea845c501e6de2 100644 --- a/services/inputflinger/BlockingQueue.h +++ b/services/inputflinger/BlockingQueue.h @@ -14,8 +14,7 @@ * limitations under the License. */ -#ifndef _UI_INPUT_BLOCKING_QUEUE_H -#define _UI_INPUT_BLOCKING_QUEUE_H +#pragma once #include #include @@ -107,6 +106,4 @@ private: std::vector mQueue GUARDED_BY(mLock); }; - } // namespace android -#endif diff --git a/services/inputflinger/InputCommonConverter.cpp b/services/inputflinger/InputCommonConverter.cpp index 8aee39fd0b59df66e4f25e3e1c89cc559218e96a..2437d0fcfc49799633259ae0a0efff26572c59f1 100644 --- a/services/inputflinger/InputCommonConverter.cpp +++ b/services/inputflinger/InputCommonConverter.cpp @@ -200,17 +200,12 @@ static common::Button getButtonState(int32_t buttonState) { return static_cast(buttonState); } -static common::ToolType getToolType(int32_t toolType) { - static_assert(static_cast(AMOTION_EVENT_TOOL_TYPE_UNKNOWN) == - common::ToolType::UNKNOWN); - static_assert(static_cast(AMOTION_EVENT_TOOL_TYPE_FINGER) == - common::ToolType::FINGER); - static_assert(static_cast(AMOTION_EVENT_TOOL_TYPE_STYLUS) == - common::ToolType::STYLUS); - static_assert(static_cast(AMOTION_EVENT_TOOL_TYPE_MOUSE) == - common::ToolType::MOUSE); - static_assert(static_cast(AMOTION_EVENT_TOOL_TYPE_ERASER) == - common::ToolType::ERASER); +static common::ToolType getToolType(ToolType toolType) { + static_assert(static_cast(ToolType::UNKNOWN) == common::ToolType::UNKNOWN); + static_assert(static_cast(ToolType::FINGER) == common::ToolType::FINGER); + static_assert(static_cast(ToolType::STYLUS) == common::ToolType::STYLUS); + static_assert(static_cast(ToolType::MOUSE) == common::ToolType::MOUSE); + static_assert(static_cast(ToolType::ERASER) == common::ToolType::ERASER); return static_cast(toolType); } @@ -263,6 +258,12 @@ static_assert(static_cast(AMOTION_EVENT_AXIS_GENERIC_13) == common static_assert(static_cast(AMOTION_EVENT_AXIS_GENERIC_14) == common::Axis::GENERIC_14); static_assert(static_cast(AMOTION_EVENT_AXIS_GENERIC_15) == common::Axis::GENERIC_15); static_assert(static_cast(AMOTION_EVENT_AXIS_GENERIC_16) == common::Axis::GENERIC_16); +// TODO(b/251196347): add GESTURE_{X,Y}_OFFSET, GESTURE_SCROLL_{X,Y}_DISTANCE, and +// GESTURE_PINCH_SCALE_FACTOR. +// If you added a new axis, consider whether this should also be exposed as a HAL axis. Update the +// static_assert below and add the new axis here, or leave a comment summarizing your decision. +static_assert(static_cast(AMOTION_EVENT_MAXIMUM_VALID_AXIS_VALUE) == + static_cast(AMOTION_EVENT_AXIS_GESTURE_PINCH_SCALE_FACTOR)); static common::VideoFrame getHalVideoFrame(const TouchVideoFrame& frame) { common::VideoFrame out; @@ -299,8 +300,8 @@ static void getHalPropertiesAndCoords(const NotifyMotionArgs& args, common::PointerCoords coords; // OK to copy bits because we have static_assert for pointerCoords axes coords.bits = args.pointerCoords[i].bits; - coords.values = std::vector(args.pointerCoords[i].values, - args.pointerCoords[i].values + + coords.values = std::vector(args.pointerCoords[i].values.cbegin(), + args.pointerCoords[i].values.cbegin() + BitSet64::count(args.pointerCoords[i].bits)); outPointerCoords.push_back(coords); } diff --git a/services/inputflinger/InputListener.cpp b/services/inputflinger/InputListener.cpp index dce327ed189276a2c76ae5eb8d45ac5faa8572b2..aa55873aa5be8f798c4ac0f2986a6743acf3cd0e 100644 --- a/services/inputflinger/InputListener.cpp +++ b/services/inputflinger/InputListener.cpp @@ -24,315 +24,39 @@ #include #include -#include #include using android::base::StringPrintf; namespace android { -// --- NotifyConfigurationChangedArgs --- - -NotifyConfigurationChangedArgs::NotifyConfigurationChangedArgs(int32_t id, nsecs_t eventTime) - : NotifyArgs(id, eventTime) {} - -NotifyConfigurationChangedArgs::NotifyConfigurationChangedArgs( - const NotifyConfigurationChangedArgs& other) - : NotifyArgs(other.id, other.eventTime) {} - -bool NotifyConfigurationChangedArgs::operator==(const NotifyConfigurationChangedArgs& rhs) const { - return id == rhs.id && eventTime == rhs.eventTime; -} - -void NotifyConfigurationChangedArgs::notify(InputListenerInterface& listener) const { - listener.notifyConfigurationChanged(this); -} - -// --- NotifyKeyArgs --- - -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) - : NotifyArgs(id, eventTime), - deviceId(deviceId), - source(source), - displayId(displayId), - policyFlags(policyFlags), - action(action), - flags(flags), - keyCode(keyCode), - scanCode(scanCode), - metaState(metaState), - downTime(downTime), - readTime(readTime) {} - -NotifyKeyArgs::NotifyKeyArgs(const NotifyKeyArgs& other) - : NotifyArgs(other.id, other.eventTime), - deviceId(other.deviceId), - source(other.source), - displayId(other.displayId), - policyFlags(other.policyFlags), - action(other.action), - flags(other.flags), - keyCode(other.keyCode), - scanCode(other.scanCode), - metaState(other.metaState), - downTime(other.downTime), - readTime(other.readTime) {} - -bool NotifyKeyArgs::operator==(const NotifyKeyArgs& rhs) const { - return id == rhs.id && eventTime == rhs.eventTime && readTime == rhs.readTime && - deviceId == rhs.deviceId && source == rhs.source && displayId == rhs.displayId && - policyFlags == rhs.policyFlags && action == rhs.action && flags == rhs.flags && - keyCode == rhs.keyCode && scanCode == rhs.scanCode && metaState == rhs.metaState && - downTime == rhs.downTime; -} - -void NotifyKeyArgs::notify(InputListenerInterface& listener) const { - listener.notifyKey(this); -} - -// --- NotifyMotionArgs --- - -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, - const std::vector& videoFrames) - : NotifyArgs(id, eventTime), - deviceId(deviceId), - source(source), - displayId(displayId), - policyFlags(policyFlags), - action(action), - actionButton(actionButton), - flags(flags), - metaState(metaState), - buttonState(buttonState), - classification(classification), - edgeFlags(edgeFlags), - pointerCount(pointerCount), - xPrecision(xPrecision), - yPrecision(yPrecision), - xCursorPosition(xCursorPosition), - yCursorPosition(yCursorPosition), - downTime(downTime), - readTime(readTime), - videoFrames(videoFrames) { - for (uint32_t i = 0; i < pointerCount; i++) { - this->pointerProperties[i].copyFrom(pointerProperties[i]); - this->pointerCoords[i].copyFrom(pointerCoords[i]); - } -} - -NotifyMotionArgs::NotifyMotionArgs(const NotifyMotionArgs& other) - : NotifyArgs(other.id, other.eventTime), - deviceId(other.deviceId), - source(other.source), - displayId(other.displayId), - policyFlags(other.policyFlags), - action(other.action), - actionButton(other.actionButton), - flags(other.flags), - metaState(other.metaState), - buttonState(other.buttonState), - classification(other.classification), - edgeFlags(other.edgeFlags), - pointerCount(other.pointerCount), - xPrecision(other.xPrecision), - yPrecision(other.yPrecision), - xCursorPosition(other.xCursorPosition), - yCursorPosition(other.yCursorPosition), - downTime(other.downTime), - readTime(other.readTime), - videoFrames(other.videoFrames) { - for (uint32_t i = 0; i < pointerCount; i++) { - pointerProperties[i].copyFrom(other.pointerProperties[i]); - pointerCoords[i].copyFrom(other.pointerCoords[i]); - } -} - -static inline bool isCursorPositionEqual(float lhs, float rhs) { - return (isnan(lhs) && isnan(rhs)) || lhs == rhs; -} - -bool NotifyMotionArgs::operator==(const NotifyMotionArgs& rhs) const { - bool equal = id == rhs.id && eventTime == rhs.eventTime && readTime == rhs.readTime && - deviceId == rhs.deviceId && source == rhs.source && displayId == rhs.displayId && - policyFlags == rhs.policyFlags && action == rhs.action && - actionButton == rhs.actionButton && flags == rhs.flags && metaState == rhs.metaState && - buttonState == rhs.buttonState && classification == rhs.classification && - edgeFlags == rhs.edgeFlags && - pointerCount == rhs.pointerCount - // PointerProperties and PointerCoords are compared separately below - && xPrecision == rhs.xPrecision && yPrecision == rhs.yPrecision && - isCursorPositionEqual(xCursorPosition, rhs.xCursorPosition) && - isCursorPositionEqual(yCursorPosition, rhs.yCursorPosition) && - downTime == rhs.downTime && videoFrames == rhs.videoFrames; - if (!equal) { - return false; - } - - for (size_t i = 0; i < pointerCount; i++) { - equal = - pointerProperties[i] == rhs.pointerProperties[i] - && pointerCoords[i] == rhs.pointerCoords[i]; - if (!equal) { - return false; - } - } - return true; -} - -std::string NotifyMotionArgs::dump() const { - std::string coords; - for (uint32_t i = 0; i < pointerCount; i++) { - if (!coords.empty()) { - coords += ", "; - } - coords += StringPrintf("{%" PRIu32 ": ", i); - coords += - StringPrintf("id=%" PRIu32 " x=%.1f y=%.1f pressure=%.1f", pointerProperties[i].id, - pointerCoords[i].getX(), pointerCoords[i].getY(), - pointerCoords[i].getAxisValue(AMOTION_EVENT_AXIS_PRESSURE)); - const int32_t toolType = pointerProperties[i].toolType; - if (toolType != AMOTION_EVENT_TOOL_TYPE_FINGER) { - coords += StringPrintf(" toolType=%s", motionToolTypeToString(toolType)); - } - const float major = pointerCoords[i].getAxisValue(AMOTION_EVENT_AXIS_TOUCH_MAJOR); - const float minor = pointerCoords[i].getAxisValue(AMOTION_EVENT_AXIS_TOUCH_MINOR); - const float orientation = pointerCoords[i].getAxisValue(AMOTION_EVENT_AXIS_ORIENTATION); - if (major != 0 || minor != 0) { - coords += StringPrintf(" major=%.1f minor=%.1f orientation=%.1f", major, minor, - orientation); - } - coords += "}"; - } - return StringPrintf("NotifyMotionArgs(id=%" PRId32 ", eventTime=%" PRId64 ", deviceId=%" PRId32 - ", source=%s, action=%s, pointerCount=%" PRIu32 - " pointers=%s, flags=0x%08x)", - id, eventTime, deviceId, inputEventSourceToString(source).c_str(), - MotionEvent::actionToString(action).c_str(), pointerCount, coords.c_str(), - flags); -} - -void NotifyMotionArgs::notify(InputListenerInterface& listener) const { - listener.notifyMotion(this); -} - -// --- NotifySwitchArgs --- - -NotifySwitchArgs::NotifySwitchArgs(int32_t id, nsecs_t eventTime, uint32_t policyFlags, - uint32_t switchValues, uint32_t switchMask) - : NotifyArgs(id, eventTime), - policyFlags(policyFlags), - switchValues(switchValues), - switchMask(switchMask) {} - -NotifySwitchArgs::NotifySwitchArgs(const NotifySwitchArgs& other) - : NotifyArgs(other.id, other.eventTime), - policyFlags(other.policyFlags), - switchValues(other.switchValues), - switchMask(other.switchMask) {} - -bool NotifySwitchArgs::operator==(const NotifySwitchArgs rhs) const { - return id == rhs.id && eventTime == rhs.eventTime && policyFlags == rhs.policyFlags && - switchValues == rhs.switchValues && switchMask == rhs.switchMask; -} - -void NotifySwitchArgs::notify(InputListenerInterface& listener) const { - listener.notifySwitch(this); -} - -// --- NotifySensorArgs --- - -NotifySensorArgs::NotifySensorArgs(int32_t id, nsecs_t eventTime, int32_t deviceId, uint32_t source, - InputDeviceSensorType sensorType, - InputDeviceSensorAccuracy accuracy, bool accuracyChanged, - nsecs_t hwTimestamp, std::vector values) - : NotifyArgs(id, eventTime), - deviceId(deviceId), - source(source), - sensorType(sensorType), - accuracy(accuracy), - accuracyChanged(accuracyChanged), - hwTimestamp(hwTimestamp), - values(std::move(values)) {} - -NotifySensorArgs::NotifySensorArgs(const NotifySensorArgs& other) - : NotifyArgs(other.id, other.eventTime), - deviceId(other.deviceId), - source(other.source), - sensorType(other.sensorType), - accuracy(other.accuracy), - accuracyChanged(other.accuracyChanged), - hwTimestamp(other.hwTimestamp), - values(other.values) {} - -bool NotifySensorArgs::operator==(const NotifySensorArgs rhs) const { - return id == rhs.id && eventTime == rhs.eventTime && sensorType == rhs.sensorType && - accuracy == rhs.accuracy && accuracyChanged == rhs.accuracyChanged && - hwTimestamp == rhs.hwTimestamp && values == rhs.values; -} - -void NotifySensorArgs::notify(InputListenerInterface& listener) const { - listener.notifySensor(this); -} - -// --- NotifyVibratorStateArgs --- - -NotifyVibratorStateArgs::NotifyVibratorStateArgs(int32_t id, nsecs_t eventTime, int32_t deviceId, - bool isOn) - : NotifyArgs(id, eventTime), deviceId(deviceId), isOn(isOn) {} - -NotifyVibratorStateArgs::NotifyVibratorStateArgs(const NotifyVibratorStateArgs& other) - : NotifyArgs(other.id, other.eventTime), deviceId(other.deviceId), isOn(other.isOn) {} - -bool NotifyVibratorStateArgs::operator==(const NotifyVibratorStateArgs rhs) const { - return id == rhs.id && eventTime == rhs.eventTime && deviceId == rhs.deviceId && - isOn == rhs.isOn; -} - -void NotifyVibratorStateArgs::notify(InputListenerInterface& listener) const { - listener.notifyVibratorState(this); -} - -// --- NotifyDeviceResetArgs --- - -NotifyDeviceResetArgs::NotifyDeviceResetArgs(int32_t id, nsecs_t eventTime, int32_t deviceId) - : NotifyArgs(id, eventTime), deviceId(deviceId) {} - -NotifyDeviceResetArgs::NotifyDeviceResetArgs(const NotifyDeviceResetArgs& other) - : NotifyArgs(other.id, other.eventTime), deviceId(other.deviceId) {} - -bool NotifyDeviceResetArgs::operator==(const NotifyDeviceResetArgs& rhs) const { - return id == rhs.id && eventTime == rhs.eventTime && deviceId == rhs.deviceId; -} - -void NotifyDeviceResetArgs::notify(InputListenerInterface& listener) const { - listener.notifyDeviceReset(this); +std::list& operator+=(std::list& keep, std::list&& consume) { + keep.splice(keep.end(), consume); + return keep; } -// --- NotifyPointerCaptureChangedArgs --- - -NotifyPointerCaptureChangedArgs::NotifyPointerCaptureChangedArgs( - int32_t id, nsecs_t eventTime, const PointerCaptureRequest& request) - : NotifyArgs(id, eventTime), request(request) {} +// --- InputListenerInterface --- -NotifyPointerCaptureChangedArgs::NotifyPointerCaptureChangedArgs( - const NotifyPointerCaptureChangedArgs& other) - : NotifyArgs(other.id, other.eventTime), request(other.request) {} +// Helper to std::visit with lambdas. +template +struct Visitor : V... {}; +// explicit deduction guide (not needed as of C++20) +template +Visitor(V...) -> Visitor; -bool NotifyPointerCaptureChangedArgs::operator==(const NotifyPointerCaptureChangedArgs& rhs) const { - return id == rhs.id && eventTime == rhs.eventTime && request == rhs.request; -} - -void NotifyPointerCaptureChangedArgs::notify(InputListenerInterface& listener) const { - listener.notifyPointerCaptureChanged(this); +void InputListenerInterface::notify(const NotifyArgs& generalArgs) { + Visitor v{ + [&](const NotifyInputDevicesChangedArgs& args) { notifyInputDevicesChanged(args); }, + [&](const NotifyConfigurationChangedArgs& args) { notifyConfigurationChanged(args); }, + [&](const NotifyKeyArgs& args) { notifyKey(args); }, + [&](const NotifyMotionArgs& args) { notifyMotion(args); }, + [&](const NotifySwitchArgs& args) { notifySwitch(args); }, + [&](const NotifySensorArgs& args) { notifySensor(args); }, + [&](const NotifyVibratorStateArgs& args) { notifyVibratorState(args); }, + [&](const NotifyDeviceResetArgs& args) { notifyDeviceReset(args); }, + [&](const NotifyPointerCaptureChangedArgs& args) { notifyPointerCaptureChanged(args); }, + }; + std::visit(v, generalArgs); } // --- QueuedInputListener --- @@ -347,50 +71,54 @@ static inline void traceEvent(const char* functionName, int32_t id) { QueuedInputListener::QueuedInputListener(InputListenerInterface& innerListener) : mInnerListener(innerListener) {} -void QueuedInputListener::notifyConfigurationChanged( - const NotifyConfigurationChangedArgs* args) { - traceEvent(__func__, args->id); - mArgsQueue.emplace_back(std::make_unique(*args)); +void QueuedInputListener::notifyInputDevicesChanged(const NotifyInputDevicesChangedArgs& args) { + traceEvent(__func__, args.id); + mArgsQueue.emplace_back(args); +} + +void QueuedInputListener::notifyConfigurationChanged(const NotifyConfigurationChangedArgs& args) { + traceEvent(__func__, args.id); + mArgsQueue.emplace_back(args); } -void QueuedInputListener::notifyKey(const NotifyKeyArgs* args) { - traceEvent(__func__, args->id); - mArgsQueue.emplace_back(std::make_unique(*args)); +void QueuedInputListener::notifyKey(const NotifyKeyArgs& args) { + traceEvent(__func__, args.id); + mArgsQueue.emplace_back(args); } -void QueuedInputListener::notifyMotion(const NotifyMotionArgs* args) { - traceEvent(__func__, args->id); - mArgsQueue.emplace_back(std::make_unique(*args)); +void QueuedInputListener::notifyMotion(const NotifyMotionArgs& args) { + traceEvent(__func__, args.id); + mArgsQueue.emplace_back(args); } -void QueuedInputListener::notifySwitch(const NotifySwitchArgs* args) { - traceEvent(__func__, args->id); - mArgsQueue.emplace_back(std::make_unique(*args)); +void QueuedInputListener::notifySwitch(const NotifySwitchArgs& args) { + traceEvent(__func__, args.id); + mArgsQueue.emplace_back(args); } -void QueuedInputListener::notifySensor(const NotifySensorArgs* args) { - traceEvent(__func__, args->id); - mArgsQueue.emplace_back(std::make_unique(*args)); +void QueuedInputListener::notifySensor(const NotifySensorArgs& args) { + traceEvent(__func__, args.id); + mArgsQueue.emplace_back(args); } -void QueuedInputListener::notifyVibratorState(const NotifyVibratorStateArgs* args) { - traceEvent(__func__, args->id); - mArgsQueue.emplace_back(std::make_unique(*args)); +void QueuedInputListener::notifyVibratorState(const NotifyVibratorStateArgs& args) { + traceEvent(__func__, args.id); + mArgsQueue.emplace_back(args); } -void QueuedInputListener::notifyDeviceReset(const NotifyDeviceResetArgs* args) { - traceEvent(__func__, args->id); - mArgsQueue.emplace_back(std::make_unique(*args)); +void QueuedInputListener::notifyDeviceReset(const NotifyDeviceResetArgs& args) { + traceEvent(__func__, args.id); + mArgsQueue.emplace_back(args); } -void QueuedInputListener::notifyPointerCaptureChanged(const NotifyPointerCaptureChangedArgs* args) { - traceEvent(__func__, args->id); - mArgsQueue.emplace_back(std::make_unique(*args)); +void QueuedInputListener::notifyPointerCaptureChanged(const NotifyPointerCaptureChangedArgs& args) { + traceEvent(__func__, args.id); + mArgsQueue.emplace_back(args); } void QueuedInputListener::flush() { - for (const std::unique_ptr& args : mArgsQueue) { - args->notify(mInnerListener); + for (const NotifyArgs& args : mArgsQueue) { + mInnerListener.notify(args); } mArgsQueue.clear(); } diff --git a/services/inputflinger/InputManager.cpp b/services/inputflinger/InputManager.cpp index 9767cd9b7139f924f66ce4f5758b01d46261bb0c..ddebcad0d30bfb273778a11981fd533928ab305a 100644 --- a/services/inputflinger/InputManager.cpp +++ b/services/inputflinger/InputManager.cpp @@ -55,14 +55,13 @@ static int32_t exceptionCodeFromStatusT(status_t status) { /** * The event flow is via the "InputListener" interface, as follows: - * InputReader -> UnwantedInteractionBlocker -> InputClassifier -> InputDispatcher + * InputReader -> UnwantedInteractionBlocker -> InputProcessor -> InputDispatcher */ -InputManager::InputManager( - const sp& readerPolicy, - const sp& dispatcherPolicy) { +InputManager::InputManager(const sp& readerPolicy, + InputDispatcherPolicyInterface& dispatcherPolicy) { mDispatcher = createInputDispatcher(dispatcherPolicy); - mClassifier = std::make_unique(*mDispatcher); - mBlocker = std::make_unique(*mClassifier); + mProcessor = std::make_unique(*mDispatcher); + mBlocker = std::make_unique(*mProcessor); mReader = createInputReader(readerPolicy, *mBlocker); } @@ -110,12 +109,8 @@ InputReaderInterface& InputManager::getReader() { return *mReader; } -UnwantedInteractionBlockerInterface& InputManager::getUnwantedInteractionBlocker() { - return *mBlocker; -} - -InputClassifierInterface& InputManager::getClassifier() { - return *mClassifier; +InputProcessorInterface& InputManager::getProcessor() { + return *mProcessor; } InputDispatcherInterface& InputManager::getDispatcher() { @@ -125,10 +120,21 @@ InputDispatcherInterface& InputManager::getDispatcher() { void InputManager::monitor() { mReader->monitor(); mBlocker->monitor(); - mClassifier->monitor(); + mProcessor->monitor(); mDispatcher->monitor(); } +void InputManager::dump(std::string& dump) { + mReader->dump(dump); + dump += '\n'; + mBlocker->dump(dump); + dump += '\n'; + mProcessor->dump(dump); + dump += '\n'; + mDispatcher->dump(dump); + dump += '\n'; +} + // Used by tests only. binder::Status InputManager::createInputChannel(const std::string& name, InputChannel* outChannel) { IPCThreadState* ipc = IPCThreadState::self(); diff --git a/services/inputflinger/InputManager.h b/services/inputflinger/InputManager.h index 8aad35bf1e6af5ff055ef40e080b7ae9511a5911..b6ad419f310ebc40066fffac1fee3bfc604a5ead 100644 --- a/services/inputflinger/InputManager.h +++ b/services/inputflinger/InputManager.h @@ -14,14 +14,13 @@ * limitations under the License. */ -#ifndef _UI_INPUT_MANAGER_H -#define _UI_INPUT_MANAGER_H +#pragma once /** * Native input manager. */ -#include "InputClassifier.h" +#include "InputProcessor.h" #include "InputReaderBase.h" #include "include/UnwantedInteractionBlockerInterface.h" @@ -52,10 +51,11 @@ class InputDispatcherThread; * this could be a palm on the screen. This stage would alter the event stream to remove either * partially (some of the pointers) or fully (all touches) the unwanted interaction. The events * are processed on the InputReader thread, without any additional queue. The events are then - * posted to the queue managed by the InputClassifier. - * 3. The InputClassifier class starts a thread to communicate with the device-specific - * classifiers. It then waits on the queue of events from UnwantedInteractionBlocker, applies - * a classification to them, and queues them for the InputDispatcher. + * posted to the queue managed by the InputProcessor. + * 3. The InputProcessor class starts a thread to communicate with the device-specific + * IInputProcessor HAL. It then waits on the queue of events from UnwantedInteractionBlocker, + * processes the events (for example, applies a classification to the events), and queues them + * for the InputDispatcher. * 4. The InputDispatcher class starts a thread that waits for new events on the * previous queue and asynchronously dispatches them to applications. * @@ -82,17 +82,17 @@ public: /* Gets the input reader. */ virtual InputReaderInterface& getReader() = 0; - /* Gets the unwanted interaction blocker. */ - virtual UnwantedInteractionBlockerInterface& getUnwantedInteractionBlocker() = 0; - - /* Gets the input classifier */ - virtual InputClassifierInterface& getClassifier() = 0; + /* Gets the input processor */ + virtual InputProcessorInterface& getProcessor() = 0; /* Gets the input dispatcher. */ virtual InputDispatcherInterface& getDispatcher() = 0; /* Check that the input stages have not deadlocked. */ virtual void monitor() = 0; + + /* Dump the state of the components controlled by the input manager. */ + virtual void dump(std::string& dump) = 0; }; class InputManager : public InputManagerInterface, public BnInputFlinger { @@ -100,18 +100,17 @@ protected: ~InputManager() override; public: - InputManager( - const sp& readerPolicy, - const sp& dispatcherPolicy); + InputManager(const sp& readerPolicy, + InputDispatcherPolicyInterface& dispatcherPolicy); status_t start() override; status_t stop() override; InputReaderInterface& getReader() override; - UnwantedInteractionBlockerInterface& getUnwantedInteractionBlocker() override; - InputClassifierInterface& getClassifier() override; + InputProcessorInterface& getProcessor() override; InputDispatcherInterface& getDispatcher() override; void monitor() override; + void dump(std::string& dump) override; status_t dump(int fd, const Vector& args) override; binder::Status createInputChannel(const std::string& name, InputChannel* outChannel) override; @@ -123,11 +122,9 @@ private: std::unique_ptr mBlocker; - std::unique_ptr mClassifier; + std::unique_ptr mProcessor; std::unique_ptr mDispatcher; }; } // namespace android - -#endif // _UI_INPUT_MANAGER_H diff --git a/services/inputflinger/InputClassifier.cpp b/services/inputflinger/InputProcessor.cpp similarity index 74% rename from services/inputflinger/InputClassifier.cpp rename to services/inputflinger/InputProcessor.cpp index 8ce2f35d7b3b091ea0831edb558fe6c7d6988a86..7a84be93b17186091ae7e0f9a08bf26c095a4058 100644 --- a/services/inputflinger/InputClassifier.cpp +++ b/services/inputflinger/InputProcessor.cpp @@ -14,20 +14,21 @@ * limitations under the License. */ -#define LOG_TAG "InputClassifier" +#define LOG_TAG "InputProcessor" -#include "InputClassifier.h" +#include "InputProcessor.h" #include "InputCommonConverter.h" #include #include #include +#include inTouchMode is set to true, the display + * identified by displayId will be changed to touch mode. Performs a permission + * check if hasPermission is set to false. + * + * This method also enqueues a a TouchModeEntry message for dispatching. * * Returns true when changing touch mode state. */ - virtual bool setInTouchMode(bool inTouchMode, int32_t pid, int32_t uid, bool hasPermission) = 0; + virtual bool setInTouchMode(bool inTouchMode, int32_t pid, int32_t uid, bool hasPermission, + int32_t displayId) = 0; /** * Sets the maximum allowed obscuring opacity by UID to propagate touches. @@ -142,13 +145,6 @@ public: */ virtual void setMaximumObscuringOpacityForTouch(float opacity) = 0; - /** - * Sets the mode of the block untrusted touches feature. - * - * TODO(b/169067926): Clean-up feature modes. - */ - virtual void setBlockUntrustedTouchesMode(android::os::BlockUntrustedTouchesMode mode) = 0; - /* Transfers touch focus from one window to another window. * * Returns true on success. False if the window did not actually have touch focus. @@ -229,8 +225,12 @@ public: * Abort the current touch stream. */ virtual void cancelCurrentTouch() = 0; + + /** + * Request that the InputDispatcher's configuration, which can be obtained through the policy, + * be updated. + */ + virtual void requestRefreshConfiguration() = 0; }; } // namespace android - -#endif // _UI_INPUT_INPUTDISPATCHER_INPUTDISPATCHERINTERFACE_H diff --git a/services/inputflinger/dispatcher/include/InputDispatcherPolicyInterface.h b/services/inputflinger/dispatcher/include/InputDispatcherPolicyInterface.h index fff1b033396d435018ed28cc550745fb3ddb831f..baea6f8eda30cb0169a3a6670cec11446152a99e 100644 --- a/services/inputflinger/dispatcher/include/InputDispatcherPolicyInterface.h +++ b/services/inputflinger/dispatcher/include/InputDispatcherPolicyInterface.h @@ -14,8 +14,7 @@ * limitations under the License. */ -#ifndef _UI_INPUT_INPUTDISPATCHER_INPUTDISPATCHERPOLICYINTERFACE_H -#define _UI_INPUT_INPUTDISPATCHER_INPUTDISPATCHERPOLICYINTERFACE_H +#pragma once #include "InputDispatcherConfiguration.h" @@ -35,12 +34,11 @@ namespace android { * The actual implementation is partially supported by callbacks into the DVM * via JNI. This interface is also mocked in the unit tests. */ -class InputDispatcherPolicyInterface : public virtual RefBase { -protected: - InputDispatcherPolicyInterface() {} - virtual ~InputDispatcherPolicyInterface() {} - +class InputDispatcherPolicyInterface { public: + InputDispatcherPolicyInterface() = default; + virtual ~InputDispatcherPolicyInterface() = default; + /* Notifies the system that a configuration change has occurred. */ virtual void notifyConfigurationChanged(nsecs_t when) = 0; @@ -74,18 +72,15 @@ public: InputDeviceSensorAccuracy accuracy) = 0; virtual void notifyVibratorState(int32_t deviceId, bool isOn) = 0; - /* Notifies the system that an untrusted touch occurred. */ - virtual void notifyUntrustedTouch(const std::string& obscuringPackage) = 0; - /* Gets the input dispatcher configuration. */ - virtual void getDispatcherConfiguration(InputDispatcherConfiguration* outConfig) = 0; + virtual InputDispatcherConfiguration getDispatcherConfiguration() = 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 * to injectInputEvent. */ - virtual bool filterInputEvent(const InputEvent* inputEvent, uint32_t policyFlags) = 0; + virtual bool filterInputEvent(const InputEvent& inputEvent, uint32_t policyFlags) = 0; /* Intercepts a key event immediately before queueing it. * The policy can use this method as an opportunity to perform power management functions @@ -94,7 +89,7 @@ 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 interceptKeyBeforeQueueing(const KeyEvent* keyEvent, uint32_t& policyFlags) = 0; + virtual void interceptKeyBeforeQueueing(const KeyEvent& keyEvent, uint32_t& policyFlags) = 0; /* Intercepts a touch, trackball or other motion event before queueing it. * The policy can use this method as an opportunity to perform power management functions @@ -103,18 +98,19 @@ 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(const int32_t displayId, nsecs_t when, + virtual void interceptMotionBeforeQueueing(int32_t displayId, 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, - const KeyEvent* keyEvent, + const KeyEvent& keyEvent, uint32_t policyFlags) = 0; /* Allows the policy a chance to perform default processing for an unhandled key. - * Returns an alternate keycode to redispatch as a fallback, or 0 to give up. */ - virtual bool dispatchUnhandledKey(const sp& token, const KeyEvent* keyEvent, - uint32_t policyFlags, KeyEvent* outFallbackKeyEvent) = 0; + * Returns an alternate key event to redispatch as a fallback, if needed. */ + virtual std::optional dispatchUnhandledKey(const sp& token, + const KeyEvent& keyEvent, + uint32_t policyFlags) = 0; /* Notifies the policy about switch events. */ @@ -142,5 +138,3 @@ public: }; } // namespace android - -#endif // _UI_INPUT_INPUTDISPATCHER_INPUTDISPATCHERPOLICYINTERFACE_H diff --git a/services/inputflinger/docs/input_coordinates.md b/services/inputflinger/docs/input_coordinates.md new file mode 100644 index 0000000000000000000000000000000000000000..77957109365466848aed3d53d8f781a73aee2720 --- /dev/null +++ b/services/inputflinger/docs/input_coordinates.md @@ -0,0 +1,113 @@ +# Input Coordinate Processing in InputFlinger + +This document aims to illustrate why we need to take care when converting +between the discrete and continuous coordinate spaces, especially when +performing rotations. + +The Linux evdev protocol works over **discrete integral** values. The same is +true for displays, which output discrete pixels. WindowManager also tracks +window bounds in pixels in the rotated logical display. + +However, our `MotionEvent` APIs +report **floating point** axis values in a **continuous space**. This disparity +is important to note when working in InputFlinger, which has to make sure the +discrete raw coordinates are converted to the continuous space correctly in all +scenarios. + +## Disparity between continuous and discrete coordinates during rotation + +Let's consider an example of device that has a 3 x 4 screen. + +### Natural orientation: No rotation + +If the user interacts with the highlighted pixel, the touchscreen would report +the discreet coordinates (0, 2). + +``` + ┌─────┬─────┬─────┠+ │ 0,0 │ 1,0 │ 2,0 │ + ├─────┼─────┼─────┤ + │ 0,1 │ 1,1 │ 2,1 │ + ├─────┼─────┼─────┤ + │█0,2█│ 1,2 │ 2,2 │ + ├─────┼─────┼─────┤ + │ 0,3 │ 1,3 │ 2,3 │ + └─────┴─────┴─────┘ +``` + +When converted to the continuous space, the point (0, 2) corresponds to the +location shown below. + +``` + 0 1 2 3 + 0 ┌─────┬─────┬─────┠+ │ │ │ │ + 1 ├─────┼─────┼─────┤ + │ │ │ │ + 2 █─────┼─────┼─────┤ + │ │ │ │ + 3 ├─────┼─────┼─────┤ + │ │ │ │ + 4 └─────┴─────┴─────┘ +``` + +### Rotated orientation: 90-degree counter-clockwise rotation + +When the device is rotated and the same place on the touchscreen is touched, the +input device will still report the same coordinates of (0, 2). + +In the rotated display, that now corresponds to the pixel (2, 2). + +``` + ┌─────┬─────┬─────┬─────┠+ │ 0,0 │ 1,0 │ 2,0 │ 3,0 │ + ├─────┼─────┼─────┼─────┤ + │ 0,1 │ 1,1 │ 2,1 │ 3,1 │ + ├─────┼─────┼─────┼─────┤ + │ 0,2 │ 1,2 │█2,2█│ 3,2 │ + └─────┴─────┴─────┴─────┘ +``` + +*It is important to note that rotating the device 90 degrees is NOT equivalent +to rotating the continuous coordinate space by 90 degrees.* + +The point (2, 2) now corresponds to a different location in the continuous space +than before, even though the user was interacting at the same place on the +touchscreen. + +``` + 0 1 2 3 4 + 0 ┌─────┬─────┬─────┬─────┠+ │ │ │ │ │ + 1 ├─────┼─────┼─────┼─────┤ + │ │ │ │ │ + 2 ├─────┼─────█─────┼─────┤ + │ │ │ │ │ + 3 └─────┴─────┴─────┴─────┘ +``` + +If we were to simply (incorrectly) rotate the continuous space from before by +90 degrees, the touched point would correspond to the location (2, 3), shown +below. This new point is outside the bounds of the display, since it does not +correspond to any pixel at that location. + +It should be impossible for a touchscreen to generate points outside the bounds +of the display, because we assume that the area of the touchscreen maps directly +to the area of the display. Therefore, that point is an invalid coordinate that +cannot be generated by an input device. + +``` + 0 1 2 3 4 + 0 ┌─────┬─────┬─────┬─────┠+ │ │ │ │ â• + 1 ├─────┼─────┼─────┼─────┤ + │ │ │ │ â• + 2 ├─────┼─────┼─────┼─────┤ + │ │ │ │ â• + 3 â””-----â”´-----â–ˆ-----â”´-----┘ +``` + +The same logic applies to windows as well. When performing hit tests to +determine if a point in the continuous space falls inside a window's bounds, +hit test must be performed in the correct orientation, since points on the right +and bottom edges of the window do not fall within the window bounds. diff --git a/services/inputflinger/host/Android.bp b/services/inputflinger/host/Android.bp index 743587c6f860a7153ef73600b681ad598d839ce8..4d2839f038c37a82446db3958f60f6ee752190bc 100644 --- a/services/inputflinger/host/Android.bp +++ b/services/inputflinger/host/Android.bp @@ -23,7 +23,7 @@ package { cc_library_shared { name: "libinputflingerhost", - + cpp_std: "c++20", srcs: [ "InputFlinger.cpp", "InputDriver.cpp", @@ -64,14 +64,17 @@ cc_binary { srcs: ["main.cpp"], - cflags: ["-Wall", "-Werror"], + cflags: [ + "-Wall", + "-Werror", + ], shared_libs: [ "libbase", "libbinder", "libinputflingerhost", "libutils", - "libinput" + "libinput", ], static_libs: [ "libarect", diff --git a/services/inputflinger/host/InputDriver.cpp b/services/inputflinger/host/InputDriver.cpp index 94c839f761bd157c1343ffce2c1dae722bbf5ff5..de99fc73ec8d2a9b409c3a1710a41687ea11a713 100644 --- a/services/inputflinger/host/InputDriver.cpp +++ b/services/inputflinger/host/InputDriver.cpp @@ -240,19 +240,16 @@ input_property_map_t* InputDriver::inputGetDevicePropertyMap(input_device_identi return nullptr; } -input_property_t* InputDriver::inputGetDeviceProperty(input_property_map_t* map, - const char* key) { - String8 keyString(key); +input_property_t* InputDriver::inputGetDeviceProperty(input_property_map_t* map, const char* key) { if (map != nullptr) { - if (map->propertyMap->hasProperty(keyString)) { - auto prop = new input_property_t(); - if (!map->propertyMap->tryGetProperty(keyString, prop->value)) { - delete prop; - return nullptr; - } - prop->key = keyString; - return prop; + std::optional value = map->propertyMap->getString(key); + if (!value.has_value()) { + return nullptr; } + auto prop = std::make_unique(); + prop->key = key; + prop->value = value->c_str(); + return prop.release(); } return nullptr; } diff --git a/services/inputflinger/host/InputDriver.h b/services/inputflinger/host/InputDriver.h index e56673b4d30a8131bb1bc10a8234b01ead8d3b64..b4c90943a8a3c2004ebaef533233f51a5c7d1a2d 100644 --- a/services/inputflinger/host/InputDriver.h +++ b/services/inputflinger/host/InputDriver.h @@ -14,8 +14,7 @@ * limitations under the License. */ -#ifndef ANDROID_INPUT_DRIVER_H -#define ANDROID_INPUT_DRIVER_H +#pragma once #include #include @@ -192,4 +191,3 @@ void input_free_device_property_map(input_host_t* host, input_property_map_t* ma } } // namespace android -#endif // ANDROID_INPUT_DRIVER_H diff --git a/services/inputflinger/host/InputFlinger.h b/services/inputflinger/host/InputFlinger.h index 3cf1b2b797947f6e3919b36a9ddea78701057d0f..388988bfcebf8a0a5cf4df5496268290122aa040 100644 --- a/services/inputflinger/host/InputFlinger.h +++ b/services/inputflinger/host/InputFlinger.h @@ -14,8 +14,7 @@ * limitations under the License. */ -#ifndef ANDROID_INPUT_FLINGER_H -#define ANDROID_INPUT_FLINGER_H +#pragma once #include #include @@ -58,5 +57,3 @@ private: }; } // namespace android - -#endif // ANDROID_INPUT_FLINGER_H diff --git a/services/inputflinger/host/InputHost.h b/services/inputflinger/host/InputHost.h index eda4a89c730844b5ae42ed0ea69c11316a4ff9aa..bdc4225738df0b07805386e28ba679e3a0d32a5e 100644 --- a/services/inputflinger/host/InputHost.h +++ b/services/inputflinger/host/InputHost.h @@ -14,8 +14,7 @@ * limitations under the License. */ -#ifndef ANDROID_INPUT_HOST_H -#define ANDROID_INPUT_HOST_H +#pragma once #include @@ -55,4 +54,3 @@ private: }; } // namespace android -#endif // ANDRIOD_INPUT_HOST_H diff --git a/services/inputflinger/include/InputListener.h b/services/inputflinger/include/InputListener.h index d9822ce1568b9c627ffd503dddda899c95fe5551..4f78f032c7acd7455862b81e63bbdc59ad5320fc 100644 --- a/services/inputflinger/include/InputListener.h +++ b/services/inputflinger/include/InputListener.h @@ -14,237 +14,18 @@ * limitations under the License. */ -#ifndef _UI_INPUT_LISTENER_H -#define _UI_INPUT_LISTENER_H +#pragma once #include #include #include #include +#include "NotifyArgs.h" namespace android { -class InputListenerInterface; - - -/* Superclass of all input event argument objects */ -struct NotifyArgs { - int32_t id; - nsecs_t eventTime; - - inline NotifyArgs() : id(0), eventTime(0) {} - - inline explicit NotifyArgs(int32_t id, nsecs_t eventTime) : id(id), eventTime(eventTime) {} - - virtual ~NotifyArgs() { } - - virtual void notify(InputListenerInterface& listener) const = 0; -}; - - -/* Describes a configuration change event. */ -struct NotifyConfigurationChangedArgs : public NotifyArgs { - - inline NotifyConfigurationChangedArgs() { } - - bool operator==(const NotifyConfigurationChangedArgs& rhs) const; - - NotifyConfigurationChangedArgs(int32_t id, nsecs_t eventTime); - - NotifyConfigurationChangedArgs(const NotifyConfigurationChangedArgs& other); - - virtual ~NotifyConfigurationChangedArgs() { } - - void notify(InputListenerInterface& listener) const override; -}; - - -/* Describes a key event. */ -struct NotifyKeyArgs : public NotifyArgs { - 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; - nsecs_t readTime; - - 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); - - bool operator==(const NotifyKeyArgs& rhs) const; - - NotifyKeyArgs(const NotifyKeyArgs& other); - - virtual ~NotifyKeyArgs() { } - - void notify(InputListenerInterface& listener) const override; -}; - - -/* Describes a motion event. */ -struct NotifyMotionArgs : public NotifyArgs { - 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; - /** - * Classification of the current touch gesture - */ - MotionClassification classification; - int32_t edgeFlags; - - uint32_t pointerCount; - PointerProperties pointerProperties[MAX_POINTERS]; - PointerCoords pointerCoords[MAX_POINTERS]; - float xPrecision; - float yPrecision; - /** - * Mouse cursor position when this event is reported relative to the origin of the specified - * display. Only valid if this is a mouse event (originates from a mouse or from a trackpad in - * gestures enabled mode. - */ - float xCursorPosition; - float yCursorPosition; - nsecs_t downTime; - nsecs_t readTime; - std::vector videoFrames; - - 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, - const std::vector& videoFrames); - - NotifyMotionArgs(const NotifyMotionArgs& other); - - virtual ~NotifyMotionArgs() { } - - bool operator==(const NotifyMotionArgs& rhs) const; - - void notify(InputListenerInterface& listener) const override; - - std::string dump() const; -}; - -/* Describes a sensor event. */ -struct NotifySensorArgs : public NotifyArgs { - int32_t deviceId; - uint32_t source; - InputDeviceSensorType sensorType; - InputDeviceSensorAccuracy accuracy; - bool accuracyChanged; - nsecs_t hwTimestamp; - std::vector values; - - inline NotifySensorArgs() {} - - NotifySensorArgs(int32_t id, nsecs_t eventTime, int32_t deviceId, uint32_t source, - InputDeviceSensorType sensorType, InputDeviceSensorAccuracy accuracy, - bool accuracyChanged, nsecs_t hwTimestamp, std::vector values); - - NotifySensorArgs(const NotifySensorArgs& other); - - bool operator==(const NotifySensorArgs rhs) const; - - ~NotifySensorArgs() override {} - - void notify(InputListenerInterface& listener) const override; -}; - -/* Describes a switch event. */ -struct NotifySwitchArgs : public NotifyArgs { - uint32_t policyFlags; - uint32_t switchValues; - uint32_t switchMask; - - inline NotifySwitchArgs() { } - - NotifySwitchArgs(int32_t id, nsecs_t eventTime, uint32_t policyFlags, uint32_t switchValues, - uint32_t switchMask); - - NotifySwitchArgs(const NotifySwitchArgs& other); - - bool operator==(const NotifySwitchArgs rhs) const; - - virtual ~NotifySwitchArgs() { } - - void notify(InputListenerInterface& listener) const override; -}; - - -/* Describes a device reset event, such as when a device is added, - * reconfigured, or removed. */ -struct NotifyDeviceResetArgs : public NotifyArgs { - int32_t deviceId; - - inline NotifyDeviceResetArgs() { } - - NotifyDeviceResetArgs(int32_t id, nsecs_t eventTime, int32_t deviceId); - - NotifyDeviceResetArgs(const NotifyDeviceResetArgs& other); - - bool operator==(const NotifyDeviceResetArgs& rhs) const; - - virtual ~NotifyDeviceResetArgs() { } - - void notify(InputListenerInterface& listener) const override; -}; - -/* Describes a change in the state of Pointer Capture. */ -struct NotifyPointerCaptureChangedArgs : public NotifyArgs { - // The sequence number of the Pointer Capture request, if enabled. - PointerCaptureRequest request; - - inline NotifyPointerCaptureChangedArgs() {} - - NotifyPointerCaptureChangedArgs(int32_t id, nsecs_t eventTime, const PointerCaptureRequest&); - - NotifyPointerCaptureChangedArgs(const NotifyPointerCaptureChangedArgs& other); - - bool operator==(const NotifyPointerCaptureChangedArgs& rhs) const; - - virtual ~NotifyPointerCaptureChangedArgs() {} - - void notify(InputListenerInterface& listener) const override; -}; - -/* Describes a vibrator state event. */ -struct NotifyVibratorStateArgs : public NotifyArgs { - int32_t deviceId; - bool isOn; - - inline NotifyVibratorStateArgs() {} - - NotifyVibratorStateArgs(int32_t id, nsecs_t eventTIme, int32_t deviceId, bool isOn); - - NotifyVibratorStateArgs(const NotifyVibratorStateArgs& other); - - bool operator==(const NotifyVibratorStateArgs rhs) const; - - virtual ~NotifyVibratorStateArgs() {} - - void notify(InputListenerInterface& listener) const override; -}; +std::list& operator+=(std::list& keep, std::list&& consume); /* * The interface used by the InputReader to notify the InputListener about input events. @@ -256,14 +37,17 @@ public: InputListenerInterface& operator=(const InputListenerInterface&) = delete; virtual ~InputListenerInterface() { } - virtual void notifyConfigurationChanged(const NotifyConfigurationChangedArgs* args) = 0; - virtual void notifyKey(const NotifyKeyArgs* args) = 0; - virtual void notifyMotion(const NotifyMotionArgs* args) = 0; - virtual void notifySwitch(const NotifySwitchArgs* args) = 0; - virtual void notifySensor(const NotifySensorArgs* args) = 0; - virtual void notifyVibratorState(const NotifyVibratorStateArgs* args) = 0; - virtual void notifyDeviceReset(const NotifyDeviceResetArgs* args) = 0; - virtual void notifyPointerCaptureChanged(const NotifyPointerCaptureChangedArgs* args) = 0; + virtual void notifyInputDevicesChanged(const NotifyInputDevicesChangedArgs& args) = 0; + virtual void notifyConfigurationChanged(const NotifyConfigurationChangedArgs& args) = 0; + virtual void notifyKey(const NotifyKeyArgs& args) = 0; + virtual void notifyMotion(const NotifyMotionArgs& args) = 0; + virtual void notifySwitch(const NotifySwitchArgs& args) = 0; + virtual void notifySensor(const NotifySensorArgs& args) = 0; + virtual void notifyVibratorState(const NotifyVibratorStateArgs& args) = 0; + virtual void notifyDeviceReset(const NotifyDeviceResetArgs& args) = 0; + virtual void notifyPointerCaptureChanged(const NotifyPointerCaptureChangedArgs& args) = 0; + + void notify(const NotifyArgs& args); }; /* @@ -275,22 +59,21 @@ class QueuedInputListener : public InputListenerInterface { public: explicit QueuedInputListener(InputListenerInterface& innerListener); - virtual void notifyConfigurationChanged(const NotifyConfigurationChangedArgs* args) override; - virtual void notifyKey(const NotifyKeyArgs* args) override; - virtual void notifyMotion(const NotifyMotionArgs* args) override; - virtual void notifySwitch(const NotifySwitchArgs* args) override; - virtual void notifySensor(const NotifySensorArgs* args) override; - virtual void notifyDeviceReset(const NotifyDeviceResetArgs* args) override; - void notifyVibratorState(const NotifyVibratorStateArgs* args) override; - void notifyPointerCaptureChanged(const NotifyPointerCaptureChangedArgs* args) override; + virtual void notifyInputDevicesChanged(const NotifyInputDevicesChangedArgs& args) override; + virtual void notifyConfigurationChanged(const NotifyConfigurationChangedArgs& args) override; + virtual void notifyKey(const NotifyKeyArgs& args) override; + virtual void notifyMotion(const NotifyMotionArgs& args) override; + virtual void notifySwitch(const NotifySwitchArgs& args) override; + virtual void notifySensor(const NotifySensorArgs& args) override; + virtual void notifyDeviceReset(const NotifyDeviceResetArgs& args) override; + void notifyVibratorState(const NotifyVibratorStateArgs& args) override; + void notifyPointerCaptureChanged(const NotifyPointerCaptureChangedArgs& args) override; void flush(); private: InputListenerInterface& mInnerListener; - std::vector> mArgsQueue; + std::vector mArgsQueue; }; } // namespace android - -#endif // _UI_INPUT_LISTENER_H diff --git a/services/inputflinger/include/InputReaderBase.h b/services/inputflinger/include/InputReaderBase.h index 41ecef365de1a1dc7017b1487272033de1064345..a93a2ea615c3057e7bbdbe1fddbaee47d32cb840 100644 --- a/services/inputflinger/include/InputReaderBase.h +++ b/services/inputflinger/include/InputReaderBase.h @@ -14,8 +14,7 @@ * limitations under the License. */ -#ifndef _UI_INPUT_READER_BASE_H -#define _UI_INPUT_READER_BASE_H +#pragma once #include #include @@ -24,6 +23,7 @@ #include #include #include +#include #include #include #include @@ -42,107 +42,6 @@ namespace android { -// --- InputReaderInterface --- - -/* The interface for the InputReader shared library. - * - * Manages one or more threads that process raw input events and sends cooked event data to an - * input listener. - * - * The implementation must guarantee thread safety for this interface. However, since the input - * listener is NOT thread safe, all calls to the listener must happen from the same thread. - */ -class InputReaderInterface { -public: - InputReaderInterface() { } - virtual ~InputReaderInterface() { } - - /* Dumps the state of the input reader. - * - * This method may be called on any thread (usually by the input manager). */ - virtual void dump(std::string& dump) = 0; - - /* 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; - - /* Makes the reader stop processing any more events. */ - virtual status_t stop() = 0; - - /* Gets information about all input devices. - * - * This method may be called on any thread (usually by the input manager). - */ - virtual std::vector getInputDevices() const = 0; - - /* Query current input state. */ - virtual int32_t getScanCodeState(int32_t deviceId, uint32_t sourceMask, - int32_t scanCode) = 0; - virtual int32_t getKeyCodeState(int32_t deviceId, uint32_t sourceMask, - int32_t keyCode) = 0; - virtual int32_t getSwitchState(int32_t deviceId, uint32_t sourceMask, - int32_t sw) = 0; - - virtual int32_t getKeyCodeForKeyLocation(int32_t deviceId, int32_t locationKeyCode) const = 0; - - /* Toggle Caps Lock */ - virtual void toggleCapsLockState(int32_t deviceId) = 0; - - /* Determine whether physical keys exist for the given framework-domain key codes. */ - virtual bool hasKeys(int32_t deviceId, uint32_t sourceMask, - size_t numCodes, const int32_t* keyCodes, uint8_t* outFlags) = 0; - - /* Requests that a reconfiguration of all input devices. - * The changes flag is a bitfield that indicates what has changed and whether - * the input devices must all be reopened. */ - virtual void requestRefreshConfiguration(uint32_t changes) = 0; - - /* Controls the vibrator of a particular input device. */ - virtual void vibrate(int32_t deviceId, const VibrationSequence& sequence, ssize_t repeat, - int32_t token) = 0; - virtual void cancelVibrate(int32_t deviceId, int32_t token) = 0; - - virtual bool isVibrating(int32_t deviceId) = 0; - - virtual std::vector getVibratorIds(int32_t deviceId) = 0; - /* Get battery level of a particular input device. */ - virtual std::optional getBatteryCapacity(int32_t deviceId) = 0; - /* Get battery status of a particular input device. */ - virtual std::optional getBatteryStatus(int32_t deviceId) = 0; - - virtual std::vector getLights(int32_t deviceId) = 0; - - 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; - - /* Enable sensor in input reader mapper. */ - virtual bool enableSensor(int32_t deviceId, InputDeviceSensorType sensorType, - std::chrono::microseconds samplingPeriod, - std::chrono::microseconds maxBatchReportLatency) = 0; - - /* Disable sensor in input reader mapper. */ - virtual void disableSensor(int32_t deviceId, InputDeviceSensorType sensorType) = 0; - - /* Flush sensor data in input reader mapper. */ - virtual void flushSensor(int32_t deviceId, InputDeviceSensorType sensorType) = 0; - - /* Set color for the light */ - virtual bool setLightColor(int32_t deviceId, int32_t lightId, int32_t color) = 0; - /* Set player ID for the light */ - virtual bool setLightPlayerId(int32_t deviceId, int32_t lightId, int32_t playerId) = 0; - /* Get light color */ - virtual std::optional getLightColor(int32_t deviceId, int32_t lightId) = 0; - /* Get light player ID */ - virtual std::optional getLightPlayerId(int32_t deviceId, int32_t lightId) = 0; -}; - // --- InputReaderConfiguration --- /* @@ -152,39 +51,51 @@ public: */ struct InputReaderConfiguration { // Describes changes that have occurred. - enum { - // The pointer speed changed. - CHANGE_POINTER_SPEED = 1 << 0, + enum class Change : uint32_t { + // The mouse pointer speed changed. + POINTER_SPEED = 1u << 0, // The pointer gesture control changed. - CHANGE_POINTER_GESTURE_ENABLEMENT = 1 << 1, + POINTER_GESTURE_ENABLEMENT = 1u << 1, // The display size or orientation changed. - CHANGE_DISPLAY_INFO = 1 << 2, + DISPLAY_INFO = 1u << 2, // The visible touches option changed. - CHANGE_SHOW_TOUCHES = 1 << 3, + SHOW_TOUCHES = 1u << 3, // The keyboard layouts must be reloaded. - CHANGE_KEYBOARD_LAYOUTS = 1 << 4, + KEYBOARD_LAYOUTS = 1u << 4, // The device name alias supplied by the may have changed for some devices. - CHANGE_DEVICE_ALIAS = 1 << 5, + DEVICE_ALIAS = 1u << 5, // The location calibration matrix changed. - CHANGE_TOUCH_AFFINE_TRANSFORMATION = 1 << 6, + TOUCH_AFFINE_TRANSFORMATION = 1u << 6, // The presence of an external stylus has changed. - CHANGE_EXTERNAL_STYLUS_PRESENCE = 1 << 7, + EXTERNAL_STYLUS_PRESENCE = 1u << 7, // The pointer capture mode has changed. - CHANGE_POINTER_CAPTURE = 1 << 8, + POINTER_CAPTURE = 1u << 8, // The set of disabled input devices (disabledDevices) has changed. - CHANGE_ENABLED_STATE = 1 << 9, + ENABLED_STATE = 1u << 9, + + // The device type has been updated. + DEVICE_TYPE = 1u << 10, + + // The keyboard layout association has changed. + KEYBOARD_LAYOUT_ASSOCIATION = 1u << 11, + + // The stylus button reporting configurations has changed. + STYLUS_BUTTON_REPORTING = 1u << 12, + + // The touchpad settings changed. + TOUCHPAD_SETTINGS = 1u << 13, // All devices must be reopened. - CHANGE_MUST_REOPEN = 1 << 31, + MUST_REOPEN = 1u << 31, }; // Gets the amount of time to disable virtual keys after the screen is touched @@ -200,10 +111,18 @@ struct InputReaderConfiguration { // Used to determine which DisplayViewport should be tied to which InputDevice. std::unordered_map portAssociations; - // The associations between input device names and display unique ids. + // The associations between input device physical port locations and display unique ids. // Used to determine which DisplayViewport should be tied to which InputDevice. std::unordered_map uniqueIdAssociations; + // The associations between input device ports device types. + // This is used to determine which device type and source should be tied to which InputDevice. + std::unordered_map deviceTypeAssociations; + + // The map from the input device physical port location to the input device layout info. + // Can be used to determine the layout of the keyboard device. + std::unordered_map keyboardLayoutAssociations; + // The suggested display ID to show the cursor. int32_t defaultPointerDisplayId; @@ -284,9 +203,30 @@ struct InputReaderConfiguration { // The latest request to enable or disable Pointer Capture. PointerCaptureRequest pointerCaptureRequest; + // The touchpad pointer speed, as a number from -7 (slowest) to 7 (fastest). + int32_t touchpadPointerSpeed; + + // True to invert the touchpad scrolling direction, so that moving two fingers downwards on the + // touchpad scrolls the content upwards. + bool touchpadNaturalScrollingEnabled; + + // True to enable tap-to-click on touchpads. + bool touchpadTapToClickEnabled; + + // True to enable a zone on the right-hand side of touchpads where clicks will be turned into + // context (a.k.a. "right") clicks. + bool touchpadRightClickZoneEnabled; + // The set of currently disabled input devices. std::set disabledDevices; + // True if stylus button reporting through motion events should be enabled, in which case + // stylus button state changes are reported through motion events. + bool stylusButtonMotionEventsEnabled; + + // True if a pointer icon should be shown for direct stylus pointers. + bool stylusPointerIconEnabled; + InputReaderConfiguration() : virtualKeyQuietTime(0), pointerVelocityControlParameters(1.0f, 500.0f, 3000.0f, @@ -307,9 +247,13 @@ struct InputReaderConfiguration { pointerGestureMovementSpeedRatio(0.8f), pointerGestureZoomSpeedRatio(0.3f), showTouches(false), - pointerCaptureRequest() {} - - static std::string changesToString(uint32_t changes); + pointerCaptureRequest(), + touchpadPointerSpeed(0), + touchpadNaturalScrollingEnabled(true), + touchpadTapToClickEnabled(true), + touchpadRightClickZoneEnabled(false), + stylusButtonMotionEventsEnabled(true), + stylusPointerIconEnabled(false) {} std::optional getDisplayViewportByType(ViewportType type) const; std::optional getDisplayViewportByUniqueId(const std::string& uniqueDisplayId) @@ -318,7 +262,6 @@ struct InputReaderConfiguration { std::optional getDisplayViewportById(int32_t displayId) const; void setDisplayViewports(const std::vector& viewports); - void dump(std::string& dump) const; void dumpViewport(std::string& dump, const DisplayViewport& viewport) const; @@ -326,6 +269,116 @@ private: std::vector mDisplays; }; +using ConfigurationChanges = ftl::Flags; + +// --- InputReaderInterface --- + +/* The interface for the InputReader shared library. + * + * Manages one or more threads that process raw input events and sends cooked event data to an + * input listener. + * + * The implementation must guarantee thread safety for this interface. However, since the input + * listener is NOT thread safe, all calls to the listener must happen from the same thread. + */ +class InputReaderInterface { +public: + InputReaderInterface() {} + virtual ~InputReaderInterface() {} + /* Dumps the state of the input reader. + * + * This method may be called on any thread (usually by the input manager). */ + virtual void dump(std::string& dump) = 0; + + /* 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; + + /* Makes the reader stop processing any more events. */ + virtual status_t stop() = 0; + + /* Gets information about all input devices. + * + * This method may be called on any thread (usually by the input manager). + */ + virtual std::vector getInputDevices() const = 0; + + /* Query current input state. */ + virtual int32_t getScanCodeState(int32_t deviceId, uint32_t sourceMask, int32_t scanCode) = 0; + virtual int32_t getKeyCodeState(int32_t deviceId, uint32_t sourceMask, int32_t keyCode) = 0; + virtual int32_t getSwitchState(int32_t deviceId, uint32_t sourceMask, int32_t sw) = 0; + + virtual void addKeyRemapping(int32_t deviceId, int32_t fromKeyCode, + int32_t toKeyCode) const = 0; + + virtual int32_t getKeyCodeForKeyLocation(int32_t deviceId, int32_t locationKeyCode) const = 0; + + /* Toggle Caps Lock */ + virtual void toggleCapsLockState(int32_t deviceId) = 0; + + /* Determine whether physical keys exist for the given framework-domain key codes. */ + virtual bool hasKeys(int32_t deviceId, uint32_t sourceMask, + const std::vector& keyCodes, uint8_t* outFlags) = 0; + + /* Requests that a reconfiguration of all input devices. + * The changes flag is a bitfield that indicates what has changed and whether + * the input devices must all be reopened. */ + virtual void requestRefreshConfiguration(ConfigurationChanges changes) = 0; + + /* Controls the vibrator of a particular input device. */ + virtual void vibrate(int32_t deviceId, const VibrationSequence& sequence, ssize_t repeat, + int32_t token) = 0; + virtual void cancelVibrate(int32_t deviceId, int32_t token) = 0; + + virtual bool isVibrating(int32_t deviceId) = 0; + + virtual std::vector getVibratorIds(int32_t deviceId) = 0; + /* Get battery level of a particular input device. */ + virtual std::optional getBatteryCapacity(int32_t deviceId) = 0; + /* Get battery status of a particular input device. */ + virtual std::optional getBatteryStatus(int32_t deviceId) = 0; + /* Get the device path for the battery of an input device. */ + virtual std::optional getBatteryDevicePath(int32_t deviceId) = 0; + + virtual std::vector getLights(int32_t deviceId) = 0; + + 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; + + /* Enable sensor in input reader mapper. */ + virtual bool enableSensor(int32_t deviceId, InputDeviceSensorType sensorType, + std::chrono::microseconds samplingPeriod, + std::chrono::microseconds maxBatchReportLatency) = 0; + + /* Disable sensor in input reader mapper. */ + virtual void disableSensor(int32_t deviceId, InputDeviceSensorType sensorType) = 0; + + /* Flush sensor data in input reader mapper. */ + virtual void flushSensor(int32_t deviceId, InputDeviceSensorType sensorType) = 0; + + /* Set color for the light */ + virtual bool setLightColor(int32_t deviceId, int32_t lightId, int32_t color) = 0; + /* Set player ID for the light */ + virtual bool setLightPlayerId(int32_t deviceId, int32_t lightId, int32_t playerId) = 0; + /* Get light color */ + virtual std::optional getLightColor(int32_t deviceId, int32_t lightId) = 0; + /* Get light player ID */ + virtual std::optional getLightPlayerId(int32_t deviceId, int32_t lightId) = 0; + + /* Get the Bluetooth address of an input device, if known. */ + virtual std::optional getBluetoothAddress(int32_t deviceId) const = 0; + + /* Sysfs node change reported. Recreate device if required to incorporate the new sysfs nodes */ + virtual void sysfsNodeChanged(const std::string& sysfsNodePath) = 0; +}; + // --- TouchAffineTransformation --- struct TouchAffineTransformation { @@ -391,9 +444,9 @@ public: /* Gets the affine calibration associated with the specified device. */ virtual TouchAffineTransformation getTouchAffineTransformation( - const std::string& inputDeviceDescriptor, int32_t surfaceRotation) = 0; + const std::string& inputDeviceDescriptor, ui::Rotation surfaceRotation) = 0; + /* Notifies the input reader policy that a stylus gesture has started. */ + virtual void notifyStylusGestureStarted(int32_t deviceId, nsecs_t eventTime) = 0; }; } // namespace android - -#endif // _UI_INPUT_READER_COMMON_H diff --git a/services/inputflinger/include/InputThread.h b/services/inputflinger/include/InputThread.h index 407365a269a39fe8d94b83b004a1355c16032057..5e75027056eca7aa4abcc9f13edb0b9325c61dfa 100644 --- a/services/inputflinger/include/InputThread.h +++ b/services/inputflinger/include/InputThread.h @@ -14,8 +14,7 @@ * limitations under the License. */ -#ifndef _UI_INPUT_THREAD_H -#define _UI_INPUT_THREAD_H +#pragma once #include @@ -42,5 +41,3 @@ private: }; } // namespace android - -#endif // _UI_INPUT_THREAD_H \ No newline at end of file diff --git a/services/inputflinger/include/NotifyArgs.h b/services/inputflinger/include/NotifyArgs.h new file mode 100644 index 0000000000000000000000000000000000000000..7d29dd9cb2d051f9ce699beaefa0acef552e85e8 --- /dev/null +++ b/services/inputflinger/include/NotifyArgs.h @@ -0,0 +1,241 @@ +/* + * 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. + */ + +#pragma once + +#include + +#include +#include +#include + +namespace android { + +/* Describes a change in any of the connected input devices. */ +struct NotifyInputDevicesChangedArgs { + int32_t id; + std::vector inputDeviceInfos; + + inline NotifyInputDevicesChangedArgs() {} + + NotifyInputDevicesChangedArgs(int32_t id, std::vector infos); + + bool operator==(const NotifyInputDevicesChangedArgs& rhs) const = default; + + NotifyInputDevicesChangedArgs(const NotifyInputDevicesChangedArgs& other) = default; + NotifyInputDevicesChangedArgs& operator=(const NotifyInputDevicesChangedArgs&) = default; +}; + +/* Describes a configuration change event. */ +struct NotifyConfigurationChangedArgs { + int32_t id; + nsecs_t eventTime; + + inline NotifyConfigurationChangedArgs() {} + + NotifyConfigurationChangedArgs(int32_t id, nsecs_t eventTime); + + bool operator==(const NotifyConfigurationChangedArgs& rhs) const = default; + + NotifyConfigurationChangedArgs(const NotifyConfigurationChangedArgs& other) = default; + NotifyConfigurationChangedArgs& operator=(const NotifyConfigurationChangedArgs&) = default; +}; + +/* Describes a key event. */ +struct NotifyKeyArgs { + int32_t id; + 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; + nsecs_t downTime; + nsecs_t readTime; + + 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); + + bool operator==(const NotifyKeyArgs& rhs) const = default; + + NotifyKeyArgs(const NotifyKeyArgs& other) = default; + NotifyKeyArgs& operator=(const NotifyKeyArgs&) = default; +}; + +/* Describes a motion event. */ +struct NotifyMotionArgs { + int32_t id; + 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; + /** + * Classification of the current touch gesture + */ + MotionClassification classification; + int32_t edgeFlags; + + uint32_t pointerCount; + PointerProperties pointerProperties[MAX_POINTERS]; + PointerCoords pointerCoords[MAX_POINTERS]; + float xPrecision; + float yPrecision; + /** + * Mouse cursor position when this event is reported relative to the origin of the specified + * display. Only valid if this is a mouse event (originates from a mouse or from a trackpad in + * gestures enabled mode. + */ + float xCursorPosition; + float yCursorPosition; + nsecs_t downTime; + nsecs_t readTime; + std::vector videoFrames; + + 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, + const std::vector& videoFrames); + + NotifyMotionArgs(const NotifyMotionArgs& other); + NotifyMotionArgs& operator=(const android::NotifyMotionArgs&) = default; + + bool operator==(const NotifyMotionArgs& rhs) const; + + std::string dump() const; +}; + +/* Describes a sensor event. */ +struct NotifySensorArgs { + int32_t id; + nsecs_t eventTime; + + int32_t deviceId; + uint32_t source; + InputDeviceSensorType sensorType; + InputDeviceSensorAccuracy accuracy; + bool accuracyChanged; + nsecs_t hwTimestamp; + std::vector values; + + inline NotifySensorArgs() {} + + NotifySensorArgs(int32_t id, nsecs_t eventTime, int32_t deviceId, uint32_t source, + InputDeviceSensorType sensorType, InputDeviceSensorAccuracy accuracy, + bool accuracyChanged, nsecs_t hwTimestamp, std::vector values); + + NotifySensorArgs(const NotifySensorArgs& other) = default; + NotifySensorArgs& operator=(const NotifySensorArgs&) = default; +}; + +/* Describes a switch event. */ +struct NotifySwitchArgs { + int32_t id; + nsecs_t eventTime; + + uint32_t policyFlags; + uint32_t switchValues; + uint32_t switchMask; + + inline NotifySwitchArgs() {} + + NotifySwitchArgs(int32_t id, nsecs_t eventTime, uint32_t policyFlags, uint32_t switchValues, + uint32_t switchMask); + + NotifySwitchArgs(const NotifySwitchArgs& other) = default; + NotifySwitchArgs& operator=(const NotifySwitchArgs&) = default; + + bool operator==(const NotifySwitchArgs& rhs) const = default; +}; + +/* Describes a device reset event, such as when a device is added, + * reconfigured, or removed. */ +struct NotifyDeviceResetArgs { + int32_t id; + nsecs_t eventTime; + + int32_t deviceId; + + inline NotifyDeviceResetArgs() {} + + NotifyDeviceResetArgs(int32_t id, nsecs_t eventTime, int32_t deviceId); + + NotifyDeviceResetArgs(const NotifyDeviceResetArgs& other) = default; + NotifyDeviceResetArgs& operator=(const NotifyDeviceResetArgs&) = default; + + bool operator==(const NotifyDeviceResetArgs& rhs) const = default; +}; + +/* Describes a change in the state of Pointer Capture. */ +struct NotifyPointerCaptureChangedArgs { + int32_t id; + nsecs_t eventTime; + + PointerCaptureRequest request; + + inline NotifyPointerCaptureChangedArgs() {} + + NotifyPointerCaptureChangedArgs(int32_t id, nsecs_t eventTime, const PointerCaptureRequest&); + + NotifyPointerCaptureChangedArgs(const NotifyPointerCaptureChangedArgs& other) = default; + NotifyPointerCaptureChangedArgs& operator=(const NotifyPointerCaptureChangedArgs&) = default; +}; + +/* Describes a vibrator state event. */ +struct NotifyVibratorStateArgs { + int32_t id; + nsecs_t eventTime; + + int32_t deviceId; + bool isOn; + + inline NotifyVibratorStateArgs() {} + + NotifyVibratorStateArgs(int32_t id, nsecs_t eventTIme, int32_t deviceId, bool isOn); + + NotifyVibratorStateArgs(const NotifyVibratorStateArgs& other) = default; + NotifyVibratorStateArgs& operator=(const NotifyVibratorStateArgs&) = default; +}; + +using NotifyArgs = + std::variant; + +const char* toString(const NotifyArgs& args); + +} // namespace android diff --git a/services/inputflinger/include/PointerControllerInterface.h b/services/inputflinger/include/PointerControllerInterface.h index db4228d86293ce1dc9a3ae3762b31f45c197090f..95f819a8dae6f698c0b52b00665f3dab2037cbc3 100644 --- a/services/inputflinger/include/PointerControllerInterface.h +++ b/services/inputflinger/include/PointerControllerInterface.h @@ -14,8 +14,7 @@ * limitations under the License. */ -#ifndef _INPUTFLINGER_POINTER_CONTROLLER_INTERFACE_H -#define _INPUTFLINGER_POINTER_CONTROLLER_INTERFACE_H +#pragma once #include #include @@ -23,6 +22,20 @@ namespace android { +struct FloatPoint { + float x; + float y; + + inline FloatPoint(float x, float y) : x(x), y(y) {} + + inline explicit FloatPoint(vec2 p) : x(p.x), y(p.y) {} + + template + operator std::tuple() { + return {x, y}; + } +}; + /** * Interface for tracking a mouse / touch pad pointer and touch pad spots. * @@ -41,23 +54,16 @@ protected: public: /* Gets the bounds of the region that the pointer can traverse. * Returns true if the bounds are available. */ - virtual bool getBounds(float* outMinX, float* outMinY, - float* outMaxX, float* outMaxY) const = 0; + virtual std::optional getBounds() const = 0; /* Move the pointer. */ virtual void move(float deltaX, float deltaY) = 0; - /* Sets a mask that indicates which buttons are pressed. */ - virtual void setButtonState(int32_t buttonState) = 0; - - /* Gets a mask that indicates which buttons are pressed. */ - virtual int32_t getButtonState() const = 0; - /* Sets the absolute location of the pointer. */ virtual void setPosition(float x, float y) = 0; /* Gets the absolute location of the pointer. */ - virtual void getPosition(float* outX, float* outY) const = 0; + virtual FloatPoint getPosition() const = 0; enum class Transition { // Fade/unfade immediately. @@ -80,6 +86,10 @@ public: POINTER, // Show spots and a spot anchor in place of the mouse pointer. SPOT, + // Show the stylus hover pointer. + STYLUS_HOVER, + + ftl_last = STYLUS_HOVER, }; /* Sets the mode of the pointer controller. */ @@ -108,5 +118,3 @@ public: }; } // namespace android - -#endif // _INPUTFLINGER_POINTER_CONTROLLER_INTERFACE_H diff --git a/services/inputflinger/include/UnwantedInteractionBlockerInterface.h b/services/inputflinger/include/UnwantedInteractionBlockerInterface.h index 1a6f8472a5aac4cdc2b1096c289a5174acc30335..64c6114ceb6bfe1094bf83335319b3acab62c07a 100644 --- a/services/inputflinger/include/UnwantedInteractionBlockerInterface.h +++ b/services/inputflinger/include/UnwantedInteractionBlockerInterface.h @@ -27,23 +27,13 @@ namespace android { */ class UnwantedInteractionBlockerInterface : public InputListenerInterface { public: - /* Notifies the input reader policy that some input devices have changed - * and provides information about all current input devices. - * Important! This call should happen on the same thread as the calls to the - * InputListenerInterface methods. - * That is, same thread should call 'notifyMotion' and 'notifyInputDevicesChanged' and - * 'notifyDeviceReset'. If this architecture changes, we will need to make the implementation - * of this interface thread-safe. - */ - virtual void notifyInputDevicesChanged(const std::vector& inputDevices) = 0; - /** * Dump the state of the interaction blocker. * This method may be called on any thread (usually by the input manager on a binder thread). */ virtual void dump(std::string& dump) = 0; - /* Called by the heatbeat to ensures that the blocker has not deadlocked. */ + /* Called by the heartbeat to ensures that the blocker has not deadlocked. */ virtual void monitor() = 0; UnwantedInteractionBlockerInterface() {} diff --git a/services/inputflinger/include/VibrationElement.h b/services/inputflinger/include/VibrationElement.h index 736041e7fd35cdd3ddbefcb3c8013b4d4fac93d7..04c2e394c82f78e57b02c318db420fb57561cb4f 100644 --- a/services/inputflinger/include/VibrationElement.h +++ b/services/inputflinger/include/VibrationElement.h @@ -14,8 +14,7 @@ * limitations under the License. */ -#ifndef _VIBRATION_ELEMENT_H -#define _VIBRATION_ELEMENT_H +#pragma once #include #include @@ -71,5 +70,3 @@ struct VibrationSequence { }; } // namespace android - -#endif // _VIBRATION_ELEMENT_H diff --git a/services/inputflinger/reader/Android.bp b/services/inputflinger/reader/Android.bp index 01146a3c8b09fbf24aa7cf75f8e8b79b390b6215..b0edb5746cdf73d53e8f1b24adb2509fba68f485 100644 --- a/services/inputflinger/reader/Android.bp +++ b/services/inputflinger/reader/Android.bp @@ -23,11 +23,13 @@ package { cc_library_headers { name: "libinputreader_headers", + host_supported: true, export_include_dirs: [ "controller", "include", "mapper", "mapper/accumulator", + "mapper/gestures", ], } @@ -36,11 +38,11 @@ filegroup { srcs: [ "EventHub.cpp", "InputDevice.cpp", + "InputReader.cpp", + "Macros.cpp", + "TouchVideoDevice.cpp", "controller/PeripheralController.cpp", - "mapper/accumulator/CursorButtonAccumulator.cpp", - "mapper/accumulator/CursorScrollAccumulator.cpp", - "mapper/accumulator/SingleTouchMotionAccumulator.cpp", - "mapper/accumulator/TouchButtonAccumulator.cpp", + "mapper/CapturedTouchpadEventConverter.cpp", "mapper/CursorInputMapper.cpp", "mapper/ExternalStylusInputMapper.cpp", "mapper/InputMapper.cpp", @@ -51,10 +53,20 @@ filegroup { "mapper/SensorInputMapper.cpp", "mapper/SingleTouchInputMapper.cpp", "mapper/SwitchInputMapper.cpp", + "mapper/TouchCursorInputMapperCommon.cpp", "mapper/TouchInputMapper.cpp", + "mapper/TouchpadInputMapper.cpp", "mapper/VibratorInputMapper.cpp", - "InputReader.cpp", - "TouchVideoDevice.cpp", + "mapper/accumulator/CursorButtonAccumulator.cpp", + "mapper/accumulator/CursorScrollAccumulator.cpp", + "mapper/accumulator/HidUsageAccumulator.cpp", + "mapper/accumulator/MultiTouchMotionAccumulator.cpp", + "mapper/accumulator/SingleTouchMotionAccumulator.cpp", + "mapper/accumulator/TouchButtonAccumulator.cpp", + "mapper/gestures/GestureConverter.cpp", + "mapper/gestures/GesturesLogging.cpp", + "mapper/gestures/HardwareStateConverter.cpp", + "mapper/gestures/PropertyProvider.cpp", ], } @@ -66,24 +78,59 @@ cc_defaults { "libcap", "libcrypto", "libcutils", - "libinput", + "libjsoncpp", "liblog", + "libPlatformProperties", "libstatslog", - "libui", "libutils", - "libPlatformProperties", ], static_libs: [ "libc++fs", + "libchrome-gestures", + "libui-types", ], header_libs: [ "libbatteryservice_headers", + "libchrome-gestures_headers", "libinputreader_headers", ], + target: { + android: { + shared_libs: [ + "libinput", + ], + }, + host: { + static_libs: [ + "libinput", + "libbinder", + ], + }, + }, +} + +cc_library_static { + name: "libinputreader_static", + defaults: [ + "inputflinger_defaults", + "libinputreader_defaults", + ], + shared_libs: [ + "libinputflinger_base", + ], + export_header_lib_headers: [ + "libbatteryservice_headers", + "libchrome-gestures_headers", + "libinputreader_headers", + ], + whole_static_libs: [ + "libchrome-gestures", + ], } cc_library_shared { name: "libinputreader", + host_supported: true, defaults: [ "inputflinger_defaults", "libinputreader_defaults", @@ -95,11 +142,21 @@ cc_library_shared { // This should consist only of dependencies from inputflinger. Other dependencies should be // in cc_defaults so that they are included in the tests. "libinputflinger_base", + "libjsoncpp", ], export_header_lib_headers: [ "libinputreader_headers", ], + target: { + host: { + include_dirs: [ + "bionic/libc/kernel/android/uapi/", + "bionic/libc/kernel/uapi", + ], + }, + }, static_libs: [ "libc++fs", + "libchrome-gestures", ], } diff --git a/services/inputflinger/reader/EventHub.cpp b/services/inputflinger/reader/EventHub.cpp index 39beed33f6abbf71824654253784b8640231228d..035416415523f6c2d7125303c06706ff9f713aa9 100644 --- a/services/inputflinger/reader/EventHub.cpp +++ b/services/inputflinger/reader/EventHub.cpp @@ -19,6 +19,7 @@ #include #include #include +#include #include #include #include @@ -28,7 +29,6 @@ #include #include #include -#include #include #include #include @@ -43,6 +43,7 @@ #include #include #include +#include #include #include #include @@ -51,7 +52,9 @@ #include #include +#include #include +#include #include "EventHub.h" @@ -74,6 +77,8 @@ static constexpr size_t OBFUSCATED_LENGTH = 8; static constexpr int32_t FF_STRONG_MAGNITUDE_CHANNEL_IDX = 0; static constexpr int32_t FF_WEAK_MAGNITUDE_CHANNEL_IDX = 1; +static constexpr size_t EVENT_BUFFER_SIZE = 256; + // Mapping for input battery class node IDs lookup. // https://www.kernel.org/doc/Documentation/power/power_supply_class.txt static const std::unordered_map BATTERY_CLASSES = @@ -115,7 +120,8 @@ static const std::unordered_map LIGHT_CLASSES = {"brightness", InputLightClass::BRIGHTNESS}, {"multi_index", InputLightClass::MULTI_INDEX}, {"multi_intensity", InputLightClass::MULTI_INTENSITY}, - {"max_brightness", InputLightClass::MAX_BRIGHTNESS}}; + {"max_brightness", InputLightClass::MAX_BRIGHTNESS}, + {"kbd_backlight", InputLightClass::KEYBOARD_BACKLIGHT}}; // Mapping for input multicolor led class node names. // https://www.kernel.org/doc/html/latest/leds/leds-class-multicolor.html @@ -129,9 +135,45 @@ const std::unordered_map LIGHT_COLORS = {{"red", LightC {"green", LightColor::GREEN}, {"blue", LightColor::BLUE}}; -static inline const char* toString(bool value) { - return value ? "true" : "false"; -} +// Mapping for country code to Layout info. +// See bCountryCode in 6.2.1 of https://usb.org/sites/default/files/hid1_11.pdf. +const std::unordered_map LAYOUT_INFOS = + {{0, RawLayoutInfo{.languageTag = "", .layoutType = ""}}, // NOT_SUPPORTED + {1, RawLayoutInfo{.languageTag = "ar-Arab", .layoutType = ""}}, // ARABIC + {2, RawLayoutInfo{.languageTag = "fr-BE", .layoutType = ""}}, // BELGIAN + {3, RawLayoutInfo{.languageTag = "fr-CA", .layoutType = ""}}, // CANADIAN_BILINGUAL + {4, RawLayoutInfo{.languageTag = "fr-CA", .layoutType = ""}}, // CANADIAN_FRENCH + {5, RawLayoutInfo{.languageTag = "cs", .layoutType = ""}}, // CZECH_REPUBLIC + {6, RawLayoutInfo{.languageTag = "da", .layoutType = ""}}, // DANISH + {7, RawLayoutInfo{.languageTag = "fi", .layoutType = ""}}, // FINNISH + {8, RawLayoutInfo{.languageTag = "fr-FR", .layoutType = ""}}, // FRENCH + {9, RawLayoutInfo{.languageTag = "de", .layoutType = ""}}, // GERMAN + {10, RawLayoutInfo{.languageTag = "el", .layoutType = ""}}, // GREEK + {11, RawLayoutInfo{.languageTag = "iw", .layoutType = ""}}, // HEBREW + {12, RawLayoutInfo{.languageTag = "hu", .layoutType = ""}}, // HUNGARY + {13, RawLayoutInfo{.languageTag = "en", .layoutType = "extended"}}, // INTERNATIONAL (ISO) + {14, RawLayoutInfo{.languageTag = "it", .layoutType = ""}}, // ITALIAN + {15, RawLayoutInfo{.languageTag = "ja", .layoutType = ""}}, // JAPAN + {16, RawLayoutInfo{.languageTag = "ko", .layoutType = ""}}, // KOREAN + {17, RawLayoutInfo{.languageTag = "es-419", .layoutType = ""}}, // LATIN_AMERICA + {18, RawLayoutInfo{.languageTag = "nl", .layoutType = ""}}, // DUTCH + {19, RawLayoutInfo{.languageTag = "nb", .layoutType = ""}}, // NORWEGIAN + {20, RawLayoutInfo{.languageTag = "fa", .layoutType = ""}}, // PERSIAN + {21, RawLayoutInfo{.languageTag = "pl", .layoutType = ""}}, // POLAND + {22, RawLayoutInfo{.languageTag = "pt", .layoutType = ""}}, // PORTUGUESE + {23, RawLayoutInfo{.languageTag = "ru", .layoutType = ""}}, // RUSSIA + {24, RawLayoutInfo{.languageTag = "sk", .layoutType = ""}}, // SLOVAKIA + {25, RawLayoutInfo{.languageTag = "es-ES", .layoutType = ""}}, // SPANISH + {26, RawLayoutInfo{.languageTag = "sv", .layoutType = ""}}, // SWEDISH + {27, RawLayoutInfo{.languageTag = "fr-CH", .layoutType = ""}}, // SWISS_FRENCH + {28, RawLayoutInfo{.languageTag = "de-CH", .layoutType = ""}}, // SWISS_GERMAN + {29, RawLayoutInfo{.languageTag = "de-CH", .layoutType = ""}}, // SWITZERLAND + {30, RawLayoutInfo{.languageTag = "zh-TW", .layoutType = ""}}, // TAIWAN + {31, RawLayoutInfo{.languageTag = "tr", .layoutType = "turkish_q"}}, // TURKISH_Q + {32, RawLayoutInfo{.languageTag = "en-GB", .layoutType = ""}}, // UK + {33, RawLayoutInfo{.languageTag = "en-US", .layoutType = ""}}, // US + {34, RawLayoutInfo{.languageTag = "", .layoutType = ""}}, // YUGOSLAVIA + {35, RawLayoutInfo{.languageTag = "tr", .layoutType = "turkish_f"}}}; // TURKISH_F static std::string sha1(const std::string& in) { SHA_CTX ctx; @@ -147,6 +189,14 @@ static std::string sha1(const std::string& in) { return out; } +/* The set of all Android key codes that correspond to buttons (bit-switches) on a stylus. */ +static constexpr std::array STYLUS_BUTTON_KEYCODES = { + AKEYCODE_STYLUS_BUTTON_PRIMARY, + AKEYCODE_STYLUS_BUTTON_SECONDARY, + AKEYCODE_STYLUS_BUTTON_TERTIARY, + AKEYCODE_STYLUS_BUTTON_TAIL, +}; + /** * Return true if name matches "v4l-touch*" */ @@ -171,7 +221,7 @@ static bool isV4lTouchNode(std::string name) { * directly from /dev. */ static bool isV4lScanningEnabled() { - return property_get_bool("ro.input.video_enabled", true /* default_value */); + return property_get_bool("ro.input.video_enabled", /*default_value=*/true); } static nsecs_t processEventTimestamp(const struct input_event& event) { @@ -187,14 +237,13 @@ static nsecs_t processEventTimestamp(const struct input_event& event) { // calls clock_gettime(CLOCK_MONOTONIC) which is implemented as a // system call that also queries ktime_get_ts(). - const nsecs_t inputEventTime = seconds_to_nanoseconds(event.time.tv_sec) + - microseconds_to_nanoseconds(event.time.tv_usec); + const nsecs_t inputEventTime = seconds_to_nanoseconds(event.input_event_sec) + + microseconds_to_nanoseconds(event.input_event_usec); return inputEventTime; } /** - * Returns the sysfs root path of the input device - * + * Returns the sysfs root path of the input device. */ static std::optional getSysfsRootPath(const char* devicePath) { std::error_code errorCode; @@ -301,6 +350,118 @@ static std::optional> getColorIndexArray( return colors; } +/** + * Read country code information exposed through the sysfs path and convert it to Layout info. + */ +static std::optional readLayoutConfiguration( + const std::filesystem::path& sysfsRootPath) { + // Check the sysfs root path + int32_t hidCountryCode = -1; + std::string str; + if (base::ReadFileToString(sysfsRootPath / "country", &str)) { + hidCountryCode = std::stoi(str, nullptr, 16); + // Update this condition if new supported country codes are added to HID spec. + if (hidCountryCode > 35 || hidCountryCode < 0) { + ALOGE("HID country code should be in range [0, 35], but for sysfs path %s it was %d", + sysfsRootPath.c_str(), hidCountryCode); + } + } + const auto it = LAYOUT_INFOS.find(hidCountryCode); + if (it != LAYOUT_INFOS.end()) { + return it->second; + } + + return std::nullopt; +} + +/** + * Read information about batteries exposed through the sysfs path. + */ +static std::unordered_map readBatteryConfiguration( + const std::filesystem::path& sysfsRootPath) { + std::unordered_map batteryInfos; + int32_t nextBatteryId = 0; + // Check if device has any battery. + const auto& paths = findSysfsNodes(sysfsRootPath, SysfsClass::POWER_SUPPLY); + for (const auto& nodePath : paths) { + RawBatteryInfo info; + info.id = ++nextBatteryId; + info.path = nodePath; + info.name = nodePath.filename(); + + // Scan the path for all the files + // Refer to https://www.kernel.org/doc/Documentation/leds/leds-class.txt + const auto& files = allFilesInPath(nodePath); + for (const auto& file : files) { + const auto it = BATTERY_CLASSES.find(file.filename().string()); + if (it != BATTERY_CLASSES.end()) { + info.flags |= it->second; + } + } + batteryInfos.insert_or_assign(info.id, info); + ALOGD("configureBatteryLocked rawBatteryId %d name %s", info.id, info.name.c_str()); + } + return batteryInfos; +} + +/** + * Read information about lights exposed through the sysfs path. + */ +static std::unordered_map readLightsConfiguration( + const std::filesystem::path& sysfsRootPath) { + std::unordered_map lightInfos; + int32_t nextLightId = 0; + // Check if device has any lights. + const auto& paths = findSysfsNodes(sysfsRootPath, SysfsClass::LEDS); + for (const auto& nodePath : paths) { + RawLightInfo info; + info.id = ++nextLightId; + info.path = nodePath; + info.name = nodePath.filename(); + info.maxBrightness = std::nullopt; + + // Light name should follow the naming pattern :: + // Refer kernel docs /leds/leds-class.html for valid supported LED names. + std::regex indexPattern("([a-zA-Z0-9_.:]*:)?([a-zA-Z0-9_.]*):([a-zA-Z0-9_.]*)"); + std::smatch results; + + if (std::regex_match(info.name, results, indexPattern)) { + // regex_match will return full match at index 0 and at index 1. For RawLightInfo + // we only care about sections and which will be at index 2 and 3. + for (int i = 2; i <= 3; i++) { + const auto it = LIGHT_CLASSES.find(results.str(i)); + if (it != LIGHT_CLASSES.end()) { + info.flags |= it->second; + } + } + + // Set name of the raw light to which represents playerIDs for LEDs that + // turn on/off based on the current player ID (Refer to PeripheralController.cpp for + // player ID logic) + info.name = results.str(3); + } + // Scan the path for all the files + // Refer to https://www.kernel.org/doc/Documentation/leds/leds-class.txt + const auto& files = allFilesInPath(nodePath); + for (const auto& file : files) { + const auto it = LIGHT_CLASSES.find(file.filename().string()); + if (it != LIGHT_CLASSES.end()) { + info.flags |= it->second; + // If the node has maximum brightness, read it + if (it->second == InputLightClass::MAX_BRIGHTNESS) { + std::string str; + if (base::ReadFileToString(file, &str)) { + info.maxBrightness = std::stoi(str); + } + } + } + } + lightInfos.insert_or_assign(info.id, info); + ALOGD("configureLightsLocked rawLightId %d name %s", info.id, info.name.c_str()); + } + return lightInfos; +} + // --- Global Functions --- ftl::Flags getAbsAxisUsage(int32_t axis, @@ -355,20 +516,32 @@ ftl::Flags getAbsAxisUsage(int32_t axis, return deviceClasses & InputDeviceClass::JOYSTICK; } +// --- RawAbsoluteAxisInfo --- + +std::ostream& operator<<(std::ostream& out, const RawAbsoluteAxisInfo& info) { + if (info.valid) { + out << "min=" << info.minValue << ", max=" << info.maxValue << ", flat=" << info.flat + << ", fuzz=" << info.fuzz << ", resolution=" << info.resolution; + } else { + out << "unknown range"; + } + return out; +} + // --- EventHub::Device --- -EventHub::Device::Device(int fd, int32_t id, const std::string& path, - const InputDeviceIdentifier& identifier) +EventHub::Device::Device(int fd, int32_t id, std::string path, InputDeviceIdentifier identifier, + std::shared_ptr assocDev) : fd(fd), id(id), - path(path), - identifier(identifier), + path(std::move(path)), + identifier(std::move(identifier)), classes(0), configuration(nullptr), virtualKeyMap(nullptr), ffEffectPlaying(false), ffEffectId(-1), - associatedDevice(nullptr), + associatedDevice(std::move(assocDev)), controllerNumber(0), enabled(true), isVirtual(fd < 0) {} @@ -506,9 +679,9 @@ status_t EventHub::Device::loadKeyMapLocked() { bool EventHub::Device::isExternalDeviceLocked() { if (configuration) { - bool value; - if (configuration->tryGetProperty(String8("device.internal"), value)) { - return !value; + std::optional isInternal = configuration->getBool("device.internal"); + if (isInternal.has_value()) { + return !isInternal.value(); } } return identifier.bus == BUS_USB || identifier.bus == BUS_BLUETOOTH; @@ -516,9 +689,9 @@ bool EventHub::Device::isExternalDeviceLocked() { bool EventHub::Device::deviceHasMicLocked() { if (configuration) { - bool value; - if (configuration->tryGetProperty(String8("audio.mic"), value)) { - return value; + std::optional hasMic = configuration->getBool("audio.mic"); + if (hasMic.has_value()) { + return hasMic.value(); } } return false; @@ -528,8 +701,8 @@ void EventHub::Device::setLedStateLocked(int32_t led, bool on) { int32_t sc; if (hasValidFd() && mapLed(led, &sc) != NAME_NOT_FOUND) { struct input_event ev; - ev.time.tv_sec = 0; - ev.time.tv_usec = 0; + ev.input_event_sec = 0; + ev.input_event_usec = 0; ev.type = EV_LED; ev.code = sc; ev.value = on ? 1 : 0; @@ -562,75 +735,6 @@ status_t EventHub::Device::mapLed(int32_t led, int32_t* outScanCode) const { return NAME_NOT_FOUND; } -// Check the sysfs path for any input device batteries, returns true if battery found. -bool EventHub::AssociatedDevice::configureBatteryLocked() { - nextBatteryId = 0; - // Check if device has any battery. - const auto& paths = findSysfsNodes(sysfsRootPath, SysfsClass::POWER_SUPPLY); - for (const auto& nodePath : paths) { - RawBatteryInfo info; - info.id = ++nextBatteryId; - info.path = nodePath; - info.name = nodePath.filename(); - - // Scan the path for all the files - // Refer to https://www.kernel.org/doc/Documentation/leds/leds-class.txt - const auto& files = allFilesInPath(nodePath); - for (const auto& file : files) { - const auto it = BATTERY_CLASSES.find(file.filename().string()); - if (it != BATTERY_CLASSES.end()) { - info.flags |= it->second; - } - } - batteryInfos.insert_or_assign(info.id, info); - ALOGD("configureBatteryLocked rawBatteryId %d name %s", info.id, info.name.c_str()); - } - return !batteryInfos.empty(); -} - -// Check the sysfs path for any input device lights, returns true if lights found. -bool EventHub::AssociatedDevice::configureLightsLocked() { - nextLightId = 0; - // Check if device has any lights. - const auto& paths = findSysfsNodes(sysfsRootPath, SysfsClass::LEDS); - for (const auto& nodePath : paths) { - RawLightInfo info; - info.id = ++nextLightId; - info.path = nodePath; - info.name = nodePath.filename(); - info.maxBrightness = std::nullopt; - size_t nameStart = info.name.rfind(":"); - if (nameStart != std::string::npos) { - // Trim the name to color name - info.name = info.name.substr(nameStart + 1); - // Set InputLightClass flag for colors - const auto it = LIGHT_CLASSES.find(info.name); - if (it != LIGHT_CLASSES.end()) { - info.flags |= it->second; - } - } - // Scan the path for all the files - // Refer to https://www.kernel.org/doc/Documentation/leds/leds-class.txt - const auto& files = allFilesInPath(nodePath); - for (const auto& file : files) { - const auto it = LIGHT_CLASSES.find(file.filename().string()); - if (it != LIGHT_CLASSES.end()) { - info.flags |= it->second; - // If the node has maximum brightness, read it - if (it->second == InputLightClass::MAX_BRIGHTNESS) { - std::string str; - if (base::ReadFileToString(file, &str)) { - info.maxBrightness = std::stoi(str); - } - } - } - } - lightInfos.insert_or_assign(info.id, info); - ALOGD("configureLightsLocked rawLightId %d name %s", info.id, info.name.c_str()); - } - return !lightInfos.empty(); -} - /** * Get the capabilities for the current process. * Crashes the system if unable to create / check / destroy the capabilities object. @@ -692,6 +796,7 @@ EventHub::EventHub(void) LOG_ALWAYS_FATAL_IF(mEpollFd < 0, "Could not create epoll instance: %s", strerror(errno)); mINotifyFd = inotify_init1(IN_CLOEXEC); + LOG_ALWAYS_FATAL_IF(mINotifyFd < 0, "Could not create inotify instance: %s", strerror(errno)); std::error_code errorCode; bool isDeviceInotifyAdded = false; @@ -783,14 +888,13 @@ int32_t EventHub::getDeviceControllerNumber(int32_t deviceId) const { return device != nullptr ? device->controllerNumber : 0; } -void EventHub::getConfiguration(int32_t deviceId, PropertyMap* outConfiguration) const { +std::optional EventHub::getConfiguration(int32_t deviceId) const { std::scoped_lock _l(mLock); Device* device = getDeviceLocked(deviceId); - if (device != nullptr && device->configuration) { - *outConfiguration = *device->configuration; - } else { - outConfiguration->clear(); + if (device == nullptr || device->configuration == nullptr) { + return {}; } + return *device->configuration; } status_t EventHub::getAbsoluteAxisInfo(int32_t deviceId, int axis, @@ -907,18 +1011,22 @@ int32_t EventHub::getKeyCodeForKeyLocation(int32_t deviceId, int32_t locationKey } int32_t outKeyCode; status_t mapKeyRes = - device->getKeyCharacterMap()->mapKey(scanCodes[0], 0 /*usageCode*/, &outKeyCode); + device->getKeyCharacterMap()->mapKey(scanCodes[0], /*usageCode=*/0, &outKeyCode); switch (mapKeyRes) { case OK: - return outKeyCode; + break; case NAME_NOT_FOUND: // key character map doesn't re-map this scanCode, hence the keyCode remains the same - return locationKeyCode; + outKeyCode = locationKeyCode; + break; default: ALOGW("Failed to get key code for key location: Key character map returned error %s", statusToString(mapKeyRes).c_str()); - return AKEYCODE_UNKNOWN; + outKeyCode = AKEYCODE_UNKNOWN; + break; } + // Remap if there is a Key remapping added to the KCM and return the remapped key + return device->getKeyCharacterMap()->applyKeyRemapping(outKeyCode); } int32_t EventHub::getSwitchState(int32_t deviceId, int32_t sw) const { @@ -957,23 +1065,15 @@ status_t EventHub::getAbsoluteAxisValue(int32_t deviceId, int32_t axis, int32_t* return -1; } -bool EventHub::markSupportedKeyCodes(int32_t deviceId, size_t numCodes, const int32_t* keyCodes, +bool EventHub::markSupportedKeyCodes(int32_t deviceId, const std::vector& keyCodes, uint8_t* outFlags) const { std::scoped_lock _l(mLock); Device* device = getDeviceLocked(deviceId); if (device != nullptr && device->keyMap.haveKeyLayout()) { - for (size_t codeIndex = 0; codeIndex < numCodes; codeIndex++) { - std::vector scanCodes = - device->keyMap.keyLayoutMap->findScanCodesForKey(keyCodes[codeIndex]); - - // check the possible scan codes identified by the layout map against the - // map of codes actually emitted by the driver - for (size_t sc = 0; sc < scanCodes.size(); sc++) { - if (device->keyBitmask.test(scanCodes[sc])) { - outFlags[codeIndex] = 1; - break; - } + for (size_t codeIndex = 0; codeIndex < keyCodes.size(); codeIndex++) { + if (device->hasKeycodeLocked(keyCodes[codeIndex])) { + outFlags[codeIndex] = 1; } } return true; @@ -981,6 +1081,18 @@ bool EventHub::markSupportedKeyCodes(int32_t deviceId, size_t numCodes, const in return false; } +void EventHub::addKeyRemapping(int32_t deviceId, int32_t fromKeyCode, int32_t toKeyCode) const { + std::scoped_lock _l(mLock); + Device* device = getDeviceLocked(deviceId); + if (device == nullptr) { + return; + } + const std::shared_ptr kcm = device->getKeyCharacterMap(); + if (kcm) { + kcm->addKeyRemapping(fromKeyCode, toKeyCode); + } +} + status_t EventHub::mapKey(int32_t deviceId, int32_t scanCode, int32_t usageCode, int32_t metaState, int32_t* outKeycode, int32_t* outMetaState, uint32_t* outFlags) const { std::scoped_lock _l(mLock); @@ -1006,7 +1118,13 @@ status_t EventHub::mapKey(int32_t deviceId, int32_t scanCode, int32_t usageCode, if (status == NO_ERROR) { if (kcm) { - kcm->tryRemapKey(*outKeycode, metaState, outKeycode, outMetaState); + // Remap keys based on user-defined key remappings and key behavior defined in the + // corresponding kcm file + *outKeycode = kcm->applyKeyRemapping(*outKeycode); + + // Remap keys based on Key behavior defined in KCM file + std::tie(*outKeycode, *outMetaState) = + kcm->applyKeyBehavior(*outKeycode, metaState); } else { *outMetaState = metaState; } @@ -1038,7 +1156,7 @@ status_t EventHub::mapAxis(int32_t deviceId, int32_t scanCode, AxisInfo* outAxis } base::Result> EventHub::mapSensor(int32_t deviceId, - int32_t absCode) { + int32_t absCode) const { std::scoped_lock _l(mLock); Device* device = getDeviceLocked(deviceId); @@ -1060,18 +1178,19 @@ const std::unordered_map& EventHub::getBatteryInfoLocke return device->associatedDevice->batteryInfos; } -const std::vector EventHub::getRawBatteryIds(int32_t deviceId) { +std::vector EventHub::getRawBatteryIds(int32_t deviceId) const { std::scoped_lock _l(mLock); std::vector batteryIds; - for (const auto [id, info] : getBatteryInfoLocked(deviceId)) { + for (const auto& [id, info] : getBatteryInfoLocked(deviceId)) { batteryIds.push_back(id); } return batteryIds; } -std::optional EventHub::getRawBatteryInfo(int32_t deviceId, int32_t batteryId) { +std::optional EventHub::getRawBatteryInfo(int32_t deviceId, + int32_t batteryId) const { std::scoped_lock _l(mLock); const auto infos = getBatteryInfoLocked(deviceId); @@ -1085,7 +1204,7 @@ std::optional EventHub::getRawBatteryInfo(int32_t deviceId, int3 } // Gets the light info map from light ID to RawLightInfo of the miscellaneous device associated -// with the deivice ID. Returns an empty map if no miscellaneous device found. +// with the device ID. Returns an empty map if no miscellaneous device found. const std::unordered_map& EventHub::getLightInfoLocked( int32_t deviceId) const { static const std::unordered_map EMPTY_LIGHT_INFO = {}; @@ -1096,18 +1215,18 @@ const std::unordered_map& EventHub::getLightInfoLocked( return device->associatedDevice->lightInfos; } -const std::vector EventHub::getRawLightIds(int32_t deviceId) { +std::vector EventHub::getRawLightIds(int32_t deviceId) const { std::scoped_lock _l(mLock); std::vector lightIds; - for (const auto [id, info] : getLightInfoLocked(deviceId)) { + for (const auto& [id, info] : getLightInfoLocked(deviceId)) { lightIds.push_back(id); } return lightIds; } -std::optional EventHub::getRawLightInfo(int32_t deviceId, int32_t lightId) { +std::optional EventHub::getRawLightInfo(int32_t deviceId, int32_t lightId) const { std::scoped_lock _l(mLock); const auto infos = getLightInfoLocked(deviceId); @@ -1120,7 +1239,7 @@ std::optional EventHub::getRawLightInfo(int32_t deviceId, int32_t return std::nullopt; } -std::optional EventHub::getLightBrightness(int32_t deviceId, int32_t lightId) { +std::optional EventHub::getLightBrightness(int32_t deviceId, int32_t lightId) const { std::scoped_lock _l(mLock); const auto infos = getLightInfoLocked(deviceId); @@ -1137,7 +1256,7 @@ std::optional EventHub::getLightBrightness(int32_t deviceId, int32_t li } std::optional> EventHub::getLightIntensities( - int32_t deviceId, int32_t lightId) { + int32_t deviceId, int32_t lightId) const { std::scoped_lock _l(mLock); const auto infos = getLightInfoLocked(deviceId); @@ -1233,6 +1352,15 @@ void EventHub::setLightIntensities(int32_t deviceId, int32_t lightId, } } +std::optional EventHub::getRawLayoutInfo(int32_t deviceId) const { + std::scoped_lock _l(mLock); + Device* device = getDeviceLocked(deviceId); + if (device == nullptr || !device->associatedDevice) { + return std::nullopt; + } + return device->associatedDevice->layoutInfo; +} + void EventHub::setExcludedDevices(const std::vector& devices) { std::scoped_lock _l(mLock); @@ -1297,12 +1425,17 @@ const std::shared_ptr EventHub::getKeyCharacterMap(int32_t devi return nullptr; } +// If provided map is null, it will reset key character map to default KCM. bool EventHub::setKeyboardLayoutOverlay(int32_t deviceId, std::shared_ptr map) { std::scoped_lock _l(mLock); Device* device = getDeviceLocked(deviceId); - if (device == nullptr || map == nullptr || device->keyMap.keyCharacterMap == nullptr) { + if (device == nullptr || device->keyMap.keyCharacterMap == nullptr) { return false; } + if (map == nullptr) { + device->keyMap.keyCharacterMap->clearLayoutOverlay(); + return true; + } device->keyMap.keyCharacterMap->combine(*map); return true; } @@ -1362,6 +1495,53 @@ void EventHub::assignDescriptorLocked(InputDeviceIdentifier& identifier) { identifier.descriptor.c_str()); } +std::shared_ptr EventHub::obtainAssociatedDeviceLocked( + const std::filesystem::path& devicePath) const { + const std::optional sysfsRootPathOpt = + getSysfsRootPath(devicePath.c_str()); + if (!sysfsRootPathOpt) { + return nullptr; + } + + const auto& path = *sysfsRootPathOpt; + + std::shared_ptr associatedDevice = std::make_shared( + AssociatedDevice{.sysfsRootPath = path, + .batteryInfos = readBatteryConfiguration(path), + .lightInfos = readLightsConfiguration(path), + .layoutInfo = readLayoutConfiguration(path)}); + + bool associatedDeviceChanged = false; + for (const auto& [id, dev] : mDevices) { + if (dev->associatedDevice && dev->associatedDevice->sysfsRootPath == path) { + if (*associatedDevice != *dev->associatedDevice) { + associatedDeviceChanged = true; + dev->associatedDevice = associatedDevice; + } + associatedDevice = dev->associatedDevice; + } + } + ALOGI_IF(associatedDeviceChanged, + "The AssociatedDevice changed for path '%s'. Using new AssociatedDevice: %s", + path.c_str(), associatedDevice->dump().c_str()); + + return associatedDevice; +} + +bool EventHub::AssociatedDevice::isChanged() const { + std::unordered_map newBatteryInfos = + readBatteryConfiguration(sysfsRootPath); + std::unordered_map newLightInfos = + readLightsConfiguration(sysfsRootPath); + std::optional newLayoutInfo = readLayoutConfiguration(sysfsRootPath); + + if (newBatteryInfos == batteryInfos && newLightInfos == lightInfos && + newLayoutInfo == layoutInfo) { + return false; + } + return true; +} + void EventHub::vibrate(int32_t deviceId, const VibrationElement& element) { std::scoped_lock _l(mLock); Device* device = getDeviceLocked(deviceId); @@ -1383,8 +1563,8 @@ void EventHub::vibrate(int32_t deviceId, const VibrationElement& element) { device->ffEffectId = effect.id; struct input_event ev; - ev.time.tv_sec = 0; - ev.time.tv_usec = 0; + ev.input_event_sec = 0; + ev.input_event_usec = 0; ev.type = EV_FF; ev.code = device->ffEffectId; ev.value = 1; @@ -1405,8 +1585,8 @@ void EventHub::cancelVibrate(int32_t deviceId) { device->ffEffectPlaying = false; struct input_event ev; - ev.time.tv_sec = 0; - ev.time.tv_usec = 0; + ev.input_event_sec = 0; + ev.input_event_usec = 0; ev.type = EV_FF; ev.code = device->ffEffectId; ev.value = 0; @@ -1419,7 +1599,7 @@ void EventHub::cancelVibrate(int32_t deviceId) { } } -std::vector EventHub::getVibratorIds(int32_t deviceId) { +std::vector EventHub::getVibratorIds(int32_t deviceId) const { std::scoped_lock _l(mLock); std::vector vibrators; Device* device = getDeviceLocked(deviceId); @@ -1500,7 +1680,7 @@ std::optional EventHub::getBatteryCapacity(int32_t deviceId, int32_t ba // the lock to prevent event processing from being blocked by this call. std::scoped_lock _l(mLock); - const auto infos = getBatteryInfoLocked(deviceId); + const auto& infos = getBatteryInfoLocked(deviceId); auto it = infos.find(batteryId); if (it == infos.end()) { return std::nullopt; @@ -1541,7 +1721,7 @@ std::optional EventHub::getBatteryStatus(int32_t deviceId, int32_t batt // the lock to prevent event processing from being blocked by this call. std::scoped_lock _l(mLock); - const auto infos = getBatteryInfoLocked(deviceId); + const auto& infos = getBatteryInfoLocked(deviceId); auto it = infos.find(batteryId); if (it == infos.end()) { return std::nullopt; @@ -1566,15 +1746,12 @@ std::optional EventHub::getBatteryStatus(int32_t deviceId, int32_t batt return std::nullopt; } -size_t EventHub::getEvents(int timeoutMillis, RawEvent* buffer, size_t bufferSize) { - ALOG_ASSERT(bufferSize >= 1); - +std::vector EventHub::getEvents(int timeoutMillis) { std::scoped_lock _l(mLock); - struct input_event readBuffer[bufferSize]; + std::array readBuffer; - RawEvent* event = buffer; - size_t capacity = bufferSize; + std::vector events; bool awoken = false; for (;;) { nsecs_t now = systemTime(SYSTEM_TIME_MONOTONIC); @@ -1594,15 +1771,17 @@ size_t EventHub::getEvents(int timeoutMillis, RawEvent* buffer, size_t bufferSiz for (auto it = mClosingDevices.begin(); it != mClosingDevices.end();) { std::unique_ptr device = std::move(*it); ALOGV("Reporting device closed: id=%d, name=%s\n", device->id, device->path.c_str()); - event->when = now; - event->deviceId = (device->id == mBuiltInKeyboardId) + const int32_t deviceId = (device->id == mBuiltInKeyboardId) ? ReservedInputDeviceId::BUILT_IN_KEYBOARD_ID : device->id; - event->type = DEVICE_REMOVED; - event += 1; + events.push_back({ + .when = now, + .deviceId = deviceId, + .type = DEVICE_REMOVED, + }); it = mClosingDevices.erase(it); mNeedToSendFinishedDeviceScan = true; - if (--capacity == 0) { + if (events.size() == EVENT_BUFFER_SIZE) { break; } } @@ -1617,10 +1796,12 @@ size_t EventHub::getEvents(int timeoutMillis, RawEvent* buffer, size_t bufferSiz std::unique_ptr device = std::move(*mOpeningDevices.rbegin()); mOpeningDevices.pop_back(); ALOGV("Reporting device opened: id=%d, name=%s\n", device->id, device->path.c_str()); - event->when = now; - event->deviceId = device->id == mBuiltInKeyboardId ? 0 : device->id; - event->type = DEVICE_ADDED; - event += 1; + const int32_t deviceId = device->id == mBuiltInKeyboardId ? 0 : device->id; + events.push_back({ + .when = now, + .deviceId = deviceId, + .type = DEVICE_ADDED, + }); // Try to find a matching video device by comparing device names for (auto it = mUnattachedVideoDevices.begin(); it != mUnattachedVideoDevices.end(); @@ -1638,17 +1819,18 @@ size_t EventHub::getEvents(int timeoutMillis, RawEvent* buffer, size_t bufferSiz ALOGW("Device id %d exists, replaced.", device->id); } mNeedToSendFinishedDeviceScan = true; - if (--capacity == 0) { + if (events.size() == EVENT_BUFFER_SIZE) { break; } } if (mNeedToSendFinishedDeviceScan) { mNeedToSendFinishedDeviceScan = false; - event->when = now; - event->type = FINISHED_DEVICE_SCAN; - event += 1; - if (--capacity == 0) { + events.push_back({ + .when = now, + .type = FINISHED_DEVICE_SCAN, + }); + if (events.size() == EVENT_BUFFER_SIZE) { break; } } @@ -1712,12 +1894,13 @@ size_t EventHub::getEvents(int timeoutMillis, RawEvent* buffer, size_t bufferSiz // This must be an input event if (eventItem.events & EPOLLIN) { int32_t readSize = - read(device->fd, readBuffer, sizeof(struct input_event) * capacity); + read(device->fd, readBuffer.data(), + sizeof(decltype(readBuffer)::value_type) * readBuffer.size()); if (readSize == 0 || (readSize < 0 && errno == ENODEV)) { // Device was removed before INotify noticed. ALOGW("could not get event, removed? (fd: %d size: %" PRId32 - " bufferSize: %zu capacity: %zu errno: %d)\n", - device->fd, readSize, bufferSize, capacity, errno); + " capacity: %zu errno: %d)\n", + device->fd, readSize, readBuffer.size(), errno); deviceChanged = true; closeDeviceLocked(*device); } else if (readSize < 0) { @@ -1727,21 +1910,21 @@ size_t EventHub::getEvents(int timeoutMillis, RawEvent* buffer, size_t bufferSiz } else if ((readSize % sizeof(struct input_event)) != 0) { ALOGE("could not get event (wrong size: %d)", readSize); } else { - int32_t deviceId = device->id == mBuiltInKeyboardId ? 0 : device->id; + const int32_t deviceId = device->id == mBuiltInKeyboardId ? 0 : device->id; - size_t count = size_t(readSize) / sizeof(struct input_event); + const size_t count = size_t(readSize) / sizeof(struct input_event); for (size_t i = 0; i < count; i++) { struct input_event& iev = readBuffer[i]; - event->when = processEventTimestamp(iev); - event->readTime = systemTime(SYSTEM_TIME_MONOTONIC); - event->deviceId = deviceId; - event->type = iev.type; - event->code = iev.code; - event->value = iev.value; - event += 1; - capacity -= 1; + events.push_back({ + .when = processEventTimestamp(iev), + .readTime = systemTime(SYSTEM_TIME_MONOTONIC), + .deviceId = deviceId, + .type = iev.type, + .code = iev.code, + .value = iev.value, + }); } - if (capacity == 0) { + if (events.size() >= EVENT_BUFFER_SIZE) { // The result buffer is full. Reset the pending event index // so we will try to read the device again on the next iteration. mPendingEventIndex -= 1; @@ -1764,7 +1947,10 @@ size_t EventHub::getEvents(int timeoutMillis, RawEvent* buffer, size_t bufferSiz // before closing the devices. if (mPendingINotify && mPendingEventIndex >= mPendingEventCount) { mPendingINotify = false; - readNotifyLocked(); + const auto res = readNotifyLocked(); + if (!res.ok()) { + ALOGW("Failed to read from inotify: %s", res.error().message().c_str()); + } deviceChanged = true; } @@ -1774,7 +1960,7 @@ size_t EventHub::getEvents(int timeoutMillis, RawEvent* buffer, size_t bufferSiz } // Return now if we have collected any events or if we were explicitly awoken. - if (event != buffer || awoken) { + if (!events.empty() || awoken) { break; } @@ -1820,7 +2006,7 @@ size_t EventHub::getEvents(int timeoutMillis, RawEvent* buffer, size_t bufferSiz } // All done, return the number of events we read. - return event - buffer; + return events; } std::vector EventHub::getVideoFrames(int32_t deviceId) { @@ -2042,12 +2228,25 @@ void EventHub::openDeviceLocked(const std::string& devicePath) { identifier.uniqueId = buffer; } + // Attempt to get the bluetooth address of an input device from the uniqueId. + if (identifier.bus == BUS_BLUETOOTH && + std::regex_match(identifier.uniqueId, + std::regex("^[A-Fa-f0-9]{2}(?::[A-Fa-f0-9]{2}){5}$"))) { + identifier.bluetoothAddress = identifier.uniqueId; + // The Bluetooth stack requires alphabetic characters to be uppercase in a valid address. + for (auto& c : *identifier.bluetoothAddress) { + c = ::toupper(c); + } + } + // Fill in the descriptor. assignDescriptorLocked(identifier); // Allocate device. (The device object takes ownership of the fd at this point.) int32_t deviceId = mNextDeviceId++; - std::unique_ptr device = std::make_unique(fd, deviceId, devicePath, identifier); + std::unique_ptr device = + std::make_unique(fd, deviceId, devicePath, identifier, + obtainAssociatedDeviceLocked(devicePath)); ALOGV("add device %d: %s\n", deviceId, devicePath.c_str()); ALOGV(" bus: %04x\n" @@ -2065,27 +2264,6 @@ void EventHub::openDeviceLocked(const std::string& devicePath) { // Load the configuration file for the device. device->loadConfigurationLocked(); - bool hasBattery = false; - bool hasLights = false; - // Check the sysfs root path - std::optional sysfsRootPath = getSysfsRootPath(devicePath.c_str()); - if (sysfsRootPath.has_value()) { - std::shared_ptr associatedDevice; - for (const auto& [id, dev] : mDevices) { - if (device->identifier.descriptor == dev->identifier.descriptor && - !dev->associatedDevice) { - associatedDevice = dev->associatedDevice; - } - } - if (!associatedDevice) { - associatedDevice = std::make_shared(sysfsRootPath.value()); - } - hasBattery = associatedDevice->configureBatteryLocked(); - hasLights = associatedDevice->configureLightsLocked(); - - device->associatedDevice = associatedDevice; - } - // Figure out the kinds of events the device reports. device->readDeviceBitMask(EVIOCGBIT(EV_KEY, 0), device->keyBitmask); device->readDeviceBitMask(EVIOCGBIT(EV_ABS, 0), device->absBitmask); @@ -2096,13 +2274,15 @@ void EventHub::openDeviceLocked(const std::string& devicePath) { device->readDeviceBitMask(EVIOCGBIT(EV_MSC, 0), device->mscBitmask); device->readDeviceBitMask(EVIOCGPROP(0), device->propBitmask); - // See if this is a keyboard. Ignore everything in the button range except for - // joystick and gamepad buttons which are handled like keyboards for the most part. + // See if this is a device with keys. This could be full keyboard, or other devices like + // gamepads, joysticks, and styluses with buttons that should generate key presses. bool haveKeyboardKeys = device->keyBitmask.any(0, BTN_MISC) || device->keyBitmask.any(BTN_WHEEL, KEY_MAX + 1); bool haveGamepadButtons = device->keyBitmask.any(BTN_MISC, BTN_MOUSE) || device->keyBitmask.any(BTN_JOYSTICK, BTN_DIGI); - if (haveKeyboardKeys || haveGamepadButtons) { + bool haveStylusButtons = device->keyBitmask.test(BTN_STYLUS) || + device->keyBitmask.test(BTN_STYLUS2) || device->keyBitmask.test(BTN_STYLUS3); + if (haveKeyboardKeys || haveGamepadButtons || haveStylusButtons) { device->classes |= InputDeviceClass::KEYBOARD; } @@ -2112,12 +2292,13 @@ void EventHub::openDeviceLocked(const std::string& devicePath) { device->classes |= InputDeviceClass::CURSOR; } - // See if this is a rotary encoder type device. - String8 deviceType = String8(); - if (device->configuration && - device->configuration->tryGetProperty(String8("device.type"), deviceType)) { - if (!deviceType.compare(String8("rotaryEncoder"))) { + // See if the device is specially configured to be of a certain type. + if (device->configuration) { + std::string deviceType = device->configuration->getString("device.type").value_or(""); + if (deviceType == "rotaryEncoder") { device->classes |= InputDeviceClass::ROTARY_ENCODER; + } else if (deviceType == "externalStylus") { + device->classes |= InputDeviceClass::EXTERNAL_STYLUS; } } @@ -2129,19 +2310,19 @@ void EventHub::openDeviceLocked(const std::string& devicePath) { // a touch screen. if (device->keyBitmask.test(BTN_TOUCH) || !haveGamepadButtons) { device->classes |= (InputDeviceClass::TOUCH | InputDeviceClass::TOUCH_MT); + if (device->propBitmask.test(INPUT_PROP_POINTER) && + !device->keyBitmask.any(BTN_TOOL_PEN, BTN_TOOL_FINGER) && !haveStylusButtons) { + device->classes |= InputDeviceClass::TOUCHPAD; + } } // Is this an old style single-touch driver? } else if (device->keyBitmask.test(BTN_TOUCH) && device->absBitmask.test(ABS_X) && device->absBitmask.test(ABS_Y)) { device->classes |= InputDeviceClass::TOUCH; - // Is this a BT stylus? + // Is this a stylus that reports contact/pressure independently of touch coordinates? } else if ((device->absBitmask.test(ABS_PRESSURE) || device->keyBitmask.test(BTN_TOUCH)) && !device->absBitmask.test(ABS_X) && !device->absBitmask.test(ABS_Y)) { device->classes |= InputDeviceClass::EXTERNAL_STYLUS; - // Keyboard will try to claim some of the buttons but we really want to reserve those so we - // can fuse it with the touch screen data, so just take them back. Note this means an - // external stylus cannot also be a keyboard device. - device->classes &= ~InputDeviceClass::KEYBOARD; } // See if this device is a joystick. @@ -2226,6 +2407,16 @@ void EventHub::openDeviceLocked(const std::string& devicePath) { break; } } + + // See if this device has any stylus buttons that we would want to fuse with touch data. + if (!device->classes.any(InputDeviceClass::TOUCH | InputDeviceClass::TOUCH_MT)) { + for (int32_t keycode : STYLUS_BUTTON_KEYCODES) { + if (device->hasKeycodeLocked(keycode)) { + device->classes |= InputDeviceClass::EXTERNAL_STYLUS; + break; + } + } + } } // If the device isn't recognized as something we handle, don't monitor it. @@ -2236,12 +2427,12 @@ void EventHub::openDeviceLocked(const std::string& devicePath) { } // Classify InputDeviceClass::BATTERY. - if (hasBattery) { + if (device->associatedDevice && !device->associatedDevice->batteryInfos.empty()) { device->classes |= InputDeviceClass::BATTERY; } // Classify InputDeviceClass::LIGHT. - if (hasLights) { + if (device->associatedDevice && !device->associatedDevice->lightInfos.empty()) { device->classes |= InputDeviceClass::LIGHT; } @@ -2309,7 +2500,7 @@ bool EventHub::tryAddVideoDeviceLocked(EventHub::Device& device, return true; } -bool EventHub::isDeviceEnabled(int32_t deviceId) { +bool EventHub::isDeviceEnabled(int32_t deviceId) const { std::scoped_lock _l(mLock); Device* device = getDeviceLocked(deviceId); if (device == nullptr) { @@ -2356,6 +2547,42 @@ status_t EventHub::disableDevice(int32_t deviceId) { return device->disable(); } +// TODO(b/274755573): Shift to uevent handling on native side and remove this method +// Currently using Java UEventObserver to trigger this which uses UEvent infrastructure that uses a +// NETLINK socket to observe UEvents. We can create similar infrastructure on Eventhub side to +// directly observe UEvents instead of triggering from Java side. +void EventHub::sysfsNodeChanged(const std::string& sysfsNodePath) { + std::scoped_lock _l(mLock); + + // Check in opening devices + for (auto it = mOpeningDevices.begin(); it != mOpeningDevices.end(); it++) { + std::unique_ptr& device = *it; + if (device->associatedDevice && + sysfsNodePath.find(device->associatedDevice->sysfsRootPath.string()) != + std::string::npos && + device->associatedDevice->isChanged()) { + it = mOpeningDevices.erase(it); + openDeviceLocked(device->path); + } + } + + // Check in already added device + std::vector devicesToReopen; + for (const auto& [id, device] : mDevices) { + if (device->associatedDevice && + sysfsNodePath.find(device->associatedDevice->sysfsRootPath.string()) != + std::string::npos && + device->associatedDevice->isChanged()) { + devicesToReopen.push_back(device.get()); + } + } + for (const auto& device : devicesToReopen) { + closeDeviceLocked(*device); + openDeviceLocked(device->path); + } + devicesToReopen.clear(); +} + void EventHub::createVirtualKeyboardLocked() { InputDeviceIdentifier identifier; identifier.name = "Virtual"; @@ -2364,7 +2591,7 @@ void EventHub::createVirtualKeyboardLocked() { std::unique_ptr device = std::make_unique(-1, ReservedInputDeviceId::VIRTUAL_KEYBOARD_ID, "", - identifier); + identifier, /*associatedDevice=*/nullptr); device->classes = InputDeviceClass::KEYBOARD | InputDeviceClass::ALPHAKEY | InputDeviceClass::DPAD | InputDeviceClass::VIRTUAL; device->loadKeyMapLocked(); @@ -2454,53 +2681,56 @@ void EventHub::closeDeviceLocked(Device& device) { mDevices.erase(device.id); } -status_t EventHub::readNotifyLocked() { - int res; - char event_buf[512]; - int event_size; - int event_pos = 0; - struct inotify_event* event; +base::Result EventHub::readNotifyLocked() { + static constexpr auto EVENT_SIZE = static_cast(sizeof(inotify_event)); + uint8_t eventBuffer[512]; + ssize_t sizeRead; ALOGV("EventHub::readNotify nfd: %d\n", mINotifyFd); - res = read(mINotifyFd, event_buf, sizeof(event_buf)); - if (res < (int)sizeof(*event)) { - if (errno == EINTR) return 0; - ALOGW("could not get event, %s\n", strerror(errno)); - return -1; - } - - while (res >= (int)sizeof(*event)) { - event = (struct inotify_event*)(event_buf + event_pos); - if (event->len) { - if (event->wd == mDeviceInputWd) { - std::string filename = std::string(DEVICE_INPUT_PATH) + "/" + event->name; - if (event->mask & IN_CREATE) { - openDeviceLocked(filename); - } else { - ALOGI("Removing device '%s' due to inotify event\n", filename.c_str()); - closeDeviceByPathLocked(filename); - } - } else if (event->wd == mDeviceWd) { - if (isV4lTouchNode(event->name)) { - std::string filename = std::string(DEVICE_PATH) + "/" + event->name; - if (event->mask & IN_CREATE) { - openVideoDeviceLocked(filename); - } else { - ALOGI("Removing video device '%s' due to inotify event", filename.c_str()); - closeVideoDeviceByPathLocked(filename); - } - } else if (strcmp(event->name, "input") == 0 && event->mask & IN_CREATE) { - addDeviceInputInotify(); - } + do { + sizeRead = read(mINotifyFd, eventBuffer, sizeof(eventBuffer)); + } while (sizeRead < 0 && errno == EINTR); + + if (sizeRead < EVENT_SIZE) return Errorf("could not get event, %s", strerror(errno)); + + for (ssize_t eventPos = 0; sizeRead >= EVENT_SIZE;) { + const inotify_event* event; + event = (const inotify_event*)(eventBuffer + eventPos); + if (event->len == 0) continue; + + handleNotifyEventLocked(*event); + + const ssize_t eventSize = EVENT_SIZE + event->len; + sizeRead -= eventSize; + eventPos += eventSize; + } + return {}; +} + +void EventHub::handleNotifyEventLocked(const inotify_event& event) { + if (event.wd == mDeviceInputWd) { + std::string filename = std::string(DEVICE_INPUT_PATH) + "/" + event.name; + if (event.mask & IN_CREATE) { + openDeviceLocked(filename); + } else { + ALOGI("Removing device '%s' due to inotify event\n", filename.c_str()); + closeDeviceByPathLocked(filename); + } + } else if (event.wd == mDeviceWd) { + if (isV4lTouchNode(event.name)) { + std::string filename = std::string(DEVICE_PATH) + "/" + event.name; + if (event.mask & IN_CREATE) { + openVideoDeviceLocked(filename); } else { - LOG_ALWAYS_FATAL("Unexpected inotify event, wd = %i", event->wd); + ALOGI("Removing video device '%s' due to inotify event", filename.c_str()); + closeVideoDeviceByPathLocked(filename); } + } else if (strcmp(event.name, "input") == 0 && event.mask & IN_CREATE) { + addDeviceInputInotify(); } - event_size = sizeof(*event) + event->len; - res -= event_size; - event_pos += event_size; + } else { + LOG_ALWAYS_FATAL("Unexpected inotify event, wd = %i", event.wd); } - return 0; } status_t EventHub::scanDirLocked(const std::string& dirname) { @@ -2530,7 +2760,7 @@ void EventHub::requestReopenDevices() { mNeedToReopenDevices = true; } -void EventHub::dump(std::string& dump) { +void EventHub::dump(std::string& dump) const { dump += "Event Hub State:\n"; { // acquire lock @@ -2556,21 +2786,29 @@ void EventHub::dump(std::string& dump) { dump += StringPrintf(INDENT3 "ControllerNumber: %d\n", device->controllerNumber); dump += StringPrintf(INDENT3 "UniqueId: %s\n", device->identifier.uniqueId.c_str()); dump += StringPrintf(INDENT3 "Identifier: bus=0x%04x, vendor=0x%04x, " - "product=0x%04x, version=0x%04x\n", + "product=0x%04x, version=0x%04x, bluetoothAddress=%s\n", device->identifier.bus, device->identifier.vendor, - device->identifier.product, device->identifier.version); + device->identifier.product, device->identifier.version, + toString(device->identifier.bluetoothAddress).c_str()); dump += StringPrintf(INDENT3 "KeyLayoutFile: %s\n", device->keyMap.keyLayoutFile.c_str()); dump += StringPrintf(INDENT3 "KeyCharacterMapFile: %s\n", device->keyMap.keyCharacterMapFile.c_str()); + if (device->associatedDevice && device->associatedDevice->layoutInfo) { + dump += StringPrintf(INDENT3 "LanguageTag: %s\n", + device->associatedDevice->layoutInfo->languageTag.c_str()); + dump += StringPrintf(INDENT3 "LayoutType: %s\n", + device->associatedDevice->layoutInfo->layoutType.c_str()); + } dump += StringPrintf(INDENT3 "ConfigurationFile: %s\n", device->configurationFile.c_str()); - dump += INDENT3 "VideoDevice: "; - if (device->videoDevice) { - dump += device->videoDevice->dump() + "\n"; - } else { - dump += "\n"; - } + dump += StringPrintf(INDENT3 "VideoDevice: %s\n", + device->videoDevice ? device->videoDevice->dump().c_str() + : ""); + dump += StringPrintf(INDENT3 "SysfsDevicePath: %s\n", + device->associatedDevice + ? device->associatedDevice->sysfsRootPath.c_str() + : ""); } dump += INDENT "Unattached video devices:\n"; @@ -2583,9 +2821,14 @@ void EventHub::dump(std::string& dump) { } // release lock } -void EventHub::monitor() { +void EventHub::monitor() const { // Acquire and release the lock to ensure that the event hub has not deadlocked. std::unique_lock lock(mLock); } -}; // namespace android +std::string EventHub::AssociatedDevice::dump() const { + return StringPrintf("path=%s, numBatteries=%zu, numLight=%zu", sysfsRootPath.c_str(), + batteryInfos.size(), lightInfos.size()); +} + +} // namespace android diff --git a/services/inputflinger/reader/InputDevice.cpp b/services/inputflinger/reader/InputDevice.cpp index 3f26aaaebe1add24ae95ee57406641c0325628a4..0a64a1c4a8fcb4c9e6b1c731dcc13632a344ffb9 100644 --- a/services/inputflinger/reader/InputDevice.cpp +++ b/services/inputflinger/reader/InputDevice.cpp @@ -20,6 +20,7 @@ #include +#include #include #include "CursorInputMapper.h" @@ -33,6 +34,7 @@ #include "SensorInputMapper.h" #include "SingleTouchInputMapper.h" #include "SwitchInputMapper.h" +#include "TouchpadInputMapper.h" #include "VibratorInputMapper.h" namespace android { @@ -63,7 +65,8 @@ bool InputDevice::isEnabled() { return enabled; } -void InputDevice::setEnabled(bool enabled, nsecs_t when) { +std::list InputDevice::setEnabled(bool enabled, nsecs_t when) { + std::list out; if (enabled && mAssociatedDisplayPort && !mAssociatedViewport) { ALOGW("Cannot enable input device %s because it is associated with port %" PRIu8 ", " "but the corresponding viewport is not found", @@ -72,7 +75,7 @@ void InputDevice::setEnabled(bool enabled, nsecs_t when) { } if (isEnabled() == enabled) { - return; + return out; } // When resetting some devices, the driver needs to be queried to ensure that a proper reset is @@ -80,13 +83,14 @@ void InputDevice::setEnabled(bool enabled, nsecs_t when) { // but before disabling the device. See MultiTouchMotionAccumulator::reset for more information. if (enabled) { for_each_subdevice([](auto& context) { context.enableDevice(); }); - reset(when); + out += reset(when); } else { - reset(when); + out += reset(when); for_each_subdevice([](auto& context) { context.disableDevice(); }); } // Must change generation to flag this device as changed bumpGeneration(); + return out; } void InputDevice::dump(std::string& dump, const std::string& eventHubDevStr) { @@ -142,88 +146,23 @@ void InputDevice::dump(std::string& dump, const std::string& eventHubDevStr) { } } -void InputDevice::addEventHubDevice(int32_t eventHubId, bool populateMappers) { +void InputDevice::addEmptyEventHubDevice(int32_t eventHubId) { if (mDevices.find(eventHubId) != mDevices.end()) { return; } std::unique_ptr contextPtr(new InputDeviceContext(*this, eventHubId)); - ftl::Flags classes = contextPtr->getDeviceClasses(); std::vector> mappers; - // Check if we should skip population - if (!populateMappers) { - mDevices.insert({eventHubId, std::make_pair(std::move(contextPtr), std::move(mappers))}); - return; - } - - // Switch-like devices. - if (classes.test(InputDeviceClass::SWITCH)) { - mappers.push_back(std::make_unique(*contextPtr)); - } - - // Scroll wheel-like devices. - if (classes.test(InputDeviceClass::ROTARY_ENCODER)) { - mappers.push_back(std::make_unique(*contextPtr)); - } - - // Vibrator-like devices. - if (classes.test(InputDeviceClass::VIBRATOR)) { - mappers.push_back(std::make_unique(*contextPtr)); - } - - // Battery-like devices or light-containing devices. - // PeripheralController will be created with associated EventHub device. - if (classes.test(InputDeviceClass::BATTERY) || classes.test(InputDeviceClass::LIGHT)) { - mController = std::make_unique(*contextPtr); - } - - // 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; - } - if (classes.test(InputDeviceClass::GAMEPAD)) { - keyboardSource |= AINPUT_SOURCE_GAMEPAD; - } - - if (keyboardSource != 0) { - mappers.push_back( - std::make_unique(*contextPtr, keyboardSource, keyboardType)); - } - - // Cursor-like devices. - if (classes.test(InputDeviceClass::CURSOR)) { - mappers.push_back(std::make_unique(*contextPtr)); - } - - // Touchscreens and touchpad devices. - if (classes.test(InputDeviceClass::TOUCH_MT)) { - mappers.push_back(std::make_unique(*contextPtr)); - } else if (classes.test(InputDeviceClass::TOUCH)) { - mappers.push_back(std::make_unique(*contextPtr)); - } - - // Joystick-like devices. - if (classes.test(InputDeviceClass::JOYSTICK)) { - mappers.push_back(std::make_unique(*contextPtr)); - } - - // Motion sensor enabled devices. - if (classes.test(InputDeviceClass::SENSOR)) { - mappers.push_back(std::make_unique(*contextPtr)); - } + mDevices.insert({eventHubId, std::make_pair(std::move(contextPtr), std::move(mappers))}); +} - // External stylus-like devices. - if (classes.test(InputDeviceClass::EXTERNAL_STYLUS)) { - mappers.push_back(std::make_unique(*contextPtr)); +void InputDevice::addEventHubDevice(int32_t eventHubId, + const InputReaderConfiguration& readerConfig) { + if (mDevices.find(eventHubId) != mDevices.end()) { + return; } + std::unique_ptr contextPtr(new InputDeviceContext(*this, eventHubId)); + std::vector> mappers = createMappers(*contextPtr, readerConfig); // insert the context into the devices set mDevices.insert({eventHubId, std::make_pair(std::move(contextPtr), std::move(mappers))}); @@ -239,8 +178,10 @@ void InputDevice::removeEventHubDevice(int32_t eventHubId) { mDevices.erase(eventHubId); } -void InputDevice::configure(nsecs_t when, const InputReaderConfiguration* config, - uint32_t changes) { +std::list InputDevice::configure(nsecs_t when, + const InputReaderConfiguration& readerConfig, + ConfigurationChanges changes) { + std::list out; mSources = 0; mClasses = ftl::Flags(0); mControllerNumber = 0; @@ -260,17 +201,28 @@ void InputDevice::configure(nsecs_t when, const InputReaderConfiguration* config mIsExternal = mClasses.test(InputDeviceClass::EXTERNAL); mHasMic = mClasses.test(InputDeviceClass::MIC); - if (!isIgnored()) { - if (!changes) { // first time only + using Change = InputReaderConfiguration::Change; + + if (!changes.any() || !isIgnored()) { + // Full configuration should happen the first time configure is called + // and when the device type is changed. Changing a device type can + // affect various other parameters so should result in a + // reconfiguration. + if (!changes.any() || changes.test(Change::DEVICE_TYPE)) { mConfiguration.clear(); for_each_subdevice([this](InputDeviceContext& context) { - PropertyMap configuration; - context.getConfiguration(&configuration); - mConfiguration.addAll(&configuration); + std::optional configuration = + getEventHub()->getConfiguration(context.getEventHubId()); + if (configuration) { + mConfiguration.addAll(&(*configuration)); + } }); + + mAssociatedDeviceType = + getValueByKey(readerConfig.deviceTypeAssociations, mIdentifier.location); } - if (!changes || (changes & InputReaderConfiguration::CHANGE_KEYBOARD_LAYOUTS)) { + if (!changes.any() || changes.test(Change::KEYBOARD_LAYOUTS)) { if (!mClasses.test(InputDeviceClass::VIRTUAL)) { std::shared_ptr keyboardLayout = mContext->getPolicy()->getKeyboardLayoutOverlay(mIdentifier); @@ -287,7 +239,7 @@ void InputDevice::configure(nsecs_t when, const InputReaderConfiguration* config } } - if (!changes || (changes & InputReaderConfiguration::CHANGE_DEVICE_ALIAS)) { + if (!changes.any() || changes.test(Change::DEVICE_ALIAS)) { if (!(mClasses.test(InputDeviceClass::VIRTUAL))) { std::string alias = mContext->getPolicy()->getDeviceAlias(mIdentifier); if (mAlias != alias) { @@ -297,13 +249,16 @@ void InputDevice::configure(nsecs_t when, const InputReaderConfiguration* config } } - if (!changes || (changes & InputReaderConfiguration::CHANGE_ENABLED_STATE)) { - auto it = config->disabledDevices.find(mId); - bool enabled = it == config->disabledDevices.end(); - setEnabled(enabled, when); + if (changes.test(Change::ENABLED_STATE)) { + // Do not execute this code on the first configure, because 'setEnabled' would call + // InputMapper::reset, and you can't reset a mapper before it has been configured. + // The mappers are configured for the first time at the bottom of this function. + auto it = readerConfig.disabledDevices.find(mId); + bool enabled = it == readerConfig.disabledDevices.end(); + out += setEnabled(enabled, when); } - if (!changes || (changes & InputReaderConfiguration::CHANGE_DISPLAY_INFO)) { + if (!changes.any() || changes.test(Change::DISPLAY_INFO)) { // In most situations, no port or name will be specified. mAssociatedDisplayPort = std::nullopt; mAssociatedDisplayUniqueId = std::nullopt; @@ -311,13 +266,14 @@ void InputDevice::configure(nsecs_t when, const InputReaderConfiguration* config // Find the display port that corresponds to the current input port. const std::string& inputPort = mIdentifier.location; if (!inputPort.empty()) { - const std::unordered_map& ports = config->portAssociations; + const std::unordered_map& ports = + readerConfig.portAssociations; const auto& displayPort = ports.find(inputPort); if (displayPort != ports.end()) { mAssociatedDisplayPort = std::make_optional(displayPort->second); } else { const std::unordered_map& displayUniqueIds = - config->uniqueIdAssociations; + readerConfig.uniqueIdAssociations; const auto& displayUniqueId = displayUniqueIds.find(inputPort); if (displayUniqueId != displayUniqueIds.end()) { mAssociatedDisplayUniqueId = displayUniqueId->second; @@ -329,9 +285,11 @@ void InputDevice::configure(nsecs_t when, const InputReaderConfiguration* config // "disabledDevices" list. If it is associated with a specific display, and it was not // explicitly disabled, then enable/disable the device based on whether we can find the // corresponding viewport. - bool enabled = (config->disabledDevices.find(mId) == config->disabledDevices.end()); + bool enabled = + (readerConfig.disabledDevices.find(mId) == readerConfig.disabledDevices.end()); if (mAssociatedDisplayPort) { - mAssociatedViewport = config->getDisplayViewportByPort(*mAssociatedDisplayPort); + mAssociatedViewport = + readerConfig.getDisplayViewportByPort(*mAssociatedDisplayPort); if (!mAssociatedViewport) { ALOGW("Input device %s should be associated with display on port %" PRIu8 ", " "but the corresponding viewport is not found.", @@ -340,7 +298,7 @@ void InputDevice::configure(nsecs_t when, const InputReaderConfiguration* config } } else if (mAssociatedDisplayUniqueId != std::nullopt) { mAssociatedViewport = - config->getDisplayViewportByUniqueId(*mAssociatedDisplayUniqueId); + readerConfig.getDisplayViewportByUniqueId(*mAssociatedDisplayUniqueId); if (!mAssociatedViewport) { ALOGW("Input device %s should be associated with display %s but the " "corresponding viewport cannot be found", @@ -349,86 +307,97 @@ void InputDevice::configure(nsecs_t when, const InputReaderConfiguration* config } } - if (changes) { + if (changes.any()) { // For first-time configuration, only allow device to be disabled after mappers have // finished configuring. This is because we need to read some of the properties from // the device's open fd. - setEnabled(enabled, when); + out += setEnabled(enabled, when); } } - for_each_mapper([this, when, config, changes](InputMapper& mapper) { - mapper.configure(when, config, changes); + for_each_mapper([this, when, &readerConfig, changes, &out](InputMapper& mapper) { + out += mapper.reconfigure(when, readerConfig, changes); mSources |= mapper.getSources(); }); // If a device is just plugged but it might be disabled, we need to update some info like // axis range of touch from each InputMapper first, then disable it. - if (!changes) { - setEnabled(config->disabledDevices.find(mId) == config->disabledDevices.end(), when); + if (!changes.any()) { + out += setEnabled(readerConfig.disabledDevices.find(mId) == + readerConfig.disabledDevices.end(), + when); } } + return out; } -void InputDevice::reset(nsecs_t when) { - for_each_mapper([when](InputMapper& mapper) { mapper.reset(when); }); +std::list InputDevice::reset(nsecs_t when) { + std::list out; + for_each_mapper([&](InputMapper& mapper) { out += mapper.reset(when); }); mContext->updateGlobalMetaState(); - notifyReset(when); + out.push_back(notifyReset(when)); + return out; } -void InputDevice::process(const RawEvent* rawEvents, size_t count) { +std::list InputDevice::process(const RawEvent* rawEvents, size_t count) { // Process all of the events in order for each mapper. // We cannot simply ask each mapper to process them in bulk because mappers may // have side-effects that must be interleaved. For example, joystick movement events and // gamepad button presses are handled by different mappers but they should be dispatched // in the order received. + std::list out; for (const RawEvent* rawEvent = rawEvents; count != 0; rawEvent++) { - if (DEBUG_RAW_EVENTS) { - ALOGD("Input event: device=%d type=0x%04x code=0x%04x value=0x%08x when=%" PRId64, - rawEvent->deviceId, rawEvent->type, rawEvent->code, rawEvent->value, - rawEvent->when); + if (debugRawEvents()) { + const auto [type, code, value] = + InputEventLookup::getLinuxEvdevLabel(rawEvent->type, rawEvent->code, + rawEvent->value); + ALOGD("Input event: eventHubDevice=%d type=%s code=%s value=%s when=%" PRId64, + rawEvent->deviceId, type.c_str(), code.c_str(), value.c_str(), rawEvent->when); } if (mDropUntilNextSync) { if (rawEvent->type == EV_SYN && rawEvent->code == SYN_REPORT) { mDropUntilNextSync = false; - if (DEBUG_RAW_EVENTS) { - ALOGD("Recovered from input event buffer overrun."); - } + ALOGD_IF(debugRawEvents(), "Recovered from input event buffer overrun."); } else { - if (DEBUG_RAW_EVENTS) { - ALOGD("Dropped input event while waiting for next input sync."); - } + ALOGD_IF(debugRawEvents(), + "Dropped input event while waiting for next input sync."); } } else if (rawEvent->type == EV_SYN && rawEvent->code == SYN_DROPPED) { ALOGI("Detected input event buffer overrun for device %s.", getName().c_str()); mDropUntilNextSync = true; - reset(rawEvent->when); + out += reset(rawEvent->when); } else { - for_each_mapper_in_subdevice(rawEvent->deviceId, [rawEvent](InputMapper& mapper) { - mapper.process(rawEvent); + for_each_mapper_in_subdevice(rawEvent->deviceId, [&](InputMapper& mapper) { + out += mapper.process(rawEvent); }); } --count; } + return out; } -void InputDevice::timeoutExpired(nsecs_t when) { - for_each_mapper([when](InputMapper& mapper) { mapper.timeoutExpired(when); }); +std::list InputDevice::timeoutExpired(nsecs_t when) { + std::list out; + for_each_mapper([&](InputMapper& mapper) { out += mapper.timeoutExpired(when); }); + return out; } -void InputDevice::updateExternalStylusState(const StylusState& state) { - for_each_mapper([state](InputMapper& mapper) { mapper.updateExternalStylusState(state); }); +std::list InputDevice::updateExternalStylusState(const StylusState& state) { + std::list out; + for_each_mapper([&](InputMapper& mapper) { out += mapper.updateExternalStylusState(state); }); + return out; } InputDeviceInfo InputDevice::getDeviceInfo() { InputDeviceInfo outDeviceInfo; outDeviceInfo.initialize(mId, mGeneration, mControllerNumber, mIdentifier, mAlias, mIsExternal, - mHasMic); + mHasMic, getAssociatedDisplayId().value_or(ADISPLAY_ID_NONE)); + for_each_mapper( - [&outDeviceInfo](InputMapper& mapper) { mapper.populateDeviceInfo(&outDeviceInfo); }); + [&outDeviceInfo](InputMapper& mapper) { mapper.populateDeviceInfo(outDeviceInfo); }); if (mController) { mController->populateDeviceInfo(&outDeviceInfo); @@ -470,12 +439,98 @@ int32_t InputDevice::getState(uint32_t sourceMask, int32_t code, GetStateFunc ge return result; } -bool InputDevice::markSupportedKeyCodes(uint32_t sourceMask, size_t numCodes, - const int32_t* keyCodes, uint8_t* outFlags) { +std::vector> InputDevice::createMappers( + InputDeviceContext& contextPtr, const InputReaderConfiguration& readerConfig) { + ftl::Flags classes = contextPtr.getDeviceClasses(); + std::vector> mappers; + + // Switch-like devices. + if (classes.test(InputDeviceClass::SWITCH)) { + mappers.push_back(createInputMapper(contextPtr, readerConfig)); + } + + // Scroll wheel-like devices. + if (classes.test(InputDeviceClass::ROTARY_ENCODER)) { + mappers.push_back(createInputMapper(contextPtr, readerConfig)); + } + + // Vibrator-like devices. + if (classes.test(InputDeviceClass::VIBRATOR)) { + mappers.push_back(createInputMapper(contextPtr, readerConfig)); + } + + // Battery-like devices or light-containing devices. + // PeripheralController will be created with associated EventHub device. + if (classes.test(InputDeviceClass::BATTERY) || classes.test(InputDeviceClass::LIGHT)) { + mController = std::make_unique(contextPtr); + } + + // 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; + } + if (classes.test(InputDeviceClass::GAMEPAD)) { + keyboardSource |= AINPUT_SOURCE_GAMEPAD; + } + + if (keyboardSource != 0) { + mappers.push_back(createInputMapper(contextPtr, readerConfig, + keyboardSource, keyboardType)); + } + + // Cursor-like devices. + if (classes.test(InputDeviceClass::CURSOR)) { + mappers.push_back(createInputMapper(contextPtr, readerConfig)); + } + + // 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) { + mappers.push_back(createInputMapper(contextPtr, readerConfig)); + } else if (classes.test(InputDeviceClass::TOUCH_MT)) { + mappers.push_back(std::make_unique(contextPtr, readerConfig)); + } else if (classes.test(InputDeviceClass::TOUCH)) { + mappers.push_back(std::make_unique(contextPtr, readerConfig)); + } + + // Joystick-like devices. + if (classes.test(InputDeviceClass::JOYSTICK)) { + mappers.push_back(createInputMapper(contextPtr, readerConfig)); + } + + // Motion sensor enabled devices. + if (classes.test(InputDeviceClass::SENSOR)) { + mappers.push_back(createInputMapper(contextPtr, readerConfig)); + } + + // External stylus-like devices. + if (classes.test(InputDeviceClass::EXTERNAL_STYLUS)) { + mappers.push_back(createInputMapper(contextPtr, readerConfig)); + } + return mappers; +} + +bool InputDevice::markSupportedKeyCodes(uint32_t sourceMask, const std::vector& keyCodes, + uint8_t* outFlags) { bool result = false; - for_each_mapper([&result, sourceMask, numCodes, keyCodes, outFlags](InputMapper& mapper) { + for_each_mapper([&result, sourceMask, keyCodes, outFlags](InputMapper& mapper) { if (sourcesMatchMask(mapper.getSources(), sourceMask)) { - result |= mapper.markSupportedKeyCodes(sourceMask, numCodes, keyCodes, outFlags); + result |= mapper.markSupportedKeyCodes(sourceMask, keyCodes, outFlags); } }); return result; @@ -498,14 +553,17 @@ int32_t InputDevice::getKeyCodeForKeyLocation(int32_t locationKeyCode) const { return *result; } -void InputDevice::vibrate(const VibrationSequence& sequence, ssize_t repeat, int32_t token) { - for_each_mapper([sequence, repeat, token](InputMapper& mapper) { - mapper.vibrate(sequence, repeat, token); - }); +std::list InputDevice::vibrate(const VibrationSequence& sequence, ssize_t repeat, + int32_t token) { + std::list out; + for_each_mapper([&](InputMapper& mapper) { out += mapper.vibrate(sequence, repeat, token); }); + return out; } -void InputDevice::cancelVibrate(int32_t token) { - for_each_mapper([token](InputMapper& mapper) { mapper.cancelVibrate(token); }); +std::list InputDevice::cancelVibrate(int32_t token) { + std::list out; + for_each_mapper([&](InputMapper& mapper) { out += mapper.cancelVibrate(token); }); + return out; } bool InputDevice::isVibrating() { @@ -548,8 +606,10 @@ void InputDevice::flushSensor(InputDeviceSensorType sensorType) { for_each_mapper([sensorType](InputMapper& mapper) { mapper.flushSensor(sensorType); }); } -void InputDevice::cancelTouch(nsecs_t when, nsecs_t readTime) { - for_each_mapper([when, readTime](InputMapper& mapper) { mapper.cancelTouch(when, readTime); }); +std::list InputDevice::cancelTouch(nsecs_t when, nsecs_t readTime) { + std::list out; + for_each_mapper([&](InputMapper& mapper) { out += mapper.cancelTouch(when, readTime); }); + return out; } bool InputDevice::setLightColor(int32_t lightId, int32_t color) { @@ -584,13 +644,18 @@ void InputDevice::updateMetaState(int32_t keyCode) { }); } +void InputDevice::addKeyRemapping(int32_t fromKeyCode, int32_t toKeyCode) { + for_each_subdevice([fromKeyCode, toKeyCode](auto& context) { + context.addKeyRemapping(fromKeyCode, toKeyCode); + }); +} + void InputDevice::bumpGeneration() { mGeneration = mContext->bumpGeneration(); } -void InputDevice::notifyReset(nsecs_t when) { - NotifyDeviceResetArgs args(mContext->getNextId(), when, mId); - mContext->getListener().notifyDeviceReset(&args); +NotifyDeviceResetArgs InputDevice::notifyReset(nsecs_t when) { + return NotifyDeviceResetArgs(mContext->getNextId(), when, mId); } std::optional InputDevice::getAssociatedDisplayId() { diff --git a/services/inputflinger/reader/InputReader.cpp b/services/inputflinger/reader/InputReader.cpp index 905b348caa5e63c72121ec1122a531445cc8ff7a..ea95f7857a0bd4d8b5354e15228bc1a5569d82d6 100644 --- a/services/inputflinger/reader/InputReader.cpp +++ b/services/inputflinger/reader/InputReader.cpp @@ -58,6 +58,17 @@ static bool isSubDevice(const InputDeviceIdentifier& identifier1, identifier1.location == identifier2.location); } +static bool isStylusPointerGestureStart(const NotifyMotionArgs& motionArgs) { + const auto actionMasked = MotionEvent::getActionMasked(motionArgs.action); + if (actionMasked != AMOTION_EVENT_ACTION_HOVER_ENTER && + actionMasked != AMOTION_EVENT_ACTION_DOWN && + actionMasked != AMOTION_EVENT_ACTION_POINTER_DOWN) { + return false; + } + const auto actionIndex = MotionEvent::getActionIndex(motionArgs.action); + return isStylusToolType(motionArgs.pointerProperties[actionIndex].toolType); +} + // --- InputReader --- InputReader::InputReader(std::shared_ptr eventHub, @@ -74,7 +85,7 @@ InputReader::InputReader(std::shared_ptr eventHub, mDisableVirtualKeysTimeout(LLONG_MIN), mNextTimeout(LLONG_MAX), mConfigurationChangesToRefresh(0) { - refreshConfigurationLocked(0); + refreshConfigurationLocked(/*changes=*/{}); updateGlobalMetaStateLocked(); } @@ -101,17 +112,19 @@ status_t InputReader::stop() { void InputReader::loopOnce() { int32_t oldGeneration; int32_t timeoutMillis; + // Copy some state so that we can access it outside the lock later. bool inputDevicesChanged = false; std::vector inputDevices; + std::list notifyArgs; { // acquire lock std::scoped_lock _l(mLock); oldGeneration = mGeneration; timeoutMillis = -1; - uint32_t changes = mConfigurationChangesToRefresh; - if (changes) { - mConfigurationChangesToRefresh = 0; + auto changes = mConfigurationChangesToRefresh; + if (changes.any()) { + mConfigurationChangesToRefresh.clear(); timeoutMillis = 0; refreshConfigurationLocked(changes); } else if (mNextTimeout != LLONG_MAX) { @@ -120,30 +133,32 @@ void InputReader::loopOnce() { } } // release lock - size_t count = mEventHub->getEvents(timeoutMillis, mEventBuffer, EVENT_BUFFER_SIZE); + std::vector events = mEventHub->getEvents(timeoutMillis); { // acquire lock std::scoped_lock _l(mLock); mReaderIsAliveCondition.notify_all(); - if (count) { - processEventsLocked(mEventBuffer, count); + if (!events.empty()) { + notifyArgs += processEventsLocked(events.data(), events.size()); } if (mNextTimeout != LLONG_MAX) { nsecs_t now = systemTime(SYSTEM_TIME_MONOTONIC); if (now >= mNextTimeout) { - if (DEBUG_RAW_EVENTS) { + if (debugRawEvents()) { ALOGD("Timeout expired, latency=%0.3fms", (now - mNextTimeout) * 0.000001f); } mNextTimeout = LLONG_MAX; - timeoutExpiredLocked(now); + notifyArgs += timeoutExpiredLocked(now); } } if (oldGeneration != mGeneration) { inputDevicesChanged = true; inputDevices = getInputDevicesLocked(); + notifyArgs.emplace_back( + NotifyInputDevicesChangedArgs{mContext.getNextId(), inputDevices}); } } // release lock @@ -152,6 +167,16 @@ void InputReader::loopOnce() { mPolicy->notifyInputDevicesChanged(inputDevices); } + // Notify the policy of the start of every new stylus gesture outside the lock. + for (const auto& args : notifyArgs) { + const auto* motionArgs = std::get_if(&args); + if (motionArgs != nullptr && isStylusPointerGestureStart(*motionArgs)) { + mPolicy->notifyStylusGestureStarted(motionArgs->deviceId, motionArgs->eventTime); + } + } + + notifyAll(std::move(notifyArgs)); + // Flush queued events out to the listener. // This must happen outside of the lock because the listener could potentially call // back into the InputReader's methods, such as getScanCodeState, or become blocked @@ -162,7 +187,8 @@ void InputReader::loopOnce() { mQueuedListener.flush(); } -void InputReader::processEventsLocked(const RawEvent* rawEvents, size_t count) { +std::list InputReader::processEventsLocked(const RawEvent* rawEvents, size_t count) { + std::list out; for (const RawEvent* rawEvent = rawEvents; count;) { int32_t type = rawEvent->type; size_t batchSize = 1; @@ -175,10 +201,10 @@ void InputReader::processEventsLocked(const RawEvent* rawEvents, size_t count) { } batchSize += 1; } - if (DEBUG_RAW_EVENTS) { + if (debugRawEvents()) { ALOGD("BatchSize: %zu Count: %zu", batchSize, count); } - processEventsForDeviceLocked(deviceId, rawEvent, batchSize); + out += processEventsForDeviceLocked(deviceId, rawEvent, batchSize); } else { switch (rawEvent->type) { case EventHubInterface::DEVICE_ADDED: @@ -198,6 +224,7 @@ void InputReader::processEventsLocked(const RawEvent* rawEvents, size_t count) { count -= batchSize; rawEvent += batchSize; } + return out; } void InputReader::addDeviceLocked(nsecs_t when, int32_t eventHubId) { @@ -208,8 +235,9 @@ void InputReader::addDeviceLocked(nsecs_t when, int32_t eventHubId) { InputDeviceIdentifier identifier = mEventHub->getDeviceIdentifier(eventHubId); std::shared_ptr device = createDeviceLocked(eventHubId, identifier); - device->configure(when, &mConfig, 0); - device->reset(when); + + notifyAll(device->configure(when, mConfig, /*changes=*/{})); + notifyAll(device->reset(when)); if (device->isIgnored()) { ALOGI("Device added: id=%d, eventHubId=%d, name='%s', descriptor='%s' " @@ -282,10 +310,12 @@ void InputReader::removeDeviceLocked(nsecs_t when, int32_t eventHubId) { notifyExternalStylusPresenceChangedLocked(); } + std::list resetEvents; if (device->hasEventHubDevices()) { - device->configure(when, &mConfig, 0); + resetEvents += device->configure(when, mConfig, /*changes=*/{}); } - device->reset(when); + resetEvents += device->reset(when); + notifyAll(std::move(resetEvents)); } std::shared_ptr InputReader::createDeviceLocked( @@ -304,25 +334,26 @@ std::shared_ptr InputReader::createDeviceLocked( device = std::make_shared(&mContext, deviceId, bumpGenerationLocked(), identifier); } - device->addEventHubDevice(eventHubId); + device->addEventHubDevice(eventHubId, mConfig); return device; } -void InputReader::processEventsForDeviceLocked(int32_t eventHubId, const RawEvent* rawEvents, - size_t count) { +std::list InputReader::processEventsForDeviceLocked(int32_t eventHubId, + const RawEvent* rawEvents, + size_t count) { auto deviceIt = mDevices.find(eventHubId); if (deviceIt == mDevices.end()) { ALOGW("Discarding event for unknown eventHubId %d.", eventHubId); - return; + return {}; } std::shared_ptr& device = deviceIt->second; if (device->isIgnored()) { // ALOGD("Discarding event for ignored deviceId %d.", deviceId); - return; + return {}; } - device->process(rawEvents, count); + return device->process(rawEvents, count); } InputDevice* InputReader::findInputDeviceLocked(int32_t deviceId) const { @@ -336,13 +367,15 @@ InputDevice* InputReader::findInputDeviceLocked(int32_t deviceId) const { return nullptr; } -void InputReader::timeoutExpiredLocked(nsecs_t when) { +std::list InputReader::timeoutExpiredLocked(nsecs_t when) { + std::list out; for (auto& devicePair : mDevices) { std::shared_ptr& device = devicePair.second; if (!device->isIgnored()) { - device->timeoutExpired(when); + out += device->timeoutExpired(when); } } + return out; } int32_t InputReader::nextInputDeviceIdLocked() { @@ -354,46 +387,50 @@ void InputReader::handleConfigurationChangedLocked(nsecs_t when) { updateGlobalMetaStateLocked(); // Enqueue configuration changed. - NotifyConfigurationChangedArgs args(mContext.getNextId(), when); - mQueuedListener.notifyConfigurationChanged(&args); + mQueuedListener.notifyConfigurationChanged({mContext.getNextId(), when}); } -void InputReader::refreshConfigurationLocked(uint32_t changes) { +void InputReader::refreshConfigurationLocked(ConfigurationChanges changes) { mPolicy->getReaderConfiguration(&mConfig); mEventHub->setExcludedDevices(mConfig.excludedDeviceNames); - if (!changes) return; + using Change = InputReaderConfiguration::Change; + if (!changes.any()) return; - ALOGI("Reconfiguring input devices, changes=%s", - InputReaderConfiguration::changesToString(changes).c_str()); + ALOGI("Reconfiguring input devices, changes=%s", changes.string().c_str()); nsecs_t now = systemTime(SYSTEM_TIME_MONOTONIC); - if (changes & InputReaderConfiguration::CHANGE_DISPLAY_INFO) { + if (changes.test(Change::DISPLAY_INFO)) { updatePointerDisplayLocked(); } - if (changes & InputReaderConfiguration::CHANGE_MUST_REOPEN) { + if (changes.test(Change::MUST_REOPEN)) { mEventHub->requestReopenDevices(); } else { for (auto& devicePair : mDevices) { std::shared_ptr& device = devicePair.second; - device->configure(now, &mConfig, changes); + notifyAll(device->configure(now, mConfig, changes)); } } - if (changes & InputReaderConfiguration::CHANGE_POINTER_CAPTURE) { + if (changes.test(Change::POINTER_CAPTURE)) { if (mCurrentPointerCaptureRequest == mConfig.pointerCaptureRequest) { ALOGV("Skipping notifying pointer capture changes: " "There was no change in the pointer capture state."); } else { mCurrentPointerCaptureRequest = mConfig.pointerCaptureRequest; - const NotifyPointerCaptureChangedArgs args(mContext.getNextId(), now, - mCurrentPointerCaptureRequest); - mQueuedListener.notifyPointerCaptureChanged(&args); + mQueuedListener.notifyPointerCaptureChanged( + {mContext.getNextId(), now, mCurrentPointerCaptureRequest}); } } } +void InputReader::notifyAll(std::list&& argsList) { + for (const NotifyArgs& args : argsList) { + mQueuedListener.notify(args); + } +} + void InputReader::updateGlobalMetaStateLocked() { mGlobalMetaState = 0; @@ -420,7 +457,7 @@ int32_t InputReader::getLedMetaStateLocked() { } void InputReader::notifyExternalStylusPresenceChangedLocked() { - refreshConfigurationLocked(InputReaderConfiguration::CHANGE_EXTERNAL_STYLUS_PRESENCE); + refreshConfigurationLocked(InputReaderConfiguration::Change::EXTERNAL_STYLUS_PRESENCE); } void InputReader::getExternalStylusDevicesLocked(std::vector& outDevices) { @@ -432,11 +469,13 @@ void InputReader::getExternalStylusDevicesLocked(std::vector& o } } -void InputReader::dispatchExternalStylusStateLocked(const StylusState& state) { +std::list InputReader::dispatchExternalStylusStateLocked(const StylusState& state) { + std::list out; for (auto& devicePair : mDevices) { std::shared_ptr& device = devicePair.second; - device->updateExternalStylusState(state); + out += device->updateExternalStylusState(state); } + return out; } void InputReader::disableVirtualKeysUntilLocked(nsecs_t time) { @@ -583,34 +622,43 @@ void InputReader::toggleCapsLockState(int32_t deviceId) { device->updateMetaState(AKEYCODE_CAPS_LOCK); } -bool InputReader::hasKeys(int32_t deviceId, uint32_t sourceMask, size_t numCodes, - const int32_t* keyCodes, uint8_t* outFlags) { +bool InputReader::hasKeys(int32_t deviceId, uint32_t sourceMask, + const std::vector& keyCodes, uint8_t* outFlags) { std::scoped_lock _l(mLock); - memset(outFlags, 0, numCodes); - return markSupportedKeyCodesLocked(deviceId, sourceMask, numCodes, keyCodes, outFlags); + memset(outFlags, 0, keyCodes.size()); + return markSupportedKeyCodesLocked(deviceId, sourceMask, keyCodes, outFlags); } bool InputReader::markSupportedKeyCodesLocked(int32_t deviceId, uint32_t sourceMask, - size_t numCodes, const int32_t* keyCodes, + const std::vector& keyCodes, uint8_t* outFlags) { bool result = false; if (deviceId >= 0) { InputDevice* device = findInputDeviceLocked(deviceId); if (device && !device->isIgnored() && sourcesMatchMask(device->getSources(), sourceMask)) { - result = device->markSupportedKeyCodes(sourceMask, numCodes, keyCodes, outFlags); + result = device->markSupportedKeyCodes(sourceMask, keyCodes, outFlags); } } else { for (auto& devicePair : mDevices) { std::shared_ptr& device = devicePair.second; if (!device->isIgnored() && sourcesMatchMask(device->getSources(), sourceMask)) { - result |= device->markSupportedKeyCodes(sourceMask, numCodes, keyCodes, outFlags); + result |= device->markSupportedKeyCodes(sourceMask, keyCodes, outFlags); } } } return result; } +void InputReader::addKeyRemapping(int32_t deviceId, int32_t fromKeyCode, int32_t toKeyCode) const { + std::scoped_lock _l(mLock); + + InputDevice* device = findInputDeviceLocked(deviceId); + if (device != nullptr) { + device->addKeyRemapping(fromKeyCode, toKeyCode); + } +} + int32_t InputReader::getKeyCodeForKeyLocation(int32_t deviceId, int32_t locationKeyCode) const { std::scoped_lock _l(mLock); @@ -623,11 +671,11 @@ int32_t InputReader::getKeyCodeForKeyLocation(int32_t deviceId, int32_t location return device->getKeyCodeForKeyLocation(locationKeyCode); } -void InputReader::requestRefreshConfiguration(uint32_t changes) { +void InputReader::requestRefreshConfiguration(ConfigurationChanges changes) { std::scoped_lock _l(mLock); - if (changes) { - bool needWake = !mConfigurationChangesToRefresh; + if (changes.any()) { + bool needWake = !mConfigurationChangesToRefresh.any(); mConfigurationChangesToRefresh |= changes; if (needWake) { @@ -642,7 +690,7 @@ void InputReader::vibrate(int32_t deviceId, const VibrationSequence& sequence, s InputDevice* device = findInputDeviceLocked(deviceId); if (device) { - device->vibrate(sequence, repeat, token); + notifyAll(device->vibrate(sequence, repeat, token)); } } @@ -651,7 +699,7 @@ void InputReader::cancelVibrate(int32_t deviceId, int32_t token) { InputDevice* device = findInputDeviceLocked(deviceId); if (device) { - device->cancelVibrate(token); + notifyAll(device->cancelVibrate(token)); } } @@ -721,7 +769,10 @@ std::optional InputReader::getBatteryCapacity(int32_t deviceId) { if (!eventHubId) return {}; const auto batteryIds = mEventHub->getRawBatteryIds(*eventHubId); - if (batteryIds.empty()) return {}; + if (batteryIds.empty()) { + ALOGW("%s: There are no battery ids for EventHub device %d", __func__, *eventHubId); + return {}; + } return mEventHub->getBatteryCapacity(*eventHubId, batteryIds.front()); } @@ -741,10 +792,35 @@ std::optional InputReader::getBatteryStatus(int32_t deviceId) { if (!eventHubId) return {}; const auto batteryIds = mEventHub->getRawBatteryIds(*eventHubId); - if (batteryIds.empty()) return {}; + if (batteryIds.empty()) { + ALOGW("%s: There are no battery ids for EventHub device %d", __func__, *eventHubId); + return {}; + } return mEventHub->getBatteryStatus(*eventHubId, batteryIds.front()); } +std::optional InputReader::getBatteryDevicePath(int32_t deviceId) { + std::scoped_lock _l(mLock); + + InputDevice* device = findInputDeviceLocked(deviceId); + if (!device) return {}; + + std::optional eventHubId = device->getBatteryEventHubId(); + if (!eventHubId) return {}; + const auto batteryIds = mEventHub->getRawBatteryIds(*eventHubId); + if (batteryIds.empty()) { + ALOGW("%s: There are no battery ids for EventHub device %d", __func__, *eventHubId); + return {}; + } + const auto batteryInfo = mEventHub->getRawBatteryInfo(*eventHubId, batteryIds.front()); + if (!batteryInfo) { + ALOGW("%s: Failed to get RawBatteryInfo for battery %d of EventHub device %d", __func__, + batteryIds.front(), *eventHubId); + return {}; + } + return batteryInfo->path; +} + std::vector InputReader::getLights(int32_t deviceId) { std::scoped_lock _l(mLock); @@ -807,6 +883,16 @@ std::optional InputReader::getLightPlayerId(int32_t deviceId, int32_t l return std::nullopt; } +std::optional InputReader::getBluetoothAddress(int32_t deviceId) const { + std::scoped_lock _l(mLock); + + InputDevice* device = findInputDeviceLocked(deviceId); + if (device) { + return device->getBluetoothAddress(); + } + return std::nullopt; +} + bool InputReader::isInputDeviceEnabled(int32_t deviceId) { std::scoped_lock _l(mLock); @@ -842,6 +928,10 @@ bool InputReader::canDispatchToDisplay(int32_t deviceId, int32_t displayId) { return *associatedDisplayId == displayId; } +void InputReader::sysfsNodeChanged(const std::string& sysfsNodePath) { + mEventHub->sysfsNodeChanged(sysfsNodePath); +} + void InputReader::dump(std::string& dump) { std::scoped_lock _l(mLock); @@ -987,18 +1077,15 @@ void InputReader::ContextImpl::getExternalStylusDevices(std::vectorgetExternalStylusDevicesLocked(outDevices); } -void InputReader::ContextImpl::dispatchExternalStylusState(const StylusState& state) { - mReader->dispatchExternalStylusStateLocked(state); +std::list InputReader::ContextImpl::dispatchExternalStylusState( + const StylusState& state) { + return mReader->dispatchExternalStylusStateLocked(state); } InputReaderPolicyInterface* InputReader::ContextImpl::getPolicy() { return mReader->mPolicy.get(); } -InputListenerInterface& InputReader::ContextImpl::getListener() { - return mReader->mQueuedListener; -} - EventHubInterface* InputReader::ContextImpl::getEventHub() { return mReader->mEventHub.get(); } diff --git a/services/inputflinger/reader/Macros.cpp b/services/inputflinger/reader/Macros.cpp new file mode 100644 index 0000000000000000000000000000000000000000..8841d0f14a74fccb6541eee5d9ddc13cb8aa90ed --- /dev/null +++ b/services/inputflinger/reader/Macros.cpp @@ -0,0 +1,43 @@ +/* + * 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. + */ + +#include "Macros.h" + +#include + +namespace { + +const bool IS_DEBUGGABLE_BUILD = +#if defined(__ANDROID__) + android::base::GetBoolProperty("ro.debuggable", false); +#else + true; +#endif + +} // namespace + +namespace android { + +bool debugRawEvents() { + if (!IS_DEBUGGABLE_BUILD) { + static const bool DEBUG_RAW_EVENTS = + __android_log_is_loggable(ANDROID_LOG_DEBUG, LOG_TAG "RawEvents", ANDROID_LOG_INFO); + return DEBUG_RAW_EVENTS; + } + return __android_log_is_loggable(ANDROID_LOG_DEBUG, LOG_TAG "RawEvents", ANDROID_LOG_INFO); +} + +} // namespace android diff --git a/services/inputflinger/reader/Macros.h b/services/inputflinger/reader/Macros.h index d8376894f6075583bd5e9b392e3acd94a3d54b4d..2bce215b3cb0e7c518f2eff062a52fda316a7cec 100644 --- a/services/inputflinger/reader/Macros.h +++ b/services/inputflinger/reader/Macros.h @@ -14,33 +14,76 @@ * limitations under the License. */ -#ifndef _UI_INPUTREADER_MACROS_H -#define _UI_INPUTREADER_MACROS_H +#pragma once #define LOG_TAG "InputReader" //#define LOG_NDEBUG 0 +#include +#include -// Log debug messages for each raw event received from the EventHub. -static constexpr bool DEBUG_RAW_EVENTS = false; +#include -// Log debug messages about virtual key processing. -static constexpr bool DEBUG_VIRTUAL_KEYS = false; +namespace android { -// Log debug messages about pointers. -static constexpr bool DEBUG_POINTERS = false; +/** + * Log debug messages for each raw event received from the EventHub. + * Enable this via "adb shell setprop log.tag.InputReaderRawEvents DEBUG". + * This requires a restart on non-debuggable (e.g. user) builds, but should take effect immediately + * on debuggable builds (e.g. userdebug). + */ +bool debugRawEvents(); -// Log debug messages about pointer assignment calculations. -static constexpr bool DEBUG_POINTER_ASSIGNMENT = false; +/** + * Log debug messages about virtual key processing. + * Enable this via "adb shell setprop log.tag.InputReaderVirtualKeys DEBUG" (requires restart) + */ +const bool DEBUG_VIRTUAL_KEYS = + __android_log_is_loggable(ANDROID_LOG_DEBUG, LOG_TAG "VirtualKeys", ANDROID_LOG_INFO); -// Log debug messages about gesture detection. -static constexpr bool DEBUG_GESTURES = false; +/** + * Log debug messages about pointers. + * Enable this via "adb shell setprop log.tag.InputReaderPointers DEBUG" (requires restart) + */ +const bool DEBUG_POINTERS = + __android_log_is_loggable(ANDROID_LOG_DEBUG, LOG_TAG "Pointers", ANDROID_LOG_INFO); -// Log debug messages about the vibrator. -static constexpr bool DEBUG_VIBRATOR = false; +/** + * Log debug messages about pointer assignment calculations. + * Enable this via "adb shell setprop log.tag.InputReaderPointerAssignment DEBUG" (requires restart) + */ +const bool DEBUG_POINTER_ASSIGNMENT = + __android_log_is_loggable(ANDROID_LOG_DEBUG, LOG_TAG "PointerAssignment", ANDROID_LOG_INFO); -// Log debug messages about fusing stylus data. -static constexpr bool DEBUG_STYLUS_FUSION = false; +/** + * Log debug messages about gesture detection. + * Enable this via "adb shell setprop log.tag.InputReaderGestures DEBUG" (requires restart) + */ +const bool DEBUG_GESTURES = + __android_log_is_loggable(ANDROID_LOG_DEBUG, LOG_TAG "Gestures", ANDROID_LOG_INFO); + +/** + * Log debug messages about the vibrator. + * Enable this via "adb shell setprop log.tag.InputReaderVibrator DEBUG" (requires restart) + */ +const bool DEBUG_VIBRATOR = + __android_log_is_loggable(ANDROID_LOG_DEBUG, LOG_TAG "Vibrator", ANDROID_LOG_INFO); + +/** + * Log debug messages about fusing stylus data. + * Enable this via "adb shell setprop log.tag.InputReaderStylusFusion DEBUG" (requires restart) + */ +const bool DEBUG_STYLUS_FUSION = + __android_log_is_loggable(ANDROID_LOG_DEBUG, LOG_TAG "StylusFusion", ANDROID_LOG_INFO); + +/** + * Log detailed debug messages about input device lights. + * Enable this via "adb shell setprop log.tag.InputReaderLightDetails DEBUG" (requires restart) + */ +const bool DEBUG_LIGHT_DETAILS = + __android_log_is_loggable(ANDROID_LOG_DEBUG, LOG_TAG "LightDetails", ANDROID_LOG_INFO); + +} // namespace android #define INDENT " " #define INDENT2 " " @@ -76,6 +119,14 @@ static inline bool sourcesMatchMask(uint32_t sources, uint32_t sourceMask) { return (sources & sourceMask & ~AINPUT_SOURCE_CLASS_MASK) != 0; } -} // namespace android +template +static inline std::optional getValueByKey(const std::unordered_map& map, K key) { + auto it = map.find(key); + std::optional value = std::nullopt; + if (it != map.end()) { + value = it->second; + } + return value; +} -#endif // _UI_INPUTREADER_MACROS_H \ No newline at end of file +} // namespace android diff --git a/services/inputflinger/reader/TouchVideoDevice.cpp b/services/inputflinger/reader/TouchVideoDevice.cpp index 2f8138b832099f549ebd72605d41e871ba7c8715..627dcba9cf26b9fb5f8f43678f289651a64fbe6a 100644 --- a/services/inputflinger/reader/TouchVideoDevice.cpp +++ b/services/inputflinger/reader/TouchVideoDevice.cpp @@ -198,8 +198,9 @@ std::optional TouchVideoDevice::readFrame() { if ((buf.flags & V4L2_BUF_FLAG_TIMESTAMP_MASK) != V4L2_BUF_FLAG_TIMESTAMP_MONOTONIC) { // We use CLOCK_MONOTONIC for input events, so if the clocks don't match, // we can't compare timestamps. Just log a warning, since this is a driver issue - ALOGW("The timestamp %ld.%ld was not acquired using CLOCK_MONOTONIC", buf.timestamp.tv_sec, - buf.timestamp.tv_usec); + ALOGW("The timestamp %lld.%lld was not acquired using CLOCK_MONOTONIC", + static_cast(buf.timestamp.tv_sec), + static_cast(buf.timestamp.tv_usec)); } std::vector data(mHeight * mWidth); const int16_t* readFrom = mReadLocations[buf.index]; diff --git a/services/inputflinger/reader/controller/PeripheralController.cpp b/services/inputflinger/reader/controller/PeripheralController.cpp index 8065f575242cb0e081865bbe81dc7f1b45760a61..a380b5eadf034d2d26f532c7a27afc08a981216e 100644 --- a/services/inputflinger/reader/controller/PeripheralController.cpp +++ b/services/inputflinger/reader/controller/PeripheralController.cpp @@ -16,15 +16,13 @@ #include #include +#include #include #include "../Macros.h" #include "PeripheralController.h" -// Log detailed debug messages about input device lights. -static constexpr bool DEBUG_LIGHT_DETAILS = false; - namespace android { static inline int32_t getAlpha(int32_t color) { @@ -73,7 +71,7 @@ std::optional PeripheralController::Light::getRawLightBrightness(i // If the light node doesn't have max brightness, use the default max brightness. int rawMaxBrightness = rawInfoOpt->maxBrightness.value_or(MAX_BRIGHTNESS); - float ratio = MAX_BRIGHTNESS / rawMaxBrightness; + float ratio = static_cast(MAX_BRIGHTNESS) / rawMaxBrightness; // Scale the returned brightness in [0, rawMaxBrightness] to [0, 255] if (rawMaxBrightness != MAX_BRIGHTNESS) { brightness = brightness * ratio; @@ -92,7 +90,7 @@ void PeripheralController::Light::setRawLightBrightness(int32_t rawLightId, int3 } // If the light node doesn't have max brightness, use the default max brightness. int rawMaxBrightness = rawInfo->maxBrightness.value_or(MAX_BRIGHTNESS); - float ratio = MAX_BRIGHTNESS / rawMaxBrightness; + float ratio = static_cast(MAX_BRIGHTNESS) / rawMaxBrightness; // Scale the requested brightness in [0, 255] to [0, rawMaxBrightness] if (rawMaxBrightness != MAX_BRIGHTNESS) { brightness = ceil(brightness / ratio); @@ -154,7 +152,7 @@ std::optional PeripheralController::MonoLight::getLightColor() { return std::nullopt; } - return toArgb(brightness.value(), 0 /* red */, 0 /* green */, 0 /* blue */); + return toArgb(brightness.value(), /*red=*/0, /*green=*/0, /*blue=*/0); } std::optional PeripheralController::RgbLight::getLightColor() { @@ -199,13 +197,12 @@ std::optional PeripheralController::MultiColorLight::getLightColor() { } std::unordered_map intensities = ret.value(); // Get red, green, blue colors - int32_t color = toArgb(0 /* brightness */, intensities.at(LightColor::RED) /* red */, - intensities.at(LightColor::GREEN) /* green */, - intensities.at(LightColor::BLUE) /* blue */); + int32_t color = toArgb(/*brightness=*/0, intensities.at(LightColor::RED), + intensities.at(LightColor::GREEN), intensities.at(LightColor::BLUE)); // Get brightness std::optional brightness = getRawLightBrightness(rawId); if (brightness.has_value()) { - return toArgb(brightness.value() /* A */, 0, 0, 0) | color; + return toArgb(/*brightness=*/brightness.value(), 0, 0, 0) | color; } return std::nullopt; } @@ -274,7 +271,8 @@ void PeripheralController::populateDeviceInfo(InputDeviceInfo* deviceInfo) { for (const auto& [lightId, light] : mLights) { // Input device light doesn't support ordinal, always pass 1. - InputDeviceLightInfo lightInfo(light->name, light->id, light->type, 1 /* ordinal */); + InputDeviceLightInfo lightInfo(light->name, light->id, light->type, light->capabilityFlags, + /*ordinal=*/1); deviceInfo->addLightInfo(lightInfo); } } @@ -287,6 +285,8 @@ void PeripheralController::dump(std::string& dump) { dump += StringPrintf(INDENT4 "Id: %d", lightId); dump += StringPrintf(INDENT4 "Name: %s", light->name.c_str()); dump += StringPrintf(INDENT4 "Type: %s", ftl::enum_string(light->type).c_str()); + dump += StringPrintf(INDENT4 "Capability flags: %s", + light->capabilityFlags.string().c_str()); light->dump(dump); } } @@ -366,6 +366,8 @@ void PeripheralController::configureLights() { std::unordered_map rawRgbIds; // Map from player Id to raw light Id std::unordered_map playerIdLightIds; + // Set of Keyboard backlights + std::set keyboardBacklightIds; // Check raw lights const std::vector rawLightIds = getDeviceContext().getRawLightIds(); @@ -394,6 +396,10 @@ void PeripheralController::configureLights() { } } } + // Check if this is a Keyboard backlight + if (rawInfo->flags.test(InputLightClass::KEYBOARD_BACKLIGHT)) { + keyboardBacklightIds.insert(rawId); + } // Check if this is an LED of RGB light if (rawInfo->flags.test(InputLightClass::RED)) { hasRedLed = true; @@ -434,8 +440,21 @@ void PeripheralController::configureLights() { ALOGD("Rgb light ids [%d, %d, %d] \n", rawRgbIds.at(LightColor::RED), rawRgbIds.at(LightColor::GREEN), rawRgbIds.at(LightColor::BLUE)); } + bool isKeyboardBacklight = keyboardBacklightIds.find(rawRgbIds.at(LightColor::RED)) != + keyboardBacklightIds.end() && + keyboardBacklightIds.find(rawRgbIds.at(LightColor::GREEN)) != + keyboardBacklightIds.end() && + keyboardBacklightIds.find(rawRgbIds.at(LightColor::BLUE)) != + keyboardBacklightIds.end() && + (!rawGlobalId.has_value() || + keyboardBacklightIds.find(rawGlobalId.value()) != keyboardBacklightIds.end()); + std::unique_ptr light = - std::make_unique(getDeviceContext(), ++mNextId, rawRgbIds, rawGlobalId); + std::make_unique(getDeviceContext(), ++mNextId, + isKeyboardBacklight + ? InputDeviceLightType::KEYBOARD_BACKLIGHT + : InputDeviceLightType::INPUT, + rawRgbIds, rawGlobalId); mLights.insert_or_assign(light->id, std::move(light)); // Remove from raw light info as they've been composed a RBG light. rawInfos.erase(rawRgbIds.at(LightColor::RED)); @@ -448,6 +467,10 @@ void PeripheralController::configureLights() { // Check the rest of raw light infos for (const auto& [rawId, rawInfo] : rawInfos) { + InputDeviceLightType type = keyboardBacklightIds.find(rawId) != keyboardBacklightIds.end() + ? InputDeviceLightType::KEYBOARD_BACKLIGHT + : InputDeviceLightType::INPUT; + // If the node is multi-color led, construct a MULTI_COLOR light if (rawInfo.flags.test(InputLightClass::MULTI_INDEX) && rawInfo.flags.test(InputLightClass::MULTI_INTENSITY)) { @@ -456,7 +479,7 @@ void PeripheralController::configureLights() { } std::unique_ptr light = std::make_unique(getDeviceContext(), rawInfo.name, ++mNextId, - rawInfo.id); + type, rawInfo.id); mLights.insert_or_assign(light->id, std::move(light)); continue; } @@ -465,7 +488,7 @@ void PeripheralController::configureLights() { ALOGD("Mono light Id %d name %s \n", rawInfo.id, rawInfo.name.c_str()); } std::unique_ptr light = std::make_unique(getDeviceContext(), rawInfo.name, - ++mNextId, rawInfo.id); + ++mNextId, type, rawInfo.id); mLights.insert_or_assign(light->id, std::move(light)); } diff --git a/services/inputflinger/reader/controller/PeripheralController.h b/services/inputflinger/reader/controller/PeripheralController.h index ac951ebe2a122e9b359ecaaa5cf25ab573e1d4a6..8ac42c3792a729a8cf729fd77c1b7814d5b766a4 100644 --- a/services/inputflinger/reader/controller/PeripheralController.h +++ b/services/inputflinger/reader/controller/PeripheralController.h @@ -14,8 +14,7 @@ * limitations under the License. */ -#ifndef _UI_INPUTREADER_LIGHT_CONTROLLER_H -#define _UI_INPUTREADER_LIGHT_CONTROLLER_H +#pragma once #include "PeripheralControllerInterface.h" @@ -68,6 +67,7 @@ private: std::string name; int32_t id; InputDeviceLightType type; + ftl::Flags capabilityFlags; virtual bool setLightColor(int32_t color) { return false; } virtual std::optional getLightColor() { return std::nullopt; } @@ -82,8 +82,10 @@ private: struct MonoLight : public Light { explicit MonoLight(InputDeviceContext& context, const std::string& name, int32_t id, - int32_t rawId) - : Light(context, name, id, InputDeviceLightType::MONO), rawId(rawId) {} + InputDeviceLightType type, int32_t rawId) + : Light(context, name, id, type), rawId(rawId) { + capabilityFlags |= InputDeviceLightCapability::BRIGHTNESS; + } int32_t rawId; bool setLightColor(int32_t color) override; @@ -92,15 +94,15 @@ private: }; struct RgbLight : public Light { - explicit RgbLight(InputDeviceContext& context, int32_t id, + explicit RgbLight(InputDeviceContext& context, int32_t id, InputDeviceLightType type, const std::unordered_map& rawRgbIds, std::optional rawGlobalId) - : Light(context, "RGB", id, InputDeviceLightType::RGB), - rawRgbIds(rawRgbIds), - rawGlobalId(rawGlobalId) { + : Light(context, "RGB", id, type), rawRgbIds(rawRgbIds), rawGlobalId(rawGlobalId) { brightness = rawGlobalId.has_value() ? getRawLightBrightness(rawGlobalId.value()).value_or(MAX_BRIGHTNESS) : MAX_BRIGHTNESS; + capabilityFlags |= InputDeviceLightCapability::BRIGHTNESS; + capabilityFlags |= InputDeviceLightCapability::RGB; } // Map from color to raw light id. std::unordered_map rawRgbIds; @@ -115,8 +117,11 @@ private: struct MultiColorLight : public Light { explicit MultiColorLight(InputDeviceContext& context, const std::string& name, int32_t id, - int32_t rawId) - : Light(context, name, id, InputDeviceLightType::MULTI_COLOR), rawId(rawId) {} + InputDeviceLightType type, int32_t rawId) + : Light(context, name, id, type), rawId(rawId) { + capabilityFlags |= InputDeviceLightCapability::BRIGHTNESS; + capabilityFlags |= InputDeviceLightCapability::RGB; + } int32_t rawId; bool setLightColor(int32_t color) override; @@ -132,7 +137,7 @@ private: // Map from player Id to raw light Id std::unordered_map rawLightIds; - bool setLightPlayerId(int32_t palyerId) override; + bool setLightPlayerId(int32_t playerId) override; std::optional getLightPlayerId() override; void dump(std::string& dump) override; }; @@ -150,5 +155,3 @@ private: }; } // namespace android - -#endif // _UI_INPUTREADER_LIGHT_CONTROLLER_H diff --git a/services/inputflinger/reader/controller/PeripheralControllerInterface.h b/services/inputflinger/reader/controller/PeripheralControllerInterface.h index 306e36119bf5c7e04495cfa58948e8fe0c972b9d..76ed1ca038e5887616a6650b3a04d730033d9728 100644 --- a/services/inputflinger/reader/controller/PeripheralControllerInterface.h +++ b/services/inputflinger/reader/controller/PeripheralControllerInterface.h @@ -14,8 +14,7 @@ * limitations under the License. */ -#ifndef _UI_INPUTREADER_INPUT_CONTROLLER_H -#define _UI_INPUTREADER_INPUT_CONTROLLER_H +#pragma once #include "EventHub.h" #include "InputDevice.h" @@ -50,5 +49,3 @@ public: }; } // namespace android - -#endif // _UI_INPUTREADER_INPUT_CONTROLLER_H diff --git a/services/inputflinger/reader/include/EventHub.h b/services/inputflinger/reader/include/EventHub.h index 54c6810a33e2d6318b099e21d2e1c05e47c39f0b..20612c767c9f5cde5faf817ae6598f14c05a1a3f 100644 --- a/services/inputflinger/reader/include/EventHub.h +++ b/services/inputflinger/reader/include/EventHub.h @@ -14,13 +14,15 @@ * limitations under the License. */ -#ifndef _RUNTIME_EVENT_HUB_H -#define _RUNTIME_EVENT_HUB_H +#pragma once #include #include #include +#include +#include #include +#include #include #include @@ -36,7 +38,6 @@ #include #include #include -#include #include #include #include @@ -44,6 +45,8 @@ #include "TouchVideoDevice.h" #include "VibrationElement.h" +struct inotify_event; + namespace android { /* Number of colors : {red, green, blue} */ @@ -65,24 +68,19 @@ struct RawEvent { /* Describes an absolute axis. */ struct RawAbsoluteAxisInfo { - bool valid; // true if the information is valid, false otherwise - - int32_t minValue; // minimum value - int32_t maxValue; // maximum value - int32_t flat; // center flat position, eg. flat == 8 means center is between -8 and 8 - int32_t fuzz; // error tolerance, eg. fuzz == 4 means value is +/- 4 due to noise - int32_t resolution; // resolution in units per mm or radians per mm - - inline void clear() { - valid = false; - minValue = 0; - maxValue = 0; - flat = 0; - fuzz = 0; - resolution = 0; - } + bool valid{false}; // true if the information is valid, false otherwise + + int32_t minValue{}; // minimum value + int32_t maxValue{}; // maximum value + int32_t flat{}; // center flat position, eg. flat == 8 means center is between -8 and 8 + int32_t fuzz{}; // error tolerance, eg. fuzz == 4 means value is +/- 4 due to noise + int32_t resolution{}; // resolution in units per mm or radians per mm + + inline void clear() { *this = RawAbsoluteAxisInfo(); } }; +std::ostream& operator<<(std::ostream& out, const RawAbsoluteAxisInfo& info); + /* * Input device classes. */ @@ -99,7 +97,7 @@ enum class InputDeviceClass : uint32_t { /* The input device is a cursor device such as a trackball or mouse. */ CURSOR = 0x00000008, - /* The input device is a multi-touch touchscreen. */ + /* The input device is a multi-touch touchscreen or touchpad. */ TOUCH_MT = 0x00000010, /* The input device is a directional pad (implies keyboard, has DPAD keys). */ @@ -135,6 +133,9 @@ enum class InputDeviceClass : uint32_t { /* The input device has sysfs controllable lights */ LIGHT = 0x00008000, + /* The input device is a touchpad, requiring an on-screen cursor. */ + TOUCHPAD = 0x00010000, + /* The input device is virtual (not a real device, not part of UI configuration). */ VIRTUAL = 0x40000000, @@ -172,6 +173,8 @@ enum class InputLightClass : uint32_t { MULTI_INTENSITY = 0x00000040, /* The input light has max brightness node. */ MAX_BRIGHTNESS = 0x00000080, + /* The input light has kbd_backlight name */ + KEYBOARD_BACKLIGHT = 0x00000100, }; enum class InputBatteryClass : uint32_t { @@ -191,6 +194,9 @@ struct RawLightInfo { ftl::Flags flags; std::array rgbIndex; std::filesystem::path path; + + bool operator==(const RawLightInfo&) const = default; + bool operator!=(const RawLightInfo&) const = default; }; /* Describes a raw battery. */ @@ -199,6 +205,18 @@ struct RawBatteryInfo { std::string name; ftl::Flags flags; std::filesystem::path path; + + bool operator==(const RawBatteryInfo&) const = default; + bool operator!=(const RawBatteryInfo&) const = default; +}; + +/* Layout information associated with the device */ +struct RawLayoutInfo { + std::string languageTag; + std::string layoutType; + + bool operator==(const RawLayoutInfo&) const = default; + bool operator!=(const RawLayoutInfo&) const = default; }; /* @@ -245,7 +263,13 @@ public: virtual int32_t getDeviceControllerNumber(int32_t deviceId) const = 0; - virtual void getConfiguration(int32_t deviceId, PropertyMap* outConfiguration) const = 0; + /** + * Get the PropertyMap for the provided EventHub device, if available. + * This acquires the device lock, so a copy is returned rather than the raw pointer + * to the device's PropertyMap. A std::nullopt may be returned if the device could + * not be found, or if it doesn't have any configuration. + */ + virtual std::optional getConfiguration(int32_t deviceId) const = 0; virtual status_t getAbsoluteAxisInfo(int32_t deviceId, int axis, RawAbsoluteAxisInfo* outAxisInfo) const = 0; @@ -256,6 +280,9 @@ public: virtual bool hasMscEvent(int32_t deviceId, int mscEvent) const = 0; + virtual void addKeyRemapping(int32_t deviceId, int32_t fromKeyCode, + int32_t toKeyCode) const = 0; + virtual status_t mapKey(int32_t deviceId, int32_t scanCode, int32_t usageCode, int32_t metaState, int32_t* outKeycode, int32_t* outMetaState, uint32_t* outFlags) const = 0; @@ -278,29 +305,30 @@ public: * * Returns the number of events obtained, or 0 if the timeout expired. */ - virtual size_t getEvents(int timeoutMillis, RawEvent* buffer, size_t bufferSize) = 0; + virtual std::vector getEvents(int timeoutMillis) = 0; virtual std::vector getVideoFrames(int32_t deviceId) = 0; - virtual base::Result> mapSensor(int32_t deviceId, - int32_t absCode) = 0; + virtual base::Result> mapSensor( + int32_t deviceId, int32_t absCode) const = 0; // Raw batteries are sysfs power_supply nodes we found from the EventHub device sysfs node, // containing the raw info of the sysfs node structure. - virtual const std::vector getRawBatteryIds(int32_t deviceId) = 0; + virtual std::vector getRawBatteryIds(int32_t deviceId) const = 0; virtual std::optional getRawBatteryInfo(int32_t deviceId, - int32_t BatteryId) = 0; + int32_t BatteryId) const = 0; // Raw lights are sysfs led light nodes we found from the EventHub device sysfs node, // containing the raw info of the sysfs node structure. - virtual const std::vector getRawLightIds(int32_t deviceId) = 0; - virtual std::optional getRawLightInfo(int32_t deviceId, int32_t lightId) = 0; - virtual std::optional getLightBrightness(int32_t deviceId, int32_t lightId) = 0; + virtual std::vector getRawLightIds(int32_t deviceId) const = 0; + virtual std::optional getRawLightInfo(int32_t deviceId, + int32_t lightId) const = 0; + virtual std::optional getLightBrightness(int32_t deviceId, int32_t lightId) const = 0; virtual void setLightBrightness(int32_t deviceId, int32_t lightId, int32_t brightness) = 0; virtual std::optional> getLightIntensities( - int32_t deviceId, int32_t lightId) = 0; + int32_t deviceId, int32_t lightId) const = 0; virtual void setLightIntensities(int32_t deviceId, int32_t lightId, std::unordered_map intensities) = 0; - /* - * Query current input state. - */ + /* Query Layout info associated with the input device. */ + virtual std::optional getRawLayoutInfo(int32_t deviceId) const = 0; + /* Query current input state. */ virtual int32_t getScanCodeState(int32_t deviceId, int32_t scanCode) const = 0; virtual int32_t getKeyCodeState(int32_t deviceId, int32_t keyCode) const = 0; virtual int32_t getSwitchState(int32_t deviceId, int32_t sw) const = 0; @@ -311,7 +339,7 @@ public: /* * Examine key input devices for specific framework keycode support */ - virtual bool markSupportedKeyCodes(int32_t deviceId, size_t numCodes, const int32_t* keyCodes, + virtual bool markSupportedKeyCodes(int32_t deviceId, const std::vector& keyCodes, uint8_t* outFlags) const = 0; virtual bool hasScanCode(int32_t deviceId, int32_t scanCode) const = 0; @@ -331,7 +359,7 @@ public: /* Control the vibrator. */ virtual void vibrate(int32_t deviceId, const VibrationElement& effect) = 0; virtual void cancelVibrate(int32_t deviceId) = 0; - virtual std::vector getVibratorIds(int32_t deviceId) = 0; + virtual std::vector getVibratorIds(int32_t deviceId) const = 0; /* Query battery level. */ virtual std::optional getBatteryCapacity(int32_t deviceId, @@ -347,19 +375,23 @@ public: virtual void wake() = 0; /* Dump EventHub state to a string. */ - virtual void dump(std::string& dump) = 0; + virtual void dump(std::string& dump) const = 0; /* Called by the heatbeat to ensures that the reader has not deadlocked. */ - virtual void monitor() = 0; + virtual void monitor() const = 0; /* Return true if the device is enabled. */ - virtual bool isDeviceEnabled(int32_t deviceId) = 0; + virtual bool isDeviceEnabled(int32_t deviceId) const = 0; /* Enable an input device */ virtual status_t enableDevice(int32_t deviceId) = 0; /* Disable an input device. Closes file descriptor to that device. */ virtual status_t disableDevice(int32_t deviceId) = 0; + + /* Sysfs node changed. Reopen the Eventhub device if any new Peripheral like Light, Battery, + * etc. is detected. */ + virtual void sysfsNodeChanged(const std::string& sysfsNodePath) = 0; }; template @@ -442,7 +474,7 @@ public: int32_t getDeviceControllerNumber(int32_t deviceId) const override final; - void getConfiguration(int32_t deviceId, PropertyMap* outConfiguration) const override final; + std::optional getConfiguration(int32_t deviceId) const override final; status_t getAbsoluteAxisInfo(int32_t deviceId, int axis, RawAbsoluteAxisInfo* outAxisInfo) const override final; @@ -453,6 +485,9 @@ public: bool hasMscEvent(int32_t deviceId, int mscEvent) const override final; + void addKeyRemapping(int32_t deviceId, int32_t fromKeyCode, + int32_t toKeyCode) const override final; + status_t mapKey(int32_t deviceId, int32_t scanCode, int32_t usageCode, int32_t metaState, int32_t* outKeycode, int32_t* outMetaState, uint32_t* outFlags) const override final; @@ -461,23 +496,27 @@ public: AxisInfo* outAxisInfo) const override final; base::Result> mapSensor( - int32_t deviceId, int32_t absCode) override final; + int32_t deviceId, int32_t absCode) const override final; - const std::vector getRawBatteryIds(int32_t deviceId) override final; + std::vector getRawBatteryIds(int32_t deviceId) const override final; std::optional getRawBatteryInfo(int32_t deviceId, - int32_t BatteryId) override final; + int32_t BatteryId) const override final; - const std::vector getRawLightIds(int32_t deviceId) override final; + std::vector getRawLightIds(int32_t deviceId) const override final; - std::optional getRawLightInfo(int32_t deviceId, int32_t lightId) override final; + std::optional getRawLightInfo(int32_t deviceId, + int32_t lightId) const override final; - std::optional getLightBrightness(int32_t deviceId, int32_t lightId) override final; + std::optional getLightBrightness(int32_t deviceId, + int32_t lightId) const override final; void setLightBrightness(int32_t deviceId, int32_t lightId, int32_t brightness) override final; std::optional> getLightIntensities( - int32_t deviceId, int32_t lightId) override final; + int32_t deviceId, int32_t lightId) const override final; void setLightIntensities(int32_t deviceId, int32_t lightId, std::unordered_map intensities) override final; + std::optional getRawLayoutInfo(int32_t deviceId) const override final; + void setExcludedDevices(const std::vector& devices) override final; int32_t getScanCodeState(int32_t deviceId, int32_t scanCode) const override final; @@ -488,10 +527,10 @@ public: status_t getAbsoluteAxisValue(int32_t deviceId, int32_t axis, int32_t* outValue) const override final; - bool markSupportedKeyCodes(int32_t deviceId, size_t numCodes, const int32_t* keyCodes, + bool markSupportedKeyCodes(int32_t deviceId, const std::vector& keyCodes, uint8_t* outFlags) const override final; - size_t getEvents(int timeoutMillis, RawEvent* buffer, size_t bufferSize) override final; + std::vector getEvents(int timeoutMillis) override final; std::vector getVideoFrames(int32_t deviceId) override final; bool hasScanCode(int32_t deviceId, int32_t scanCode) const override final; @@ -510,15 +549,15 @@ public: void vibrate(int32_t deviceId, const VibrationElement& effect) override final; void cancelVibrate(int32_t deviceId) override final; - std::vector getVibratorIds(int32_t deviceId) override final; + std::vector getVibratorIds(int32_t deviceId) const override final; void requestReopenDevices() override final; void wake() override final; - void dump(std::string& dump) override final; + void dump(std::string& dump) const override final; - void monitor() override final; + void monitor() const override final; std::optional getBatteryCapacity(int32_t deviceId, int32_t batteryId) const override final; @@ -526,29 +565,29 @@ public: std::optional getBatteryStatus(int32_t deviceId, int32_t batteryId) const override final; - bool isDeviceEnabled(int32_t deviceId) override final; + bool isDeviceEnabled(int32_t deviceId) const override final; status_t enableDevice(int32_t deviceId) override final; status_t disableDevice(int32_t deviceId) override final; + void sysfsNodeChanged(const std::string& sysfsNodePath) override final; + ~EventHub() override; private: + // Holds information about the sysfs device associated with the Device. struct AssociatedDevice { - // The device descriptor from evdev device the misc device associated with. - std::string descriptor; // The sysfs root path of the misc device. std::filesystem::path sysfsRootPath; - - int32_t nextBatteryId; - int32_t nextLightId; - std::unordered_map batteryInfos; - std::unordered_map lightInfos; - explicit AssociatedDevice(std::filesystem::path sysfsRootPath) - : sysfsRootPath(sysfsRootPath), nextBatteryId(0), nextLightId(0) {} - bool configureBatteryLocked(); - bool configureLightsLocked(); + std::unordered_map batteryInfos; + std::unordered_map lightInfos; + std::optional layoutInfo; + + bool isChanged() const; + bool operator==(const AssociatedDevice&) const = default; + bool operator!=(const AssociatedDevice&) const = default; + std::string dump() const; }; struct Device { @@ -581,13 +620,13 @@ private: int16_t ffEffectId; // initially -1 // A shared_ptr of a device associated with the input device. - // The input devices with same descriptor has the same associated device. - std::shared_ptr associatedDevice; + // The input devices that have the same sysfs path have the same associated device. + std::shared_ptr associatedDevice; int32_t controllerNumber; - Device(int fd, int32_t id, const std::string& path, - const InputDeviceIdentifier& identifier); + Device(int fd, int32_t id, std::string path, InputDeviceIdentifier identifier, + std::shared_ptr assocDev); ~Device(); void close(); @@ -632,6 +671,8 @@ private: void createVirtualKeyboardLocked() REQUIRES(mLock); void addDeviceLocked(std::unique_ptr device) REQUIRES(mLock); void assignDescriptorLocked(InputDeviceIdentifier& identifier) REQUIRES(mLock); + std::shared_ptr obtainAssociatedDeviceLocked( + const std::filesystem::path& devicePath) const REQUIRES(mLock); void closeDeviceByPathLocked(const std::string& devicePath) REQUIRES(mLock); void closeVideoDeviceByPathLocked(const std::string& devicePath) REQUIRES(mLock); @@ -648,7 +689,8 @@ private: status_t scanDirLocked(const std::string& dirname) REQUIRES(mLock); status_t scanVideoDirLocked(const std::string& dirname) REQUIRES(mLock); void scanDevicesLocked() REQUIRES(mLock); - status_t readNotifyLocked() REQUIRES(mLock); + base::Result readNotifyLocked() REQUIRES(mLock); + void handleNotifyEventLocked(const inotify_event&) REQUIRES(mLock); Device* getDeviceLocked(int32_t deviceId) const REQUIRES(mLock); Device* getDeviceByPathLocked(const std::string& devicePath) const REQUIRES(mLock); @@ -728,5 +770,3 @@ private: }; } // namespace android - -#endif // _RUNTIME_EVENT_HUB_H diff --git a/services/inputflinger/reader/include/InputDevice.h b/services/inputflinger/reader/include/InputDevice.h index b3a24af8a92065583eaf3573cbfea473646cb54c..0b8a608891620699403eccfaed965ae305a2d747 100644 --- a/services/inputflinger/reader/include/InputDevice.h +++ b/services/inputflinger/reader/include/InputDevice.h @@ -14,8 +14,7 @@ * limitations under the License. */ -#ifndef _UI_INPUTREADER_INPUT_DEVICE_H -#define _UI_INPUTREADER_INPUT_DEVICE_H +#pragma once #include #include @@ -30,6 +29,7 @@ #include "EventHub.h" #include "InputReaderBase.h" #include "InputReaderContext.h" +#include "NotifyArgs.h" namespace android { @@ -51,6 +51,10 @@ public: inline int32_t getGeneration() const { return mGeneration; } inline const std::string getName() const { return mIdentifier.name; } inline const std::string getDescriptor() { return mIdentifier.descriptor; } + inline std::optional getBluetoothAddress() const { + return mIdentifier.bluetoothAddress; + } + inline const std::string getLocation() const { return mIdentifier.location; } inline ftl::Flags getClasses() const { return mClasses; } inline uint32_t getSources() const { return mSources; } inline bool hasEventHubDevices() const { return !mDevices.empty(); } @@ -62,6 +66,9 @@ public: inline std::optional getAssociatedDisplayUniqueId() const { return mAssociatedDisplayUniqueId; } + inline std::optional getDeviceTypeAssociation() const { + return mAssociatedDeviceType; + } inline std::optional getAssociatedViewport() const { return mAssociatedViewport; } @@ -70,29 +77,33 @@ public: inline bool isIgnored() { return !getMapperCount(); } bool isEnabled(); - void setEnabled(bool enabled, nsecs_t when); + [[nodiscard]] std::list setEnabled(bool enabled, nsecs_t when); void dump(std::string& dump, const std::string& eventHubDevStr); - void addEventHubDevice(int32_t eventHubId, bool populateMappers = true); + void addEmptyEventHubDevice(int32_t eventHubId); + void addEventHubDevice(int32_t eventHubId, const InputReaderConfiguration& readerConfig); void removeEventHubDevice(int32_t eventHubId); - void configure(nsecs_t when, const InputReaderConfiguration* config, uint32_t changes); - void reset(nsecs_t when); - void process(const RawEvent* rawEvents, size_t count); - void timeoutExpired(nsecs_t when); - void updateExternalStylusState(const StylusState& state); + [[nodiscard]] std::list configure(nsecs_t when, + const InputReaderConfiguration& readerConfig, + ConfigurationChanges changes); + [[nodiscard]] std::list reset(nsecs_t when); + [[nodiscard]] std::list process(const RawEvent* rawEvents, size_t count); + [[nodiscard]] std::list timeoutExpired(nsecs_t when); + [[nodiscard]] std::list updateExternalStylusState(const StylusState& state); InputDeviceInfo getDeviceInfo(); int32_t getKeyCodeState(uint32_t sourceMask, int32_t keyCode); int32_t getScanCodeState(uint32_t sourceMask, int32_t scanCode); int32_t getSwitchState(uint32_t sourceMask, int32_t switchCode); int32_t getKeyCodeForKeyLocation(int32_t locationKeyCode) const; - bool markSupportedKeyCodes(uint32_t sourceMask, size_t numCodes, const int32_t* keyCodes, + bool markSupportedKeyCodes(uint32_t sourceMask, const std::vector& keyCodes, uint8_t* outFlags); - void vibrate(const VibrationSequence& sequence, ssize_t repeat, int32_t token); - void cancelVibrate(int32_t token); + [[nodiscard]] std::list vibrate(const VibrationSequence& sequence, ssize_t repeat, + int32_t token); + [[nodiscard]] std::list cancelVibrate(int32_t token); bool isVibrating(); std::vector getVibratorIds(); - void cancelTouch(nsecs_t when, nsecs_t readTime); + [[nodiscard]] std::list cancelTouch(nsecs_t when, nsecs_t readTime); bool enableSensor(InputDeviceSensorType sensorType, std::chrono::microseconds samplingPeriod, std::chrono::microseconds maxBatchReportLatency); void disableSensor(InputDeviceSensorType sensorType); @@ -108,9 +119,11 @@ public: int32_t getMetaState(); void updateMetaState(int32_t keyCode); + void addKeyRemapping(int32_t fromKeyCode, int32_t toKeyCode); + void bumpGeneration(); - void notifyReset(nsecs_t when); + [[nodiscard]] NotifyDeviceResetArgs notifyReset(nsecs_t when); inline const PropertyMap& getConfiguration() { return mConfiguration; } inline EventHubInterface* getEventHub() { return mContext->getEventHub(); } @@ -125,7 +138,7 @@ public: template T& addMapper(int32_t eventHubId, Args... args) { // ensure a device entry exists for this eventHubId - addEventHubDevice(eventHubId, false); + addEmptyEventHubDevice(eventHubId); // create mapper auto& devicePair = mDevices[eventHubId]; @@ -136,11 +149,21 @@ public: return *mapper; } + template + T& constructAndAddMapper(int32_t eventHubId, Args... args) { + // create mapper + auto& devicePair = mDevices[eventHubId]; + auto& deviceContext = devicePair.first; + auto& mappers = devicePair.second; + mappers.push_back(createInputMapper(*deviceContext, args...)); + return static_cast(*mappers.back()); + } + // construct and add a controller to the input device template T& addController(int32_t eventHubId) { // ensure a device entry exists for this eventHubId - addEventHubDevice(eventHubId, false); + addEmptyEventHubDevice(eventHubId); // create controller auto& devicePair = mDevices[eventHubId]; @@ -171,6 +194,7 @@ private: bool mIsExternal; std::optional mAssociatedDisplayPort; std::optional mAssociatedDisplayUniqueId; + std::optional mAssociatedDeviceType; std::optional mAssociatedViewport; bool mHasMic; bool mDropUntilNextSync; @@ -178,6 +202,9 @@ private: typedef int32_t (InputMapper::*GetStateFunc)(uint32_t sourceMask, int32_t code); int32_t getState(uint32_t sourceMask, int32_t code, GetStateFunc getStateFunc); + std::vector> createMappers( + InputDeviceContext& contextPtr, const InputReaderConfiguration& readerConfig); + PropertyMap mConfiguration; // helpers to interate over the devices collection @@ -256,9 +283,6 @@ public: inline int32_t getDeviceControllerNumber() const { return mEventHub->getDeviceControllerNumber(mId); } - inline void getConfiguration(PropertyMap* outConfiguration) const { - return mEventHub->getConfiguration(mId, outConfiguration); - } inline status_t getAbsoluteAxisInfo(int32_t code, RawAbsoluteAxisInfo* axisInfo) const { return mEventHub->getAbsoluteAxisInfo(mId, code, axisInfo); } @@ -271,6 +295,10 @@ public: inline bool hasMscEvent(int mscEvent) const { return mEventHub->hasMscEvent(mId, mscEvent); } + inline void addKeyRemapping(int32_t fromKeyCode, int32_t toKeyCode) const { + mEventHub->addKeyRemapping(mId, fromKeyCode, toKeyCode); + } + inline status_t mapKey(int32_t scanCode, int32_t usageCode, int32_t metaState, int32_t* outKeycode, int32_t* outMetaState, uint32_t* outFlags) const { return mEventHub->mapKey(mId, scanCode, usageCode, metaState, outKeycode, outMetaState, @@ -321,9 +349,9 @@ public: inline status_t getAbsoluteAxisValue(int32_t code, int32_t* outValue) const { return mEventHub->getAbsoluteAxisValue(mId, code, outValue); } - inline bool markSupportedKeyCodes(size_t numCodes, const int32_t* keyCodes, + inline bool markSupportedKeyCodes(const std::vector& keyCodes, uint8_t* outFlags) const { - return mEventHub->markSupportedKeyCodes(mId, numCodes, keyCodes, outFlags); + return mEventHub->markSupportedKeyCodes(mId, keyCodes, outFlags); } inline bool hasScanCode(int32_t scanCode) const { return mEventHub->hasScanCode(mId, scanCode); @@ -340,6 +368,9 @@ public: inline bool setKeyboardLayoutOverlay(std::shared_ptr map) { return mEventHub->setKeyboardLayoutOverlay(mId, map); } + inline const std::optional getRawLayoutInfo() { + return mEventHub->getRawLayoutInfo(mId); + } inline void vibrate(const VibrationElement& element) { return mEventHub->vibrate(mId, element); } @@ -368,8 +399,11 @@ public: mEventHub->getAbsoluteAxisInfo(mId, code, &info); return info.valid; } - inline bool isKeyPressed(int32_t code) const { - return mEventHub->getScanCodeState(mId, code) == AKEY_STATE_DOWN; + inline bool isKeyPressed(int32_t scanCode) const { + return mEventHub->getScanCodeState(mId, scanCode) == AKEY_STATE_DOWN; + } + inline bool isKeyCodePressed(int32_t keyCode) const { + return mEventHub->getKeyCodeState(mId, keyCode) == AKEY_STATE_DOWN; } inline int32_t getAbsoluteAxisValue(int32_t code) const { int32_t value; @@ -380,21 +414,27 @@ public: inline status_t enableDevice() { return mEventHub->enableDevice(mId); } inline status_t disableDevice() { return mEventHub->disableDevice(mId); } - inline const std::string getName() { return mDevice.getName(); } + inline const std::string getName() const { return mDevice.getName(); } inline const std::string getDescriptor() { return mDevice.getDescriptor(); } - inline bool isExternal() { return mDevice.isExternal(); } + inline const std::string getLocation() { return mDevice.getLocation(); } + inline bool isExternal() const { return mDevice.isExternal(); } inline std::optional getAssociatedDisplayPort() const { return mDevice.getAssociatedDisplayPort(); } inline std::optional getAssociatedDisplayUniqueId() const { return mDevice.getAssociatedDisplayUniqueId(); } + inline std::optional getDeviceTypeAssociation() const { + return mDevice.getDeviceTypeAssociation(); + } inline std::optional getAssociatedViewport() const { return mDevice.getAssociatedViewport(); } - inline void cancelTouch(nsecs_t when, nsecs_t readTime) { mDevice.cancelTouch(when, readTime); } + [[nodiscard]] inline std::list cancelTouch(nsecs_t when, nsecs_t readTime) { + return mDevice.cancelTouch(when, readTime); + } inline void bumpGeneration() { mDevice.bumpGeneration(); } - inline const PropertyMap& getConfiguration() { return mDevice.getConfiguration(); } + inline const PropertyMap& getConfiguration() const { return mDevice.getConfiguration(); } private: InputDevice& mDevice; @@ -405,5 +445,3 @@ private: }; } // namespace android - -#endif //_UI_INPUTREADER_INPUT_DEVICE_H diff --git a/services/inputflinger/reader/include/InputReader.h b/services/inputflinger/reader/include/InputReader.h index daeaa1dbe3a55865922804d92807c464aef193c1..9112913565105e936647297965c5a650f72b13e1 100644 --- a/services/inputflinger/reader/include/InputReader.h +++ b/services/inputflinger/reader/include/InputReader.h @@ -14,8 +14,7 @@ * limitations under the License. */ -#ifndef _UI_INPUTREADER_INPUT_READER_H -#define _UI_INPUTREADER_INPUT_READER_H +#pragma once #include #include @@ -69,14 +68,16 @@ public: 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; + void addKeyRemapping(int32_t deviceId, int32_t fromKeyCode, int32_t toKeyCode) const override; + int32_t getKeyCodeForKeyLocation(int32_t deviceId, int32_t locationKeyCode) const override; void toggleCapsLockState(int32_t deviceId) override; - bool hasKeys(int32_t deviceId, uint32_t sourceMask, size_t numCodes, const int32_t* keyCodes, + bool hasKeys(int32_t deviceId, uint32_t sourceMask, const std::vector& keyCodes, uint8_t* outFlags) override; - void requestRefreshConfiguration(uint32_t changes) override; + void requestRefreshConfiguration(ConfigurationChanges changes) override; void vibrate(int32_t deviceId, const VibrationSequence& sequence, ssize_t repeat, int32_t token) override; @@ -100,6 +101,8 @@ public: std::optional getBatteryStatus(int32_t deviceId) override; + std::optional getBatteryDevicePath(int32_t deviceId) override; + std::vector getLights(int32_t deviceId) override; std::vector getSensors(int32_t deviceId) override; @@ -112,6 +115,10 @@ public: std::optional getLightPlayerId(int32_t deviceId, int32_t lightId) override; + std::optional getBluetoothAddress(int32_t deviceId) const override; + + void sysfsNodeChanged(const std::string& sysfsNodePath) override; + protected: // These members are protected so they can be instrumented by test cases. virtual std::shared_ptr createDeviceLocked(int32_t deviceId, @@ -141,10 +148,9 @@ protected: int32_t bumpGeneration() NO_THREAD_SAFETY_ANALYSIS override; void getExternalStylusDevices(std::vector& outDevices) REQUIRES(mReader->mLock) override; - void dispatchExternalStylusState(const StylusState& outState) + [[nodiscard]] std::list dispatchExternalStylusState(const StylusState& outState) REQUIRES(mReader->mLock) override; InputReaderPolicyInterface* getPolicy() REQUIRES(mReader->mLock) override; - InputListenerInterface& getListener() REQUIRES(mReader->mLock) override; EventHubInterface* getEventHub() REQUIRES(mReader->mLock) override; int32_t getNextId() NO_THREAD_SAFETY_ANALYSIS override; void updateLedMetaState(int32_t metaState) REQUIRES(mReader->mLock) override; @@ -169,10 +175,6 @@ private: InputReaderConfiguration mConfig GUARDED_BY(mLock); - // The event queue. - static const int EVENT_BUFFER_SIZE = 256; - RawEvent mEventBuffer[EVENT_BUFFER_SIZE] GUARDED_BY(mLock); - // An input device can represent a collection of EventHub devices. This map provides a way // to lookup the input device instance from the EventHub device id. std::unordered_map> mDevices @@ -184,13 +186,15 @@ private: mDeviceToEventHubIdsMap GUARDED_BY(mLock); // low-level input event decoding and device management - void processEventsLocked(const RawEvent* rawEvents, size_t count) REQUIRES(mLock); + [[nodiscard]] std::list processEventsLocked(const RawEvent* rawEvents, size_t count) + REQUIRES(mLock); void addDeviceLocked(nsecs_t when, int32_t eventHubId) REQUIRES(mLock); void removeDeviceLocked(nsecs_t when, int32_t eventHubId) REQUIRES(mLock); - void processEventsForDeviceLocked(int32_t eventHubId, const RawEvent* rawEvents, size_t count) - REQUIRES(mLock); - void timeoutExpiredLocked(nsecs_t when) REQUIRES(mLock); + [[nodiscard]] std::list processEventsForDeviceLocked(int32_t eventHubId, + const RawEvent* rawEvents, + size_t count) REQUIRES(mLock); + [[nodiscard]] std::list timeoutExpiredLocked(nsecs_t when) REQUIRES(mLock); void handleConfigurationChangedLocked(nsecs_t when) REQUIRES(mLock); @@ -204,7 +208,8 @@ private: void notifyExternalStylusPresenceChangedLocked() REQUIRES(mLock); void getExternalStylusDevicesLocked(std::vector& outDevices) REQUIRES(mLock); - void dispatchExternalStylusStateLocked(const StylusState& state) REQUIRES(mLock); + [[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; @@ -228,8 +233,10 @@ private: nsecs_t mNextTimeout GUARDED_BY(mLock); void requestTimeoutAtTimeLocked(nsecs_t when) REQUIRES(mLock); - uint32_t mConfigurationChangesToRefresh GUARDED_BY(mLock); - void refreshConfigurationLocked(uint32_t changes) REQUIRES(mLock); + ConfigurationChanges mConfigurationChangesToRefresh GUARDED_BY(mLock); + void refreshConfigurationLocked(ConfigurationChanges changes) REQUIRES(mLock); + + void notifyAll(std::list&& argsList); PointerCaptureRequest mCurrentPointerCaptureRequest GUARDED_BY(mLock); @@ -237,13 +244,12 @@ private: typedef int32_t (InputDevice::*GetStateFunc)(uint32_t sourceMask, int32_t code); int32_t getStateLocked(int32_t deviceId, uint32_t sourceMask, int32_t code, GetStateFunc getStateFunc) REQUIRES(mLock); - bool markSupportedKeyCodesLocked(int32_t deviceId, uint32_t sourceMask, size_t numCodes, - const int32_t* keyCodes, uint8_t* outFlags) REQUIRES(mLock); + bool markSupportedKeyCodesLocked(int32_t deviceId, uint32_t sourceMask, + const std::vector& keyCodes, uint8_t* outFlags) + REQUIRES(mLock); // find an InputDevice from an InputDevice id InputDevice* findInputDeviceLocked(int32_t deviceId) const REQUIRES(mLock); }; } // namespace android - -#endif // _UI_INPUTREADER_INPUT_READER_H diff --git a/services/inputflinger/reader/include/InputReaderContext.h b/services/inputflinger/reader/include/InputReaderContext.h index 823d160f8651eb55478c977d9af9ccb6d25f1281..0beace19ab4c20e39a796589e8102ffc45cb6a80 100644 --- a/services/inputflinger/reader/include/InputReaderContext.h +++ b/services/inputflinger/reader/include/InputReaderContext.h @@ -14,10 +14,10 @@ * limitations under the License. */ -#ifndef _UI_INPUTREADER_INPUT_READER_CONTEXT_H -#define _UI_INPUTREADER_INPUT_READER_CONTEXT_H +#pragma once #include +#include "NotifyArgs.h" #include @@ -52,10 +52,10 @@ public: virtual int32_t bumpGeneration() = 0; virtual void getExternalStylusDevices(std::vector& outDevices) = 0; - virtual void dispatchExternalStylusState(const StylusState& outState) = 0; + [[nodiscard]] virtual std::list dispatchExternalStylusState( + const StylusState& outState) = 0; virtual InputReaderPolicyInterface* getPolicy() = 0; - virtual InputListenerInterface& getListener() = 0; virtual EventHubInterface* getEventHub() = 0; virtual int32_t getNextId() = 0; @@ -65,5 +65,3 @@ public: }; } // namespace android - -#endif // _UI_INPUTREADER_INPUT_READER_CONTEXT_H diff --git a/services/inputflinger/reader/include/StylusState.h b/services/inputflinger/reader/include/StylusState.h index 17f158c9e17c0ecf3d3ec4264232feab2ad55622..d042784c9e51b95c06cafabda56f5fd63ae45ed8 100644 --- a/services/inputflinger/reader/include/StylusState.h +++ b/services/inputflinger/reader/include/StylusState.h @@ -14,8 +14,7 @@ * limitations under the License. */ -#ifndef _UI_INPUTREADER_STYLUS_STATE_H -#define _UI_INPUTREADER_STYLUS_STATE_H +#pragma once #include @@ -25,29 +24,19 @@ namespace android { struct StylusState { /* Time the stylus event was received. */ - nsecs_t when; - /* Pressure as reported by the stylus, normalized to the range [0, 1.0]. */ - float pressure; + nsecs_t when{}; + /* + * Pressure as reported by the stylus if supported, normalized to the range [0, 1.0]. + * The presence of a pressure value indicates that the stylus is able to tell whether it is + * touching the display. + */ + std::optional pressure{}; /* The state of the stylus buttons as a bitfield (e.g. AMOTION_EVENT_BUTTON_SECONDARY). */ - uint32_t buttons; - /* Which tool type the stylus is currently using (e.g. AMOTION_EVENT_TOOL_TYPE_ERASER). */ - int32_t toolType; + uint32_t buttons{}; + /* Which tool type the stylus is currently using (e.g. ToolType::ERASER). */ + ToolType toolType{ToolType::UNKNOWN}; - void copyFrom(const StylusState& other) { - when = other.when; - pressure = other.pressure; - buttons = other.buttons; - toolType = other.toolType; - } - - void clear() { - when = LLONG_MAX; - pressure = 0.f; - buttons = 0; - toolType = AMOTION_EVENT_TOOL_TYPE_UNKNOWN; - } + void clear() { *this = StylusState{}; } }; } // namespace android - -#endif // _UI_INPUTREADER_STYLUS_STATE_H diff --git a/services/inputflinger/reader/include/TouchVideoDevice.h b/services/inputflinger/reader/include/TouchVideoDevice.h index 7de9b830b266dbb2494b56ec73c3a2bf8268d771..08eba31199fbf78dc7b213878a88dec1525d5a9c 100644 --- a/services/inputflinger/reader/include/TouchVideoDevice.h +++ b/services/inputflinger/reader/include/TouchVideoDevice.h @@ -14,8 +14,7 @@ * limitations under the License. */ -#ifndef _UI_INPUTFLINGER_TOUCH_VIDEO_DEVICE_H -#define _UI_INPUTFLINGER_TOUCH_VIDEO_DEVICE_H +#pragma once #include #include @@ -123,5 +122,3 @@ private: }; } // namespace android - -#endif // _UI_INPUTFLINGER_TOUCH_VIDEO_DEVICE_H diff --git a/services/inputflinger/reader/mapper/CapturedTouchpadEventConverter.cpp b/services/inputflinger/reader/mapper/CapturedTouchpadEventConverter.cpp new file mode 100644 index 0000000000000000000000000000000000000000..061c6a3e75dafd05e377d9c712b7e4532e236241 --- /dev/null +++ b/services/inputflinger/reader/mapper/CapturedTouchpadEventConverter.cpp @@ -0,0 +1,328 @@ +/* + * 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 "CapturedTouchpadEventConverter.h" + +#include + +#include +#include +#include +#include +#include + +namespace android { + +namespace { + +int32_t actionWithIndex(int32_t action, int32_t index) { + return action | (index << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT); +} + +template +size_t firstUnmarkedBit(T set) { + // TODO: replace with std::countr_one from when that's available + LOG_ALWAYS_FATAL_IF(set.all()); + size_t i = 0; + while (set.test(i)) { + i++; + } + return i; +} + +} // namespace + +CapturedTouchpadEventConverter::CapturedTouchpadEventConverter( + InputReaderContext& readerContext, const InputDeviceContext& deviceContext, + MultiTouchMotionAccumulator& motionAccumulator, int32_t deviceId) + : mDeviceId(deviceId), + mReaderContext(readerContext), + mDeviceContext(deviceContext), + mMotionAccumulator(motionAccumulator), + mHasTouchMinor(deviceContext.hasAbsoluteAxis(ABS_MT_TOUCH_MINOR)), + mHasToolMinor(deviceContext.hasAbsoluteAxis(ABS_MT_WIDTH_MINOR)) { + RawAbsoluteAxisInfo orientationInfo; + deviceContext.getAbsoluteAxisInfo(ABS_MT_ORIENTATION, &orientationInfo); + if (orientationInfo.valid) { + if (orientationInfo.maxValue > 0) { + mOrientationScale = M_PI_2 / orientationInfo.maxValue; + } else if (orientationInfo.minValue < 0) { + mOrientationScale = -M_PI_2 / orientationInfo.minValue; + } + } + + // TODO(b/275369880): support touch.pressure.calibration and .scale properties when captured. + RawAbsoluteAxisInfo pressureInfo; + deviceContext.getAbsoluteAxisInfo(ABS_MT_PRESSURE, &pressureInfo); + if (pressureInfo.valid && pressureInfo.maxValue > 0) { + mPressureScale = 1.0 / pressureInfo.maxValue; + } + + RawAbsoluteAxisInfo touchMajorInfo, toolMajorInfo; + deviceContext.getAbsoluteAxisInfo(ABS_MT_TOUCH_MAJOR, &touchMajorInfo); + deviceContext.getAbsoluteAxisInfo(ABS_MT_WIDTH_MAJOR, &toolMajorInfo); + mHasTouchMajor = touchMajorInfo.valid; + mHasToolMajor = toolMajorInfo.valid; + if (mHasTouchMajor && touchMajorInfo.maxValue != 0) { + mSizeScale = 1.0f / touchMajorInfo.maxValue; + } else if (mHasToolMajor && toolMajorInfo.maxValue != 0) { + mSizeScale = 1.0f / toolMajorInfo.maxValue; + } +} + +std::string CapturedTouchpadEventConverter::dump() const { + std::stringstream out; + out << "Orientation scale: " << mOrientationScale << "\n"; + out << "Pressure scale: " << mPressureScale << "\n"; + out << "Size scale: " << mSizeScale << "\n"; + + out << "Dimension axes:"; + if (mHasTouchMajor) out << " touch major"; + if (mHasTouchMinor) out << ", touch minor"; + if (mHasToolMajor) out << ", tool major"; + if (mHasToolMinor) out << ", tool minor"; + out << "\n"; + + out << "Down time: " << mDownTime << "\n"; + out << StringPrintf("Button state: 0x%08x\n", mButtonState); + + out << StringPrintf("Pointer IDs in use: %s\n", mPointerIdsInUse.to_string().c_str()); + + out << "Pointer IDs for slot numbers:\n"; + out << addLinePrefix(dumpMap(mPointerIdForSlotNumber), " ") << "\n"; + return out.str(); +} + +void CapturedTouchpadEventConverter::populateMotionRanges(InputDeviceInfo& info) const { + tryAddRawMotionRange(/*byref*/ info, AMOTION_EVENT_AXIS_X, ABS_MT_POSITION_X); + tryAddRawMotionRange(/*byref*/ info, AMOTION_EVENT_AXIS_Y, ABS_MT_POSITION_Y); + tryAddRawMotionRange(/*byref*/ info, AMOTION_EVENT_AXIS_TOUCH_MAJOR, ABS_MT_TOUCH_MAJOR); + tryAddRawMotionRange(/*byref*/ info, AMOTION_EVENT_AXIS_TOUCH_MINOR, ABS_MT_TOUCH_MINOR); + tryAddRawMotionRange(/*byref*/ info, AMOTION_EVENT_AXIS_TOOL_MAJOR, ABS_MT_WIDTH_MAJOR); + tryAddRawMotionRange(/*byref*/ info, AMOTION_EVENT_AXIS_TOOL_MINOR, ABS_MT_WIDTH_MINOR); + + RawAbsoluteAxisInfo pressureInfo; + mDeviceContext.getAbsoluteAxisInfo(ABS_MT_PRESSURE, &pressureInfo); + if (pressureInfo.valid) { + info.addMotionRange(AMOTION_EVENT_AXIS_PRESSURE, SOURCE, 0, 1, 0, 0, 0); + } + + RawAbsoluteAxisInfo orientationInfo; + mDeviceContext.getAbsoluteAxisInfo(ABS_MT_ORIENTATION, &orientationInfo); + if (orientationInfo.valid && (orientationInfo.maxValue > 0 || orientationInfo.minValue < 0)) { + info.addMotionRange(AMOTION_EVENT_AXIS_ORIENTATION, SOURCE, -M_PI_2, M_PI_2, 0, 0, 0); + } + + if (mHasTouchMajor || mHasToolMajor) { + info.addMotionRange(AMOTION_EVENT_AXIS_SIZE, SOURCE, 0, 1, 0, 0, 0); + } +} + +void CapturedTouchpadEventConverter::tryAddRawMotionRange(InputDeviceInfo& deviceInfo, + int32_t androidAxis, + int32_t evdevAxis) const { + RawAbsoluteAxisInfo info; + mDeviceContext.getAbsoluteAxisInfo(evdevAxis, &info); + if (info.valid) { + deviceInfo.addMotionRange(androidAxis, SOURCE, info.minValue, info.maxValue, info.flat, + info.fuzz, info.resolution); + } +} + +void CapturedTouchpadEventConverter::reset() { + mCursorButtonAccumulator.reset(mDeviceContext); + mDownTime = 0; + mPointerIdsInUse.reset(); + mPointerIdForSlotNumber.clear(); +} + +std::list CapturedTouchpadEventConverter::process(const RawEvent& rawEvent) { + std::list out; + if (rawEvent.type == EV_SYN && rawEvent.code == SYN_REPORT) { + out = sync(rawEvent.when, rawEvent.readTime); + mMotionAccumulator.finishSync(); + } + + mCursorButtonAccumulator.process(&rawEvent); + mMotionAccumulator.process(&rawEvent); + return out; +} + +std::list CapturedTouchpadEventConverter::sync(nsecs_t when, nsecs_t readTime) { + std::list out; + std::vector coords; + std::vector properties; + std::map coordsIndexForSlotNumber; + + // For all the touches that were already down, send a MOVE event with their updated coordinates. + // A convention of the MotionEvent API is that pointer coordinates in UP events match the + // pointer's coordinates from the previous MOVE, so we still include touches here even if + // they've been lifted in this evdev frame. + if (!mPointerIdForSlotNumber.empty()) { + for (const auto [slotNumber, pointerId] : mPointerIdForSlotNumber) { + // Note that we don't check whether the touch has actually moved — it's rare for a touch + // to stay perfectly still between frames, and if it does the worst that can happen is + // an extra MOVE event, so it's not worth the overhead of checking for changes. + coordsIndexForSlotNumber[slotNumber] = coords.size(); + coords.push_back(makePointerCoordsForSlot(mMotionAccumulator.getSlot(slotNumber))); + properties.push_back({.id = pointerId, .toolType = ToolType::FINGER}); + } + out.push_back( + makeMotionArgs(when, readTime, AMOTION_EVENT_ACTION_MOVE, coords, properties)); + } + + std::vector upSlots, downSlots; + for (size_t i = 0; i < mMotionAccumulator.getSlotCount(); i++) { + const MultiTouchMotionAccumulator::Slot& slot = mMotionAccumulator.getSlot(i); + // Some touchpads continue to report contacts even after they've identified them as palms. + // We don't currently have a way to mark these as palms when reporting to apps, so don't + // report them at all. + const bool isInUse = slot.isInUse() && slot.getToolType() != ToolType::PALM; + const bool wasInUse = mPointerIdForSlotNumber.find(i) != mPointerIdForSlotNumber.end(); + if (isInUse && !wasInUse) { + downSlots.push_back(i); + } else if (!isInUse && wasInUse) { + upSlots.push_back(i); + } + } + + // Send BUTTON_RELEASE events. (This has to happen before any UP events to avoid sending + // BUTTON_RELEASE events without any pointers.) + uint32_t newButtonState; + if (coords.size() - upSlots.size() + downSlots.size() == 0) { + // If there won't be any pointers down after this evdev sync, we won't be able to send + // button updates on their own, as motion events without pointers are invalid. To avoid + // erroneously reporting buttons being held for long periods, send BUTTON_RELEASE events for + // all pressed buttons when the last pointer is lifted. + // + // This also prevents us from sending BUTTON_PRESS events too early in the case of touchpads + // which report a button press one evdev sync before reporting a touch going down. + newButtonState = 0; + } else { + newButtonState = mCursorButtonAccumulator.getButtonState(); + } + for (uint32_t button = 1; button <= AMOTION_EVENT_BUTTON_FORWARD; button <<= 1) { + if (!(newButtonState & button) && mButtonState & button) { + mButtonState &= ~button; + out.push_back(makeMotionArgs(when, readTime, AMOTION_EVENT_ACTION_BUTTON_RELEASE, + coords, properties, /*actionButton=*/button)); + } + } + + // For any touches that were lifted, send UP or POINTER_UP events. + for (size_t slotNumber : upSlots) { + const size_t indexToRemove = coordsIndexForSlotNumber.at(slotNumber); + const bool cancel = mMotionAccumulator.getSlot(slotNumber).getToolType() == ToolType::PALM; + int32_t action; + if (coords.size() == 1) { + action = cancel ? AMOTION_EVENT_ACTION_CANCEL : AMOTION_EVENT_ACTION_UP; + } else { + action = actionWithIndex(AMOTION_EVENT_ACTION_POINTER_UP, indexToRemove); + } + out.push_back(makeMotionArgs(when, readTime, action, coords, properties, /*actionButton=*/0, + /*flags=*/cancel ? AMOTION_EVENT_FLAG_CANCELED : 0)); + + freePointerIdForSlot(slotNumber); + coords.erase(coords.begin() + indexToRemove); + properties.erase(properties.begin() + indexToRemove); + // Now that we've removed some coords and properties, we might have to update the slot + // number to coords index mapping. + coordsIndexForSlotNumber.erase(slotNumber); + for (auto& [_, index] : coordsIndexForSlotNumber) { + if (index > indexToRemove) { + index--; + } + } + } + + // For new touches, send DOWN or POINTER_DOWN events. + for (size_t slotNumber : downSlots) { + const size_t coordsIndex = coords.size(); + const int32_t action = coords.empty() + ? AMOTION_EVENT_ACTION_DOWN + : actionWithIndex(AMOTION_EVENT_ACTION_POINTER_DOWN, coordsIndex); + + coordsIndexForSlotNumber[slotNumber] = coordsIndex; + coords.push_back(makePointerCoordsForSlot(mMotionAccumulator.getSlot(slotNumber))); + properties.push_back( + {.id = allocatePointerIdToSlot(slotNumber), .toolType = ToolType::FINGER}); + + out.push_back(makeMotionArgs(when, readTime, action, coords, properties)); + } + + for (uint32_t button = 1; button <= AMOTION_EVENT_BUTTON_FORWARD; button <<= 1) { + if (newButtonState & button && !(mButtonState & button)) { + mButtonState |= button; + out.push_back(makeMotionArgs(when, readTime, AMOTION_EVENT_ACTION_BUTTON_PRESS, coords, + properties, /*actionButton=*/button)); + } + } + return out; +} + +NotifyMotionArgs CapturedTouchpadEventConverter::makeMotionArgs( + nsecs_t when, nsecs_t readTime, int32_t action, const std::vector& coords, + const std::vector& properties, int32_t actionButton, int32_t flags) { + 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, + /*actionButton=*/actionButton, flags, + mReaderContext.getGlobalMetaState(), mButtonState, + MotionClassification::NONE, AMOTION_EVENT_EDGE_FLAG_NONE, coords.size(), + properties.data(), coords.data(), /*xPrecision=*/1.0f, + /*yPrecision=*/1.0f, AMOTION_EVENT_INVALID_CURSOR_POSITION, + AMOTION_EVENT_INVALID_CURSOR_POSITION, mDownTime, /*videoFrames=*/{}); +} + +PointerCoords CapturedTouchpadEventConverter::makePointerCoordsForSlot( + const MultiTouchMotionAccumulator::Slot& slot) const { + PointerCoords coords; + coords.clear(); + coords.setAxisValue(AMOTION_EVENT_AXIS_X, slot.getX()); + coords.setAxisValue(AMOTION_EVENT_AXIS_Y, slot.getY()); + coords.setAxisValue(AMOTION_EVENT_AXIS_TOUCH_MAJOR, slot.getTouchMajor()); + coords.setAxisValue(AMOTION_EVENT_AXIS_TOUCH_MINOR, slot.getTouchMinor()); + coords.setAxisValue(AMOTION_EVENT_AXIS_TOOL_MAJOR, slot.getToolMajor()); + coords.setAxisValue(AMOTION_EVENT_AXIS_TOOL_MINOR, slot.getToolMinor()); + coords.setAxisValue(AMOTION_EVENT_AXIS_ORIENTATION, slot.getOrientation() * mOrientationScale); + coords.setAxisValue(AMOTION_EVENT_AXIS_PRESSURE, slot.getPressure() * mPressureScale); + float size = 0; + // TODO(b/275369880): support touch.size.calibration and .isSummed properties when captured. + if (mHasTouchMajor) { + size = mHasTouchMinor ? (slot.getTouchMajor() + slot.getTouchMinor()) / 2 + : slot.getTouchMajor(); + } else if (mHasToolMajor) { + size = mHasToolMinor ? (slot.getToolMajor() + slot.getToolMinor()) / 2 + : slot.getToolMajor(); + } + coords.setAxisValue(AMOTION_EVENT_AXIS_SIZE, size * mSizeScale); + return coords; +} + +int32_t CapturedTouchpadEventConverter::allocatePointerIdToSlot(size_t slotNumber) { + const int32_t pointerId = firstUnmarkedBit(mPointerIdsInUse); + mPointerIdsInUse.set(pointerId); + mPointerIdForSlotNumber[slotNumber] = pointerId; + return pointerId; +} + +void CapturedTouchpadEventConverter::freePointerIdForSlot(size_t slotNumber) { + mPointerIdsInUse.reset(mPointerIdForSlotNumber.at(slotNumber)); + mPointerIdForSlotNumber.erase(slotNumber); +} + +} // namespace android diff --git a/services/inputflinger/reader/mapper/CapturedTouchpadEventConverter.h b/services/inputflinger/reader/mapper/CapturedTouchpadEventConverter.h new file mode 100644 index 0000000000000000000000000000000000000000..9b6df7a2222eb6064d7ef59486094625c2c71546 --- /dev/null +++ b/services/inputflinger/reader/mapper/CapturedTouchpadEventConverter.h @@ -0,0 +1,83 @@ +/* + * 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. + */ + +#pragma once + +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include "EventHub.h" +#include "InputDevice.h" +#include "accumulator/CursorButtonAccumulator.h" +#include "accumulator/MultiTouchMotionAccumulator.h" +#include "accumulator/TouchButtonAccumulator.h" + +namespace android { + +class CapturedTouchpadEventConverter { +public: + explicit CapturedTouchpadEventConverter(InputReaderContext& readerContext, + const InputDeviceContext& deviceContext, + MultiTouchMotionAccumulator& motionAccumulator, + int32_t deviceId); + std::string dump() const; + void populateMotionRanges(InputDeviceInfo& info) const; + void reset(); + [[nodiscard]] std::list process(const RawEvent& rawEvent); + +private: + void tryAddRawMotionRange(InputDeviceInfo& deviceInfo, int32_t androidAxis, + int32_t evdevAxis) const; + [[nodiscard]] std::list sync(nsecs_t when, nsecs_t readTime); + [[nodiscard]] NotifyMotionArgs makeMotionArgs(nsecs_t when, nsecs_t readTime, int32_t action, + const std::vector& coords, + const std::vector& properties, + int32_t actionButton = 0, int32_t flags = 0); + PointerCoords makePointerCoordsForSlot(const MultiTouchMotionAccumulator::Slot& slot) const; + int32_t allocatePointerIdToSlot(size_t slotNumber); + void freePointerIdForSlot(size_t slotNumber); + + const int32_t mDeviceId; + InputReaderContext& mReaderContext; + const InputDeviceContext& mDeviceContext; + CursorButtonAccumulator mCursorButtonAccumulator; + MultiTouchMotionAccumulator& mMotionAccumulator; + + float mOrientationScale = 0; + float mPressureScale = 1; + float mSizeScale = 0; + bool mHasTouchMajor; + const bool mHasTouchMinor; + bool mHasToolMajor; + const bool mHasToolMinor; + nsecs_t mDownTime = 0; + uint32_t mButtonState = 0; + + std::bitset mPointerIdsInUse; + std::map mPointerIdForSlotNumber; + + static constexpr uint32_t SOURCE = AINPUT_SOURCE_TOUCHPAD; +}; + +} // namespace android diff --git a/services/inputflinger/reader/mapper/CursorInputMapper.cpp b/services/inputflinger/reader/mapper/CursorInputMapper.cpp index 09b20fd8e1adede929bcd48ad81c98ec930e1d9d..c684ed40b6e2472c40fcc431e598a29332c5cb9e 100644 --- a/services/inputflinger/reader/mapper/CursorInputMapper.cpp +++ b/services/inputflinger/reader/mapper/CursorInputMapper.cpp @@ -20,6 +20,8 @@ #include "CursorInputMapper.h" +#include + #include "CursorButtonAccumulator.h" #include "CursorScrollAccumulator.h" #include "PointerControllerInterface.h" @@ -66,8 +68,10 @@ void CursorMotionAccumulator::finishSync() { // --- CursorInputMapper --- -CursorInputMapper::CursorInputMapper(InputDeviceContext& deviceContext) - : InputMapper(deviceContext) {} +CursorInputMapper::CursorInputMapper(InputDeviceContext& deviceContext, + const InputReaderConfiguration& readerConfig) + : InputMapper(deviceContext, readerConfig), + mLastEventTime(std::numeric_limits::min()) {} CursorInputMapper::~CursorInputMapper() { if (mPointerController != nullptr) { @@ -79,30 +83,31 @@ uint32_t CursorInputMapper::getSources() const { return mSource; } -void CursorInputMapper::populateDeviceInfo(InputDeviceInfo* info) { +void CursorInputMapper::populateDeviceInfo(InputDeviceInfo& info) { InputMapper::populateDeviceInfo(info); if (mParameters.mode == Parameters::Mode::POINTER) { - float minX, minY, maxX, maxY; - if (mPointerController->getBounds(&minX, &minY, &maxX, &maxY)) { - info->addMotionRange(AMOTION_EVENT_AXIS_X, mSource, minX, maxX, 0.0f, 0.0f, 0.0f); - info->addMotionRange(AMOTION_EVENT_AXIS_Y, mSource, minY, maxY, 0.0f, 0.0f, 0.0f); + if (const auto bounds = mPointerController->getBounds(); bounds) { + info.addMotionRange(AMOTION_EVENT_AXIS_X, mSource, bounds->left, bounds->right, 0.0f, + 0.0f, 0.0f); + info.addMotionRange(AMOTION_EVENT_AXIS_Y, mSource, bounds->top, bounds->bottom, 0.0f, + 0.0f, 0.0f); } } else { - info->addMotionRange(AMOTION_EVENT_AXIS_X, mSource, -1.0f, 1.0f, 0.0f, mXScale, 0.0f); - info->addMotionRange(AMOTION_EVENT_AXIS_Y, mSource, -1.0f, 1.0f, 0.0f, mYScale, 0.0f); - info->addMotionRange(AMOTION_EVENT_AXIS_RELATIVE_X, mSource, -1.0f, 1.0f, 0.0f, mXScale, - 0.0f); - info->addMotionRange(AMOTION_EVENT_AXIS_RELATIVE_Y, mSource, -1.0f, 1.0f, 0.0f, mYScale, - 0.0f); + info.addMotionRange(AMOTION_EVENT_AXIS_X, mSource, -1.0f, 1.0f, 0.0f, mXScale, 0.0f); + info.addMotionRange(AMOTION_EVENT_AXIS_Y, mSource, -1.0f, 1.0f, 0.0f, mYScale, 0.0f); + info.addMotionRange(AMOTION_EVENT_AXIS_RELATIVE_X, mSource, -1.0f, 1.0f, 0.0f, mXScale, + 0.0f); + info.addMotionRange(AMOTION_EVENT_AXIS_RELATIVE_Y, mSource, -1.0f, 1.0f, 0.0f, mYScale, + 0.0f); } - info->addMotionRange(AMOTION_EVENT_AXIS_PRESSURE, mSource, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f); + info.addMotionRange(AMOTION_EVENT_AXIS_PRESSURE, mSource, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f); if (mCursorScrollAccumulator.haveRelativeVWheel()) { - info->addMotionRange(AMOTION_EVENT_AXIS_VSCROLL, mSource, -1.0f, 1.0f, 0.0f, 0.0f, 0.0f); + info.addMotionRange(AMOTION_EVENT_AXIS_VSCROLL, mSource, -1.0f, 1.0f, 0.0f, 0.0f, 0.0f); } if (mCursorScrollAccumulator.haveRelativeHWheel()) { - info->addMotionRange(AMOTION_EVENT_AXIS_HSCROLL, mSource, -1.0f, 1.0f, 0.0f, 0.0f, 0.0f); + info.addMotionRange(AMOTION_EVENT_AXIS_HSCROLL, mSource, -1.0f, 1.0f, 0.0f, 0.0f, 0.0f); } } @@ -117,6 +122,10 @@ void CursorInputMapper::dump(std::string& dump) { toString(mCursorScrollAccumulator.haveRelativeVWheel())); dump += StringPrintf(INDENT3 "HaveHWheel: %s\n", toString(mCursorScrollAccumulator.haveRelativeHWheel())); + dump += StringPrintf(INDENT3 "WheelYVelocityControlParameters: %s", + mWheelYVelocityControl.getParameters().dump().c_str()); + dump += StringPrintf(INDENT3 "WheelXVelocityControlParameters: %s", + 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()); @@ -126,143 +135,56 @@ void CursorInputMapper::dump(std::string& dump) { dump += StringPrintf(INDENT3 "DownTime: %" PRId64 "\n", mDownTime); } -void CursorInputMapper::configure(nsecs_t when, const InputReaderConfiguration* config, - uint32_t changes) { - InputMapper::configure(when, config, changes); - - if (!changes) { // first time only - mCursorScrollAccumulator.configure(getDeviceContext()); - - // Configure basic parameters. - configureParameters(); - - // Configure device mode. - switch (mParameters.mode) { - case Parameters::Mode::POINTER_RELATIVE: - // Should not happen during first time configuration. - ALOGE("Cannot start a device in MODE_POINTER_RELATIVE, starting in MODE_POINTER"); - mParameters.mode = Parameters::Mode::POINTER; - [[fallthrough]]; - case Parameters::Mode::POINTER: - mSource = AINPUT_SOURCE_MOUSE; - mXPrecision = 1.0f; - mYPrecision = 1.0f; - mXScale = 1.0f; - mYScale = 1.0f; - mPointerController = getContext()->getPointerController(getDeviceId()); - break; - case Parameters::Mode::NAVIGATION: - mSource = AINPUT_SOURCE_TRACKBALL; - mXPrecision = TRACKBALL_MOVEMENT_THRESHOLD; - mYPrecision = TRACKBALL_MOVEMENT_THRESHOLD; - mXScale = 1.0f / TRACKBALL_MOVEMENT_THRESHOLD; - mYScale = 1.0f / TRACKBALL_MOVEMENT_THRESHOLD; - break; - } +std::list CursorInputMapper::reconfigure(nsecs_t when, + const InputReaderConfiguration& readerConfig, + ConfigurationChanges changes) { + std::list out = InputMapper::reconfigure(when, readerConfig, changes); - mVWheelScale = 1.0f; - mHWheelScale = 1.0f; + if (!changes.any()) { // first time only + configureBasicParams(); } - const bool configurePointerCapture = mParameters.mode != Parameters::Mode::NAVIGATION && - ((!changes && config->pointerCaptureRequest.enable) || - (changes & InputReaderConfiguration::CHANGE_POINTER_CAPTURE)); + const bool configurePointerCapture = !changes.any() || + (mParameters.mode != Parameters::Mode::NAVIGATION && + changes.test(InputReaderConfiguration::Change::POINTER_CAPTURE)); if (configurePointerCapture) { - if (config->pointerCaptureRequest.enable) { - 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"); - } - } else { - if (mParameters.mode == Parameters::Mode::POINTER_RELATIVE) { - mParameters.mode = Parameters::Mode::POINTER; - mSource = AINPUT_SOURCE_MOUSE; - } else { - ALOGE("Cannot release pointer capture, device is not in MODE_POINTER_RELATIVE"); - } - } - bumpGeneration(); - if (changes) { - NotifyDeviceResetArgs args(getContext()->getNextId(), when, getDeviceId()); - getListener().notifyDeviceReset(&args); - } + configureOnPointerCapture(readerConfig); + out.push_back(NotifyDeviceResetArgs(getContext()->getNextId(), when, getDeviceId())); } - if (!changes || (changes & InputReaderConfiguration::CHANGE_POINTER_SPEED) || + if (!changes.any() || changes.test(InputReaderConfiguration::Change::POINTER_SPEED) || configurePointerCapture) { - if (mParameters.mode == Parameters::Mode::POINTER_RELATIVE) { - // Disable any acceleration or scaling for the pointer when Pointer Capture is enabled. - mPointerVelocityControl.setParameters(FLAT_VELOCITY_CONTROL_PARAMS); - mWheelXVelocityControl.setParameters(FLAT_VELOCITY_CONTROL_PARAMS); - mWheelYVelocityControl.setParameters(FLAT_VELOCITY_CONTROL_PARAMS); - } else { - mPointerVelocityControl.setParameters(config->pointerVelocityControlParameters); - mWheelXVelocityControl.setParameters(config->wheelVelocityControlParameters); - mWheelYVelocityControl.setParameters(config->wheelVelocityControlParameters); - } + configureOnChangePointerSpeed(readerConfig); } - if (!changes || (changes & InputReaderConfiguration::CHANGE_DISPLAY_INFO) || + if (!changes.any() || changes.test(InputReaderConfiguration::Change::DISPLAY_INFO) || configurePointerCapture) { - const bool isPointer = mParameters.mode == Parameters::Mode::POINTER; - - mDisplayId = ADISPLAY_ID_NONE; - if (auto viewport = mDeviceContext.getAssociatedViewport(); viewport) { - // This InputDevice is associated with a viewport. - // Only generate events for the associated display. - const bool mismatchedPointerDisplay = - isPointer && (viewport->displayId != mPointerController->getDisplayId()); - mDisplayId = mismatchedPointerDisplay ? std::nullopt - : std::make_optional(viewport->displayId); - } else if (isPointer) { - // The InputDevice is not associated with a viewport, but it controls the mouse pointer. - mDisplayId = mPointerController->getDisplayId(); - } - - mOrientation = DISPLAY_ORIENTATION_0; - const bool isOrientedDevice = - (mParameters.orientationAware && mParameters.hasAssociatedDisplay); - // InputReader works in the un-rotated display coordinate space, so we don't need to do - // anything if the device is already orientation-aware. If the device is not - // orientation-aware, then we need to apply the inverse rotation of the display so that - // when the display rotation is applied later as a part of the per-window transform, we - // get the expected screen coordinates. When pointer capture is enabled, we do not apply any - // rotations and report values directly from the input device. - if (!isOrientedDevice && mDisplayId && - mParameters.mode != Parameters::Mode::POINTER_RELATIVE) { - if (auto viewport = config->getDisplayViewportById(*mDisplayId); viewport) { - mOrientation = getInverseRotation(viewport->orientation); - } - } - - bumpGeneration(); + configureOnChangeDisplayInfo(readerConfig); } + return out; } -void CursorInputMapper::configureParameters() { - mParameters.mode = Parameters::Mode::POINTER; - String8 cursorModeString; - if (getDeviceContext().getConfiguration().tryGetProperty(String8("cursor.mode"), - cursorModeString)) { - if (cursorModeString == "navigation") { - mParameters.mode = Parameters::Mode::NAVIGATION; - } else if (cursorModeString != "pointer" && cursorModeString != "default") { - ALOGW("Invalid value for cursor.mode: '%s'", cursorModeString.c_str()); +CursorInputMapper::Parameters CursorInputMapper::computeParameters( + const InputDeviceContext& deviceContext) { + Parameters parameters; + parameters.mode = Parameters::Mode::POINTER; + const PropertyMap& config = deviceContext.getConfiguration(); + std::optional cursorModeString = config.getString("cursor.mode"); + if (cursorModeString.has_value()) { + if (*cursorModeString == "navigation") { + parameters.mode = Parameters::Mode::NAVIGATION; + } else if (*cursorModeString != "pointer" && *cursorModeString != "default") { + ALOGW("Invalid value for cursor.mode: '%s'", cursorModeString->c_str()); } } - mParameters.orientationAware = false; - getDeviceContext().getConfiguration().tryGetProperty(String8("cursor.orientationAware"), - mParameters.orientationAware); + parameters.orientationAware = config.getBool("cursor.orientationAware").value_or(false); - mParameters.hasAssociatedDisplay = false; - if (mParameters.mode == Parameters::Mode::POINTER || mParameters.orientationAware) { - mParameters.hasAssociatedDisplay = true; + parameters.hasAssociatedDisplay = false; + if (parameters.mode == Parameters::Mode::POINTER || parameters.orientationAware) { + parameters.hasAssociatedDisplay = true; } + return parameters; } void CursorInputMapper::dumpParameters(std::string& dump) { @@ -273,9 +195,10 @@ void CursorInputMapper::dumpParameters(std::string& dump) { dump += StringPrintf(INDENT4 "OrientationAware: %s\n", toString(mParameters.orientationAware)); } -void CursorInputMapper::reset(nsecs_t when) { +std::list CursorInputMapper::reset(nsecs_t when) { mButtonState = 0; mDownTime = 0; + mLastEventTime = std::numeric_limits::min(); mPointerVelocityControl.reset(); mWheelXVelocityControl.reset(); @@ -285,23 +208,31 @@ void CursorInputMapper::reset(nsecs_t when) { mCursorMotionAccumulator.reset(getDeviceContext()); mCursorScrollAccumulator.reset(getDeviceContext()); - InputMapper::reset(when); + return InputMapper::reset(when); } -void CursorInputMapper::process(const RawEvent* rawEvent) { +std::list CursorInputMapper::process(const RawEvent* rawEvent) { + std::list out; mCursorButtonAccumulator.process(rawEvent); mCursorMotionAccumulator.process(rawEvent); mCursorScrollAccumulator.process(rawEvent); if (rawEvent->type == EV_SYN && rawEvent->code == SYN_REPORT) { - sync(rawEvent->when, rawEvent->readTime); + const auto [eventTime, readTime] = + applyBluetoothTimestampSmoothening(getDeviceContext().getDeviceIdentifier(), + rawEvent->when, rawEvent->readTime, + mLastEventTime); + out += sync(eventTime, readTime); + mLastEventTime = eventTime; } + return out; } -void CursorInputMapper::sync(nsecs_t when, nsecs_t readTime) { +std::list CursorInputMapper::sync(nsecs_t when, nsecs_t readTime) { + std::list out; if (!mDisplayId) { // Ignore events when there is no target display configured. - return; + return out; } int32_t lastButtonState = mButtonState; @@ -335,7 +266,7 @@ void CursorInputMapper::sync(nsecs_t when, nsecs_t readTime) { PointerProperties pointerProperties; pointerProperties.clear(); pointerProperties.id = 0; - pointerProperties.toolType = AMOTION_EVENT_TOOL_TYPE_MOUSE; + pointerProperties.toolType = ToolType::MOUSE; PointerCoords pointerCoords; pointerCoords.clear(); @@ -358,15 +289,10 @@ void CursorInputMapper::sync(nsecs_t when, nsecs_t readTime) { if (moved) { mPointerController->move(deltaX, deltaY); } - - if (buttonsChanged) { - mPointerController->setButtonState(currentButtonState); - } - mPointerController->unfade(PointerControllerInterface::Transition::IMMEDIATE); } - mPointerController->getPosition(&xCursorPosition, &yCursorPosition); + std::tie(xCursorPosition, yCursorPosition) = mPointerController->getPosition(); pointerCoords.setAxisValue(AMOTION_EVENT_AXIS_X, xCursorPosition); pointerCoords.setAxisValue(AMOTION_EVENT_AXIS_Y, yCursorPosition); @@ -392,8 +318,9 @@ void CursorInputMapper::sync(nsecs_t when, nsecs_t readTime) { } // Synthesize key down from buttons if needed. - synthesizeButtonKeys(getContext(), AKEY_EVENT_ACTION_DOWN, when, readTime, getDeviceId(), - mSource, *mDisplayId, policyFlags, lastButtonState, currentButtonState); + out += synthesizeButtonKeys(getContext(), AKEY_EVENT_ACTION_DOWN, when, readTime, getDeviceId(), + mSource, *mDisplayId, policyFlags, lastButtonState, + currentButtonState); // Send motion event. if (downChanged || moved || scrolled || buttonsChanged) { @@ -413,40 +340,38 @@ void CursorInputMapper::sync(nsecs_t when, nsecs_t readTime) { while (!released.isEmpty()) { int32_t actionButton = BitSet32::valueForBit(released.clearFirstMarkedBit()); buttonState &= ~actionButton; - NotifyMotionArgs releaseArgs(getContext()->getNextId(), when, readTime, - getDeviceId(), mSource, *mDisplayId, policyFlags, - AMOTION_EVENT_ACTION_BUTTON_RELEASE, actionButton, 0, - metaState, buttonState, MotionClassification::NONE, - AMOTION_EVENT_EDGE_FLAG_NONE, 1, &pointerProperties, - &pointerCoords, mXPrecision, mYPrecision, - xCursorPosition, yCursorPosition, downTime, - /* videoFrames */ {}); - getListener().notifyMotion(&releaseArgs); + out.push_back(NotifyMotionArgs(getContext()->getNextId(), when, readTime, + getDeviceId(), mSource, *mDisplayId, policyFlags, + AMOTION_EVENT_ACTION_BUTTON_RELEASE, actionButton, 0, + metaState, buttonState, MotionClassification::NONE, + AMOTION_EVENT_EDGE_FLAG_NONE, 1, &pointerProperties, + &pointerCoords, mXPrecision, mYPrecision, + xCursorPosition, yCursorPosition, downTime, + /* videoFrames */ {})); } } - NotifyMotionArgs args(getContext()->getNextId(), when, readTime, getDeviceId(), mSource, - *mDisplayId, policyFlags, motionEventAction, 0, 0, metaState, - currentButtonState, MotionClassification::NONE, - AMOTION_EVENT_EDGE_FLAG_NONE, 1, &pointerProperties, &pointerCoords, - mXPrecision, mYPrecision, xCursorPosition, yCursorPosition, downTime, - /* videoFrames */ {}); - getListener().notifyMotion(&args); + out.push_back(NotifyMotionArgs(getContext()->getNextId(), when, readTime, getDeviceId(), + mSource, *mDisplayId, policyFlags, motionEventAction, 0, 0, + metaState, currentButtonState, MotionClassification::NONE, + AMOTION_EVENT_EDGE_FLAG_NONE, 1, &pointerProperties, + &pointerCoords, mXPrecision, mYPrecision, xCursorPosition, + yCursorPosition, downTime, + /* videoFrames */ {})); if (buttonsPressed) { BitSet32 pressed(buttonsPressed); while (!pressed.isEmpty()) { int32_t actionButton = BitSet32::valueForBit(pressed.clearFirstMarkedBit()); buttonState |= actionButton; - NotifyMotionArgs pressArgs(getContext()->getNextId(), when, readTime, getDeviceId(), - mSource, *mDisplayId, policyFlags, - AMOTION_EVENT_ACTION_BUTTON_PRESS, actionButton, 0, - metaState, buttonState, MotionClassification::NONE, - AMOTION_EVENT_EDGE_FLAG_NONE, 1, &pointerProperties, - &pointerCoords, mXPrecision, mYPrecision, - xCursorPosition, yCursorPosition, downTime, - /* videoFrames */ {}); - getListener().notifyMotion(&pressArgs); + out.push_back(NotifyMotionArgs(getContext()->getNextId(), when, readTime, + getDeviceId(), mSource, *mDisplayId, policyFlags, + AMOTION_EVENT_ACTION_BUTTON_PRESS, actionButton, 0, + metaState, buttonState, MotionClassification::NONE, + AMOTION_EVENT_EDGE_FLAG_NONE, 1, &pointerProperties, + &pointerCoords, mXPrecision, mYPrecision, + xCursorPosition, yCursorPosition, downTime, + /* videoFrames */ {})); } } @@ -454,14 +379,14 @@ void CursorInputMapper::sync(nsecs_t when, nsecs_t readTime) { // Send hover move after UP to tell the application that the mouse is hovering now. if (motionEventAction == AMOTION_EVENT_ACTION_UP && (mSource == AINPUT_SOURCE_MOUSE)) { - NotifyMotionArgs hoverArgs(getContext()->getNextId(), when, readTime, getDeviceId(), - mSource, *mDisplayId, policyFlags, - AMOTION_EVENT_ACTION_HOVER_MOVE, 0, 0, metaState, - currentButtonState, MotionClassification::NONE, - AMOTION_EVENT_EDGE_FLAG_NONE, 1, &pointerProperties, - &pointerCoords, mXPrecision, mYPrecision, xCursorPosition, - yCursorPosition, downTime, /* videoFrames */ {}); - getListener().notifyMotion(&hoverArgs); + out.push_back(NotifyMotionArgs(getContext()->getNextId(), when, readTime, getDeviceId(), + mSource, *mDisplayId, policyFlags, + AMOTION_EVENT_ACTION_HOVER_MOVE, 0, 0, metaState, + currentButtonState, MotionClassification::NONE, + AMOTION_EVENT_EDGE_FLAG_NONE, 1, &pointerProperties, + &pointerCoords, mXPrecision, mYPrecision, + xCursorPosition, yCursorPosition, downTime, + /* videoFrames */ {})); } // Send scroll events. @@ -469,23 +394,25 @@ void CursorInputMapper::sync(nsecs_t when, nsecs_t readTime) { pointerCoords.setAxisValue(AMOTION_EVENT_AXIS_VSCROLL, vscroll); pointerCoords.setAxisValue(AMOTION_EVENT_AXIS_HSCROLL, hscroll); - NotifyMotionArgs scrollArgs(getContext()->getNextId(), when, readTime, getDeviceId(), - mSource, *mDisplayId, policyFlags, - AMOTION_EVENT_ACTION_SCROLL, 0, 0, metaState, - currentButtonState, MotionClassification::NONE, - AMOTION_EVENT_EDGE_FLAG_NONE, 1, &pointerProperties, - &pointerCoords, mXPrecision, mYPrecision, xCursorPosition, - yCursorPosition, downTime, /* videoFrames */ {}); - getListener().notifyMotion(&scrollArgs); + out.push_back(NotifyMotionArgs(getContext()->getNextId(), when, readTime, getDeviceId(), + mSource, *mDisplayId, policyFlags, + AMOTION_EVENT_ACTION_SCROLL, 0, 0, metaState, + currentButtonState, MotionClassification::NONE, + AMOTION_EVENT_EDGE_FLAG_NONE, 1, &pointerProperties, + &pointerCoords, mXPrecision, mYPrecision, + xCursorPosition, yCursorPosition, downTime, + /* videoFrames */ {})); } } // Synthesize key up from buttons if needed. - synthesizeButtonKeys(getContext(), AKEY_EVENT_ACTION_UP, when, readTime, getDeviceId(), mSource, - *mDisplayId, policyFlags, lastButtonState, currentButtonState); + out += synthesizeButtonKeys(getContext(), AKEY_EVENT_ACTION_UP, when, readTime, getDeviceId(), + mSource, *mDisplayId, policyFlags, lastButtonState, + currentButtonState); mCursorMotionAccumulator.finishSync(); mCursorScrollAccumulator.finishSync(); + return out; } int32_t CursorInputMapper::getScanCodeState(uint32_t sourceMask, int32_t scanCode) { @@ -500,4 +427,106 @@ std::optional CursorInputMapper::getAssociatedDisplayId() { return mDisplayId; } +void CursorInputMapper::configureBasicParams() { + mCursorScrollAccumulator.configure(getDeviceContext()); + + // Configure basic parameters. + mParameters = computeParameters(getDeviceContext()); + + // Configure device mode. + switch (mParameters.mode) { + case Parameters::Mode::POINTER_RELATIVE: + // Should not happen during first time configuration. + ALOGE("Cannot start a device in MODE_POINTER_RELATIVE, starting in MODE_POINTER"); + mParameters.mode = Parameters::Mode::POINTER; + [[fallthrough]]; + case Parameters::Mode::POINTER: + mSource = AINPUT_SOURCE_MOUSE; + mXPrecision = 1.0f; + mYPrecision = 1.0f; + mXScale = 1.0f; + mYScale = 1.0f; + mPointerController = getContext()->getPointerController(getDeviceId()); + break; + case Parameters::Mode::NAVIGATION: + mSource = AINPUT_SOURCE_TRACKBALL; + mXPrecision = TRACKBALL_MOVEMENT_THRESHOLD; + mYPrecision = TRACKBALL_MOVEMENT_THRESHOLD; + mXScale = 1.0f / TRACKBALL_MOVEMENT_THRESHOLD; + mYScale = 1.0f / TRACKBALL_MOVEMENT_THRESHOLD; + break; + } + + mVWheelScale = 1.0f; + mHWheelScale = 1.0f; +} + +void CursorInputMapper::configureOnPointerCapture(const InputReaderConfiguration& config) { + if (config.pointerCaptureRequest.enable) { + 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"); + } + } else { + if (mParameters.mode == Parameters::Mode::POINTER_RELATIVE) { + mParameters.mode = Parameters::Mode::POINTER; + mSource = AINPUT_SOURCE_MOUSE; + } else { + ALOGE("Cannot release pointer capture, device is not in MODE_POINTER_RELATIVE"); + } + } + bumpGeneration(); +} + +void CursorInputMapper::configureOnChangePointerSpeed(const InputReaderConfiguration& config) { + if (mParameters.mode == Parameters::Mode::POINTER_RELATIVE) { + // Disable any acceleration or scaling for the pointer when Pointer Capture is enabled. + mPointerVelocityControl.setParameters(FLAT_VELOCITY_CONTROL_PARAMS); + mWheelXVelocityControl.setParameters(FLAT_VELOCITY_CONTROL_PARAMS); + mWheelYVelocityControl.setParameters(FLAT_VELOCITY_CONTROL_PARAMS); + } else { + mPointerVelocityControl.setParameters(config.pointerVelocityControlParameters); + mWheelXVelocityControl.setParameters(config.wheelVelocityControlParameters); + mWheelYVelocityControl.setParameters(config.wheelVelocityControlParameters); + } +} + +void CursorInputMapper::configureOnChangeDisplayInfo(const InputReaderConfiguration& config) { + const bool isPointer = mParameters.mode == Parameters::Mode::POINTER; + + mDisplayId = ADISPLAY_ID_NONE; + if (auto viewport = mDeviceContext.getAssociatedViewport(); viewport) { + // This InputDevice is associated with a viewport. + // Only generate events for the associated display. + const bool mismatchedPointerDisplay = + isPointer && (viewport->displayId != mPointerController->getDisplayId()); + mDisplayId = + mismatchedPointerDisplay ? std::nullopt : std::make_optional(viewport->displayId); + } else if (isPointer) { + // The InputDevice is not associated with a viewport, but it controls the mouse pointer. + mDisplayId = mPointerController->getDisplayId(); + } + + mOrientation = ui::ROTATION_0; + const bool isOrientedDevice = + (mParameters.orientationAware && mParameters.hasAssociatedDisplay); + // InputReader works in the un-rotated display coordinate space, so we don't need to do + // anything if the device is already orientation-aware. If the device is not + // orientation-aware, then we need to apply the inverse rotation of the display so that + // when the display rotation is applied later as a part of the per-window transform, we + // get the expected screen coordinates. When pointer capture is enabled, we do not apply any + // rotations and report values directly from the input device. + if (!isOrientedDevice && mDisplayId && mParameters.mode != Parameters::Mode::POINTER_RELATIVE) { + if (auto viewport = config.getDisplayViewportById(*mDisplayId); viewport) { + mOrientation = getInverseRotation(viewport->orientation); + } + } + + bumpGeneration(); +} + } // namespace android diff --git a/services/inputflinger/reader/mapper/CursorInputMapper.h b/services/inputflinger/reader/mapper/CursorInputMapper.h index 60b3dd9ee0b89c082887ddf15048e50049837524..b879bfdd08dde168e6a89ec97cf534f1181bc180 100644 --- a/services/inputflinger/reader/mapper/CursorInputMapper.h +++ b/services/inputflinger/reader/mapper/CursorInputMapper.h @@ -14,8 +14,7 @@ * limitations under the License. */ -#ifndef _UI_INPUTREADER_CURSOR_INPUT_MAPPER_H -#define _UI_INPUTREADER_CURSOR_INPUT_MAPPER_H +#pragma once #include "CursorButtonAccumulator.h" #include "CursorScrollAccumulator.h" @@ -23,6 +22,7 @@ #include #include +#include namespace android { @@ -53,16 +53,20 @@ private: class CursorInputMapper : public InputMapper { public: - explicit CursorInputMapper(InputDeviceContext& deviceContext); + template + friend std::unique_ptr createInputMapper(InputDeviceContext& deviceContext, + const InputReaderConfiguration& readerConfig, + Args... args); virtual ~CursorInputMapper(); virtual uint32_t getSources() const override; - virtual void populateDeviceInfo(InputDeviceInfo* deviceInfo) override; + virtual void populateDeviceInfo(InputDeviceInfo& deviceInfo) override; virtual void dump(std::string& dump) override; - virtual void configure(nsecs_t when, const InputReaderConfiguration* config, - uint32_t changes) override; - virtual void reset(nsecs_t when) override; - virtual void process(const RawEvent* rawEvent) override; + [[nodiscard]] std::list reconfigure(nsecs_t when, + const InputReaderConfiguration& readerConfig, + ConfigurationChanges changes) override; + [[nodiscard]] std::list reset(nsecs_t when) override; + [[nodiscard]] std::list process(const RawEvent* rawEvent) override; virtual int32_t getScanCodeState(uint32_t sourceMask, int32_t scanCode) override; @@ -115,19 +119,25 @@ private: // 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; - int32_t mOrientation; + ui::Rotation mOrientation; std::shared_ptr mPointerController; int32_t mButtonState; nsecs_t mDownTime; + nsecs_t mLastEventTime; - void configureParameters(); + explicit CursorInputMapper(InputDeviceContext& deviceContext, + const InputReaderConfiguration& readerConfig); void dumpParameters(std::string& dump); + void configureBasicParams(); + void configureOnPointerCapture(const InputReaderConfiguration& config); + void configureOnChangePointerSpeed(const InputReaderConfiguration& config); + void configureOnChangeDisplayInfo(const InputReaderConfiguration& config); - void sync(nsecs_t when, nsecs_t readTime); + [[nodiscard]] std::list sync(nsecs_t when, nsecs_t readTime); + + static Parameters computeParameters(const InputDeviceContext& deviceContext); }; } // namespace android - -#endif // _UI_INPUTREADER_CURSOR_INPUT_MAPPER_H diff --git a/services/inputflinger/reader/mapper/ExternalStylusInputMapper.cpp b/services/inputflinger/reader/mapper/ExternalStylusInputMapper.cpp index 6b5d37f8d5c433d695179501679685af54abef85..987d2d022114e560e3c65cde565ceedd4d795723 100644 --- a/services/inputflinger/reader/mapper/ExternalStylusInputMapper.cpp +++ b/services/inputflinger/reader/mapper/ExternalStylusInputMapper.cpp @@ -23,17 +23,20 @@ namespace android { -ExternalStylusInputMapper::ExternalStylusInputMapper(InputDeviceContext& deviceContext) - : InputMapper(deviceContext) {} +ExternalStylusInputMapper::ExternalStylusInputMapper(InputDeviceContext& deviceContext, + const InputReaderConfiguration& readerConfig) + : InputMapper(deviceContext, readerConfig), mTouchButtonAccumulator(deviceContext) {} uint32_t ExternalStylusInputMapper::getSources() const { return AINPUT_SOURCE_STYLUS; } -void ExternalStylusInputMapper::populateDeviceInfo(InputDeviceInfo* info) { +void ExternalStylusInputMapper::populateDeviceInfo(InputDeviceInfo& info) { InputMapper::populateDeviceInfo(info); - info->addMotionRange(AMOTION_EVENT_AXIS_PRESSURE, AINPUT_SOURCE_STYLUS, 0.0f, 1.0f, 0.0f, 0.0f, - 0.0f); + if (mRawPressureAxis.valid) { + info.addMotionRange(AMOTION_EVENT_AXIS_PRESSURE, AINPUT_SOURCE_STYLUS, 0.0f, 1.0f, 0.0f, + 0.0f, 0.0f); + } } void ExternalStylusInputMapper::dump(std::string& dump) { @@ -44,49 +47,52 @@ void ExternalStylusInputMapper::dump(std::string& dump) { dumpStylusState(dump, mStylusState); } -void ExternalStylusInputMapper::configure(nsecs_t when, const InputReaderConfiguration* config, - uint32_t changes) { +std::list ExternalStylusInputMapper::reconfigure(nsecs_t when, + const InputReaderConfiguration& config, + ConfigurationChanges changes) { getAbsoluteAxisInfo(ABS_PRESSURE, &mRawPressureAxis); - mTouchButtonAccumulator.configure(getDeviceContext()); + mTouchButtonAccumulator.configure(); + return {}; } -void ExternalStylusInputMapper::reset(nsecs_t when) { +std::list ExternalStylusInputMapper::reset(nsecs_t when) { mSingleTouchMotionAccumulator.reset(getDeviceContext()); - mTouchButtonAccumulator.reset(getDeviceContext()); - InputMapper::reset(when); + mTouchButtonAccumulator.reset(); + return InputMapper::reset(when); } -void ExternalStylusInputMapper::process(const RawEvent* rawEvent) { +std::list ExternalStylusInputMapper::process(const RawEvent* rawEvent) { + std::list out; mSingleTouchMotionAccumulator.process(rawEvent); mTouchButtonAccumulator.process(rawEvent); if (rawEvent->type == EV_SYN && rawEvent->code == SYN_REPORT) { - sync(rawEvent->when); + out += sync(rawEvent->when); } + return out; } -void ExternalStylusInputMapper::sync(nsecs_t when) { +std::list ExternalStylusInputMapper::sync(nsecs_t when) { mStylusState.clear(); mStylusState.when = when; mStylusState.toolType = mTouchButtonAccumulator.getToolType(); - if (mStylusState.toolType == AMOTION_EVENT_TOOL_TYPE_UNKNOWN) { - mStylusState.toolType = AMOTION_EVENT_TOOL_TYPE_STYLUS; + if (mStylusState.toolType == ToolType::UNKNOWN) { + mStylusState.toolType = ToolType::STYLUS; } - int32_t pressure = mSingleTouchMotionAccumulator.getAbsolutePressure(); if (mRawPressureAxis.valid) { - mStylusState.pressure = float(pressure) / mRawPressureAxis.maxValue; - } else if (mTouchButtonAccumulator.isToolActive()) { - mStylusState.pressure = 1.0f; - } else { - mStylusState.pressure = 0.0f; + auto rawPressure = static_cast(mSingleTouchMotionAccumulator.getAbsolutePressure()); + mStylusState.pressure = (rawPressure - mRawPressureAxis.minValue) / + static_cast(mRawPressureAxis.maxValue - mRawPressureAxis.minValue); + } else if (mTouchButtonAccumulator.hasButtonTouch()) { + mStylusState.pressure = mTouchButtonAccumulator.isHovering() ? 0.0f : 1.0f; } mStylusState.buttons = mTouchButtonAccumulator.getButtonState(); - getContext()->dispatchExternalStylusState(mStylusState); + return getContext()->dispatchExternalStylusState(mStylusState); } } // namespace android diff --git a/services/inputflinger/reader/mapper/ExternalStylusInputMapper.h b/services/inputflinger/reader/mapper/ExternalStylusInputMapper.h index 516aa51a14a43bb26400511f17e598ecddd5468c..97df02b69fe3e3bf8d5e26e49f81d365b2d40b7f 100644 --- a/services/inputflinger/reader/mapper/ExternalStylusInputMapper.h +++ b/services/inputflinger/reader/mapper/ExternalStylusInputMapper.h @@ -14,8 +14,7 @@ * limitations under the License. */ -#ifndef _UI_INPUTREADER_EXTERNAL_STYLUS_INPUT_MAPPER_H -#define _UI_INPUTREADER_EXTERNAL_STYLUS_INPUT_MAPPER_H +#pragma once #include "InputMapper.h" @@ -27,16 +26,20 @@ namespace android { class ExternalStylusInputMapper : public InputMapper { public: - explicit ExternalStylusInputMapper(InputDeviceContext& deviceContext); + template + friend std::unique_ptr createInputMapper(InputDeviceContext& deviceContext, + const InputReaderConfiguration& readerConfig, + Args... args); virtual ~ExternalStylusInputMapper() = default; - virtual uint32_t getSources() const override; - virtual void populateDeviceInfo(InputDeviceInfo* deviceInfo) override; - virtual void dump(std::string& dump) override; - virtual void configure(nsecs_t when, const InputReaderConfiguration* config, - uint32_t changes) override; - virtual void reset(nsecs_t when) override; - virtual void process(const RawEvent* rawEvent) override; + uint32_t getSources() const override; + void populateDeviceInfo(InputDeviceInfo& deviceInfo) override; + void dump(std::string& dump) override; + [[nodiscard]] std::list reconfigure(nsecs_t when, + const InputReaderConfiguration& config, + ConfigurationChanges changes) override; + [[nodiscard]] std::list reset(nsecs_t when) override; + [[nodiscard]] std::list process(const RawEvent* rawEvent) override; private: SingleTouchMotionAccumulator mSingleTouchMotionAccumulator; @@ -45,9 +48,9 @@ private: StylusState mStylusState; - void sync(nsecs_t when); + explicit ExternalStylusInputMapper(InputDeviceContext& deviceContext, + const InputReaderConfiguration& readerConfig); + [[nodiscard]] std::list sync(nsecs_t when); }; } // namespace android - -#endif // _UI_INPUTREADER_EXTERNAL_STYLUS_INPUT_MAPPER_H \ No newline at end of file diff --git a/services/inputflinger/reader/mapper/InputMapper.cpp b/services/inputflinger/reader/mapper/InputMapper.cpp index 7b185e029c7472d2b518c2d2c5539a89db14da07..0692dbbe0a032114c40d9dd81d91410de7c243ec 100644 --- a/services/inputflinger/reader/mapper/InputMapper.cpp +++ b/services/inputflinger/reader/mapper/InputMapper.cpp @@ -18,26 +18,37 @@ #include "InputMapper.h" +#include + #include "InputDevice.h" +#include "input/PrintTools.h" namespace android { -InputMapper::InputMapper(InputDeviceContext& deviceContext) : mDeviceContext(deviceContext) {} +InputMapper::InputMapper(InputDeviceContext& deviceContext, + const InputReaderConfiguration& readerConfig) + : mDeviceContext(deviceContext) {} InputMapper::~InputMapper() {} -void InputMapper::populateDeviceInfo(InputDeviceInfo* info) { - info->addSource(getSources()); +void InputMapper::populateDeviceInfo(InputDeviceInfo& info) { + info.addSource(getSources()); } void InputMapper::dump(std::string& dump) {} -void InputMapper::configure(nsecs_t when, const InputReaderConfiguration* config, - uint32_t changes) {} +std::list InputMapper::reconfigure(nsecs_t when, const InputReaderConfiguration& config, + ConfigurationChanges changes) { + return {}; +} -void InputMapper::reset(nsecs_t when) {} +std::list InputMapper::reset(nsecs_t when) { + return {}; +} -void InputMapper::timeoutExpired(nsecs_t when) {} +std::list InputMapper::timeoutExpired(nsecs_t when) { + return {}; +} int32_t InputMapper::getKeyCodeState(uint32_t sourceMask, int32_t keyCode) { return AKEY_STATE_UNKNOWN; @@ -55,14 +66,19 @@ int32_t InputMapper::getKeyCodeForKeyLocation(int32_t locationKeyCode) const { return AKEYCODE_UNKNOWN; } -bool InputMapper::markSupportedKeyCodes(uint32_t sourceMask, size_t numCodes, - const int32_t* keyCodes, uint8_t* outFlags) { +bool InputMapper::markSupportedKeyCodes(uint32_t sourceMask, const std::vector& keyCodes, + uint8_t* outFlags) { return false; } -void InputMapper::vibrate(const VibrationSequence& sequence, ssize_t repeat, int32_t token) {} +std::list InputMapper::vibrate(const VibrationSequence& sequence, ssize_t repeat, + int32_t token) { + return {}; +} -void InputMapper::cancelVibrate(int32_t token) {} +std::list InputMapper::cancelVibrate(int32_t token) { + return {}; +} bool InputMapper::isVibrating() { return false; @@ -72,7 +88,9 @@ std::vector InputMapper::getVibratorIds() { return {}; } -void InputMapper::cancelTouch(nsecs_t when, nsecs_t readTime) {} +std::list InputMapper::cancelTouch(nsecs_t when, nsecs_t readTime) { + return {}; +} bool InputMapper::enableSensor(InputDeviceSensorType sensorType, std::chrono::microseconds samplingPeriod, @@ -92,7 +110,9 @@ bool InputMapper::updateMetaState(int32_t keyCode) { return false; } -void InputMapper::updateExternalStylusState(const StylusState& state) {} +std::list InputMapper::updateExternalStylusState(const StylusState& state) { + return {}; +} status_t InputMapper::getAbsoluteAxisInfo(int32_t axis, RawAbsoluteAxisInfo* axisInfo) { return getDeviceContext().getAbsoluteAxisInfo(axis, axisInfo); @@ -104,17 +124,14 @@ void InputMapper::bumpGeneration() { void InputMapper::dumpRawAbsoluteAxisInfo(std::string& dump, const RawAbsoluteAxisInfo& axis, const char* name) { - if (axis.valid) { - dump += StringPrintf(INDENT4 "%s: min=%d, max=%d, flat=%d, fuzz=%d, resolution=%d\n", name, - axis.minValue, axis.maxValue, axis.flat, axis.fuzz, axis.resolution); - } else { - dump += StringPrintf(INDENT4 "%s: unknown range\n", name); - } + std::stringstream out; + out << INDENT4 << name << ": " << axis << "\n"; + dump += out.str(); } void InputMapper::dumpStylusState(std::string& dump, const StylusState& state) { dump += StringPrintf(INDENT4 "When: %" PRId64 "\n", state.when); - dump += StringPrintf(INDENT4 "Pressure: %f\n", state.pressure); + dump += StringPrintf(INDENT4 "Pressure: %s\n", toString(state.pressure).c_str()); dump += StringPrintf(INDENT4 "Button State: 0x%08x\n", state.buttons); dump += StringPrintf(INDENT4 "Tool Type: %" PRId32 "\n", state.toolType); } diff --git a/services/inputflinger/reader/mapper/InputMapper.h b/services/inputflinger/reader/mapper/InputMapper.h index fce6409b3fbd578abf146fdfa2ac91bd5b577550..06de4c25e3777438c7c1a9e5aba2a0b414b6907d 100644 --- a/services/inputflinger/reader/mapper/InputMapper.h +++ b/services/inputflinger/reader/mapper/InputMapper.h @@ -14,33 +14,54 @@ * limitations under the License. */ -#ifndef _UI_INPUTREADER_INPUT_MAPPER_H -#define _UI_INPUTREADER_INPUT_MAPPER_H +#pragma once #include "EventHub.h" #include "InputDevice.h" #include "InputListener.h" #include "InputReaderContext.h" +#include "NotifyArgs.h" #include "StylusState.h" #include "VibrationElement.h" namespace android { +/** + * This is the factory method that must be used to create any InputMapper + */ +template +std::unique_ptr createInputMapper(InputDeviceContext& deviceContext, + const InputReaderConfiguration& readerConfig, Args... args) { + // Using `new` to access non-public constructors. + std::unique_ptr mapper(new T(deviceContext, readerConfig, args...)); + // We need to reset and configure the mapper to ensure it is ready to process event + nsecs_t now = systemTime(SYSTEM_TIME_MONOTONIC); + std::list unused = mapper->reset(now); + unused += mapper->reconfigure(now, readerConfig, /*changes=*/{}); + return mapper; +} /* An input mapper transforms raw input events into cooked event data. * A single input device can have multiple associated input mappers in order to interpret * different classes of events. * * InputMapper lifecycle: - * - create - * - configure with 0 changes + * - create and configure with 0 changes * - reset - * - process, process, process (may occasionally reconfigure with non-zero changes or reset) + * - process, process, process (may occasionally reconfigure or reset) * - reset * - destroy */ class InputMapper { public: - explicit InputMapper(InputDeviceContext& deviceContext); + /** + * Subclasses must either provide a public constructor + * or must be-friend the factory method. + */ + template + friend std::unique_ptr createInputMapper(InputDeviceContext& deviceContext, + const InputReaderConfiguration& readerConfig, + Args... args); + virtual ~InputMapper(); inline int32_t getDeviceId() { return mDeviceContext.getId(); } @@ -49,28 +70,30 @@ public: inline const std::string getDeviceName() const { return mDeviceContext.getName(); } inline InputReaderContext* getContext() { return mDeviceContext.getContext(); } inline InputReaderPolicyInterface* getPolicy() { return getContext()->getPolicy(); } - inline InputListenerInterface& getListener() { return getContext()->getListener(); } virtual uint32_t getSources() const = 0; - virtual void populateDeviceInfo(InputDeviceInfo* deviceInfo); + virtual void populateDeviceInfo(InputDeviceInfo& deviceInfo); virtual void dump(std::string& dump); - virtual void configure(nsecs_t when, const InputReaderConfiguration* config, uint32_t changes); - virtual void reset(nsecs_t when); - virtual void process(const RawEvent* rawEvent) = 0; - virtual void timeoutExpired(nsecs_t when); + [[nodiscard]] virtual std::list reconfigure(nsecs_t when, + 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 timeoutExpired(nsecs_t when); virtual int32_t getKeyCodeState(uint32_t sourceMask, int32_t keyCode); virtual int32_t getScanCodeState(uint32_t sourceMask, int32_t scanCode); virtual int32_t getSwitchState(uint32_t sourceMask, int32_t switchCode); virtual int32_t getKeyCodeForKeyLocation(int32_t locationKeyCode) const; - virtual bool markSupportedKeyCodes(uint32_t sourceMask, size_t numCodes, - const int32_t* keyCodes, uint8_t* outFlags); - virtual void vibrate(const VibrationSequence& sequence, ssize_t repeat, int32_t token); - virtual void cancelVibrate(int32_t token); + virtual bool markSupportedKeyCodes(uint32_t sourceMask, const std::vector& keyCodes, + uint8_t* outFlags); + [[nodiscard]] virtual std::list vibrate(const VibrationSequence& sequence, + ssize_t repeat, int32_t token); + [[nodiscard]] virtual std::list cancelVibrate(int32_t token); virtual bool isVibrating(); virtual std::vector getVibratorIds(); - virtual void cancelTouch(nsecs_t when, nsecs_t readTime); + [[nodiscard]] virtual std::list cancelTouch(nsecs_t when, nsecs_t readTime); virtual bool enableSensor(InputDeviceSensorType sensorType, std::chrono::microseconds samplingPeriod, std::chrono::microseconds maxBatchReportLatency); @@ -92,7 +115,7 @@ public: */ virtual bool updateMetaState(int32_t keyCode); - virtual void updateExternalStylusState(const StylusState& state); + [[nodiscard]] virtual std::list updateExternalStylusState(const StylusState& state); virtual std::optional getAssociatedDisplayId() { return std::nullopt; } virtual void updateLedState(bool reset) {} @@ -100,6 +123,9 @@ public: protected: InputDeviceContext& mDeviceContext; + explicit InputMapper(InputDeviceContext& deviceContext, + const InputReaderConfiguration& readerConfig); + status_t getAbsoluteAxisInfo(int32_t axis, RawAbsoluteAxisInfo* axisInfo); void bumpGeneration(); @@ -109,5 +135,3 @@ protected: }; } // namespace android - -#endif // _UI_INPUTREADER_INPUT_MAPPER_H diff --git a/services/inputflinger/reader/mapper/JoystickInputMapper.cpp b/services/inputflinger/reader/mapper/JoystickInputMapper.cpp index 7d30d0c1eb320eae296cfab1ca5344c5dad901a9..099a95541e1d9aa58b6f7464cd24fc5f6c64f7be 100644 --- a/services/inputflinger/reader/mapper/JoystickInputMapper.cpp +++ b/services/inputflinger/reader/mapper/JoystickInputMapper.cpp @@ -20,8 +20,9 @@ namespace android { -JoystickInputMapper::JoystickInputMapper(InputDeviceContext& deviceContext) - : InputMapper(deviceContext) {} +JoystickInputMapper::JoystickInputMapper(InputDeviceContext& deviceContext, + const InputReaderConfiguration& readerConfig) + : InputMapper(deviceContext, readerConfig) {} JoystickInputMapper::~JoystickInputMapper() {} @@ -29,7 +30,7 @@ uint32_t JoystickInputMapper::getSources() const { return AINPUT_SOURCE_JOYSTICK; } -void JoystickInputMapper::populateDeviceInfo(InputDeviceInfo* info) { +void JoystickInputMapper::populateDeviceInfo(InputDeviceInfo& info) { InputMapper::populateDeviceInfo(info); for (const auto& [_, axis] : mAxes) { @@ -41,16 +42,16 @@ void JoystickInputMapper::populateDeviceInfo(InputDeviceInfo* info) { } } -void JoystickInputMapper::addMotionRange(int32_t axisId, const Axis& axis, InputDeviceInfo* info) { - info->addMotionRange(axisId, AINPUT_SOURCE_JOYSTICK, axis.min, axis.max, axis.flat, axis.fuzz, - axis.resolution); +void JoystickInputMapper::addMotionRange(int32_t axisId, const Axis& axis, InputDeviceInfo& info) { + info.addMotionRange(axisId, AINPUT_SOURCE_JOYSTICK, axis.min, axis.max, axis.flat, axis.fuzz, + axis.resolution); /* In order to ease the transition for developers from using the old axes * to the newer, more semantically correct axes, we'll continue to register * the old axes as duplicates of their corresponding new ones. */ int32_t compatAxis = getCompatAxis(axisId); if (compatAxis >= 0) { - info->addMotionRange(compatAxis, AINPUT_SOURCE_JOYSTICK, axis.min, axis.max, axis.flat, - axis.fuzz, axis.resolution); + info.addMotionRange(compatAxis, AINPUT_SOURCE_JOYSTICK, axis.min, axis.max, axis.flat, + axis.fuzz, axis.resolution); } } @@ -103,11 +104,12 @@ void JoystickInputMapper::dump(std::string& dump) { } } -void JoystickInputMapper::configure(nsecs_t when, const InputReaderConfiguration* config, - uint32_t changes) { - InputMapper::configure(when, config, changes); +std::list JoystickInputMapper::reconfigure(nsecs_t when, + const InputReaderConfiguration& config, + ConfigurationChanges changes) { + std::list out = InputMapper::reconfigure(when, config, changes); - if (!changes) { // first time only + if (!changes.any()) { // first time only // Collect all axes. for (int32_t abs = 0; abs <= ABS_MAX; abs++) { if (!(getAbsAxisUsage(abs, getDeviceContext().getDeviceClasses()) @@ -145,12 +147,12 @@ void JoystickInputMapper::configure(nsecs_t when, const InputReaderConfiguration for (auto it = mAxes.begin(); it != mAxes.end(); /*increment it inside loop*/) { Axis& axis = it->second; if (axis.axisInfo.axis < 0) { - while (nextGenericAxisId <= AMOTION_EVENT_AXIS_GENERIC_16 && + while (nextGenericAxisId <= AMOTION_EVENT_MAXIMUM_VALID_AXIS_VALUE && haveAxis(nextGenericAxisId)) { nextGenericAxisId += 1; } - if (nextGenericAxisId <= AMOTION_EVENT_AXIS_GENERIC_16) { + if (nextGenericAxisId <= AMOTION_EVENT_MAXIMUM_VALID_AXIS_VALUE) { axis.axisInfo.axis = nextGenericAxisId; nextGenericAxisId += 1; } else { @@ -164,6 +166,7 @@ void JoystickInputMapper::configure(nsecs_t when, const InputReaderConfiguration it++; } } + return out; } JoystickInputMapper::Axis JoystickInputMapper::createAxis(const AxisInfo& axisInfo, @@ -246,17 +249,18 @@ bool JoystickInputMapper::isCenteredAxis(int32_t axis) { } } -void JoystickInputMapper::reset(nsecs_t when) { +std::list JoystickInputMapper::reset(nsecs_t when) { // Recenter all axes. for (std::pair& pair : mAxes) { Axis& axis = pair.second; axis.resetValue(); } - InputMapper::reset(when); + return InputMapper::reset(when); } -void JoystickInputMapper::process(const RawEvent* rawEvent) { +std::list JoystickInputMapper::process(const RawEvent* rawEvent) { + std::list out; switch (rawEvent->type) { case EV_ABS: { auto it = mAxes.find(rawEvent->code); @@ -298,16 +302,18 @@ void JoystickInputMapper::process(const RawEvent* rawEvent) { case EV_SYN: switch (rawEvent->code) { case SYN_REPORT: - sync(rawEvent->when, rawEvent->readTime, false /*force*/); + out += sync(rawEvent->when, rawEvent->readTime, /*force=*/false); break; } break; } + return out; } -void JoystickInputMapper::sync(nsecs_t when, nsecs_t readTime, bool force) { +std::list JoystickInputMapper::sync(nsecs_t when, nsecs_t readTime, bool force) { + std::list out; if (!filterAxes(force)) { - return; + return out; } int32_t metaState = getContext()->getGlobalMetaState(); @@ -316,7 +322,7 @@ void JoystickInputMapper::sync(nsecs_t when, nsecs_t readTime, bool force) { PointerProperties pointerProperties; pointerProperties.clear(); pointerProperties.id = 0; - pointerProperties.toolType = AMOTION_EVENT_TOOL_TYPE_UNKNOWN; + pointerProperties.toolType = ToolType::UNKNOWN; PointerCoords pointerCoords; pointerCoords.clear(); @@ -340,13 +346,14 @@ void JoystickInputMapper::sync(nsecs_t when, nsecs_t readTime, bool force) { displayId = getDeviceContext().getAssociatedViewport()->displayId; } - NotifyMotionArgs args(getContext()->getNextId(), when, readTime, getDeviceId(), - AINPUT_SOURCE_JOYSTICK, displayId, policyFlags, AMOTION_EVENT_ACTION_MOVE, - 0, 0, metaState, buttonState, MotionClassification::NONE, - AMOTION_EVENT_EDGE_FLAG_NONE, 1, &pointerProperties, &pointerCoords, 0, 0, - AMOTION_EVENT_INVALID_CURSOR_POSITION, - AMOTION_EVENT_INVALID_CURSOR_POSITION, 0, /* videoFrames */ {}); - getListener().notifyMotion(&args); + out.push_back(NotifyMotionArgs(getContext()->getNextId(), when, readTime, getDeviceId(), + AINPUT_SOURCE_JOYSTICK, displayId, policyFlags, + AMOTION_EVENT_ACTION_MOVE, 0, 0, metaState, buttonState, + MotionClassification::NONE, AMOTION_EVENT_EDGE_FLAG_NONE, 1, + &pointerProperties, &pointerCoords, 0, 0, + AMOTION_EVENT_INVALID_CURSOR_POSITION, + AMOTION_EVENT_INVALID_CURSOR_POSITION, 0, /* videoFrames */ {})); + return out; } void JoystickInputMapper::setPointerCoordsAxisValue(PointerCoords* pointerCoords, int32_t axis, diff --git a/services/inputflinger/reader/mapper/JoystickInputMapper.h b/services/inputflinger/reader/mapper/JoystickInputMapper.h index 307bf5b38b5258c76e8eebc4a5f91553357ed923..313f0922b75de1021d4692b16030e5cd4ac3265b 100644 --- a/services/inputflinger/reader/mapper/JoystickInputMapper.h +++ b/services/inputflinger/reader/mapper/JoystickInputMapper.h @@ -14,8 +14,7 @@ * limitations under the License. */ -#ifndef _UI_INPUTREADER_JOYSTICK_INPUT_MAPPER_H -#define _UI_INPUTREADER_JOYSTICK_INPUT_MAPPER_H +#pragma once #include "InputMapper.h" @@ -23,16 +22,20 @@ namespace android { class JoystickInputMapper : public InputMapper { public: - explicit JoystickInputMapper(InputDeviceContext& deviceContext); + template + friend std::unique_ptr createInputMapper(InputDeviceContext& deviceContext, + const InputReaderConfiguration& readerConfig, + Args... args); virtual ~JoystickInputMapper(); virtual uint32_t getSources() const override; - virtual void populateDeviceInfo(InputDeviceInfo* deviceInfo) override; + virtual void populateDeviceInfo(InputDeviceInfo& deviceInfo) override; virtual void dump(std::string& dump) override; - virtual void configure(nsecs_t when, const InputReaderConfiguration* config, - uint32_t changes) override; - virtual void reset(nsecs_t when) override; - virtual void process(const RawEvent* rawEvent) override; + [[nodiscard]] std::list reconfigure(nsecs_t when, + const InputReaderConfiguration& config, + ConfigurationChanges changes) override; + [[nodiscard]] std::list reset(nsecs_t when) override; + [[nodiscard]] std::list process(const RawEvent* rawEvent) override; private: struct Axis { @@ -86,13 +89,16 @@ private: } }; + explicit JoystickInputMapper(InputDeviceContext& deviceContext, + const InputReaderConfiguration& readerConfig); + static Axis createAxis(const AxisInfo& AxisInfo, const RawAbsoluteAxisInfo& rawAxisInfo, bool explicitlyMapped); // Axes indexed by raw ABS_* axis index. std::unordered_map mAxes; - void sync(nsecs_t when, nsecs_t readTime, bool force); + [[nodiscard]] std::list sync(nsecs_t when, nsecs_t readTime, bool force); bool haveAxis(int32_t axisId); void pruneAxes(bool ignoreExplicitlyMappedAxes); @@ -106,10 +112,8 @@ private: static bool isCenteredAxis(int32_t axis); static int32_t getCompatAxis(int32_t axis); - static void addMotionRange(int32_t axisId, const Axis& axis, InputDeviceInfo* info); + static void addMotionRange(int32_t axisId, const Axis& axis, InputDeviceInfo& info); static void setPointerCoordsAxisValue(PointerCoords* pointerCoords, int32_t axis, float value); }; } // namespace android - -#endif // _UI_INPUTREADER_JOYSTICK_INPUT_MAPPER_H \ No newline at end of file diff --git a/services/inputflinger/reader/mapper/KeyboardInputMapper.cpp b/services/inputflinger/reader/mapper/KeyboardInputMapper.cpp index 2ac81781faf1c0a9bd40038477a9ed1410053a1a..7388752805e590f612698343faa07b3e6bae1408 100644 --- a/services/inputflinger/reader/mapper/KeyboardInputMapper.cpp +++ b/services/inputflinger/reader/mapper/KeyboardInputMapper.cpp @@ -20,90 +20,63 @@ #include "KeyboardInputMapper.h" +#include + namespace android { // --- Static Definitions --- -static int32_t rotateValueUsingRotationMap(int32_t value, int32_t orientation, - const int32_t map[][4], size_t mapSize) { - if (orientation != DISPLAY_ORIENTATION_0) { - for (size_t i = 0; i < mapSize; i++) { - if (value == map[i][0]) { - return map[i][orientation]; - } - } - } - return value; -} - -static const int32_t keyCodeRotationMap[][4] = { - // key codes enumerated counter-clockwise with the original (unrotated) key first - // no rotation, 90 degree rotation, 180 degree rotation, 270 degree rotation - {AKEYCODE_DPAD_DOWN, AKEYCODE_DPAD_RIGHT, AKEYCODE_DPAD_UP, AKEYCODE_DPAD_LEFT}, - {AKEYCODE_DPAD_RIGHT, AKEYCODE_DPAD_UP, AKEYCODE_DPAD_LEFT, AKEYCODE_DPAD_DOWN}, - {AKEYCODE_DPAD_UP, AKEYCODE_DPAD_LEFT, AKEYCODE_DPAD_DOWN, AKEYCODE_DPAD_RIGHT}, - {AKEYCODE_DPAD_LEFT, AKEYCODE_DPAD_DOWN, AKEYCODE_DPAD_RIGHT, AKEYCODE_DPAD_UP}, - {AKEYCODE_SYSTEM_NAVIGATION_DOWN, AKEYCODE_SYSTEM_NAVIGATION_RIGHT, - AKEYCODE_SYSTEM_NAVIGATION_UP, AKEYCODE_SYSTEM_NAVIGATION_LEFT}, - {AKEYCODE_SYSTEM_NAVIGATION_RIGHT, AKEYCODE_SYSTEM_NAVIGATION_UP, - AKEYCODE_SYSTEM_NAVIGATION_LEFT, AKEYCODE_SYSTEM_NAVIGATION_DOWN}, - {AKEYCODE_SYSTEM_NAVIGATION_UP, AKEYCODE_SYSTEM_NAVIGATION_LEFT, - AKEYCODE_SYSTEM_NAVIGATION_DOWN, AKEYCODE_SYSTEM_NAVIGATION_RIGHT}, - {AKEYCODE_SYSTEM_NAVIGATION_LEFT, AKEYCODE_SYSTEM_NAVIGATION_DOWN, - AKEYCODE_SYSTEM_NAVIGATION_RIGHT, AKEYCODE_SYSTEM_NAVIGATION_UP}, -}; - -static const size_t keyCodeRotationMapSize = - sizeof(keyCodeRotationMap) / sizeof(keyCodeRotationMap[0]); - -static int32_t rotateStemKey(int32_t value, int32_t orientation, const int32_t map[][2], - size_t mapSize) { - if (orientation == DISPLAY_ORIENTATION_180) { - for (size_t i = 0; i < mapSize; i++) { - if (value == map[i][0]) { - return map[i][1]; +static int32_t rotateKeyCode(int32_t keyCode, ui::Rotation orientation) { + static constexpr int32_t KEYCODE_ROTATION_MAP[][4] = { + // key codes enumerated counter-clockwise with the original (unrotated) key first + // no rotation, 90 degree rotation, 180 degree rotation, 270 degree rotation + {AKEYCODE_DPAD_DOWN, AKEYCODE_DPAD_RIGHT, AKEYCODE_DPAD_UP, AKEYCODE_DPAD_LEFT}, + {AKEYCODE_DPAD_RIGHT, AKEYCODE_DPAD_UP, AKEYCODE_DPAD_LEFT, AKEYCODE_DPAD_DOWN}, + {AKEYCODE_DPAD_UP, AKEYCODE_DPAD_LEFT, AKEYCODE_DPAD_DOWN, AKEYCODE_DPAD_RIGHT}, + {AKEYCODE_DPAD_LEFT, AKEYCODE_DPAD_DOWN, AKEYCODE_DPAD_RIGHT, AKEYCODE_DPAD_UP}, + {AKEYCODE_SYSTEM_NAVIGATION_DOWN, AKEYCODE_SYSTEM_NAVIGATION_RIGHT, + AKEYCODE_SYSTEM_NAVIGATION_UP, AKEYCODE_SYSTEM_NAVIGATION_LEFT}, + {AKEYCODE_SYSTEM_NAVIGATION_RIGHT, AKEYCODE_SYSTEM_NAVIGATION_UP, + AKEYCODE_SYSTEM_NAVIGATION_LEFT, AKEYCODE_SYSTEM_NAVIGATION_DOWN}, + {AKEYCODE_SYSTEM_NAVIGATION_UP, AKEYCODE_SYSTEM_NAVIGATION_LEFT, + AKEYCODE_SYSTEM_NAVIGATION_DOWN, AKEYCODE_SYSTEM_NAVIGATION_RIGHT}, + {AKEYCODE_SYSTEM_NAVIGATION_LEFT, AKEYCODE_SYSTEM_NAVIGATION_DOWN, + AKEYCODE_SYSTEM_NAVIGATION_RIGHT, AKEYCODE_SYSTEM_NAVIGATION_UP}, + }; + + if (orientation != ui::ROTATION_0) { + for (const auto& rotation : KEYCODE_ROTATION_MAP) { + if (rotation[static_cast(ui::ROTATION_0)] == keyCode) { + return rotation[static_cast(orientation)]; } } } - return value; + return keyCode; } -// The mapping can be defined using input device configuration properties keyboard.rotated.stem_X -static int32_t stemKeyRotationMap[][2] = { - // key codes enumerated with the original (unrotated) key first - // no rotation, 180 degree rotation - {AKEYCODE_STEM_PRIMARY, AKEYCODE_STEM_PRIMARY}, - {AKEYCODE_STEM_1, AKEYCODE_STEM_1}, - {AKEYCODE_STEM_2, AKEYCODE_STEM_2}, - {AKEYCODE_STEM_3, AKEYCODE_STEM_3}, -}; - -static const size_t stemKeyRotationMapSize = - sizeof(stemKeyRotationMap) / sizeof(stemKeyRotationMap[0]); - -static int32_t rotateKeyCode(int32_t keyCode, int32_t orientation) { - keyCode = rotateStemKey(keyCode, orientation, stemKeyRotationMap, stemKeyRotationMapSize); - return rotateValueUsingRotationMap(keyCode, orientation, keyCodeRotationMap, - keyCodeRotationMapSize); +static bool isSupportedScanCode(int32_t scanCode) { + // KeyboardInputMapper handles keys from keyboards, gamepads, and styluses. + return scanCode < BTN_MOUSE || (scanCode >= BTN_JOYSTICK && scanCode < BTN_DIGI) || + scanCode == BTN_STYLUS || scanCode == BTN_STYLUS2 || scanCode == BTN_STYLUS3 || + scanCode >= BTN_WHEEL; } // --- KeyboardInputMapper --- -KeyboardInputMapper::KeyboardInputMapper(InputDeviceContext& deviceContext, uint32_t source, - int32_t keyboardType) - : InputMapper(deviceContext), mSource(source), mKeyboardType(keyboardType) {} - -KeyboardInputMapper::~KeyboardInputMapper() {} +KeyboardInputMapper::KeyboardInputMapper(InputDeviceContext& deviceContext, + const InputReaderConfiguration& readerConfig, + uint32_t source, int32_t keyboardType) + : InputMapper(deviceContext, readerConfig), mSource(source), mKeyboardType(keyboardType) {} uint32_t KeyboardInputMapper::getSources() const { return mSource; } -int32_t KeyboardInputMapper::getOrientation() { +ui::Rotation KeyboardInputMapper::getOrientation() { if (mViewport) { return mViewport->orientation; } - return DISPLAY_ORIENTATION_0; + return ui::ROTATION_0; } int32_t KeyboardInputMapper::getDisplayId() { @@ -113,11 +86,21 @@ int32_t KeyboardInputMapper::getDisplayId() { return ADISPLAY_ID_NONE; } -void KeyboardInputMapper::populateDeviceInfo(InputDeviceInfo* info) { +void KeyboardInputMapper::populateDeviceInfo(InputDeviceInfo& info) { InputMapper::populateDeviceInfo(info); - info->setKeyboardType(mKeyboardType); - info->setKeyCharacterMap(getDeviceContext().getKeyCharacterMap()); + info.setKeyboardType(mKeyboardType); + info.setKeyCharacterMap(getDeviceContext().getKeyCharacterMap()); + + if (mKeyboardLayoutInfo) { + info.setKeyboardLayoutInfo(*mKeyboardLayoutInfo); + } else { + std::optional layoutInfo = getDeviceContext().getRawLayoutInfo(); + if (layoutInfo) { + info.setKeyboardLayoutInfo( + KeyboardLayoutInfo(layoutInfo->languageTag, layoutInfo->layoutType)); + } + } } void KeyboardInputMapper::dump(std::string& dump) { @@ -127,149 +110,98 @@ void KeyboardInputMapper::dump(std::string& dump) { dump += StringPrintf(INDENT3 "Orientation: %d\n", getOrientation()); dump += StringPrintf(INDENT3 "KeyDowns: %zu keys currently down\n", mKeyDowns.size()); dump += StringPrintf(INDENT3 "MetaState: 0x%0x\n", mMetaState); - dump += StringPrintf(INDENT3 "DownTime: %" PRId64 "\n", mDownTime); + dump += INDENT3 "KeyboardLayoutInfo: "; + if (mKeyboardLayoutInfo) { + dump += mKeyboardLayoutInfo->languageTag + ", " + mKeyboardLayoutInfo->layoutType + "\n"; + } else { + dump += "\n"; + } } std::optional KeyboardInputMapper::findViewport( - nsecs_t when, const InputReaderConfiguration* config) { + const InputReaderConfiguration& readerConfig) { if (getDeviceContext().getAssociatedViewport()) { return getDeviceContext().getAssociatedViewport(); } // No associated display defined, try to find default display if orientationAware. if (mParameters.orientationAware) { - return config->getDisplayViewportByType(ViewportType::INTERNAL); + return readerConfig.getDisplayViewportByType(ViewportType::INTERNAL); } return std::nullopt; } -void KeyboardInputMapper::configure(nsecs_t when, const InputReaderConfiguration* config, - uint32_t changes) { - InputMapper::configure(when, config, changes); +std::list KeyboardInputMapper::reconfigure(nsecs_t when, + const InputReaderConfiguration& config, + ConfigurationChanges changes) { + std::list out = InputMapper::reconfigure(when, config, changes); - if (!changes) { // first time only + if (!changes.any()) { // first time only // Configure basic parameters. configureParameters(); } - if (!changes || (changes & InputReaderConfiguration::CHANGE_DISPLAY_INFO)) { - mViewport = findViewport(when, config); + if (!changes.any() || changes.test(InputReaderConfiguration::Change::DISPLAY_INFO)) { + mViewport = findViewport(config); } -} -static void mapStemKey(int32_t keyCode, const PropertyMap& config, char const* property) { - int32_t mapped = 0; - if (config.tryGetProperty(String8(property), mapped) && mapped > 0) { - for (size_t i = 0; i < stemKeyRotationMapSize; i++) { - if (stemKeyRotationMap[i][0] == keyCode) { - stemKeyRotationMap[i][1] = mapped; - return; - } + if (!changes.any() || + changes.test(InputReaderConfiguration::Change::KEYBOARD_LAYOUT_ASSOCIATION)) { + std::optional newKeyboardLayoutInfo = + getValueByKey(config.keyboardLayoutAssociations, getDeviceContext().getLocation()); + if (mKeyboardLayoutInfo != newKeyboardLayoutInfo) { + mKeyboardLayoutInfo = newKeyboardLayoutInfo; + bumpGeneration(); } } + + return out; } void KeyboardInputMapper::configureParameters() { - mParameters.orientationAware = false; const PropertyMap& config = getDeviceContext().getConfiguration(); - config.tryGetProperty(String8("keyboard.orientationAware"), mParameters.orientationAware); - - if (mParameters.orientationAware) { - mapStemKey(AKEYCODE_STEM_PRIMARY, config, "keyboard.rotated.stem_primary"); - mapStemKey(AKEYCODE_STEM_1, config, "keyboard.rotated.stem_1"); - mapStemKey(AKEYCODE_STEM_2, config, "keyboard.rotated.stem_2"); - mapStemKey(AKEYCODE_STEM_3, config, "keyboard.rotated.stem_3"); - } - - mParameters.handlesKeyRepeat = false; - config.tryGetProperty(String8("keyboard.handlesKeyRepeat"), mParameters.handlesKeyRepeat); - - mParameters.doNotWakeByDefault = false; - config.tryGetProperty(String8("keyboard.doNotWakeByDefault"), mParameters.doNotWakeByDefault); + mParameters.orientationAware = config.getBool("keyboard.orientationAware").value_or(false); + mParameters.handlesKeyRepeat = config.getBool("keyboard.handlesKeyRepeat").value_or(false); + mParameters.doNotWakeByDefault = config.getBool("keyboard.doNotWakeByDefault").value_or(false); } -void KeyboardInputMapper::dumpParameters(std::string& dump) { +void KeyboardInputMapper::dumpParameters(std::string& dump) const { dump += INDENT3 "Parameters:\n"; dump += StringPrintf(INDENT4 "OrientationAware: %s\n", toString(mParameters.orientationAware)); dump += StringPrintf(INDENT4 "HandlesKeyRepeat: %s\n", toString(mParameters.handlesKeyRepeat)); } -void KeyboardInputMapper::reset(nsecs_t when) { - mMetaState = AMETA_NONE; - mDownTime = 0; - mKeyDowns.clear(); - mCurrentHidUsage = 0; +std::list KeyboardInputMapper::reset(nsecs_t when) { + std::list out = cancelAllDownKeys(when); + mHidUsageAccumulator.reset(); resetLedState(); - InputMapper::reset(when); + out += InputMapper::reset(when); + return out; } -void KeyboardInputMapper::process(const RawEvent* rawEvent) { +std::list KeyboardInputMapper::process(const RawEvent* rawEvent) { + std::list out; + mHidUsageAccumulator.process(*rawEvent); switch (rawEvent->type) { case EV_KEY: { int32_t scanCode = rawEvent->code; - int32_t usageCode = mCurrentHidUsage; - mCurrentHidUsage = 0; - if (isKeyboardOrGamepadKey(scanCode)) { - processKey(rawEvent->when, rawEvent->readTime, rawEvent->value != 0, scanCode, - usageCode); - } - break; - } - case EV_MSC: { - if (rawEvent->code == MSC_SCAN) { - mCurrentHidUsage = rawEvent->value; + if (isSupportedScanCode(scanCode)) { + out += processKey(rawEvent->when, rawEvent->readTime, rawEvent->value != 0, + scanCode, mHidUsageAccumulator.consumeCurrentHidUsage()); } break; } - case EV_SYN: { - if (rawEvent->code == SYN_REPORT) { - mCurrentHidUsage = 0; - } - } } + return out; } -bool KeyboardInputMapper::isKeyboardOrGamepadKey(int32_t scanCode) { - return scanCode < BTN_MOUSE || scanCode >= BTN_WHEEL || - (scanCode >= BTN_MISC && scanCode < BTN_MOUSE) || - (scanCode >= BTN_JOYSTICK && scanCode < BTN_DIGI); -} - -bool KeyboardInputMapper::isMediaKey(int32_t keyCode) { - switch (keyCode) { - case AKEYCODE_MEDIA_PLAY: - case AKEYCODE_MEDIA_PAUSE: - case AKEYCODE_MEDIA_PLAY_PAUSE: - case AKEYCODE_MUTE: - case AKEYCODE_HEADSETHOOK: - case AKEYCODE_MEDIA_STOP: - case AKEYCODE_MEDIA_NEXT: - case AKEYCODE_MEDIA_PREVIOUS: - case AKEYCODE_MEDIA_REWIND: - case AKEYCODE_MEDIA_RECORD: - case AKEYCODE_MEDIA_FAST_FORWARD: - case AKEYCODE_MEDIA_SKIP_FORWARD: - case AKEYCODE_MEDIA_SKIP_BACKWARD: - case AKEYCODE_MEDIA_STEP_FORWARD: - case AKEYCODE_MEDIA_STEP_BACKWARD: - case AKEYCODE_MEDIA_AUDIO_TRACK: - case AKEYCODE_VOLUME_UP: - case AKEYCODE_VOLUME_DOWN: - case AKEYCODE_VOLUME_MUTE: - case AKEYCODE_TV_AUDIO_DESCRIPTION: - case AKEYCODE_TV_AUDIO_DESCRIPTION_MIX_UP: - case AKEYCODE_TV_AUDIO_DESCRIPTION_MIX_DOWN: - return true; - } - return false; -} - -void KeyboardInputMapper::processKey(nsecs_t when, nsecs_t readTime, bool down, int32_t scanCode, - int32_t usageCode) { +std::list KeyboardInputMapper::processKey(nsecs_t when, nsecs_t readTime, bool down, + int32_t scanCode, int32_t usageCode) { + std::list out; int32_t keyCode; int32_t keyMetaState; uint32_t policyFlags; @@ -281,6 +213,8 @@ void KeyboardInputMapper::processKey(nsecs_t when, nsecs_t readTime, bool down, policyFlags = 0; } + nsecs_t downTime = when; + std::optional keyDownIndex = findKeyDownIndex(scanCode); if (down) { // Rotate key codes according to orientation if needed. if (mParameters.orientationAware) { @@ -288,40 +222,39 @@ void KeyboardInputMapper::processKey(nsecs_t when, nsecs_t readTime, bool down, } // Add key down. - ssize_t keyDownIndex = findKeyDown(scanCode); - if (keyDownIndex >= 0) { + if (keyDownIndex) { // key repeat, be sure to use same keycode as before in case of rotation - keyCode = mKeyDowns[keyDownIndex].keyCode; + keyCode = mKeyDowns[*keyDownIndex].keyCode; + downTime = mKeyDowns[*keyDownIndex].downTime; } else { // key down if ((policyFlags & POLICY_FLAG_VIRTUAL) && getContext()->shouldDropVirtualKey(when, keyCode, scanCode)) { - return; + return out; } if (policyFlags & POLICY_FLAG_GESTURE) { - getDeviceContext().cancelTouch(when, readTime); + out += getDeviceContext().cancelTouch(when, readTime); } KeyDown keyDown; keyDown.keyCode = keyCode; keyDown.scanCode = scanCode; + keyDown.downTime = when; mKeyDowns.push_back(keyDown); } - - mDownTime = when; } else { // Remove key down. - ssize_t keyDownIndex = findKeyDown(scanCode); - if (keyDownIndex >= 0) { + if (keyDownIndex) { // key up, be sure to use same keycode as before in case of rotation - keyCode = mKeyDowns[keyDownIndex].keyCode; - mKeyDowns.erase(mKeyDowns.begin() + (size_t)keyDownIndex); + keyCode = mKeyDowns[*keyDownIndex].keyCode; + downTime = mKeyDowns[*keyDownIndex].downTime; + mKeyDowns.erase(mKeyDowns.begin() + *keyDownIndex); } else { // key was not actually down ALOGI("Dropping key up from device %s because the key was not down. " "keyCode=%d, scanCode=%d", getDeviceName().c_str(), keyCode, scanCode); - return; + return out; } } @@ -333,16 +266,12 @@ void KeyboardInputMapper::processKey(nsecs_t when, nsecs_t readTime, bool down, keyMetaState = mMetaState; } - nsecs_t downTime = mDownTime; - - // Key down on external an keyboard should wake the device. + // 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. - // TODO: Use the input device configuration to control this behavior more finely. - if (down && getDeviceContext().isExternal() && !mParameters.doNotWakeByDefault && - !isMediaKey(keyCode)) { + if (down && getDeviceContext().isExternal() && !mParameters.doNotWakeByDefault) { policyFlags |= POLICY_FLAG_WAKE; } @@ -350,21 +279,22 @@ void KeyboardInputMapper::processKey(nsecs_t when, nsecs_t readTime, bool down, policyFlags |= POLICY_FLAG_DISABLE_KEY_REPEAT; } - NotifyKeyArgs args(getContext()->getNextId(), when, readTime, getDeviceId(), mSource, - getDisplayId(), policyFlags, - down ? AKEY_EVENT_ACTION_DOWN : AKEY_EVENT_ACTION_UP, - AKEY_EVENT_FLAG_FROM_SYSTEM, keyCode, scanCode, keyMetaState, downTime); - getListener().notifyKey(&args); + out.emplace_back(NotifyKeyArgs(getContext()->getNextId(), when, readTime, getDeviceId(), + mSource, getDisplayId(), policyFlags, + down ? AKEY_EVENT_ACTION_DOWN : AKEY_EVENT_ACTION_UP, + AKEY_EVENT_FLAG_FROM_SYSTEM, keyCode, scanCode, keyMetaState, + downTime)); + return out; } -ssize_t KeyboardInputMapper::findKeyDown(int32_t scanCode) { +std::optional KeyboardInputMapper::findKeyDownIndex(int32_t scanCode) { size_t n = mKeyDowns.size(); for (size_t i = 0; i < n; i++) { if (mKeyDowns[i].scanCode == scanCode) { return i; } } - return -1; + return {}; } int32_t KeyboardInputMapper::getKeyCodeState(uint32_t sourceMask, int32_t keyCode) { @@ -379,9 +309,10 @@ int32_t KeyboardInputMapper::getKeyCodeForKeyLocation(int32_t locationKeyCode) c return getDeviceContext().getKeyCodeForKeyLocation(locationKeyCode); } -bool KeyboardInputMapper::markSupportedKeyCodes(uint32_t sourceMask, size_t numCodes, - const int32_t* keyCodes, uint8_t* outFlags) { - return getDeviceContext().markSupportedKeyCodes(numCodes, keyCodes, outFlags); +bool KeyboardInputMapper::markSupportedKeyCodes(uint32_t sourceMask, + const std::vector& keyCodes, + uint8_t* outFlags) { + return getDeviceContext().markSupportedKeyCodes(keyCodes, outFlags); } int32_t KeyboardInputMapper::getMetaState() { @@ -433,13 +364,12 @@ void KeyboardInputMapper::updateLedState(bool reset) { mMetaState |= getContext()->getLedMetaState(); constexpr int32_t META_NUM = 3; - const std::array keyCodes = {AKEYCODE_CAPS_LOCK, AKEYCODE_NUM_LOCK, - AKEYCODE_SCROLL_LOCK}; + const std::vector keyCodes{AKEYCODE_CAPS_LOCK, AKEYCODE_NUM_LOCK, + AKEYCODE_SCROLL_LOCK}; const std::array metaCodes = {AMETA_CAPS_LOCK_ON, AMETA_NUM_LOCK_ON, AMETA_SCROLL_LOCK_ON}; std::array flags = {0, 0, 0}; - bool hasKeyLayout = - getDeviceContext().markSupportedKeyCodes(META_NUM, keyCodes.data(), flags.data()); + bool hasKeyLayout = getDeviceContext().markSupportedKeyCodes(keyCodes, flags.data()); // If the device doesn't have the physical meta key it shouldn't generate the corresponding // meta state. if (hasKeyLayout) { @@ -473,4 +403,20 @@ std::optional KeyboardInputMapper::getAssociatedDisplayId() { return std::nullopt; } +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, + AKEY_EVENT_FLAG_FROM_SYSTEM | AKEY_EVENT_FLAG_CANCELED, + mKeyDowns[i].keyCode, mKeyDowns[i].scanCode, AMETA_NONE, + mKeyDowns[i].downTime)); + } + mKeyDowns.clear(); + mMetaState = AMETA_NONE; + return out; +} + } // namespace android diff --git a/services/inputflinger/reader/mapper/KeyboardInputMapper.h b/services/inputflinger/reader/mapper/KeyboardInputMapper.h index 378769639eac19179aa3504adb4dcdc856319cd2..cd3d3c49e92e7f771195a0657a5c4efa76df9dd9 100644 --- a/services/inputflinger/reader/mapper/KeyboardInputMapper.h +++ b/services/inputflinger/reader/mapper/KeyboardInputMapper.h @@ -14,92 +14,96 @@ * limitations under the License. */ -#ifndef _UI_INPUTREADER_KEYBOARD_INPUT_MAPPER_H -#define _UI_INPUTREADER_KEYBOARD_INPUT_MAPPER_H +#pragma once +#include "HidUsageAccumulator.h" #include "InputMapper.h" namespace android { class KeyboardInputMapper : public InputMapper { public: - KeyboardInputMapper(InputDeviceContext& deviceContext, uint32_t source, int32_t keyboardType); - virtual ~KeyboardInputMapper(); - - virtual uint32_t getSources() const override; - virtual void populateDeviceInfo(InputDeviceInfo* deviceInfo) override; - virtual void dump(std::string& dump) override; - virtual void configure(nsecs_t when, const InputReaderConfiguration* config, - uint32_t changes) override; - virtual void reset(nsecs_t when) override; - virtual void process(const RawEvent* rawEvent) override; - - virtual int32_t getKeyCodeState(uint32_t sourceMask, int32_t keyCode) override; - virtual int32_t getScanCodeState(uint32_t sourceMask, int32_t scanCode) override; - virtual bool markSupportedKeyCodes(uint32_t sourceMask, size_t numCodes, - const int32_t* keyCodes, uint8_t* outFlags) override; - virtual int32_t getKeyCodeForKeyLocation(int32_t locationKeyCode) const override; - - virtual int32_t getMetaState() override; - virtual bool updateMetaState(int32_t keyCode) override; - virtual std::optional getAssociatedDisplayId() override; - virtual void updateLedState(bool reset); + template + friend std::unique_ptr createInputMapper(InputDeviceContext& deviceContext, + const InputReaderConfiguration& readerConfig, + Args... args); + ~KeyboardInputMapper() override = default; + + uint32_t getSources() const override; + void populateDeviceInfo(InputDeviceInfo& deviceInfo) override; + void dump(std::string& dump) override; + [[nodiscard]] std::list reconfigure(nsecs_t when, + const InputReaderConfiguration& config, + ConfigurationChanges changes) override; + [[nodiscard]] std::list reset(nsecs_t when) 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; + bool markSupportedKeyCodes(uint32_t sourceMask, const std::vector& keyCodes, + uint8_t* outFlags) override; + int32_t getKeyCodeForKeyLocation(int32_t locationKeyCode) const override; + + int32_t getMetaState() override; + bool updateMetaState(int32_t keyCode) override; + std::optional getAssociatedDisplayId() override; + void updateLedState(bool reset) override; private: // The current viewport. - std::optional mViewport; + std::optional mViewport{}; struct KeyDown { - int32_t keyCode; - int32_t scanCode; + nsecs_t downTime{}; + int32_t keyCode{}; + int32_t scanCode{}; }; - uint32_t mSource; - int32_t mKeyboardType; + uint32_t mSource{}; + int32_t mKeyboardType{}; + std::optional mKeyboardLayoutInfo; - std::vector mKeyDowns; // keys that are down - int32_t mMetaState; - nsecs_t mDownTime; // time of most recent key down + std::vector mKeyDowns{}; // keys that are down + int32_t mMetaState{}; - int32_t mCurrentHidUsage; // most recent HID usage seen this packet, or 0 if none + HidUsageAccumulator mHidUsageAccumulator; struct LedState { - bool avail; // led is available - bool on; // we think the led is currently on + bool avail{}; // led is available + bool on{}; // we think the led is currently on }; - LedState mCapsLockLedState; - LedState mNumLockLedState; - LedState mScrollLockLedState; + LedState mCapsLockLedState{}; + LedState mNumLockLedState{}; + LedState mScrollLockLedState{}; // Immutable configuration parameters. struct Parameters { - bool orientationAware; - bool handlesKeyRepeat; - bool doNotWakeByDefault; - } mParameters; - + bool orientationAware{}; + bool handlesKeyRepeat{}; + bool doNotWakeByDefault{}; + } mParameters{}; + + KeyboardInputMapper(InputDeviceContext& deviceContext, + const InputReaderConfiguration& readerConfig, uint32_t source, + int32_t keyboardType); void configureParameters(); - void dumpParameters(std::string& dump); + void dumpParameters(std::string& dump) const; - int32_t getOrientation(); + ui::Rotation getOrientation(); int32_t getDisplayId(); - bool isKeyboardOrGamepadKey(int32_t scanCode); - bool isMediaKey(int32_t keyCode); - - void processKey(nsecs_t when, nsecs_t readTime, bool down, int32_t scanCode, int32_t usageCode); + [[nodiscard]] std::list processKey(nsecs_t when, nsecs_t readTime, bool down, + int32_t scanCode, int32_t usageCode); bool updateMetaStateIfNeeded(int32_t keyCode, bool down); - ssize_t findKeyDown(int32_t scanCode); + std::optional findKeyDownIndex(int32_t scanCode); void resetLedState(); void initializeLedState(LedState& ledState, int32_t led); void updateLedStateForModifier(LedState& ledState, int32_t led, int32_t modifier, bool reset); - std::optional findViewport(nsecs_t when, - const InputReaderConfiguration* config); + std::optional findViewport(const InputReaderConfiguration& readerConfig); + [[nodiscard]] std::list cancelAllDownKeys(nsecs_t when); }; } // namespace android - -#endif // _UI_INPUTREADER_KEYBOARD_INPUT_MAPPER_H \ No newline at end of file diff --git a/services/inputflinger/reader/mapper/MultiTouchInputMapper.cpp b/services/inputflinger/reader/mapper/MultiTouchInputMapper.cpp index cc323ad42e1cf1b40a9d81354283b52ad2fbab51..9c87c62a7c9d0a639e15fcc27287e8058d3e9b80 100644 --- a/services/inputflinger/reader/mapper/MultiTouchInputMapper.cpp +++ b/services/inputflinger/reader/mapper/MultiTouchInputMapper.cpp @@ -16,9 +16,8 @@ #include "../Macros.h" -#include "MultiTouchInputMapper.h" - #include +#include "MultiTouchInputMapper.h" namespace android { @@ -27,217 +26,29 @@ namespace android { // Maximum number of slots supported when using the slot-based Multitouch Protocol B. static constexpr size_t MAX_SLOTS = 32; -// --- MultiTouchMotionAccumulator --- - -MultiTouchMotionAccumulator::MultiTouchMotionAccumulator() - : mCurrentSlot(-1), - mSlots(nullptr), - mSlotCount(0), - mUsingSlotsProtocol(false), - mHaveStylus(false) {} - -MultiTouchMotionAccumulator::~MultiTouchMotionAccumulator() { - delete[] mSlots; -} - -void MultiTouchMotionAccumulator::configure(InputDeviceContext& deviceContext, size_t slotCount, - bool usingSlotsProtocol) { - mSlotCount = slotCount; - mUsingSlotsProtocol = usingSlotsProtocol; - mHaveStylus = deviceContext.hasAbsoluteAxis(ABS_MT_TOOL_TYPE); - - delete[] mSlots; - mSlots = new Slot[slotCount]; - - mCurrentSlot = -1; - if (mUsingSlotsProtocol) { - // Query the driver for the current slot index and use it as the initial slot - // before we start reading events from the device. It is possible that the - // current slot index will not be the same as it was when the first event was - // written into the evdev buffer, which means the input mapper could start - // out of sync with the initial state of the events in the evdev buffer. - // In the extremely unlikely case that this happens, the data from - // two slots will be confused until the next ABS_MT_SLOT event is received. - // This can cause the touch point to "jump", but at least there will be - // no stuck touches. - int32_t initialSlot; - if (const auto status = deviceContext.getAbsoluteAxisValue(ABS_MT_SLOT, &initialSlot); - status == OK) { - mCurrentSlot = initialSlot; - } else { - ALOGD("Could not retrieve current multi-touch slot index. status=%d", status); - } - } -} - -void MultiTouchMotionAccumulator::resetSlots() { - if (mSlots) { - for (size_t i = 0; i < mSlotCount; i++) { - mSlots[i].clear(); - } - } - mCurrentSlot = -1; -} - -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; - newSlot = true; - } - } else if (mCurrentSlot < 0) { - mCurrentSlot = 0; - } - - if (mCurrentSlot < 0 || size_t(mCurrentSlot) >= mSlotCount) { - if (DEBUG_POINTERS) { - if (newSlot) { - ALOGW("MultiTouch device emitted invalid slot index %d but it " - "should be between 0 and %zd; ignoring this slot.", - mCurrentSlot, mSlotCount - 1); - } - } - } else { - Slot* slot = &mSlots[mCurrentSlot]; - // If mUsingSlotsProtocol is true, it means the raw pointer has axis info of - // ABS_MT_TRACKING_ID and ABS_MT_SLOT, so driver should send a valid trackingId while - // updating the slot. - if (!mUsingSlotsProtocol) { - slot->mInUse = true; - } - - switch (rawEvent->code) { - case ABS_MT_POSITION_X: - slot->mAbsMTPositionX = rawEvent->value; - warnIfNotInUse(*rawEvent, *slot); - break; - case ABS_MT_POSITION_Y: - slot->mAbsMTPositionY = rawEvent->value; - warnIfNotInUse(*rawEvent, *slot); - break; - case ABS_MT_TOUCH_MAJOR: - slot->mAbsMTTouchMajor = rawEvent->value; - break; - case ABS_MT_TOUCH_MINOR: - slot->mAbsMTTouchMinor = rawEvent->value; - slot->mHaveAbsMTTouchMinor = true; - break; - case ABS_MT_WIDTH_MAJOR: - slot->mAbsMTWidthMajor = rawEvent->value; - break; - case ABS_MT_WIDTH_MINOR: - slot->mAbsMTWidthMinor = rawEvent->value; - slot->mHaveAbsMTWidthMinor = true; - break; - case ABS_MT_ORIENTATION: - slot->mAbsMTOrientation = rawEvent->value; - break; - case ABS_MT_TRACKING_ID: - if (mUsingSlotsProtocol && rawEvent->value < 0) { - // The slot is no longer in use but it retains its previous contents, - // which may be reused for subsequent touches. - slot->mInUse = false; - } else { - slot->mInUse = true; - slot->mAbsMTTrackingId = rawEvent->value; - } - break; - case ABS_MT_PRESSURE: - slot->mAbsMTPressure = rawEvent->value; - break; - case ABS_MT_DISTANCE: - slot->mAbsMTDistance = rawEvent->value; - break; - case ABS_MT_TOOL_TYPE: - slot->mAbsMTToolType = rawEvent->value; - slot->mHaveAbsMTToolType = true; - break; - } - } - } 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; - } -} - -void MultiTouchMotionAccumulator::finishSync() { - if (!mUsingSlotsProtocol) { - resetSlots(); - } -} - -bool MultiTouchMotionAccumulator::hasStylus() const { - return mHaveStylus; -} - -void MultiTouchMotionAccumulator::warnIfNotInUse(const RawEvent& event, const Slot& slot) { - if (!slot.mInUse) { - ALOGW("Received unexpected event (0x%0x, 0x%0x) for slot %i with tracking id %i", - event.code, event.value, mCurrentSlot, slot.mAbsMTTrackingId); - } -} - -// --- MultiTouchMotionAccumulator::Slot --- - -MultiTouchMotionAccumulator::Slot::Slot() { - clear(); -} - -void MultiTouchMotionAccumulator::Slot::clear() { - mInUse = false; - mHaveAbsMTTouchMinor = false; - mHaveAbsMTWidthMinor = false; - mHaveAbsMTToolType = false; - mAbsMTPositionX = 0; - mAbsMTPositionY = 0; - mAbsMTTouchMajor = 0; - mAbsMTTouchMinor = 0; - mAbsMTWidthMajor = 0; - mAbsMTWidthMinor = 0; - mAbsMTOrientation = 0; - mAbsMTTrackingId = -1; - mAbsMTPressure = 0; - mAbsMTDistance = 0; - mAbsMTToolType = 0; -} - -int32_t MultiTouchMotionAccumulator::Slot::getToolType() const { - if (mHaveAbsMTToolType) { - switch (mAbsMTToolType) { - case MT_TOOL_FINGER: - return AMOTION_EVENT_TOOL_TYPE_FINGER; - case MT_TOOL_PEN: - return AMOTION_EVENT_TOOL_TYPE_STYLUS; - case MT_TOOL_PALM: - return AMOTION_EVENT_TOOL_TYPE_PALM; - } - } - return AMOTION_EVENT_TOOL_TYPE_UNKNOWN; -} - // --- MultiTouchInputMapper --- -MultiTouchInputMapper::MultiTouchInputMapper(InputDeviceContext& deviceContext) - : TouchInputMapper(deviceContext) {} +MultiTouchInputMapper::MultiTouchInputMapper(InputDeviceContext& deviceContext, + const InputReaderConfiguration& readerConfig) + : TouchInputMapper(deviceContext, readerConfig) {} MultiTouchInputMapper::~MultiTouchInputMapper() {} -void MultiTouchInputMapper::reset(nsecs_t when) { +std::list MultiTouchInputMapper::reset(nsecs_t when) { // The evdev multi-touch protocol does not allow userspace applications to query the initial or // current state of the pointers at any time. This means if we clear our accumulated state when // resetting the input mapper, there's no way to rebuild the full initial state of the pointers. // We can only wait for updates to all the pointers and axes. Rather than clearing the state and // rebuilding the state from scratch, we work around this kernel API limitation by never // fully clearing any state specific to the multi-touch protocol. - TouchInputMapper::reset(when); + return TouchInputMapper::reset(when); } -void MultiTouchInputMapper::process(const RawEvent* rawEvent) { - TouchInputMapper::process(rawEvent); +std::list MultiTouchInputMapper::process(const RawEvent* rawEvent) { + std::list out = TouchInputMapper::process(rawEvent); mMultiTouchMotionAccumulator.process(rawEvent); + return out; } std::optional MultiTouchInputMapper::getActiveBitId( @@ -261,14 +72,14 @@ void MultiTouchInputMapper::syncTouch(nsecs_t when, RawState* outState) { mHavePointerIds = true; for (size_t inIndex = 0; inIndex < inCount; inIndex++) { - const MultiTouchMotionAccumulator::Slot* inSlot = + const MultiTouchMotionAccumulator::Slot& inSlot = mMultiTouchMotionAccumulator.getSlot(inIndex); - if (!inSlot->isInUse()) { + if (!inSlot.isInUse()) { continue; } - if (inSlot->getToolType() == AMOTION_EVENT_TOOL_TYPE_PALM) { - std::optional id = getActiveBitId(*inSlot); + if (inSlot.getToolType() == ToolType::PALM) { + std::optional id = getActiveBitId(inSlot); if (id) { outState->rawPointerData.canceledIdBits.markBit(id.value()); } @@ -289,38 +100,49 @@ void MultiTouchInputMapper::syncTouch(nsecs_t when, RawState* outState) { } RawPointerData::Pointer& outPointer = outState->rawPointerData.pointers[outCount]; - outPointer.x = inSlot->getX(); - outPointer.y = inSlot->getY(); - outPointer.pressure = inSlot->getPressure(); - outPointer.touchMajor = inSlot->getTouchMajor(); - outPointer.touchMinor = inSlot->getTouchMinor(); - outPointer.toolMajor = inSlot->getToolMajor(); - outPointer.toolMinor = inSlot->getToolMinor(); - outPointer.orientation = inSlot->getOrientation(); - outPointer.distance = inSlot->getDistance(); + outPointer.x = inSlot.getX(); + outPointer.y = inSlot.getY(); + outPointer.pressure = inSlot.getPressure(); + outPointer.touchMajor = inSlot.getTouchMajor(); + outPointer.touchMinor = inSlot.getTouchMinor(); + outPointer.toolMajor = inSlot.getToolMajor(); + outPointer.toolMinor = inSlot.getToolMinor(); + outPointer.orientation = inSlot.getOrientation(); + outPointer.distance = inSlot.getDistance(); outPointer.tiltX = 0; outPointer.tiltY = 0; - outPointer.toolType = inSlot->getToolType(); - if (outPointer.toolType == AMOTION_EVENT_TOOL_TYPE_UNKNOWN) { + outPointer.toolType = inSlot.getToolType(); + if (outPointer.toolType == ToolType::UNKNOWN) { outPointer.toolType = mTouchButtonAccumulator.getToolType(); - if (outPointer.toolType == AMOTION_EVENT_TOOL_TYPE_UNKNOWN) { - outPointer.toolType = AMOTION_EVENT_TOOL_TYPE_FINGER; + if (outPointer.toolType == ToolType::UNKNOWN) { + outPointer.toolType = ToolType::FINGER; + } + } else if (outPointer.toolType == ToolType::STYLUS && !mStylusMtToolSeen) { + mStylusMtToolSeen = true; + // The multi-touch device produced a stylus event with MT_TOOL_PEN. Dynamically + // re-configure this input device so that we add SOURCE_STYLUS if we haven't already. + // This is to cover the case where we cannot reliably detect whether a multi-touch + // device will ever produce stylus events when it is initially being configured. + if (!isFromSource(mSource, AINPUT_SOURCE_STYLUS)) { + // Add the stylus source immediately so that it is included in any events generated + // before we have a chance to re-configure the device. + mSource |= AINPUT_SOURCE_STYLUS; + bumpGeneration(); } } - if (shouldSimulateStylusWithTouch() && - outPointer.toolType == AMOTION_EVENT_TOOL_TYPE_FINGER) { - outPointer.toolType = AMOTION_EVENT_TOOL_TYPE_STYLUS; + if (shouldSimulateStylusWithTouch() && outPointer.toolType == ToolType::FINGER) { + outPointer.toolType = ToolType::STYLUS; } - bool isHovering = mTouchButtonAccumulator.getToolType() != AMOTION_EVENT_TOOL_TYPE_MOUSE && + bool isHovering = mTouchButtonAccumulator.getToolType() != ToolType::MOUSE && (mTouchButtonAccumulator.isHovering() || - (mRawPointerAxes.pressure.valid && inSlot->getPressure() <= 0)); + (mRawPointerAxes.pressure.valid && inSlot.getPressure() <= 0)); outPointer.isHovering = isHovering; // Assign pointer id using tracking id if available. if (mHavePointerIds) { - int32_t trackingId = inSlot->getTrackingId(); + int32_t trackingId = inSlot.getTrackingId(); int32_t id = -1; if (trackingId >= 0) { for (BitSet32 idBits(mPointerIdBits); !idBits.isEmpty();) { @@ -380,15 +202,15 @@ void MultiTouchInputMapper::configureRawPointerAxes() { slotCount = MAX_SLOTS; } mMultiTouchMotionAccumulator.configure(getDeviceContext(), slotCount, - true /*usingSlotsProtocol*/); + /*usingSlotsProtocol=*/true); } else { mMultiTouchMotionAccumulator.configure(getDeviceContext(), MAX_POINTERS, - false /*usingSlotsProtocol*/); + /*usingSlotsProtocol=*/false); } } bool MultiTouchInputMapper::hasStylus() const { - return mMultiTouchMotionAccumulator.hasStylus() || mTouchButtonAccumulator.hasStylus() || + return mStylusMtToolSeen || mTouchButtonAccumulator.hasStylus() || shouldSimulateStylusWithTouch(); } diff --git a/services/inputflinger/reader/mapper/MultiTouchInputMapper.h b/services/inputflinger/reader/mapper/MultiTouchInputMapper.h index e7d935082aada2197716b5206f3dcd90dfc551af..f300ee15bd922ec1ca04fa5565a77bae138528a1 100644 --- a/services/inputflinger/reader/mapper/MultiTouchInputMapper.h +++ b/services/inputflinger/reader/mapper/MultiTouchInputMapper.h @@ -14,88 +14,26 @@ * limitations under the License. */ -#ifndef _UI_INPUTREADER_MULTI_TOUCH_INPUT_MAPPER_H -#define _UI_INPUTREADER_MULTI_TOUCH_INPUT_MAPPER_H +#pragma once #include "TouchInputMapper.h" +#include "accumulator/MultiTouchMotionAccumulator.h" namespace android { -/* Keeps track of the state of multi-touch protocol. */ -class MultiTouchMotionAccumulator { -public: - class Slot { - public: - inline bool isInUse() const { return mInUse; } - inline int32_t getX() const { return mAbsMTPositionX; } - inline int32_t getY() const { return mAbsMTPositionY; } - inline int32_t getTouchMajor() const { return mAbsMTTouchMajor; } - inline int32_t getTouchMinor() const { - return mHaveAbsMTTouchMinor ? mAbsMTTouchMinor : mAbsMTTouchMajor; - } - inline int32_t getToolMajor() const { return mAbsMTWidthMajor; } - inline int32_t getToolMinor() const { - return mHaveAbsMTWidthMinor ? mAbsMTWidthMinor : mAbsMTWidthMajor; - } - inline int32_t getOrientation() const { return mAbsMTOrientation; } - inline int32_t getTrackingId() const { return mAbsMTTrackingId; } - inline int32_t getPressure() const { return mAbsMTPressure; } - inline int32_t getDistance() const { return mAbsMTDistance; } - inline int32_t getToolType() const; - - private: - friend class MultiTouchMotionAccumulator; - - bool mInUse; - bool mHaveAbsMTTouchMinor; - bool mHaveAbsMTWidthMinor; - bool mHaveAbsMTToolType; - - int32_t mAbsMTPositionX; - int32_t mAbsMTPositionY; - int32_t mAbsMTTouchMajor; - int32_t mAbsMTTouchMinor; - int32_t mAbsMTWidthMajor; - int32_t mAbsMTWidthMinor; - int32_t mAbsMTOrientation; - int32_t mAbsMTTrackingId; - int32_t mAbsMTPressure; - int32_t mAbsMTDistance; - int32_t mAbsMTToolType; - - Slot(); - void clear(); - }; - - MultiTouchMotionAccumulator(); - ~MultiTouchMotionAccumulator(); - - void configure(InputDeviceContext& deviceContext, size_t slotCount, bool usingSlotsProtocol); - void process(const RawEvent* rawEvent); - void finishSync(); - bool hasStylus() const; - - inline size_t getSlotCount() const { return mSlotCount; } - inline const Slot* getSlot(size_t index) const { return &mSlots[index]; } - -private: - int32_t mCurrentSlot; - Slot* mSlots; - size_t mSlotCount; - bool mUsingSlotsProtocol; - bool mHaveStylus; - - void resetSlots(); - void warnIfNotInUse(const RawEvent& event, const Slot& slot); -}; - class MultiTouchInputMapper : public TouchInputMapper { public: - explicit MultiTouchInputMapper(InputDeviceContext& deviceContext); + template + friend std::unique_ptr createInputMapper(InputDeviceContext& deviceContext, + const InputReaderConfiguration& readerConfig, + Args... args); + explicit MultiTouchInputMapper(InputDeviceContext& deviceContext, + const InputReaderConfiguration& readerConfig); + ~MultiTouchInputMapper() override; - void reset(nsecs_t when) override; - void process(const RawEvent* rawEvent) override; + [[nodiscard]] std::list reset(nsecs_t when) override; + [[nodiscard]] std::list process(const RawEvent* rawEvent) override; protected: void syncTouch(nsecs_t when, RawState* outState) override; @@ -118,8 +56,8 @@ private: // Specifies the pointer id bits that are in use, and their associated tracking id. BitSet32 mPointerIdBits; int32_t mPointerTrackingIdMap[MAX_POINTER_ID + 1]; + + bool mStylusMtToolSeen{false}; }; } // namespace android - -#endif // _UI_INPUTREADER_MULTI_TOUCH_INPUT_MAPPER_H \ No newline at end of file diff --git a/services/inputflinger/reader/mapper/RotaryEncoderInputMapper.cpp b/services/inputflinger/reader/mapper/RotaryEncoderInputMapper.cpp index eca25f6e6af9556109bb40e5813a593d954170ba..13f2e59db909bf58251b071c57bd26f110fcfc66 100644 --- a/services/inputflinger/reader/mapper/RotaryEncoderInputMapper.cpp +++ b/services/inputflinger/reader/mapper/RotaryEncoderInputMapper.cpp @@ -20,12 +20,15 @@ #include "RotaryEncoderInputMapper.h" +#include + #include "CursorScrollAccumulator.h" namespace android { -RotaryEncoderInputMapper::RotaryEncoderInputMapper(InputDeviceContext& deviceContext) - : InputMapper(deviceContext), mOrientation(DISPLAY_ORIENTATION_0) { +RotaryEncoderInputMapper::RotaryEncoderInputMapper(InputDeviceContext& deviceContext, + const InputReaderConfiguration& readerConfig) + : InputMapper(deviceContext, readerConfig), mOrientation(ui::ROTATION_0) { mSource = AINPUT_SOURCE_ROTARY_ENCODER; } @@ -35,22 +38,23 @@ uint32_t RotaryEncoderInputMapper::getSources() const { return mSource; } -void RotaryEncoderInputMapper::populateDeviceInfo(InputDeviceInfo* info) { +void RotaryEncoderInputMapper::populateDeviceInfo(InputDeviceInfo& info) { InputMapper::populateDeviceInfo(info); if (mRotaryEncoderScrollAccumulator.haveRelativeVWheel()) { - float res = 0.0f; - if (!getDeviceContext().getConfiguration().tryGetProperty(String8("device.res"), res)) { + const PropertyMap& config = getDeviceContext().getConfiguration(); + std::optional res = config.getFloat("device.res"); + if (!res.has_value()) { ALOGW("Rotary Encoder device configuration file didn't specify resolution!\n"); } - if (!getDeviceContext().getConfiguration().tryGetProperty(String8("device.scalingFactor"), - mScalingFactor)) { + std::optional scalingFactor = config.getFloat("device.scalingFactor"); + if (!scalingFactor.has_value()) { ALOGW("Rotary Encoder device configuration file didn't specify scaling factor," "default to 1.0!\n"); - mScalingFactor = 1.0f; } - info->addMotionRange(AMOTION_EVENT_AXIS_SCROLL, mSource, -1.0f, 1.0f, 0.0f, 0.0f, - res * mScalingFactor); + mScalingFactor = scalingFactor.value_or(1.0f); + info.addMotionRange(AMOTION_EVENT_AXIS_SCROLL, mSource, -1.0f, 1.0f, 0.0f, 0.0f, + res.value_or(0.0f) * mScalingFactor); } } @@ -60,77 +64,82 @@ void RotaryEncoderInputMapper::dump(std::string& dump) { toString(mRotaryEncoderScrollAccumulator.haveRelativeVWheel())); } -void RotaryEncoderInputMapper::configure(nsecs_t when, const InputReaderConfiguration* config, - uint32_t changes) { - InputMapper::configure(when, config, changes); - if (!changes) { +std::list RotaryEncoderInputMapper::reconfigure(nsecs_t when, + const InputReaderConfiguration& config, + ConfigurationChanges changes) { + std::list out = InputMapper::reconfigure(when, config, changes); + if (!changes.any()) { mRotaryEncoderScrollAccumulator.configure(getDeviceContext()); } - if (!changes || (changes & InputReaderConfiguration::CHANGE_DISPLAY_INFO)) { + if (!changes.any() || changes.test(InputReaderConfiguration::Change::DISPLAY_INFO)) { std::optional internalViewport = - config->getDisplayViewportByType(ViewportType::INTERNAL); + config.getDisplayViewportByType(ViewportType::INTERNAL); if (internalViewport) { mOrientation = internalViewport->orientation; } else { - mOrientation = DISPLAY_ORIENTATION_0; + mOrientation = ui::ROTATION_0; } } + return out; } -void RotaryEncoderInputMapper::reset(nsecs_t when) { +std::list RotaryEncoderInputMapper::reset(nsecs_t when) { mRotaryEncoderScrollAccumulator.reset(getDeviceContext()); - InputMapper::reset(when); + return InputMapper::reset(when); } -void RotaryEncoderInputMapper::process(const RawEvent* rawEvent) { +std::list RotaryEncoderInputMapper::process(const RawEvent* rawEvent) { + std::list out; mRotaryEncoderScrollAccumulator.process(rawEvent); if (rawEvent->type == EV_SYN && rawEvent->code == SYN_REPORT) { - sync(rawEvent->when, rawEvent->readTime); + out += sync(rawEvent->when, rawEvent->readTime); } + return out; } -void RotaryEncoderInputMapper::sync(nsecs_t when, nsecs_t readTime) { - PointerCoords pointerCoords; - pointerCoords.clear(); - - PointerProperties pointerProperties; - pointerProperties.clear(); - pointerProperties.id = 0; - pointerProperties.toolType = AMOTION_EVENT_TOOL_TYPE_UNKNOWN; +std::list RotaryEncoderInputMapper::sync(nsecs_t when, nsecs_t readTime) { + std::list out; float scroll = mRotaryEncoderScrollAccumulator.getRelativeVWheel(); bool scrolled = scroll != 0; - // This is not a pointer, so it's not associated with a display. - int32_t displayId = ADISPLAY_ID_NONE; - - // Moving the rotary encoder should wake the device (if specified). - uint32_t policyFlags = 0; - if (scrolled && getDeviceContext().isExternal()) { - policyFlags |= POLICY_FLAG_WAKE; - } - - if (mOrientation == DISPLAY_ORIENTATION_180) { - scroll = -scroll; - } - // Send motion event. 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; + + if (mOrientation == ui::ROTATION_180) { + scroll = -scroll; + } + + PointerCoords pointerCoords; + pointerCoords.clear(); pointerCoords.setAxisValue(AMOTION_EVENT_AXIS_SCROLL, scroll * mScalingFactor); - NotifyMotionArgs scrollArgs(getContext()->getNextId(), when, readTime, getDeviceId(), - mSource, displayId, policyFlags, AMOTION_EVENT_ACTION_SCROLL, 0, - 0, metaState, /* buttonState */ 0, MotionClassification::NONE, - AMOTION_EVENT_EDGE_FLAG_NONE, 1, &pointerProperties, - &pointerCoords, 0, 0, AMOTION_EVENT_INVALID_CURSOR_POSITION, - AMOTION_EVENT_INVALID_CURSOR_POSITION, 0, /* videoFrames */ {}); - getListener().notifyMotion(&scrollArgs); + PointerProperties pointerProperties; + pointerProperties.clear(); + pointerProperties.id = 0; + pointerProperties.toolType = ToolType::UNKNOWN; + + uint32_t policyFlags = 0; + if (getDeviceContext().isExternal()) { + policyFlags |= POLICY_FLAG_WAKE; + } + + out.push_back( + NotifyMotionArgs(getContext()->getNextId(), when, readTime, getDeviceId(), mSource, + displayId, policyFlags, AMOTION_EVENT_ACTION_SCROLL, 0, 0, + metaState, /* buttonState */ 0, MotionClassification::NONE, + AMOTION_EVENT_EDGE_FLAG_NONE, 1, &pointerProperties, + &pointerCoords, 0, 0, AMOTION_EVENT_INVALID_CURSOR_POSITION, + AMOTION_EVENT_INVALID_CURSOR_POSITION, 0, /* videoFrames */ {})); } mRotaryEncoderScrollAccumulator.finishSync(); + return out; } } // namespace android diff --git a/services/inputflinger/reader/mapper/RotaryEncoderInputMapper.h b/services/inputflinger/reader/mapper/RotaryEncoderInputMapper.h index 1859355a93eb058fe029f5cc4685d2fb5888d1d9..9e2e8c4342a5b6818ed4eea041c50bc549f6b653 100644 --- a/services/inputflinger/reader/mapper/RotaryEncoderInputMapper.h +++ b/services/inputflinger/reader/mapper/RotaryEncoderInputMapper.h @@ -14,8 +14,9 @@ * limitations under the License. */ -#ifndef _UI_INPUTREADER_ROTARY_ENCODER_INPUT_MAPPER_H -#define _UI_INPUTREADER_ROTARY_ENCODER_INPUT_MAPPER_H +#pragma once + +#include #include "CursorScrollAccumulator.h" #include "InputMapper.h" @@ -24,27 +25,31 @@ namespace android { class RotaryEncoderInputMapper : public InputMapper { public: - explicit RotaryEncoderInputMapper(InputDeviceContext& deviceContext); + template + friend std::unique_ptr createInputMapper(InputDeviceContext& deviceContext, + const InputReaderConfiguration& readerConfig, + Args... args); virtual ~RotaryEncoderInputMapper(); virtual uint32_t getSources() const override; - virtual void populateDeviceInfo(InputDeviceInfo* deviceInfo) override; + virtual void populateDeviceInfo(InputDeviceInfo& deviceInfo) override; virtual void dump(std::string& dump) override; - virtual void configure(nsecs_t when, const InputReaderConfiguration* config, - uint32_t changes) override; - virtual void reset(nsecs_t when) override; - virtual void process(const RawEvent* rawEvent) override; + [[nodiscard]] std::list reconfigure(nsecs_t when, + const InputReaderConfiguration& config, + ConfigurationChanges changes) override; + [[nodiscard]] std::list reset(nsecs_t when) override; + [[nodiscard]] std::list process(const RawEvent* rawEvent) override; private: CursorScrollAccumulator mRotaryEncoderScrollAccumulator; int32_t mSource; float mScalingFactor; - int32_t mOrientation; + ui::Rotation mOrientation; - void sync(nsecs_t when, nsecs_t readTime); + explicit RotaryEncoderInputMapper(InputDeviceContext& deviceContext, + const InputReaderConfiguration& readerConfig); + [[nodiscard]] std::list sync(nsecs_t when, nsecs_t readTime); }; } // namespace android - -#endif // _UI_INPUTREADER_ROTARY_ENCODER_INPUT_MAPPER_H \ No newline at end of file diff --git a/services/inputflinger/reader/mapper/SensorInputMapper.cpp b/services/inputflinger/reader/mapper/SensorInputMapper.cpp index b01c2bc13f58b805968f94e5b569b0e72b9ec3a8..a131e3598f4fb07f86028d8caa1e8ba745e49005 100644 --- a/services/inputflinger/reader/mapper/SensorInputMapper.cpp +++ b/services/inputflinger/reader/mapper/SensorInputMapper.cpp @@ -52,8 +52,9 @@ static void convertFromLinuxToAndroid(std::vector& values, } } -SensorInputMapper::SensorInputMapper(InputDeviceContext& deviceContext) - : InputMapper(deviceContext) {} +SensorInputMapper::SensorInputMapper(InputDeviceContext& deviceContext, + const InputReaderConfiguration& readerConfig) + : InputMapper(deviceContext, readerConfig) {} SensorInputMapper::~SensorInputMapper() {} @@ -61,12 +62,6 @@ uint32_t SensorInputMapper::getSources() const { return AINPUT_SOURCE_SENSOR; } -template -bool SensorInputMapper::tryGetProperty(std::string keyName, T& outValue) { - const auto& config = getDeviceContext().getConfiguration(); - return config.tryGetProperty(String8(keyName.c_str()), outValue); -} - void SensorInputMapper::parseSensorConfiguration(InputDeviceSensorType sensorType, int32_t absCode, int32_t sensorDataIndex, const Axis& axis) { auto it = mSensors.find(sensorType); @@ -79,12 +74,12 @@ void SensorInputMapper::parseSensorConfiguration(InputDeviceSensorType sensorTyp } } -void SensorInputMapper::populateDeviceInfo(InputDeviceInfo* info) { +void SensorInputMapper::populateDeviceInfo(InputDeviceInfo& info) { InputMapper::populateDeviceInfo(info); for (const auto& [sensorType, sensor] : mSensors) { - info->addSensorInfo(sensor.sensorInfo); - info->setHasSensor(true); + info.addSensorInfo(sensor.sensorInfo); + info.setHasSensor(true); } } @@ -122,11 +117,12 @@ void SensorInputMapper::dump(std::string& dump) { } } -void SensorInputMapper::configure(nsecs_t when, const InputReaderConfiguration* config, - uint32_t changes) { - InputMapper::configure(when, config, changes); +std::list SensorInputMapper::reconfigure(nsecs_t when, + const InputReaderConfiguration& config, + ConfigurationChanges changes) { + std::list out = InputMapper::reconfigure(when, config, changes); - if (!changes) { // first time only + if (!changes.any()) { // first time only mDeviceEnabled = true; // Check if device has MSC_TIMESTAMP event. mHasHardwareTimestamp = getDeviceContext().hasMscEvent(MSC_TIMESTAMP); @@ -158,6 +154,7 @@ void SensorInputMapper::configure(nsecs_t when, const InputReaderConfiguration* } } } + return out; } SensorInputMapper::Axis SensorInputMapper::createAxis(const AxisInfo& axisInfo, @@ -185,7 +182,7 @@ SensorInputMapper::Axis SensorInputMapper::createAxis(const AxisInfo& axisInfo, return Axis(rawAxisInfo, axisInfo, scale, offset, min, max, flat, fuzz, resolution, filter); } -void SensorInputMapper::reset(nsecs_t when) { +std::list SensorInputMapper::reset(nsecs_t when) { // Recenter all axes. for (std::pair& pair : mAxes) { Axis& axis = pair.second; @@ -193,12 +190,23 @@ void SensorInputMapper::reset(nsecs_t when) { } mHardwareTimestamp = 0; mPrevMscTime = 0; - InputMapper::reset(when); + return InputMapper::reset(when); } SensorInputMapper::Sensor SensorInputMapper::createSensor(InputDeviceSensorType sensorType, const Axis& axis) { InputDeviceIdentifier identifier = getDeviceContext().getDeviceIdentifier(); + const auto& config = getDeviceContext().getConfiguration(); + + std::string prefix = "sensor." + ftl::enum_string(sensorType); + transform(prefix.begin(), prefix.end(), prefix.begin(), ::tolower); + + int32_t flags = 0; + std::optional reportingMode = config.getInt(prefix + ".reportingMode"); + if (reportingMode.has_value()) { + flags |= (*reportingMode & REPORTING_MODE_MASK) << REPORTING_MODE_SHIFT; + } + // Sensor Id will be assigned to device Id to distinguish same sensor from multiple input // devices, in such a way that the sensor Id will be same as input device Id. // The sensorType is to distinguish different sensors within one device. @@ -206,29 +214,16 @@ SensorInputMapper::Sensor SensorInputMapper::createSensor(InputDeviceSensorType InputDeviceSensorInfo sensorInfo(identifier.name, std::to_string(identifier.vendor), identifier.version, sensorType, InputDeviceSensorAccuracy::ACCURACY_HIGH, - axis.max /* maxRange */, axis.scale /* resolution */, - 0.0f /* power */, 0 /* minDelay */, - 0 /* fifoReservedEventCount */, 0 /* fifoMaxEventCount */, - ftl::enum_string(sensorType), 0 /* maxDelay */, 0 /* flags */, - getDeviceId()); - - std::string prefix = "sensor." + ftl::enum_string(sensorType); - transform(prefix.begin(), prefix.end(), prefix.begin(), ::tolower); - - int32_t reportingMode = 0; - if (!tryGetProperty(prefix + ".reportingMode", reportingMode)) { - sensorInfo.flags |= (reportingMode & REPORTING_MODE_MASK) << REPORTING_MODE_SHIFT; - } - - tryGetProperty(prefix + ".maxDelay", sensorInfo.maxDelay); - - tryGetProperty(prefix + ".minDelay", sensorInfo.minDelay); - - tryGetProperty(prefix + ".power", sensorInfo.power); - - tryGetProperty(prefix + ".fifoReservedEventCount", sensorInfo.fifoReservedEventCount); - - tryGetProperty(prefix + ".fifoMaxEventCount", sensorInfo.fifoMaxEventCount); + /*maxRange=*/axis.max, /*resolution=*/axis.scale, + /*power=*/config.getFloat(prefix + ".power").value_or(0.0f), + /*minDelay=*/config.getInt(prefix + ".minDelay").value_or(0), + /*fifoReservedEventCount=*/ + config.getInt(prefix + ".fifoReservedEventCount").value_or(0), + /*fifoMaxEventCount=*/ + config.getInt(prefix + ".fifoMaxEventCount").value_or(0), + ftl::enum_string(sensorType), + /*maxDelay=*/config.getInt(prefix + ".maxDelay").value_or(0), + /*flags=*/flags, getDeviceId()); return Sensor(sensorInfo); } @@ -256,7 +251,8 @@ void SensorInputMapper::processHardWareTimestamp(nsecs_t evTime, int32_t mscTime mPrevMscTime = static_cast(mscTime); } -void SensorInputMapper::process(const RawEvent* rawEvent) { +std::list SensorInputMapper::process(const RawEvent* rawEvent) { + std::list out; switch (rawEvent->type) { case EV_ABS: { auto it = mAxes.find(rawEvent->code); @@ -274,7 +270,7 @@ void SensorInputMapper::process(const RawEvent* rawEvent) { Axis& axis = pair.second; axis.currentValue = axis.newValue; } - sync(rawEvent->when, false /*force*/); + out += sync(rawEvent->when, /*force=*/false); break; } break; @@ -287,6 +283,7 @@ void SensorInputMapper::process(const RawEvent* rawEvent) { break; } } + return out; } bool SensorInputMapper::setSensorEnabled(InputDeviceSensorType sensorType, bool enabled) { @@ -340,7 +337,7 @@ bool SensorInputMapper::enableSensor(InputDeviceSensorType sensorType, maxBatchReportLatency.count()); } - if (!setSensorEnabled(sensorType, true /* enabled */)) { + if (!setSensorEnabled(sensorType, /*enabled=*/true)) { return false; } @@ -363,7 +360,7 @@ void SensorInputMapper::disableSensor(InputDeviceSensorType sensorType) { ALOGD("Disable Sensor %s", ftl::enum_string(sensorType).c_str()); } - if (!setSensorEnabled(sensorType, false /* enabled */)) { + if (!setSensorEnabled(sensorType, /*enabled=*/false)) { return; } @@ -375,7 +372,8 @@ void SensorInputMapper::disableSensor(InputDeviceSensorType sensorType) { } } -void SensorInputMapper::sync(nsecs_t when, bool force) { +std::list SensorInputMapper::sync(nsecs_t when, bool force) { + std::list out; for (auto& [sensorType, sensor] : mSensors) { // Skip if sensor not enabled if (!sensor.enabled) { @@ -405,17 +403,17 @@ void SensorInputMapper::sync(nsecs_t when, bool force) { // Convert to Android unit convertFromLinuxToAndroid(values, sensorType); // Notify dispatcher for sensor event - NotifySensorArgs args(getContext()->getNextId(), when, getDeviceId(), - AINPUT_SOURCE_SENSOR, sensorType, sensor.sensorInfo.accuracy, - sensor.accuracy != - sensor.sensorInfo.accuracy /* accuracyChanged */, - timestamp /* hwTimestamp */, values); - - getListener().notifySensor(&args); + out.push_back(NotifySensorArgs(getContext()->getNextId(), when, getDeviceId(), + AINPUT_SOURCE_SENSOR, sensorType, + sensor.sensorInfo.accuracy, + /*accuracyChanged=*/sensor.accuracy != + sensor.sensorInfo.accuracy, + /*hwTimestamp=*/timestamp, values)); sensor.lastSampleTimeNs = timestamp; sensor.accuracy = sensor.sensorInfo.accuracy; } } + return out; } } // namespace android diff --git a/services/inputflinger/reader/mapper/SensorInputMapper.h b/services/inputflinger/reader/mapper/SensorInputMapper.h index 27a61771f269f69e23e1d04f80a9b4b1cb9274ee..a55dcd19057bd837ca85a1f7c13c243659731ee4 100644 --- a/services/inputflinger/reader/mapper/SensorInputMapper.h +++ b/services/inputflinger/reader/mapper/SensorInputMapper.h @@ -14,8 +14,10 @@ * limitations under the License. */ -#ifndef _UI_INPUTREADER_SENSOR_INPUT_MAPPER_H -#define _UI_INPUTREADER_SENSOR_INPUT_MAPPER_H +#pragma once + +#include +#include #include "InputMapper.h" @@ -25,15 +27,20 @@ static constexpr ssize_t SENSOR_VEC_LEN = 3; class SensorInputMapper : public InputMapper { public: - explicit SensorInputMapper(InputDeviceContext& deviceContext); + template + friend std::unique_ptr createInputMapper(InputDeviceContext& deviceContext, + const InputReaderConfiguration& readerConfig, + Args... args); ~SensorInputMapper() override; uint32_t getSources() const override; - void populateDeviceInfo(InputDeviceInfo* deviceInfo) override; + void populateDeviceInfo(InputDeviceInfo& deviceInfo) override; void dump(std::string& dump) override; - void configure(nsecs_t when, const InputReaderConfiguration* config, uint32_t changes) override; - void reset(nsecs_t when) override; - void process(const RawEvent* rawEvent) override; + [[nodiscard]] std::list reconfigure(nsecs_t when, + const InputReaderConfiguration& config, + ConfigurationChanges changes) override; + [[nodiscard]] std::list reset(nsecs_t when) 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; @@ -101,6 +108,9 @@ private: } }; + explicit SensorInputMapper(InputDeviceContext& deviceContext, + const InputReaderConfiguration& readerConfig); + static Axis createAxis(const AxisInfo& AxisInfo, const RawAbsoluteAxisInfo& rawAxisInfo); // Axes indexed by raw ABS_* axis index. @@ -117,10 +127,7 @@ private: // Sensor list std::unordered_map mSensors; - void sync(nsecs_t when, bool force); - - template - bool tryGetProperty(std::string keyName, T& outValue); + [[nodiscard]] std::list sync(nsecs_t when, bool force); void parseSensorConfiguration(InputDeviceSensorType sensorType, int32_t absCode, int32_t sensorDataIndex, const Axis& axis); @@ -133,5 +140,3 @@ private: }; } // namespace android - -#endif // _UI_INPUTREADER_SENSOR_INPUT_MAPPER_H \ No newline at end of file diff --git a/services/inputflinger/reader/mapper/SingleTouchInputMapper.cpp b/services/inputflinger/reader/mapper/SingleTouchInputMapper.cpp index 4fff9bebb5aca6eea748dd6b0f3553dcf3762627..ed0e27067f4e55a3bfc96b43d5402c389127d5c3 100644 --- a/services/inputflinger/reader/mapper/SingleTouchInputMapper.cpp +++ b/services/inputflinger/reader/mapper/SingleTouchInputMapper.cpp @@ -18,21 +18,23 @@ namespace android { -SingleTouchInputMapper::SingleTouchInputMapper(InputDeviceContext& deviceContext) - : TouchInputMapper(deviceContext) {} +SingleTouchInputMapper::SingleTouchInputMapper(InputDeviceContext& deviceContext, + const InputReaderConfiguration& readerConfig) + : TouchInputMapper(deviceContext, readerConfig) {} SingleTouchInputMapper::~SingleTouchInputMapper() {} -void SingleTouchInputMapper::reset(nsecs_t when) { +std::list SingleTouchInputMapper::reset(nsecs_t when) { mSingleTouchMotionAccumulator.reset(getDeviceContext()); - TouchInputMapper::reset(when); + return TouchInputMapper::reset(when); } -void SingleTouchInputMapper::process(const RawEvent* rawEvent) { - TouchInputMapper::process(rawEvent); +std::list SingleTouchInputMapper::process(const RawEvent* rawEvent) { + std::list out = TouchInputMapper::process(rawEvent); mSingleTouchMotionAccumulator.process(rawEvent); + return out; } void SingleTouchInputMapper::syncTouch(nsecs_t when, RawState* outState) { @@ -40,7 +42,7 @@ void SingleTouchInputMapper::syncTouch(nsecs_t when, RawState* outState) { outState->rawPointerData.pointerCount = 1; outState->rawPointerData.idToIndex[0] = 0; - bool isHovering = mTouchButtonAccumulator.getToolType() != AMOTION_EVENT_TOOL_TYPE_MOUSE && + bool isHovering = mTouchButtonAccumulator.getToolType() != ToolType::MOUSE && (mTouchButtonAccumulator.isHovering() || (mRawPointerAxes.pressure.valid && mSingleTouchMotionAccumulator.getAbsolutePressure() <= 0)); @@ -60,8 +62,8 @@ void SingleTouchInputMapper::syncTouch(nsecs_t when, RawState* outState) { outPointer.tiltX = mSingleTouchMotionAccumulator.getAbsoluteTiltX(); outPointer.tiltY = mSingleTouchMotionAccumulator.getAbsoluteTiltY(); outPointer.toolType = mTouchButtonAccumulator.getToolType(); - if (outPointer.toolType == AMOTION_EVENT_TOOL_TYPE_UNKNOWN) { - outPointer.toolType = AMOTION_EVENT_TOOL_TYPE_FINGER; + if (outPointer.toolType == ToolType::UNKNOWN) { + outPointer.toolType = ToolType::FINGER; } outPointer.isHovering = isHovering; } diff --git a/services/inputflinger/reader/mapper/SingleTouchInputMapper.h b/services/inputflinger/reader/mapper/SingleTouchInputMapper.h index 9cb3f67f201886e4a68cc15aed9b5876fc62b993..dac53cf7001fd249add0e3ea405c3c502ff34dc1 100644 --- a/services/inputflinger/reader/mapper/SingleTouchInputMapper.h +++ b/services/inputflinger/reader/mapper/SingleTouchInputMapper.h @@ -14,8 +14,7 @@ * limitations under the License. */ -#ifndef _UI_INPUTREADER_SINGLE_TOUCH_INPUT_MAPPER_H -#define _UI_INPUTREADER_SINGLE_TOUCH_INPUT_MAPPER_H +#pragma once #include "SingleTouchMotionAccumulator.h" #include "TouchInputMapper.h" @@ -24,11 +23,17 @@ namespace android { class SingleTouchInputMapper : public TouchInputMapper { public: - explicit SingleTouchInputMapper(InputDeviceContext& deviceContext); + template + friend std::unique_ptr createInputMapper(InputDeviceContext& deviceContext, + const InputReaderConfiguration& readerConfig, + Args... args); + explicit SingleTouchInputMapper(InputDeviceContext& deviceContext, + const InputReaderConfiguration& readerConfig); + ~SingleTouchInputMapper() override; - void reset(nsecs_t when) override; - void process(const RawEvent* rawEvent) override; + [[nodiscard]] std::list reset(nsecs_t when) override; + [[nodiscard]] std::list process(const RawEvent* rawEvent) override; protected: void syncTouch(nsecs_t when, RawState* outState) override; @@ -40,5 +45,3 @@ private: }; } // namespace android - -#endif // _UI_INPUTREADER_SINGLE_TOUCH_INPUT_MAPPER_H \ No newline at end of file diff --git a/services/inputflinger/reader/mapper/SwitchInputMapper.cpp b/services/inputflinger/reader/mapper/SwitchInputMapper.cpp index ebb5de66ed48a48a8c237e19ba446106d85067a2..05338da146652296964c79734b9ab07ac3dce794 100644 --- a/services/inputflinger/reader/mapper/SwitchInputMapper.cpp +++ b/services/inputflinger/reader/mapper/SwitchInputMapper.cpp @@ -20,8 +20,9 @@ namespace android { -SwitchInputMapper::SwitchInputMapper(InputDeviceContext& deviceContext) - : InputMapper(deviceContext), mSwitchValues(0), mUpdatedSwitchMask(0) {} +SwitchInputMapper::SwitchInputMapper(InputDeviceContext& deviceContext, + const InputReaderConfiguration& readerConfig) + : InputMapper(deviceContext, readerConfig), mSwitchValues(0), mUpdatedSwitchMask(0) {} SwitchInputMapper::~SwitchInputMapper() {} @@ -29,7 +30,8 @@ uint32_t SwitchInputMapper::getSources() const { return AINPUT_SOURCE_SWITCH; } -void SwitchInputMapper::process(const RawEvent* rawEvent) { +std::list SwitchInputMapper::process(const RawEvent* rawEvent) { + std::list out; switch (rawEvent->type) { case EV_SW: processSwitch(rawEvent->code, rawEvent->value); @@ -37,9 +39,10 @@ void SwitchInputMapper::process(const RawEvent* rawEvent) { case EV_SYN: if (rawEvent->code == SYN_REPORT) { - sync(rawEvent->when); + out += sync(rawEvent->when); } } + return out; } void SwitchInputMapper::processSwitch(int32_t switchCode, int32_t switchValue) { @@ -53,15 +56,16 @@ void SwitchInputMapper::processSwitch(int32_t switchCode, int32_t switchValue) { } } -void SwitchInputMapper::sync(nsecs_t when) { +std::list SwitchInputMapper::sync(nsecs_t when) { + std::list out; if (mUpdatedSwitchMask) { uint32_t updatedSwitchValues = mSwitchValues & mUpdatedSwitchMask; - NotifySwitchArgs args(getContext()->getNextId(), when, 0 /*policyFlags*/, - updatedSwitchValues, mUpdatedSwitchMask); - getListener().notifySwitch(&args); + out.push_back(NotifySwitchArgs(getContext()->getNextId(), when, /*policyFlags=*/0, + updatedSwitchValues, mUpdatedSwitchMask)); mUpdatedSwitchMask = 0; } + return out; } int32_t SwitchInputMapper::getSwitchState(uint32_t sourceMask, int32_t switchCode) { diff --git a/services/inputflinger/reader/mapper/SwitchInputMapper.h b/services/inputflinger/reader/mapper/SwitchInputMapper.h index 64b9aa27b4e735045a2fb38b97d487f71b0dc5fb..2fb48bbf2538ebd460e913e7701a10272bf2da60 100644 --- a/services/inputflinger/reader/mapper/SwitchInputMapper.h +++ b/services/inputflinger/reader/mapper/SwitchInputMapper.h @@ -14,8 +14,7 @@ * limitations under the License. */ -#ifndef _UI_INPUTREADER_SWITCH_INPUT_MAPPER_H -#define _UI_INPUTREADER_SWITCH_INPUT_MAPPER_H +#pragma once #include "InputMapper.h" @@ -23,11 +22,14 @@ namespace android { class SwitchInputMapper : public InputMapper { public: - explicit SwitchInputMapper(InputDeviceContext& deviceContext); + template + friend std::unique_ptr createInputMapper(InputDeviceContext& deviceContext, + const InputReaderConfiguration& readerConfig, + Args... args); virtual ~SwitchInputMapper(); virtual uint32_t getSources() const override; - virtual void 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; @@ -36,10 +38,10 @@ private: uint32_t mSwitchValues; uint32_t mUpdatedSwitchMask; + explicit SwitchInputMapper(InputDeviceContext& deviceContext, + const InputReaderConfiguration& readerConfig); void processSwitch(int32_t switchCode, int32_t switchValue); - void sync(nsecs_t when); + [[nodiscard]] std::list sync(nsecs_t when); }; } // namespace android - -#endif // _UI_INPUTREADER_SWITCH_INPUT_MAPPER_H \ No newline at end of file diff --git a/services/inputflinger/reader/mapper/TouchCursorInputMapperCommon.cpp b/services/inputflinger/reader/mapper/TouchCursorInputMapperCommon.cpp new file mode 100644 index 0000000000000000000000000000000000000000..c12e95dfa04cc8600cd1bfe329e644a13b7aefba --- /dev/null +++ b/services/inputflinger/reader/mapper/TouchCursorInputMapperCommon.cpp @@ -0,0 +1,127 @@ +/* + * 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. + */ + +#include +#include +#include + +#include "EventHub.h" +#include "InputListener.h" +#include "InputReaderContext.h" + +namespace android { + +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 lastButtonState, int32_t currentButtonState, int32_t buttonState, int32_t keyCode) { + std::list out; + if ((action == AKEY_EVENT_ACTION_DOWN && !(lastButtonState & buttonState) && + (currentButtonState & buttonState)) || + (action == AKEY_EVENT_ACTION_UP && (lastButtonState & buttonState) && + !(currentButtonState & buttonState))) { + out.push_back(NotifyKeyArgs(context->getNextId(), when, readTime, deviceId, source, + displayId, policyFlags, action, 0, keyCode, 0, + context->getGlobalMetaState(), when)); + } + return out; +} + +} // namespace + +ui::Rotation getInverseRotation(ui::Rotation orientation) { + switch (orientation) { + case ui::ROTATION_90: + return ui::ROTATION_270; + case ui::ROTATION_270: + return ui::ROTATION_90; + default: + return orientation; + } +} + +void rotateDelta(ui::Rotation orientation, float* deltaX, float* deltaY) { + float temp; + switch (orientation) { + case ui::ROTATION_90: + temp = *deltaX; + *deltaX = *deltaY; + *deltaY = -temp; + break; + + case ui::ROTATION_180: + *deltaX = -*deltaX; + *deltaY = -*deltaY; + break; + + case ui::ROTATION_270: + temp = *deltaX; + *deltaX = -*deltaY; + *deltaY = temp; + break; + + default: + break; + } +} + +bool isPointerDown(int32_t buttonState) { + return buttonState & + (AMOTION_EVENT_BUTTON_PRIMARY | AMOTION_EVENT_BUTTON_SECONDARY | + AMOTION_EVENT_BUTTON_TERTIARY); +} + +[[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 lastButtonState, int32_t currentButtonState) { + std::list out; + out += synthesizeButtonKey(context, action, when, readTime, deviceId, source, displayId, + policyFlags, lastButtonState, currentButtonState, + AMOTION_EVENT_BUTTON_BACK, AKEYCODE_BACK); + out += synthesizeButtonKey(context, action, when, readTime, deviceId, source, displayId, + policyFlags, lastButtonState, currentButtonState, + AMOTION_EVENT_BUTTON_FORWARD, AKEYCODE_FORWARD); + return out; +} + +std::tuple applyBluetoothTimestampSmoothening( + const InputDeviceIdentifier& identifier, nsecs_t currentEventTime, nsecs_t readTime, + nsecs_t lastEventTime) { + if (identifier.bus != BUS_BLUETOOTH) { + return {currentEventTime, readTime}; + } + + // Assume the fastest rate at which a Bluetooth touch device can report input events is one + // every 4 milliseconds, or 250 Hz. Timestamps for successive events from a Bluetooth device + // will be separated by at least this amount. + constexpr static nsecs_t MIN_BLUETOOTH_TIMESTAMP_DELTA = ms2ns(4); + // We define a maximum smoothing time delta so that we don't generate events too far into the + // future. + constexpr static nsecs_t MAX_BLUETOOTH_SMOOTHING_DELTA = ms2ns(32); + const nsecs_t smoothenedEventTime = + std::min(std::max(currentEventTime, lastEventTime + MIN_BLUETOOTH_TIMESTAMP_DELTA), + currentEventTime + MAX_BLUETOOTH_SMOOTHING_DELTA); + // If we are modifying the event time, treat this event as a synthetically generated event for + // latency tracking purposes and use the event time as the read time (zero read latency). + const nsecs_t smoothenedReadTime = + smoothenedEventTime != currentEventTime ? currentEventTime : readTime; + return {smoothenedEventTime, smoothenedReadTime}; +} + +} // namespace android diff --git a/services/inputflinger/reader/mapper/TouchCursorInputMapperCommon.h b/services/inputflinger/reader/mapper/TouchCursorInputMapperCommon.h index 31a3d2e172deb64ad16d5a00f3093514e90467b3..3023e686c2941c844cbc2c1941986378307168f6 100644 --- a/services/inputflinger/reader/mapper/TouchCursorInputMapperCommon.h +++ b/services/inputflinger/reader/mapper/TouchCursorInputMapperCommon.h @@ -14,11 +14,11 @@ * limitations under the License. */ -#ifndef _UI_INPUTREADER_TOUCH_CURSOR_INPUT_MAPPER_COMMON_H -#define _UI_INPUTREADER_TOUCH_CURSOR_INPUT_MAPPER_COMMON_H +#pragma once #include #include +#include #include "EventHub.h" #include "InputListener.h" @@ -26,78 +26,30 @@ namespace android { -// --- Static Definitions --- +ui::Rotation getInverseRotation(ui::Rotation orientation); -static int32_t getInverseRotation(int32_t orientation) { - switch (orientation) { - case DISPLAY_ORIENTATION_90: - return DISPLAY_ORIENTATION_270; - case DISPLAY_ORIENTATION_270: - return DISPLAY_ORIENTATION_90; - default: - return orientation; - } -} - -static void rotateDelta(int32_t orientation, float* deltaX, float* deltaY) { - float temp; - switch (orientation) { - case DISPLAY_ORIENTATION_90: - temp = *deltaX; - *deltaX = *deltaY; - *deltaY = -temp; - break; - - case DISPLAY_ORIENTATION_180: - *deltaX = -*deltaX; - *deltaY = -*deltaY; - break; - - case DISPLAY_ORIENTATION_270: - temp = *deltaX; - *deltaX = -*deltaY; - *deltaY = temp; - break; - - default: - break; - } -} +void rotateDelta(ui::Rotation orientation, float* deltaX, float* deltaY); // Returns true if the pointer should be reported as being down given the specified // button states. This determines whether the event is reported as a touch event. -static bool isPointerDown(int32_t buttonState) { - return buttonState & - (AMOTION_EVENT_BUTTON_PRIMARY | AMOTION_EVENT_BUTTON_SECONDARY | - AMOTION_EVENT_BUTTON_TERTIARY); -} - -static void 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 lastButtonState, - int32_t currentButtonState, int32_t buttonState, int32_t keyCode) { - if ((action == AKEY_EVENT_ACTION_DOWN && !(lastButtonState & buttonState) && - (currentButtonState & buttonState)) || - (action == AKEY_EVENT_ACTION_UP && (lastButtonState & buttonState) && - !(currentButtonState & buttonState))) { - NotifyKeyArgs args(context->getNextId(), when, readTime, deviceId, source, displayId, - policyFlags, action, 0, keyCode, 0, context->getGlobalMetaState(), when); - context->getListener().notifyKey(&args); - } -} - -static void 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 lastButtonState, - int32_t currentButtonState) { - synthesizeButtonKey(context, action, when, readTime, deviceId, source, displayId, policyFlags, - lastButtonState, currentButtonState, AMOTION_EVENT_BUTTON_BACK, - AKEYCODE_BACK); - synthesizeButtonKey(context, action, when, readTime, deviceId, source, displayId, policyFlags, - lastButtonState, currentButtonState, AMOTION_EVENT_BUTTON_FORWARD, - AKEYCODE_FORWARD); -} +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 lastButtonState, int32_t currentButtonState); + +// For devices connected over Bluetooth, although they may produce events at a consistent rate, +// the events might end up reaching Android in a "batched" manner through the Bluetooth +// stack, where a few events may be clumped together and processed around the same time. +// In this case, if the input device or its driver does not send or process the actual event +// generation timestamps, the event time will set to whenever the kernel received the event. +// When the timestamp deltas are minuscule for these batched events, any changes in x or y +// coordinates result in extremely large instantaneous velocities, which can negatively impact +// user experience. To avoid this, we augment the timestamps so that subsequent event timestamps +// differ by at least a minimum delta value. +std::tuple applyBluetoothTimestampSmoothening( + const InputDeviceIdentifier& identifier, nsecs_t currentEventTime, nsecs_t readTime, + nsecs_t lastEventTime); } // namespace android - -#endif // _UI_INPUTREADER_TOUCH_CURSOR_INPUT_MAPPER_COMMON_H diff --git a/services/inputflinger/reader/mapper/TouchInputMapper.cpp b/services/inputflinger/reader/mapper/TouchInputMapper.cpp index bc1add59d75348bbe6f61016c328343d1e07f715..f2b0a4b0a7d74511bc980299848186753f1f0729 100644 --- a/services/inputflinger/reader/mapper/TouchInputMapper.cpp +++ b/services/inputflinger/reader/mapper/TouchInputMapper.cpp @@ -21,31 +21,44 @@ #include "TouchInputMapper.h" #include +#include #include "CursorButtonAccumulator.h" #include "CursorScrollAccumulator.h" #include "TouchButtonAccumulator.h" #include "TouchCursorInputMapperCommon.h" +#include "ui/Rotation.h" namespace android { // --- Constants --- -// Maximum amount of latency to add to touch events while waiting for data from an -// external stylus. -static constexpr nsecs_t EXTERNAL_STYLUS_DATA_TIMEOUT = ms2ns(72); - -// Maximum amount of time to wait on touch data before pushing out new pressure data. -static constexpr nsecs_t TOUCH_DATA_TIMEOUT = ms2ns(20); - // Artificial latency on synthetic events created from stylus data without corresponding touch // data. static constexpr nsecs_t STYLUS_DATA_LATENCY = ms2ns(10); +// Minimum width between two pointers to determine a gesture as freeform gesture in mm +static const float MIN_FREEFORM_GESTURE_WIDTH_IN_MILLIMETER = 30; // --- Static Definitions --- static const DisplayViewport kUninitializedViewport; +static std::string toString(const Rect& rect) { + return base::StringPrintf("Rect{%d, %d, %d, %d}", rect.left, rect.top, rect.right, rect.bottom); +} + +static std::string toString(const ui::Size& size) { + return base::StringPrintf("%dx%d", size.width, size.height); +} + +static bool isPointInRect(const Rect& rect, vec2 p) { + return p.x >= rect.left && p.x < rect.right && p.y >= rect.top && p.y < rect.bottom; +} + +static std::string toString(const InputDeviceUsiVersion& v) { + return base::StringPrintf("%d.%d", v.majorVersion, v.minorVersion); +} + template inline static void swap(T& a, T& b) { T temp = a; @@ -71,53 +84,24 @@ inline static int32_t signExtendNybble(int32_t value) { return value >= 8 ? value - 16 : value; } -// --- RawPointerAxes --- - -RawPointerAxes::RawPointerAxes() { - clear(); +static ui::Size getNaturalDisplaySize(const DisplayViewport& viewport) { + ui::Size rotatedDisplaySize{viewport.deviceWidth, viewport.deviceHeight}; + if (viewport.orientation == ui::ROTATION_90 || viewport.orientation == ui::ROTATION_270) { + std::swap(rotatedDisplaySize.width, rotatedDisplaySize.height); + } + return rotatedDisplaySize; } -void RawPointerAxes::clear() { - x.clear(); - y.clear(); - pressure.clear(); - touchMajor.clear(); - touchMinor.clear(); - toolMajor.clear(); - toolMinor.clear(); - orientation.clear(); - distance.clear(); - tiltX.clear(); - tiltY.clear(); - trackingId.clear(); - slot.clear(); +static int32_t filterButtonState(InputReaderConfiguration& config, int32_t buttonState) { + if (!config.stylusButtonMotionEventsEnabled) { + buttonState &= + ~(AMOTION_EVENT_BUTTON_STYLUS_PRIMARY | AMOTION_EVENT_BUTTON_STYLUS_SECONDARY); + } + return buttonState; } // --- RawPointerData --- -RawPointerData::RawPointerData() { - clear(); -} - -void RawPointerData::clear() { - pointerCount = 0; - clearIdBits(); -} - -void RawPointerData::copyFrom(const RawPointerData& other) { - pointerCount = other.pointerCount; - hoveringIdBits = other.hoveringIdBits; - touchingIdBits = other.touchingIdBits; - canceledIdBits = other.canceledIdBits; - - for (uint32_t i = 0; i < pointerCount; i++) { - pointers[i] = other.pointers[i]; - - int id = pointers[i].id; - idToIndex[id] = other.idToIndex[id]; - } -} - void RawPointerData::getCentroidOfTouchingPointers(float* outX, float* outY) const { float x = 0, y = 0; uint32_t count = touchingIdBits.count(); @@ -135,48 +119,13 @@ void RawPointerData::getCentroidOfTouchingPointers(float* outX, float* outY) con *outY = y; } -// --- CookedPointerData --- - -CookedPointerData::CookedPointerData() { - clear(); -} - -void CookedPointerData::clear() { - pointerCount = 0; - hoveringIdBits.clear(); - touchingIdBits.clear(); - canceledIdBits.clear(); - validIdBits.clear(); -} - -void CookedPointerData::copyFrom(const CookedPointerData& other) { - pointerCount = other.pointerCount; - hoveringIdBits = other.hoveringIdBits; - touchingIdBits = other.touchingIdBits; - validIdBits = other.validIdBits; - - for (uint32_t i = 0; i < pointerCount; i++) { - pointerProperties[i].copyFrom(other.pointerProperties[i]); - pointerCoords[i].copyFrom(other.pointerCoords[i]); - - int id = pointerProperties[i].id; - idToIndex[id] = other.idToIndex[id]; - } -} - // --- TouchInputMapper --- -TouchInputMapper::TouchInputMapper(InputDeviceContext& deviceContext) - : InputMapper(deviceContext), - mSource(0), - mDeviceMode(DeviceMode::DISABLED), - mDisplayWidth(-1), - mDisplayHeight(-1), - mPhysicalWidth(-1), - mPhysicalHeight(-1), - mPhysicalLeft(0), - mPhysicalTop(0), - mInputDeviceOrientation(DISPLAY_ORIENTATION_0) {} +TouchInputMapper::TouchInputMapper(InputDeviceContext& deviceContext, + const InputReaderConfiguration& readerConfig) + : InputMapper(deviceContext, readerConfig), + mTouchButtonAccumulator(deviceContext), + mConfig(readerConfig) {} TouchInputMapper::~TouchInputMapper() {} @@ -184,76 +133,65 @@ uint32_t TouchInputMapper::getSources() const { return mSource; } -void TouchInputMapper::populateDeviceInfo(InputDeviceInfo* info) { +void TouchInputMapper::populateDeviceInfo(InputDeviceInfo& info) { InputMapper::populateDeviceInfo(info); - if (mDeviceMode != DeviceMode::DISABLED) { - info->addMotionRange(mOrientedRanges.x); - info->addMotionRange(mOrientedRanges.y); - info->addMotionRange(mOrientedRanges.pressure); + if (mDeviceMode == DeviceMode::DISABLED) { + return; + } - if (mDeviceMode == DeviceMode::UNSCALED && mSource == AINPUT_SOURCE_TOUCHPAD) { - // Populate RELATIVE_X and RELATIVE_Y motion ranges for touchpad capture mode. - // - // RELATIVE_X and RELATIVE_Y motion ranges should be the largest possible relative - // motion, i.e. the hardware dimensions, as the finger could move completely across the - // touchpad in one sample cycle. - const InputDeviceInfo::MotionRange& x = mOrientedRanges.x; - const InputDeviceInfo::MotionRange& y = mOrientedRanges.y; - info->addMotionRange(AMOTION_EVENT_AXIS_RELATIVE_X, mSource, -x.max, x.max, x.flat, - x.fuzz, x.resolution); - info->addMotionRange(AMOTION_EVENT_AXIS_RELATIVE_Y, mSource, -y.max, y.max, y.flat, - y.fuzz, y.resolution); - } + info.addMotionRange(mOrientedRanges.x); + info.addMotionRange(mOrientedRanges.y); + info.addMotionRange(mOrientedRanges.pressure); - if (mOrientedRanges.haveSize) { - info->addMotionRange(mOrientedRanges.size); - } + if (mDeviceMode == DeviceMode::UNSCALED && mSource == AINPUT_SOURCE_TOUCHPAD) { + // Populate RELATIVE_X and RELATIVE_Y motion ranges for touchpad capture mode. + // + // RELATIVE_X and RELATIVE_Y motion ranges should be the largest possible relative + // motion, i.e. the hardware dimensions, as the finger could move completely across the + // touchpad in one sample cycle. + const InputDeviceInfo::MotionRange& x = mOrientedRanges.x; + const InputDeviceInfo::MotionRange& y = mOrientedRanges.y; + info.addMotionRange(AMOTION_EVENT_AXIS_RELATIVE_X, mSource, -x.max, x.max, x.flat, x.fuzz, + x.resolution); + info.addMotionRange(AMOTION_EVENT_AXIS_RELATIVE_Y, mSource, -y.max, y.max, y.flat, y.fuzz, + y.resolution); + } - if (mOrientedRanges.haveTouchSize) { - info->addMotionRange(mOrientedRanges.touchMajor); - info->addMotionRange(mOrientedRanges.touchMinor); - } + if (mOrientedRanges.size) { + info.addMotionRange(*mOrientedRanges.size); + } - if (mOrientedRanges.haveToolSize) { - info->addMotionRange(mOrientedRanges.toolMajor); - info->addMotionRange(mOrientedRanges.toolMinor); - } + if (mOrientedRanges.touchMajor) { + info.addMotionRange(*mOrientedRanges.touchMajor); + info.addMotionRange(*mOrientedRanges.touchMinor); + } - if (mOrientedRanges.haveOrientation) { - info->addMotionRange(mOrientedRanges.orientation); - } + if (mOrientedRanges.toolMajor) { + info.addMotionRange(*mOrientedRanges.toolMajor); + info.addMotionRange(*mOrientedRanges.toolMinor); + } - if (mOrientedRanges.haveDistance) { - info->addMotionRange(mOrientedRanges.distance); - } + if (mOrientedRanges.orientation) { + info.addMotionRange(*mOrientedRanges.orientation); + } - if (mOrientedRanges.haveTilt) { - info->addMotionRange(mOrientedRanges.tilt); - } + if (mOrientedRanges.distance) { + info.addMotionRange(*mOrientedRanges.distance); + } - if (mCursorScrollAccumulator.haveRelativeVWheel()) { - info->addMotionRange(AMOTION_EVENT_AXIS_VSCROLL, mSource, -1.0f, 1.0f, 0.0f, 0.0f, - 0.0f); - } - if (mCursorScrollAccumulator.haveRelativeHWheel()) { - info->addMotionRange(AMOTION_EVENT_AXIS_HSCROLL, mSource, -1.0f, 1.0f, 0.0f, 0.0f, - 0.0f); - } - if (mCalibration.coverageCalibration == Calibration::CoverageCalibration::BOX) { - const InputDeviceInfo::MotionRange& x = mOrientedRanges.x; - const InputDeviceInfo::MotionRange& y = mOrientedRanges.y; - info->addMotionRange(AMOTION_EVENT_AXIS_GENERIC_1, mSource, x.min, x.max, x.flat, - x.fuzz, x.resolution); - info->addMotionRange(AMOTION_EVENT_AXIS_GENERIC_2, mSource, y.min, y.max, y.flat, - y.fuzz, y.resolution); - info->addMotionRange(AMOTION_EVENT_AXIS_GENERIC_3, mSource, x.min, x.max, x.flat, - x.fuzz, x.resolution); - info->addMotionRange(AMOTION_EVENT_AXIS_GENERIC_4, mSource, y.min, y.max, y.flat, - y.fuzz, y.resolution); - } - info->setButtonUnderPad(mParameters.hasButtonUnderPad); + if (mOrientedRanges.tilt) { + info.addMotionRange(*mOrientedRanges.tilt); + } + + if (mCursorScrollAccumulator.haveRelativeVWheel()) { + info.addMotionRange(AMOTION_EVENT_AXIS_VSCROLL, mSource, -1.0f, 1.0f, 0.0f, 0.0f, 0.0f); + } + if (mCursorScrollAccumulator.haveRelativeHWheel()) { + info.addMotionRange(AMOTION_EVENT_AXIS_HSCROLL, mSource, -1.0f, 1.0f, 0.0f, 0.0f, 0.0f); } + info.setButtonUnderPad(mParameters.hasButtonUnderPad); + info.setUsiVersion(mParameters.usiVersion); } void TouchInputMapper::dump(std::string& dump) { @@ -267,10 +205,10 @@ void TouchInputMapper::dump(std::string& dump) { dumpDisplay(dump); dump += StringPrintf(INDENT3 "Translation and Scaling Factors:\n"); - dump += StringPrintf(INDENT4 "XScale: %0.3f\n", mXScale); - dump += StringPrintf(INDENT4 "YScale: %0.3f\n", mYScale); - dump += StringPrintf(INDENT4 "XPrecision: %0.3f\n", mXPrecision); - dump += StringPrintf(INDENT4 "YPrecision: %0.3f\n", mYPrecision); + mRawToDisplay.dump(dump, "RawToDisplay Transform:", INDENT4); + mRawRotation.dump(dump, "RawRotation Transform:", INDENT4); + dump += StringPrintf(INDENT4 "OrientedXPrecision: %0.3f\n", mOrientedXPrecision); + dump += StringPrintf(INDENT4 "OrientedYPrecision: %0.3f\n", mOrientedYPrecision); dump += StringPrintf(INDENT4 "GeometricScale: %0.3f\n", mGeometricScale); dump += StringPrintf(INDENT4 "PressureScale: %0.3f\n", mPressureScale); dump += StringPrintf(INDENT4 "SizeScale: %0.3f\n", mSizeScale); @@ -290,11 +228,12 @@ void TouchInputMapper::dump(std::string& dump) { dump += StringPrintf(INDENT4 "[%d]: id=%d, x=%d, y=%d, pressure=%d, " "touchMajor=%d, touchMinor=%d, toolMajor=%d, toolMinor=%d, " "orientation=%d, tiltX=%d, tiltY=%d, distance=%d, " - "toolType=%d, isHovering=%s\n", + "toolType=%s, isHovering=%s\n", i, pointer.id, pointer.x, pointer.y, pointer.pressure, pointer.touchMajor, pointer.touchMinor, pointer.toolMajor, pointer.toolMinor, pointer.orientation, pointer.tiltX, pointer.tiltY, - pointer.distance, pointer.toolType, toString(pointer.isHovering)); + pointer.distance, ftl::enum_string(pointer.toolType).c_str(), + toString(pointer.isHovering)); } dump += StringPrintf(INDENT3 "Last Cooked Button State: 0x%08x\n", @@ -309,7 +248,7 @@ void TouchInputMapper::dump(std::string& dump) { "pressure=%0.3f, touchMajor=%0.3f, touchMinor=%0.3f, " "toolMajor=%0.3f, toolMinor=%0.3f, " "orientation=%0.3f, tilt=%0.3f, distance=%0.3f, " - "toolType=%d, isHovering=%s\n", + "toolType=%s, isHovering=%s\n", i, pointerProperties.id, pointerCoords.getX(), pointerCoords.getY(), pointerCoords.getAxisValue(AMOTION_EVENT_AXIS_RELATIVE_X), pointerCoords.getAxisValue(AMOTION_EVENT_AXIS_RELATIVE_Y), @@ -321,16 +260,19 @@ void TouchInputMapper::dump(std::string& dump) { pointerCoords.getAxisValue(AMOTION_EVENT_AXIS_ORIENTATION), pointerCoords.getAxisValue(AMOTION_EVENT_AXIS_TILT), pointerCoords.getAxisValue(AMOTION_EVENT_AXIS_DISTANCE), - pointerProperties.toolType, + ftl::enum_string(pointerProperties.toolType).c_str(), toString(mLastCookedState.cookedPointerData.isHovering(i))); } dump += INDENT3 "Stylus Fusion:\n"; dump += StringPrintf(INDENT4 "ExternalStylusConnected: %s\n", toString(mExternalStylusConnected)); - dump += StringPrintf(INDENT4 "External Stylus ID: %" PRId64 "\n", mExternalStylusId); + dump += StringPrintf(INDENT4 "Fused External Stylus Pointer ID: %s\n", + toString(mFusedStylusPointerId).c_str()); dump += StringPrintf(INDENT4 "External Stylus Data Timeout: %" PRId64 "\n", mExternalStylusFusionTimeout); + dump += StringPrintf(INDENT4 " External Stylus Buttons Applied: 0x%08x", + mExternalStylusButtonsApplied); dump += INDENT3 "External Stylus State:\n"; dumpStylusState(dump, mExternalStylusState); @@ -344,19 +286,23 @@ void TouchInputMapper::dump(std::string& dump) { } } -void TouchInputMapper::configure(nsecs_t when, const InputReaderConfiguration* config, - uint32_t changes) { - InputMapper::configure(when, config, changes); +std::list TouchInputMapper::reconfigure(nsecs_t when, + const InputReaderConfiguration& config, + ConfigurationChanges changes) { + std::list out = InputMapper::reconfigure(when, config, changes); - mConfig = *config; + mConfig = config; - if (!changes) { // first time only + // Full configuration should happen the first time configure is called and + // when the device type is changed. Changing a device type can affect + // various other parameters so should result in a reconfiguration. + if (!changes.any() || changes.test(InputReaderConfiguration::Change::DEVICE_TYPE)) { // Configure basic parameters. - configureParameters(); + mParameters = computeParameters(getDeviceContext()); // Configure common accumulators. mCursorScrollAccumulator.configure(getDeviceContext()); - mTouchButtonAccumulator.configure(getDeviceContext()); + mTouchButtonAccumulator.configure(); // Configure absolute axis information. configureRawPointerAxes(); @@ -366,41 +312,41 @@ void TouchInputMapper::configure(nsecs_t when, const InputReaderConfiguration* c resolveCalibration(); } - if (!changes || (changes & InputReaderConfiguration::CHANGE_TOUCH_AFFINE_TRANSFORMATION)) { + if (!changes.any() || + changes.test(InputReaderConfiguration::Change::TOUCH_AFFINE_TRANSFORMATION)) { // Update location calibration to reflect current settings updateAffineTransformation(); } - if (!changes || (changes & InputReaderConfiguration::CHANGE_POINTER_SPEED)) { + if (!changes.any() || changes.test(InputReaderConfiguration::Change::POINTER_SPEED)) { // Update pointer speed. mPointerVelocityControl.setParameters(mConfig.pointerVelocityControlParameters); mWheelXVelocityControl.setParameters(mConfig.wheelVelocityControlParameters); mWheelYVelocityControl.setParameters(mConfig.wheelVelocityControlParameters); } + using namespace ftl::flag_operators; bool resetNeeded = false; - if (!changes || - (changes & - (InputReaderConfiguration::CHANGE_DISPLAY_INFO | - InputReaderConfiguration::CHANGE_POINTER_CAPTURE | - InputReaderConfiguration::CHANGE_POINTER_GESTURE_ENABLEMENT | - InputReaderConfiguration::CHANGE_SHOW_TOUCHES | - InputReaderConfiguration::CHANGE_EXTERNAL_STYLUS_PRESENCE))) { + if (!changes.any() || + 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 // scaling factors. configureInputDevice(when, &resetNeeded); } - if (changes && resetNeeded) { - // If the device needs to be reset, cancel any ongoing gestures and reset the state. - cancelTouch(when, when); - reset(when); + if (changes.any() && resetNeeded) { + out += reset(when); // Send reset, unless this is the first time the device has been configured, // in which case the reader will call reset itself after all mappers are ready. - NotifyDeviceResetArgs args(getContext()->getNextId(), when, getDeviceId()); - getListener().notifyDeviceReset(&args); + out.emplace_back(NotifyDeviceResetArgs(getContext()->getNextId(), when, getDeviceId())); } + return out; } void TouchInputMapper::resolveExternalStylusPresence() { @@ -413,104 +359,119 @@ void TouchInputMapper::resolveExternalStylusPresence() { } } -void TouchInputMapper::configureParameters() { +TouchInputMapper::Parameters TouchInputMapper::computeParameters( + const InputDeviceContext& deviceContext) { + Parameters parameters; // Use the pointer presentation mode for devices that do not support distinct // multitouch. The spot-based presentation relies on being able to accurately // locate two or more fingers on the touch pad. - mParameters.gestureMode = getDeviceContext().hasInputProperty(INPUT_PROP_SEMI_MT) + parameters.gestureMode = deviceContext.hasInputProperty(INPUT_PROP_SEMI_MT) ? Parameters::GestureMode::SINGLE_TOUCH : Parameters::GestureMode::MULTI_TOUCH; - String8 gestureModeString; - if (getDeviceContext().getConfiguration().tryGetProperty(String8("touch.gestureMode"), - gestureModeString)) { - if (gestureModeString == "single-touch") { - mParameters.gestureMode = Parameters::GestureMode::SINGLE_TOUCH; - } else if (gestureModeString == "multi-touch") { - mParameters.gestureMode = Parameters::GestureMode::MULTI_TOUCH; - } else if (gestureModeString != "default") { - ALOGW("Invalid value for touch.gestureMode: '%s'", gestureModeString.c_str()); + const PropertyMap& config = deviceContext.getConfiguration(); + std::optional gestureModeString = config.getString("touch.gestureMode"); + if (gestureModeString.has_value()) { + if (*gestureModeString == "single-touch") { + parameters.gestureMode = Parameters::GestureMode::SINGLE_TOUCH; + } else if (*gestureModeString == "multi-touch") { + parameters.gestureMode = Parameters::GestureMode::MULTI_TOUCH; + } else if (*gestureModeString != "default") { + ALOGW("Invalid value for touch.gestureMode: '%s'", gestureModeString->c_str()); } } - if (getDeviceContext().hasInputProperty(INPUT_PROP_DIRECT)) { - // The device is a touch screen. - mParameters.deviceType = Parameters::DeviceType::TOUCH_SCREEN; - } else if (getDeviceContext().hasInputProperty(INPUT_PROP_POINTER)) { - // The device is a pointing device like a track pad. - mParameters.deviceType = Parameters::DeviceType::POINTER; - } else if (getDeviceContext().hasRelativeAxis(REL_X) || - getDeviceContext().hasRelativeAxis(REL_Y)) { - // The device is a cursor device with a touch pad attached. - // By default don't use the touch pad to move the pointer. - mParameters.deviceType = Parameters::DeviceType::TOUCH_PAD; - } else { - // The device is a touch pad of unknown purpose. - mParameters.deviceType = Parameters::DeviceType::POINTER; - } - - mParameters.hasButtonUnderPad = getDeviceContext().hasInputProperty(INPUT_PROP_BUTTONPAD); + parameters.deviceType = computeDeviceType(deviceContext); - String8 deviceTypeString; - if (getDeviceContext().getConfiguration().tryGetProperty(String8("touch.deviceType"), - deviceTypeString)) { - if (deviceTypeString == "touchScreen") { - mParameters.deviceType = Parameters::DeviceType::TOUCH_SCREEN; - } else if (deviceTypeString == "touchPad") { - mParameters.deviceType = Parameters::DeviceType::TOUCH_PAD; - } else if (deviceTypeString == "touchNavigation") { - mParameters.deviceType = Parameters::DeviceType::TOUCH_NAVIGATION; - } else if (deviceTypeString == "pointer") { - mParameters.deviceType = Parameters::DeviceType::POINTER; - } else if (deviceTypeString != "default") { - ALOGW("Invalid value for touch.deviceType: '%s'", deviceTypeString.c_str()); - } - } + parameters.hasButtonUnderPad = deviceContext.hasInputProperty(INPUT_PROP_BUTTONPAD); - mParameters.orientationAware = mParameters.deviceType == Parameters::DeviceType::TOUCH_SCREEN; - getDeviceContext().getConfiguration().tryGetProperty(String8("touch.orientationAware"), - mParameters.orientationAware); + parameters.orientationAware = + config.getBool("touch.orientationAware") + .value_or(parameters.deviceType == Parameters::DeviceType::TOUCH_SCREEN); - mParameters.orientation = Parameters::Orientation::ORIENTATION_0; - String8 orientationString; - if (getDeviceContext().getConfiguration().tryGetProperty(String8("touch.orientation"), - orientationString)) { - if (mParameters.deviceType != Parameters::DeviceType::TOUCH_SCREEN) { + parameters.orientation = ui::ROTATION_0; + std::optional orientationString = config.getString("touch.orientation"); + if (orientationString.has_value()) { + if (parameters.deviceType != Parameters::DeviceType::TOUCH_SCREEN) { ALOGW("The configuration 'touch.orientation' is only supported for touchscreens."); - } else if (orientationString == "ORIENTATION_90") { - mParameters.orientation = Parameters::Orientation::ORIENTATION_90; - } else if (orientationString == "ORIENTATION_180") { - mParameters.orientation = Parameters::Orientation::ORIENTATION_180; - } else if (orientationString == "ORIENTATION_270") { - mParameters.orientation = Parameters::Orientation::ORIENTATION_270; - } else if (orientationString != "ORIENTATION_0") { - ALOGW("Invalid value for touch.orientation: '%s'", orientationString.c_str()); + } else if (*orientationString == "ORIENTATION_90") { + parameters.orientation = ui::ROTATION_90; + } else if (*orientationString == "ORIENTATION_180") { + parameters.orientation = ui::ROTATION_180; + } else if (*orientationString == "ORIENTATION_270") { + parameters.orientation = ui::ROTATION_270; + } else if (*orientationString != "ORIENTATION_0") { + ALOGW("Invalid value for touch.orientation: '%s'", orientationString->c_str()); } } - mParameters.hasAssociatedDisplay = false; - mParameters.associatedDisplayIsExternal = false; - if (mParameters.orientationAware || - mParameters.deviceType == Parameters::DeviceType::TOUCH_SCREEN || - mParameters.deviceType == Parameters::DeviceType::POINTER) { - mParameters.hasAssociatedDisplay = true; - if (mParameters.deviceType == Parameters::DeviceType::TOUCH_SCREEN) { - mParameters.associatedDisplayIsExternal = getDeviceContext().isExternal(); - String8 uniqueDisplayId; - getDeviceContext().getConfiguration().tryGetProperty(String8("touch.displayId"), - uniqueDisplayId); - mParameters.uniqueDisplayId = uniqueDisplayId.c_str(); + parameters.hasAssociatedDisplay = false; + parameters.associatedDisplayIsExternal = false; + if (parameters.orientationAware || + parameters.deviceType == Parameters::DeviceType::TOUCH_SCREEN || + parameters.deviceType == Parameters::DeviceType::POINTER || + (parameters.deviceType == Parameters::DeviceType::TOUCH_NAVIGATION && + deviceContext.getAssociatedViewport())) { + parameters.hasAssociatedDisplay = true; + if (parameters.deviceType == Parameters::DeviceType::TOUCH_SCREEN) { + parameters.associatedDisplayIsExternal = deviceContext.isExternal(); + parameters.uniqueDisplayId = config.getString("touch.displayId").value_or("").c_str(); } } - if (getDeviceContext().getAssociatedDisplayPort()) { - mParameters.hasAssociatedDisplay = true; + if (deviceContext.getAssociatedDisplayPort()) { + parameters.hasAssociatedDisplay = true; } // Initial downs on external touch devices should wake the device. // Normally we don't do this for internal touch screens to prevent them from waking // up in your pocket but you can enable it using the input device configuration. - mParameters.wake = getDeviceContext().isExternal(); - getDeviceContext().getConfiguration().tryGetProperty(String8("touch.wake"), mParameters.wake); + parameters.wake = config.getBool("touch.wake").value_or(deviceContext.isExternal()); + + std::optional usiVersionMajor = config.getInt("touch.usiVersionMajor"); + std::optional usiVersionMinor = config.getInt("touch.usiVersionMinor"); + if (usiVersionMajor.has_value() && usiVersionMinor.has_value()) { + parameters.usiVersion = { + .majorVersion = *usiVersionMajor, + .minorVersion = *usiVersionMinor, + }; + } + + parameters.enableForInactiveViewport = + config.getBool("touch.enableForInactiveViewport").value_or(false); + + return parameters; +} + +TouchInputMapper::Parameters::DeviceType TouchInputMapper::computeDeviceType( + const InputDeviceContext& deviceContext) { + Parameters::DeviceType deviceType; + if (deviceContext.hasInputProperty(INPUT_PROP_DIRECT)) { + // The device is a touch screen. + deviceType = Parameters::DeviceType::TOUCH_SCREEN; + } else if (deviceContext.hasInputProperty(INPUT_PROP_POINTER)) { + // The device is a pointing device like a track pad. + deviceType = Parameters::DeviceType::POINTER; + } else { + // The device is a touch pad of unknown purpose. + deviceType = Parameters::DeviceType::POINTER; + } + + // Type association takes precedence over the device type found in the idc file. + std::string deviceTypeString = deviceContext.getDeviceTypeAssociation().value_or(""); + if (deviceTypeString.empty()) { + deviceTypeString = + deviceContext.getConfiguration().getString("touch.deviceType").value_or(""); + } + if (deviceTypeString == "touchScreen") { + deviceType = Parameters::DeviceType::TOUCH_SCREEN; + } else if (deviceTypeString == "touchNavigation") { + deviceType = Parameters::DeviceType::TOUCH_NAVIGATION; + } else if (deviceTypeString == "pointer") { + deviceType = Parameters::DeviceType::POINTER; + } else if (deviceTypeString != "default" && deviceTypeString != "") { + ALOGW("Invalid value for touch.deviceType: '%s'", deviceTypeString.c_str()); + } + return deviceType; } void TouchInputMapper::dumpParameters(std::string& dump) { @@ -527,6 +488,10 @@ void TouchInputMapper::dumpParameters(std::string& dump) { mParameters.uniqueDisplayId.c_str()); dump += StringPrintf(INDENT4 "OrientationAware: %s\n", toString(mParameters.orientationAware)); dump += INDENT4 "Orientation: " + ftl::enum_string(mParameters.orientation) + "\n"; + dump += StringPrintf(INDENT4 "UsiVersion: %s\n", + toString(mParameters.usiVersion, toString).c_str()); + dump += StringPrintf(INDENT4 "EnableForInactiveViewport: %s\n", + toString(mParameters.enableForInactiveViewport)); } void TouchInputMapper::configureRawPointerAxes() { @@ -634,7 +599,7 @@ void TouchInputMapper::initializeSizeRanges() { } // Size of diagonal axis. - const float diagonalSize = hypotf(mDisplayWidth, mDisplayHeight); + const float diagonalSize = hypotf(mDisplayBounds.width, mDisplayBounds.height); // Size factors. if (mRawPointerAxes.touchMajor.valid && mRawPointerAxes.touchMajor.maxValue != 0) { @@ -645,57 +610,58 @@ void TouchInputMapper::initializeSizeRanges() { mSizeScale = 0.0f; } - mOrientedRanges.haveTouchSize = true; - mOrientedRanges.haveToolSize = true; - mOrientedRanges.haveSize = true; + mOrientedRanges.touchMajor = InputDeviceInfo::MotionRange{ + .axis = AMOTION_EVENT_AXIS_TOUCH_MAJOR, + .source = mSource, + .min = 0, + .max = diagonalSize, + .flat = 0, + .fuzz = 0, + .resolution = 0, + }; - mOrientedRanges.touchMajor.axis = AMOTION_EVENT_AXIS_TOUCH_MAJOR; - mOrientedRanges.touchMajor.source = mSource; - mOrientedRanges.touchMajor.min = 0; - mOrientedRanges.touchMajor.max = diagonalSize; - mOrientedRanges.touchMajor.flat = 0; - mOrientedRanges.touchMajor.fuzz = 0; - mOrientedRanges.touchMajor.resolution = 0; if (mRawPointerAxes.touchMajor.valid) { mRawPointerAxes.touchMajor.resolution = clampResolution("touchMajor", mRawPointerAxes.touchMajor.resolution); - mOrientedRanges.touchMajor.resolution = mRawPointerAxes.touchMajor.resolution; + mOrientedRanges.touchMajor->resolution = mRawPointerAxes.touchMajor.resolution; } mOrientedRanges.touchMinor = mOrientedRanges.touchMajor; - mOrientedRanges.touchMinor.axis = AMOTION_EVENT_AXIS_TOUCH_MINOR; + mOrientedRanges.touchMinor->axis = AMOTION_EVENT_AXIS_TOUCH_MINOR; if (mRawPointerAxes.touchMinor.valid) { mRawPointerAxes.touchMinor.resolution = clampResolution("touchMinor", mRawPointerAxes.touchMinor.resolution); - mOrientedRanges.touchMinor.resolution = mRawPointerAxes.touchMinor.resolution; - } - - mOrientedRanges.toolMajor.axis = AMOTION_EVENT_AXIS_TOOL_MAJOR; - mOrientedRanges.toolMajor.source = mSource; - mOrientedRanges.toolMajor.min = 0; - mOrientedRanges.toolMajor.max = diagonalSize; - mOrientedRanges.toolMajor.flat = 0; - mOrientedRanges.toolMajor.fuzz = 0; - mOrientedRanges.toolMajor.resolution = 0; + mOrientedRanges.touchMinor->resolution = mRawPointerAxes.touchMinor.resolution; + } + + mOrientedRanges.toolMajor = InputDeviceInfo::MotionRange{ + .axis = AMOTION_EVENT_AXIS_TOOL_MAJOR, + .source = mSource, + .min = 0, + .max = diagonalSize, + .flat = 0, + .fuzz = 0, + .resolution = 0, + }; if (mRawPointerAxes.toolMajor.valid) { mRawPointerAxes.toolMajor.resolution = clampResolution("toolMajor", mRawPointerAxes.toolMajor.resolution); - mOrientedRanges.toolMajor.resolution = mRawPointerAxes.toolMajor.resolution; + mOrientedRanges.toolMajor->resolution = mRawPointerAxes.toolMajor.resolution; } mOrientedRanges.toolMinor = mOrientedRanges.toolMajor; - mOrientedRanges.toolMinor.axis = AMOTION_EVENT_AXIS_TOOL_MINOR; + mOrientedRanges.toolMinor->axis = AMOTION_EVENT_AXIS_TOOL_MINOR; if (mRawPointerAxes.toolMinor.valid) { mRawPointerAxes.toolMinor.resolution = clampResolution("toolMinor", mRawPointerAxes.toolMinor.resolution); - mOrientedRanges.toolMinor.resolution = mRawPointerAxes.toolMinor.resolution; + mOrientedRanges.toolMinor->resolution = mRawPointerAxes.toolMinor.resolution; } if (mCalibration.sizeCalibration == Calibration::SizeCalibration::GEOMETRIC) { - mOrientedRanges.touchMajor.resolution *= mGeometricScale; - mOrientedRanges.touchMinor.resolution *= mGeometricScale; - mOrientedRanges.toolMajor.resolution *= mGeometricScale; - mOrientedRanges.toolMinor.resolution *= mGeometricScale; + mOrientedRanges.touchMajor->resolution *= mGeometricScale; + mOrientedRanges.touchMinor->resolution *= mGeometricScale; + mOrientedRanges.toolMajor->resolution *= mGeometricScale; + mOrientedRanges.toolMinor->resolution *= mGeometricScale; } else { // Support for other calibrations can be added here. ALOGW("%s calibration is not supported for size ranges at the moment. " @@ -703,21 +669,23 @@ void TouchInputMapper::initializeSizeRanges() { ftl::enum_string(mCalibration.sizeCalibration).c_str()); } - mOrientedRanges.size.axis = AMOTION_EVENT_AXIS_SIZE; - mOrientedRanges.size.source = mSource; - mOrientedRanges.size.min = 0; - mOrientedRanges.size.max = 1.0; - mOrientedRanges.size.flat = 0; - mOrientedRanges.size.fuzz = 0; - mOrientedRanges.size.resolution = 0; + mOrientedRanges.size = InputDeviceInfo::MotionRange{ + .axis = AMOTION_EVENT_AXIS_SIZE, + .source = mSource, + .min = 0, + .max = 1.0, + .flat = 0, + .fuzz = 0, + .resolution = 0, + }; } void TouchInputMapper::initializeOrientedRanges() { // Configure X and Y factors. - mXScale = float(mDisplayWidth) / mRawPointerAxes.getRawWidth(); - mYScale = float(mDisplayHeight) / mRawPointerAxes.getRawHeight(); - mXPrecision = 1.0f / mXScale; - mYPrecision = 1.0f / mYScale; + const float orientedScaleX = mRawToDisplay.getScaleX(); + const float orientedScaleY = mRawToDisplay.getScaleY(); + mOrientedXPrecision = 1.0f / orientedScaleX; + mOrientedYPrecision = 1.0f / orientedScaleY; mOrientedRanges.x.axis = AMOTION_EVENT_AXIS_X; mOrientedRanges.x.source = mSource; @@ -727,7 +695,7 @@ void TouchInputMapper::initializeOrientedRanges() { // Scale factor for terms that are not oriented in a particular axis. // If the pixels are square then xScale == yScale otherwise we fake it // by choosing an average. - mGeometricScale = avg(mXScale, mYScale); + mGeometricScale = avg(orientedScaleX, orientedScaleY); initializeSizeRanges(); @@ -736,21 +704,23 @@ void TouchInputMapper::initializeOrientedRanges() { float pressureMax = 1.0; if (mCalibration.pressureCalibration == Calibration::PressureCalibration::PHYSICAL || mCalibration.pressureCalibration == Calibration::PressureCalibration::AMPLITUDE) { - if (mCalibration.havePressureScale) { - mPressureScale = mCalibration.pressureScale; + if (mCalibration.pressureScale) { + mPressureScale = *mCalibration.pressureScale; pressureMax = mPressureScale * mRawPointerAxes.pressure.maxValue; } else if (mRawPointerAxes.pressure.valid && mRawPointerAxes.pressure.maxValue != 0) { mPressureScale = 1.0f / mRawPointerAxes.pressure.maxValue; } } - mOrientedRanges.pressure.axis = AMOTION_EVENT_AXIS_PRESSURE; - mOrientedRanges.pressure.source = mSource; - mOrientedRanges.pressure.min = 0; - mOrientedRanges.pressure.max = pressureMax; - mOrientedRanges.pressure.flat = 0; - mOrientedRanges.pressure.fuzz = 0; - mOrientedRanges.pressure.resolution = 0; + mOrientedRanges.pressure = InputDeviceInfo::MotionRange{ + .axis = AMOTION_EVENT_AXIS_PRESSURE, + .source = mSource, + .min = 0, + .max = pressureMax, + .flat = 0, + .fuzz = 0, + .resolution = 0, + }; // Tilt mTiltXCenter = 0; @@ -771,29 +741,30 @@ void TouchInputMapper::initializeOrientedRanges() { mTiltYScale = 1.0 / mRawPointerAxes.tiltY.resolution; } - mOrientedRanges.haveTilt = true; - - mOrientedRanges.tilt.axis = AMOTION_EVENT_AXIS_TILT; - mOrientedRanges.tilt.source = mSource; - mOrientedRanges.tilt.min = 0; - mOrientedRanges.tilt.max = M_PI_2; - mOrientedRanges.tilt.flat = 0; - mOrientedRanges.tilt.fuzz = 0; - mOrientedRanges.tilt.resolution = 0; + mOrientedRanges.tilt = InputDeviceInfo::MotionRange{ + .axis = AMOTION_EVENT_AXIS_TILT, + .source = mSource, + .min = 0, + .max = M_PI_2, + .flat = 0, + .fuzz = 0, + .resolution = 0, + }; } // Orientation mOrientationScale = 0; if (mHaveTilt) { - mOrientedRanges.haveOrientation = true; - - mOrientedRanges.orientation.axis = AMOTION_EVENT_AXIS_ORIENTATION; - mOrientedRanges.orientation.source = mSource; - mOrientedRanges.orientation.min = -M_PI; - mOrientedRanges.orientation.max = M_PI; - mOrientedRanges.orientation.flat = 0; - mOrientedRanges.orientation.fuzz = 0; - mOrientedRanges.orientation.resolution = 0; + mOrientedRanges.orientation = InputDeviceInfo::MotionRange{ + .axis = AMOTION_EVENT_AXIS_ORIENTATION, + .source = mSource, + .min = -M_PI, + .max = M_PI, + .flat = 0, + .fuzz = 0, + .resolution = 0, + }; + } else if (mCalibration.orientationCalibration != Calibration::OrientationCalibration::NONE) { if (mCalibration.orientationCalibration == Calibration::OrientationCalibration::INTERPOLATED) { @@ -808,78 +779,138 @@ void TouchInputMapper::initializeOrientedRanges() { } } - mOrientedRanges.haveOrientation = true; - - mOrientedRanges.orientation.axis = AMOTION_EVENT_AXIS_ORIENTATION; - mOrientedRanges.orientation.source = mSource; - mOrientedRanges.orientation.min = -M_PI_2; - mOrientedRanges.orientation.max = M_PI_2; - mOrientedRanges.orientation.flat = 0; - mOrientedRanges.orientation.fuzz = 0; - mOrientedRanges.orientation.resolution = 0; + mOrientedRanges.orientation = InputDeviceInfo::MotionRange{ + .axis = AMOTION_EVENT_AXIS_ORIENTATION, + .source = mSource, + .min = -M_PI_2, + .max = M_PI_2, + .flat = 0, + .fuzz = 0, + .resolution = 0, + }; } // Distance mDistanceScale = 0; if (mCalibration.distanceCalibration != Calibration::DistanceCalibration::NONE) { if (mCalibration.distanceCalibration == Calibration::DistanceCalibration::SCALED) { - if (mCalibration.haveDistanceScale) { - mDistanceScale = mCalibration.distanceScale; - } else { - mDistanceScale = 1.0f; - } - } - - mOrientedRanges.haveDistance = true; - - mOrientedRanges.distance.axis = AMOTION_EVENT_AXIS_DISTANCE; - mOrientedRanges.distance.source = mSource; - mOrientedRanges.distance.min = mRawPointerAxes.distance.minValue * mDistanceScale; - mOrientedRanges.distance.max = mRawPointerAxes.distance.maxValue * mDistanceScale; - mOrientedRanges.distance.flat = 0; - mOrientedRanges.distance.fuzz = mRawPointerAxes.distance.fuzz * mDistanceScale; - mOrientedRanges.distance.resolution = 0; - } - - // Compute oriented precision, scales and ranges. - // Note that the maximum value reported is an inclusive maximum value so it is one - // unit less than the total width or height of the display. - switch (mInputDeviceOrientation) { - case DISPLAY_ORIENTATION_90: - case DISPLAY_ORIENTATION_270: - mOrientedXPrecision = mYPrecision; - mOrientedYPrecision = mXPrecision; - - mOrientedRanges.x.min = 0; - mOrientedRanges.x.max = mDisplayHeight - 1; - mOrientedRanges.x.flat = 0; - mOrientedRanges.x.fuzz = 0; - mOrientedRanges.x.resolution = mRawPointerAxes.y.resolution * mYScale; - - mOrientedRanges.y.min = 0; - mOrientedRanges.y.max = mDisplayWidth - 1; - mOrientedRanges.y.flat = 0; - mOrientedRanges.y.fuzz = 0; - mOrientedRanges.y.resolution = mRawPointerAxes.x.resolution * mXScale; - break; + mDistanceScale = mCalibration.distanceScale.value_or(1.0f); + } + + mOrientedRanges.distance = InputDeviceInfo::MotionRange{ + + .axis = AMOTION_EVENT_AXIS_DISTANCE, + .source = mSource, + .min = mRawPointerAxes.distance.minValue * mDistanceScale, + .max = mRawPointerAxes.distance.maxValue * mDistanceScale, + .flat = 0, + .fuzz = mRawPointerAxes.distance.fuzz * mDistanceScale, + .resolution = 0, + }; + } + + // Oriented X/Y range (in the rotated display's orientation) + const FloatRect rawFrame = Rect{mRawPointerAxes.x.minValue, mRawPointerAxes.y.minValue, + mRawPointerAxes.x.maxValue, mRawPointerAxes.y.maxValue} + .toFloatRect(); + const auto orientedRangeRect = mRawToRotatedDisplay.transform(rawFrame); + mOrientedRanges.x.min = orientedRangeRect.left; + mOrientedRanges.y.min = orientedRangeRect.top; + mOrientedRanges.x.max = orientedRangeRect.right; + mOrientedRanges.y.max = orientedRangeRect.bottom; + + // Oriented flat (in the rotated display's orientation) + const auto orientedFlat = + transformWithoutTranslation(mRawToRotatedDisplay, + {static_cast(mRawPointerAxes.x.flat), + static_cast(mRawPointerAxes.y.flat)}); + mOrientedRanges.x.flat = std::abs(orientedFlat.x); + mOrientedRanges.y.flat = std::abs(orientedFlat.y); + + // Oriented fuzz (in the rotated display's orientation) + const auto orientedFuzz = + transformWithoutTranslation(mRawToRotatedDisplay, + {static_cast(mRawPointerAxes.x.fuzz), + static_cast(mRawPointerAxes.y.fuzz)}); + mOrientedRanges.x.fuzz = std::abs(orientedFuzz.x); + mOrientedRanges.y.fuzz = std::abs(orientedFuzz.y); + + // Oriented resolution (in the rotated display's orientation) + const auto orientedRes = + transformWithoutTranslation(mRawToRotatedDisplay, + {static_cast(mRawPointerAxes.x.resolution), + static_cast(mRawPointerAxes.y.resolution)}); + mOrientedRanges.x.resolution = std::abs(orientedRes.x); + mOrientedRanges.y.resolution = std::abs(orientedRes.y); +} - default: - mOrientedXPrecision = mXPrecision; - mOrientedYPrecision = mYPrecision; - - mOrientedRanges.x.min = 0; - mOrientedRanges.x.max = mDisplayWidth - 1; - mOrientedRanges.x.flat = 0; - mOrientedRanges.x.fuzz = 0; - mOrientedRanges.x.resolution = mRawPointerAxes.x.resolution * mXScale; - - mOrientedRanges.y.min = 0; - mOrientedRanges.y.max = mDisplayHeight - 1; - mOrientedRanges.y.flat = 0; - mOrientedRanges.y.fuzz = 0; - mOrientedRanges.y.resolution = mRawPointerAxes.y.resolution * mYScale; - break; +void TouchInputMapper::computeInputTransforms() { + constexpr auto isRotated = [](const ui::Transform::RotationFlags& rotation) { + return rotation == ui::Transform::ROT_90 || rotation == ui::Transform::ROT_270; + }; + + // See notes about input coordinates in the inputflinger docs: + // //frameworks/native/services/inputflinger/docs/input_coordinates.md + + // Step 1: Undo the raw offset so that the raw coordinate space now starts at (0, 0). + ui::Transform undoOffsetInRaw; + undoOffsetInRaw.set(-mRawPointerAxes.x.minValue, -mRawPointerAxes.y.minValue); + + // Step 2: Rotate the raw coordinates to account for input device orientation. The coordinates + // will now be in the same orientation as the display in ROTATION_0. + // Note: Negating an ui::Rotation value will give its inverse rotation. + const auto inputDeviceOrientation = ui::Transform::toRotationFlags(-mParameters.orientation); + const ui::Size orientedRawSize = isRotated(inputDeviceOrientation) + ? ui::Size{mRawPointerAxes.getRawHeight(), mRawPointerAxes.getRawWidth()} + : ui::Size{mRawPointerAxes.getRawWidth(), mRawPointerAxes.getRawHeight()}; + // When rotating raw values, account for the extra unit added when calculating the raw range. + const auto orientInRaw = ui::Transform(inputDeviceOrientation, orientedRawSize.width - 1, + orientedRawSize.height - 1); + + // Step 3: Rotate the raw coordinates to account for the display rotation. The coordinates will + // now be in the same orientation as the rotated display. There is no need to rotate the + // coordinates to the display rotation if the device is not orientation-aware. + const auto viewportRotation = ui::Transform::toRotationFlags(-mViewport.orientation); + const auto rotatedRawSize = mParameters.orientationAware && isRotated(viewportRotation) + ? ui::Size{orientedRawSize.height, orientedRawSize.width} + : orientedRawSize; + // When rotating raw values, account for the extra unit added when calculating the raw range. + const auto rotateInRaw = mParameters.orientationAware + ? ui::Transform(viewportRotation, rotatedRawSize.width - 1, rotatedRawSize.height - 1) + : ui::Transform(); + + // Step 4: Scale the raw coordinates to the display space. + // - In DIRECT mode, we assume that the raw surface of the touch device maps perfectly to + // the surface of the display panel. This is usually true for touchscreens. + // - In POINTER mode, we cannot assume that the display and the touch device have the same + // aspect ratio, since it is likely to be untrue for devices like external drawing tablets. + // In this case, we used a fixed scale so that 1) we use the same scale across both the x and + // y axes to ensure the mapping does not stretch gestures, and 2) the entire region of the + // display can be reached by the touch device. + // - From this point onward, we are no longer in the discrete space of the raw coordinates but + // are in the continuous space of the logical display. + ui::Transform scaleRawToDisplay; + const float xScale = static_cast(mViewport.deviceWidth) / rotatedRawSize.width; + const float yScale = static_cast(mViewport.deviceHeight) / rotatedRawSize.height; + if (mDeviceMode == DeviceMode::DIRECT) { + scaleRawToDisplay.set(xScale, 0, 0, yScale); + } else if (mDeviceMode == DeviceMode::POINTER) { + const float fixedScale = std::max(xScale, yScale); + scaleRawToDisplay.set(fixedScale, 0, 0, fixedScale); + } else { + LOG_ALWAYS_FATAL("computeInputTransform can only be used for DIRECT and POINTER modes"); } + + // Step 5: Undo the display rotation to bring us back to the un-rotated display coordinate space + // that InputReader uses. + const auto undoRotateInDisplay = + ui::Transform(viewportRotation, mViewport.deviceWidth, mViewport.deviceHeight) + .inverse(); + + // Now put it all together! + mRawToRotatedDisplay = (scaleRawToDisplay * (rotateInRaw * (orientInRaw * undoOffsetInRaw))); + mRawToDisplay = (undoRotateInDisplay * mRawToRotatedDisplay); + mRawRotation = ui::Transform{mRawToDisplay.getOrientation()}; } void TouchInputMapper::configureInputDevice(nsecs_t when, bool* outResetNeeded) { @@ -926,15 +957,19 @@ void TouchInputMapper::configureInputDevice(nsecs_t when, bool* outResetNeeded) "becomes available.", getDeviceName().c_str()); mDeviceMode = DeviceMode::DISABLED; - } else if (!newViewportOpt->isActive) { + } else if (!mParameters.enableForInactiveViewport && !newViewportOpt->isActive) { ALOGI("Disabling %s (device %i) because the associated viewport is not active", getDeviceName().c_str(), getDeviceId()); mDeviceMode = DeviceMode::DISABLED; } // Raw width and height in the natural orientation. - const int32_t rawWidth = mRawPointerAxes.getRawWidth(); - const int32_t rawHeight = mRawPointerAxes.getRawHeight(); + const ui::Size rawSize{mRawPointerAxes.getRawWidth(), mRawPointerAxes.getRawHeight()}; + const int32_t rawXResolution = mRawPointerAxes.x.resolution; + const int32_t rawYResolution = mRawPointerAxes.y.resolution; + // Calculate the mean resolution when both x and y resolution are set, otherwise set it to 0. + const float rawMeanResolution = + (rawXResolution > 0 && rawYResolution > 0) ? (rawXResolution + rawYResolution) / 2 : 0; const DisplayViewport& newViewport = newViewportOpt.value_or(kUninitializedViewport); const bool viewportChanged = mViewport != newViewport; @@ -945,96 +980,39 @@ void TouchInputMapper::configureInputDevice(nsecs_t when, bool* outResetNeeded) mViewport = newViewport; if (mDeviceMode == DeviceMode::DIRECT || mDeviceMode == DeviceMode::POINTER) { - // Convert rotated viewport to the natural orientation. - int32_t naturalPhysicalWidth, naturalPhysicalHeight; - int32_t naturalPhysicalLeft, naturalPhysicalTop; - int32_t naturalDeviceWidth, naturalDeviceHeight; - - // Apply the inverse of the input device orientation so that the input device is - // configured in the same orientation as the viewport. The input device orientation will - // be re-applied by mInputDeviceOrientation. - const int32_t naturalDeviceOrientation = - (mViewport.orientation - static_cast(mParameters.orientation) + 4) % 4; - switch (naturalDeviceOrientation) { - case DISPLAY_ORIENTATION_90: - naturalPhysicalWidth = mViewport.physicalBottom - mViewport.physicalTop; - naturalPhysicalHeight = mViewport.physicalRight - mViewport.physicalLeft; - naturalPhysicalLeft = mViewport.deviceHeight - mViewport.physicalBottom; - naturalPhysicalTop = mViewport.physicalLeft; - naturalDeviceWidth = mViewport.deviceHeight; - naturalDeviceHeight = mViewport.deviceWidth; - break; - case DISPLAY_ORIENTATION_180: - naturalPhysicalWidth = mViewport.physicalRight - mViewport.physicalLeft; - naturalPhysicalHeight = mViewport.physicalBottom - mViewport.physicalTop; - naturalPhysicalLeft = mViewport.deviceWidth - mViewport.physicalRight; - naturalPhysicalTop = mViewport.deviceHeight - mViewport.physicalBottom; - naturalDeviceWidth = mViewport.deviceWidth; - naturalDeviceHeight = mViewport.deviceHeight; - break; - case DISPLAY_ORIENTATION_270: - naturalPhysicalWidth = mViewport.physicalBottom - mViewport.physicalTop; - naturalPhysicalHeight = mViewport.physicalRight - mViewport.physicalLeft; - naturalPhysicalLeft = mViewport.physicalTop; - naturalPhysicalTop = mViewport.deviceWidth - mViewport.physicalRight; - naturalDeviceWidth = mViewport.deviceHeight; - naturalDeviceHeight = mViewport.deviceWidth; - break; - case DISPLAY_ORIENTATION_0: - default: - naturalPhysicalWidth = mViewport.physicalRight - mViewport.physicalLeft; - naturalPhysicalHeight = mViewport.physicalBottom - mViewport.physicalTop; - naturalPhysicalLeft = mViewport.physicalLeft; - naturalPhysicalTop = mViewport.physicalTop; - naturalDeviceWidth = mViewport.deviceWidth; - naturalDeviceHeight = mViewport.deviceHeight; - break; - } - - if (naturalPhysicalHeight == 0 || naturalPhysicalWidth == 0) { - ALOGE("Viewport is not set properly: %s", mViewport.toString().c_str()); - naturalPhysicalHeight = naturalPhysicalHeight == 0 ? 1 : naturalPhysicalHeight; - naturalPhysicalWidth = naturalPhysicalWidth == 0 ? 1 : naturalPhysicalWidth; - } + const auto oldDisplayBounds = mDisplayBounds; - mPhysicalWidth = naturalPhysicalWidth; - mPhysicalHeight = naturalPhysicalHeight; - mPhysicalLeft = naturalPhysicalLeft; - mPhysicalTop = naturalPhysicalTop; - - const int32_t oldDisplayWidth = mDisplayWidth; - const int32_t oldDisplayHeight = mDisplayHeight; - mDisplayWidth = naturalDeviceWidth; - mDisplayHeight = naturalDeviceHeight; + mDisplayBounds = getNaturalDisplaySize(mViewport); + mPhysicalFrameInRotatedDisplay = {mViewport.physicalLeft, mViewport.physicalTop, + mViewport.physicalRight, mViewport.physicalBottom}; + // TODO(b/257118693): Remove the dependence on the old orientation/rotation logic that + // uses mInputDeviceOrientation. The new logic uses the transforms calculated in + // computeInputTransforms(). // InputReader works in the un-rotated display coordinate space, so we don't need to do // anything if the device is already orientation-aware. If the device is not // orientation-aware, then we need to apply the inverse rotation of the display so that // when the display rotation is applied later as a part of the per-window transform, we // get the expected screen coordinates. mInputDeviceOrientation = mParameters.orientationAware - ? DISPLAY_ORIENTATION_0 + ? ui::ROTATION_0 : getInverseRotation(mViewport.orientation); // For orientation-aware devices that work in the un-rotated coordinate space, the // viewport update should be skipped if it is only a change in the orientation. - skipViewportUpdate = mParameters.orientationAware && mDisplayWidth == oldDisplayWidth && - mDisplayHeight == oldDisplayHeight && viewportOrientationChanged; + skipViewportUpdate = !viewportDisplayIdChanged && mParameters.orientationAware && + mDisplayBounds == oldDisplayBounds && viewportOrientationChanged; // Apply the input device orientation for the device. - mInputDeviceOrientation = - (mInputDeviceOrientation + static_cast(mParameters.orientation)) % 4; + mInputDeviceOrientation = mInputDeviceOrientation + mParameters.orientation; + computeInputTransforms(); } else { - mPhysicalWidth = rawWidth; - mPhysicalHeight = rawHeight; - mPhysicalLeft = 0; - mPhysicalTop = 0; - - mDisplayWidth = rawWidth; - mDisplayHeight = rawHeight; - mInputDeviceOrientation = DISPLAY_ORIENTATION_0; + mDisplayBounds = rawSize; + mPhysicalFrameInRotatedDisplay = Rect{mDisplayBounds}; + mInputDeviceOrientation = ui::ROTATION_0; + mRawToDisplay.reset(); + mRawToDisplay.set(-mRawPointerAxes.x.minValue, -mRawPointerAxes.y.minValue); + mRawToRotatedDisplay = mRawToDisplay; } - // If displayId changed, do not skip viewport update. - skipViewportUpdate &= !viewportDisplayIdChanged; } // If moving between pointer modes, need to reset some state. @@ -1043,12 +1021,18 @@ void TouchInputMapper::configureInputDevice(nsecs_t when, bool* outResetNeeded) mOrientedRanges.clear(); } - // Create pointer controller if needed, and keep it around if Pointer Capture is enabled to - // preserve the cursor position. - if (mDeviceMode == DeviceMode::POINTER || - (mDeviceMode == DeviceMode::DIRECT && mConfig.showTouches) || - (mParameters.deviceType == Parameters::DeviceType::POINTER && - mConfig.pointerCaptureRequest.enable)) { + // 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.enable) || + // - 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()); } @@ -1064,9 +1048,9 @@ void TouchInputMapper::configureInputDevice(nsecs_t when, bool* outResetNeeded) } if ((viewportChanged && !skipViewportUpdate) || deviceModeChanged) { - ALOGI("Device reconfigured: id=%d, name='%s', size %dx%d, orientation %d, mode %d, " + ALOGI("Device reconfigured: id=%d, name='%s', size %s, orientation %d, mode %d, " "display id %d", - getDeviceId(), getDeviceName().c_str(), mDisplayWidth, mDisplayHeight, + getDeviceId(), getDeviceName().c_str(), toString(mDisplayBounds).c_str(), mInputDeviceOrientation, mDeviceMode, mViewport.displayId); configureVirtualKeys(); @@ -1078,8 +1062,8 @@ void TouchInputMapper::configureInputDevice(nsecs_t when, bool* outResetNeeded) if (mDeviceMode == DeviceMode::POINTER) { // Compute pointer gesture detection parameters. - float rawDiagonal = hypotf(rawWidth, rawHeight); - float displayDiagonal = hypotf(mDisplayWidth, mDisplayHeight); + float rawDiagonal = hypotf(rawSize.width, rawSize.height); + float displayDiagonal = hypotf(mDisplayBounds.width, mDisplayBounds.height); // Scale movements such that one whole swipe of the touch pad covers a // given area relative to the diagonal size of the display when no acceleration @@ -1097,10 +1081,14 @@ void TouchInputMapper::configureInputDevice(nsecs_t when, bool* outResetNeeded) mConfig.pointerGestureZoomSpeedRatio * displayDiagonal / rawDiagonal; mPointerYZoomScale = mPointerXZoomScale; - // Max width between pointers to detect a swipe gesture is more than some fraction - // of the diagonal axis of the touch pad. Touches that are wider than this are - // translated into freeform gestures. - mPointerGestureMaxSwipeWidth = mConfig.pointerGestureSwipeMaxWidthRatio * rawDiagonal; + // Calculate the min freeform gesture width. It will be 0 when the resolution of any + // axis is non positive value. + const float minFreeformGestureWidth = + rawMeanResolution * MIN_FREEFORM_GESTURE_WIDTH_IN_MILLIMETER; + + mPointerGestureMaxSwipeWidth = + std::max(mConfig.pointerGestureSwipeMaxWidthRatio * rawDiagonal, + minFreeformGestureWidth); } // Inform the dispatcher about the changes. @@ -1111,12 +1099,9 @@ void TouchInputMapper::configureInputDevice(nsecs_t when, bool* outResetNeeded) void TouchInputMapper::dumpDisplay(std::string& dump) { dump += StringPrintf(INDENT3 "%s\n", mViewport.toString().c_str()); - dump += StringPrintf(INDENT3 "DisplayWidth: %dpx\n", mDisplayWidth); - dump += StringPrintf(INDENT3 "DisplayHeight: %dpx\n", mDisplayHeight); - dump += StringPrintf(INDENT3 "PhysicalWidth: %dpx\n", mPhysicalWidth); - dump += StringPrintf(INDENT3 "PhysicalHeight: %dpx\n", mPhysicalHeight); - dump += StringPrintf(INDENT3 "PhysicalLeft: %d\n", mPhysicalLeft); - dump += StringPrintf(INDENT3 "PhysicalTop: %d\n", mPhysicalTop); + dump += StringPrintf(INDENT3 "DisplayBounds: %s\n", toString(mDisplayBounds).c_str()); + dump += StringPrintf(INDENT3 "PhysicalFrameInRotatedDisplay: %s\n", + toString(mPhysicalFrameInRotatedDisplay).c_str()); dump += StringPrintf(INDENT3 "InputDeviceOrientation: %d\n", mInputDeviceOrientation); } @@ -1155,17 +1140,17 @@ void TouchInputMapper::configureVirtualKeys() { int32_t halfWidth = virtualKeyDefinition.width / 2; int32_t halfHeight = virtualKeyDefinition.height / 2; - virtualKey.hitLeft = - (virtualKeyDefinition.centerX - halfWidth) * touchScreenWidth / mDisplayWidth + + virtualKey.hitLeft = (virtualKeyDefinition.centerX - halfWidth) * touchScreenWidth / + mDisplayBounds.width + touchScreenLeft; - virtualKey.hitRight = - (virtualKeyDefinition.centerX + halfWidth) * touchScreenWidth / mDisplayWidth + + virtualKey.hitRight = (virtualKeyDefinition.centerX + halfWidth) * touchScreenWidth / + mDisplayBounds.width + touchScreenLeft; - virtualKey.hitTop = - (virtualKeyDefinition.centerY - halfHeight) * touchScreenHeight / mDisplayHeight + + virtualKey.hitTop = (virtualKeyDefinition.centerY - halfHeight) * touchScreenHeight / + mDisplayBounds.height + touchScreenTop; - virtualKey.hitBottom = - (virtualKeyDefinition.centerY + halfHeight) * touchScreenHeight / mDisplayHeight + + virtualKey.hitBottom = (virtualKeyDefinition.centerY + halfHeight) * touchScreenHeight / + mDisplayBounds.height + touchScreenTop; mVirtualKeys.push_back(virtualKey); } @@ -1191,89 +1176,79 @@ void TouchInputMapper::parseCalibration() { // Size out.sizeCalibration = Calibration::SizeCalibration::DEFAULT; - String8 sizeCalibrationString; - if (in.tryGetProperty(String8("touch.size.calibration"), sizeCalibrationString)) { - if (sizeCalibrationString == "none") { + std::optional sizeCalibrationString = in.getString("touch.size.calibration"); + if (sizeCalibrationString.has_value()) { + if (*sizeCalibrationString == "none") { out.sizeCalibration = Calibration::SizeCalibration::NONE; - } else if (sizeCalibrationString == "geometric") { + } else if (*sizeCalibrationString == "geometric") { out.sizeCalibration = Calibration::SizeCalibration::GEOMETRIC; - } else if (sizeCalibrationString == "diameter") { + } else if (*sizeCalibrationString == "diameter") { out.sizeCalibration = Calibration::SizeCalibration::DIAMETER; - } else if (sizeCalibrationString == "box") { + } else if (*sizeCalibrationString == "box") { out.sizeCalibration = Calibration::SizeCalibration::BOX; - } else if (sizeCalibrationString == "area") { + } else if (*sizeCalibrationString == "area") { out.sizeCalibration = Calibration::SizeCalibration::AREA; - } else if (sizeCalibrationString != "default") { - ALOGW("Invalid value for touch.size.calibration: '%s'", sizeCalibrationString.c_str()); + } else if (*sizeCalibrationString != "default") { + ALOGW("Invalid value for touch.size.calibration: '%s'", sizeCalibrationString->c_str()); } } - out.haveSizeScale = in.tryGetProperty(String8("touch.size.scale"), out.sizeScale); - out.haveSizeBias = in.tryGetProperty(String8("touch.size.bias"), out.sizeBias); - out.haveSizeIsSummed = in.tryGetProperty(String8("touch.size.isSummed"), out.sizeIsSummed); + out.sizeScale = in.getFloat("touch.size.scale"); + out.sizeBias = in.getFloat("touch.size.bias"); + out.sizeIsSummed = in.getBool("touch.size.isSummed"); // Pressure out.pressureCalibration = Calibration::PressureCalibration::DEFAULT; - String8 pressureCalibrationString; - if (in.tryGetProperty(String8("touch.pressure.calibration"), pressureCalibrationString)) { - if (pressureCalibrationString == "none") { + std::optional pressureCalibrationString = + in.getString("touch.pressure.calibration"); + if (pressureCalibrationString.has_value()) { + if (*pressureCalibrationString == "none") { out.pressureCalibration = Calibration::PressureCalibration::NONE; - } else if (pressureCalibrationString == "physical") { + } else if (*pressureCalibrationString == "physical") { out.pressureCalibration = Calibration::PressureCalibration::PHYSICAL; - } else if (pressureCalibrationString == "amplitude") { + } else if (*pressureCalibrationString == "amplitude") { out.pressureCalibration = Calibration::PressureCalibration::AMPLITUDE; - } else if (pressureCalibrationString != "default") { + } else if (*pressureCalibrationString != "default") { ALOGW("Invalid value for touch.pressure.calibration: '%s'", - pressureCalibrationString.c_str()); + pressureCalibrationString->c_str()); } } - out.havePressureScale = in.tryGetProperty(String8("touch.pressure.scale"), out.pressureScale); + out.pressureScale = in.getFloat("touch.pressure.scale"); // Orientation out.orientationCalibration = Calibration::OrientationCalibration::DEFAULT; - String8 orientationCalibrationString; - if (in.tryGetProperty(String8("touch.orientation.calibration"), orientationCalibrationString)) { - if (orientationCalibrationString == "none") { + std::optional orientationCalibrationString = + in.getString("touch.orientation.calibration"); + if (orientationCalibrationString.has_value()) { + if (*orientationCalibrationString == "none") { out.orientationCalibration = Calibration::OrientationCalibration::NONE; - } else if (orientationCalibrationString == "interpolated") { + } else if (*orientationCalibrationString == "interpolated") { out.orientationCalibration = Calibration::OrientationCalibration::INTERPOLATED; - } else if (orientationCalibrationString == "vector") { + } else if (*orientationCalibrationString == "vector") { out.orientationCalibration = Calibration::OrientationCalibration::VECTOR; - } else if (orientationCalibrationString != "default") { + } else if (*orientationCalibrationString != "default") { ALOGW("Invalid value for touch.orientation.calibration: '%s'", - orientationCalibrationString.c_str()); + orientationCalibrationString->c_str()); } } // Distance out.distanceCalibration = Calibration::DistanceCalibration::DEFAULT; - String8 distanceCalibrationString; - if (in.tryGetProperty(String8("touch.distance.calibration"), distanceCalibrationString)) { - if (distanceCalibrationString == "none") { + std::optional distanceCalibrationString = + in.getString("touch.distance.calibration"); + if (distanceCalibrationString.has_value()) { + if (*distanceCalibrationString == "none") { out.distanceCalibration = Calibration::DistanceCalibration::NONE; - } else if (distanceCalibrationString == "scaled") { + } else if (*distanceCalibrationString == "scaled") { out.distanceCalibration = Calibration::DistanceCalibration::SCALED; - } else if (distanceCalibrationString != "default") { + } else if (*distanceCalibrationString != "default") { ALOGW("Invalid value for touch.distance.calibration: '%s'", - distanceCalibrationString.c_str()); + distanceCalibrationString->c_str()); } } - out.haveDistanceScale = in.tryGetProperty(String8("touch.distance.scale"), out.distanceScale); - - out.coverageCalibration = Calibration::CoverageCalibration::DEFAULT; - String8 coverageCalibrationString; - if (in.tryGetProperty(String8("touch.coverage.calibration"), coverageCalibrationString)) { - if (coverageCalibrationString == "none") { - out.coverageCalibration = Calibration::CoverageCalibration::NONE; - } else if (coverageCalibrationString == "box") { - out.coverageCalibration = Calibration::CoverageCalibration::BOX; - } else if (coverageCalibrationString != "default") { - ALOGW("Invalid value for touch.coverage.calibration: '%s'", - coverageCalibrationString.c_str()); - } - } + out.distanceScale = in.getFloat("touch.distance.scale"); } void TouchInputMapper::resolveCalibration() { @@ -1312,11 +1287,6 @@ void TouchInputMapper::resolveCalibration() { } else { mCalibration.distanceCalibration = Calibration::DistanceCalibration::NONE; } - - // Coverage - if (mCalibration.coverageCalibration == Calibration::CoverageCalibration::DEFAULT) { - mCalibration.coverageCalibration = Calibration::CoverageCalibration::NONE; - } } void TouchInputMapper::dumpCalibration(std::string& dump) { @@ -1325,17 +1295,17 @@ void TouchInputMapper::dumpCalibration(std::string& dump) { dump += INDENT4 "touch.size.calibration: "; dump += ftl::enum_string(mCalibration.sizeCalibration) + "\n"; - if (mCalibration.haveSizeScale) { - dump += StringPrintf(INDENT4 "touch.size.scale: %0.3f\n", mCalibration.sizeScale); + if (mCalibration.sizeScale) { + dump += StringPrintf(INDENT4 "touch.size.scale: %0.3f\n", *mCalibration.sizeScale); } - if (mCalibration.haveSizeBias) { - dump += StringPrintf(INDENT4 "touch.size.bias: %0.3f\n", mCalibration.sizeBias); + if (mCalibration.sizeBias) { + dump += StringPrintf(INDENT4 "touch.size.bias: %0.3f\n", *mCalibration.sizeBias); } - if (mCalibration.haveSizeIsSummed) { + if (mCalibration.sizeIsSummed) { dump += StringPrintf(INDENT4 "touch.size.isSummed: %s\n", - toString(mCalibration.sizeIsSummed)); + toString(*mCalibration.sizeIsSummed)); } // Pressure @@ -1353,8 +1323,8 @@ void TouchInputMapper::dumpCalibration(std::string& dump) { ALOG_ASSERT(false); } - if (mCalibration.havePressureScale) { - dump += StringPrintf(INDENT4 "touch.pressure.scale: %0.3f\n", mCalibration.pressureScale); + if (mCalibration.pressureScale) { + dump += StringPrintf(INDENT4 "touch.pressure.scale: %0.3f\n", *mCalibration.pressureScale); } // Orientation @@ -1384,19 +1354,8 @@ void TouchInputMapper::dumpCalibration(std::string& dump) { ALOG_ASSERT(false); } - if (mCalibration.haveDistanceScale) { - dump += StringPrintf(INDENT4 "touch.distance.scale: %0.3f\n", mCalibration.distanceScale); - } - - switch (mCalibration.coverageCalibration) { - case Calibration::CoverageCalibration::NONE: - dump += INDENT4 "touch.coverage.calibration: none\n"; - break; - case Calibration::CoverageCalibration::BOX: - dump += INDENT4 "touch.coverage.calibration: box\n"; - break; - default: - ALOG_ASSERT(false); + if (mCalibration.distanceScale) { + dump += StringPrintf(INDENT4 "touch.distance.scale: %0.3f\n", *mCalibration.distanceScale); } } @@ -1416,10 +1375,13 @@ void TouchInputMapper::updateAffineTransformation() { mInputDeviceOrientation); } -void TouchInputMapper::reset(nsecs_t when) { +std::list TouchInputMapper::reset(nsecs_t when) { + std::list out = cancelTouch(when, when); + updateTouchSpots(); + mCursorButtonAccumulator.reset(getDeviceContext()); mCursorScrollAccumulator.reset(getDeviceContext()); - mTouchButtonAccumulator.reset(getDeviceContext()); + mTouchButtonAccumulator.reset(); mPointerVelocityControl.reset(); mWheelXVelocityControl.reset(); @@ -1447,14 +1409,15 @@ void TouchInputMapper::reset(nsecs_t when) { mPointerController->clearSpots(); } - InputMapper::reset(when); + return out += InputMapper::reset(when); } void TouchInputMapper::resetExternalStylus() { mExternalStylusState.clear(); - mExternalStylusId = -1; + mFusedStylusPointerId.reset(); mExternalStylusFusionTimeout = LLONG_MAX; mExternalStylusDataPending = false; + mExternalStylusButtonsApplied = 0; } void TouchInputMapper::clearStylusDataPendingFlags() { @@ -1462,17 +1425,20 @@ void TouchInputMapper::clearStylusDataPendingFlags() { mExternalStylusFusionTimeout = LLONG_MAX; } -void TouchInputMapper::process(const RawEvent* 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) { - sync(rawEvent->when, rawEvent->readTime); + out += sync(rawEvent->when, rawEvent->readTime); } + return out; } -void TouchInputMapper::sync(nsecs_t when, nsecs_t readTime) { +std::list TouchInputMapper::sync(nsecs_t when, nsecs_t readTime) { + std::list out; if (mDeviceMode == DeviceMode::DISABLED) { // Only save the last pending state when the device is disabled. mRawStatesPending.clear(); @@ -1486,8 +1452,9 @@ void TouchInputMapper::sync(nsecs_t when, nsecs_t readTime) { next.readTime = readTime; // Sync button state. - next.buttonState = - mTouchButtonAccumulator.getButtonState() | mCursorButtonAccumulator.getButtonState(); + next.buttonState = filterButtonState(mConfig, + mTouchButtonAccumulator.getButtonState() | + mCursorButtonAccumulator.getButtonState()); // Sync scroll next.rawVScroll = mCursorScrollAccumulator.getRelativeVWheel(); @@ -1501,19 +1468,22 @@ void TouchInputMapper::sync(nsecs_t when, nsecs_t readTime) { const RawState& last = mRawStatesPending.size() == 1 ? mCurrentRawState : mRawStatesPending.rbegin()[1]; + std::tie(next.when, next.readTime) = + applyBluetoothTimestampSmoothening(getDeviceContext().getDeviceIdentifier(), when, + readTime, last.when); + // Assign pointer ids. if (!mHavePointerIds) { assignPointerIds(last, next); } - if (DEBUG_RAW_EVENTS) { - ALOGD("syncTouch: pointerCount %d -> %d, touching ids 0x%08x -> 0x%08x, " - "hovering ids 0x%08x -> 0x%08x, canceled ids 0x%08x", - last.rawPointerData.pointerCount, next.rawPointerData.pointerCount, - last.rawPointerData.touchingIdBits.value, next.rawPointerData.touchingIdBits.value, - last.rawPointerData.hoveringIdBits.value, next.rawPointerData.hoveringIdBits.value, - next.rawPointerData.canceledIdBits.value); - } + ALOGD_IF(debugRawEvents(), + "syncTouch: pointerCount %d -> %d, touching ids 0x%08x -> 0x%08x, " + "hovering ids 0x%08x -> 0x%08x, canceled ids 0x%08x", + last.rawPointerData.pointerCount, next.rawPointerData.pointerCount, + last.rawPointerData.touchingIdBits.value, next.rawPointerData.touchingIdBits.value, + last.rawPointerData.hoveringIdBits.value, next.rawPointerData.hoveringIdBits.value, + next.rawPointerData.canceledIdBits.value); if (!next.rawPointerData.touchingIdBits.isEmpty() && !next.rawPointerData.hoveringIdBits.isEmpty() && @@ -1522,16 +1492,15 @@ void TouchInputMapper::sync(nsecs_t when, nsecs_t readTime) { next.rawPointerData.hoveringIdBits.value); } - processRawTouches(false /*timeout*/); + out += processRawTouches(/*timeout=*/false); + return out; } -void TouchInputMapper::processRawTouches(bool timeout) { +std::list TouchInputMapper::processRawTouches(bool timeout) { + std::list out; if (mDeviceMode == DeviceMode::DISABLED) { - // Drop all input if the device is disabled. - cancelTouch(mCurrentRawState.when, mCurrentRawState.readTime); - mCurrentCookedState.clear(); - updateTouchSpots(); - return; + // Do not process raw event while the device is disabled. + return out; } // Drain any pending touch states. The invariant here is that the mCurrentRawState is always @@ -1551,12 +1520,12 @@ void TouchInputMapper::processRawTouches(bool timeout) { // All ready to go. clearStylusDataPendingFlags(); - mCurrentRawState.copyFrom(next); + mCurrentRawState = next; if (mCurrentRawState.when < mLastRawState.when) { mCurrentRawState.when = mLastRawState.when; mCurrentRawState.readTime = mLastRawState.readTime; } - cookAndDispatch(mCurrentRawState.when, mCurrentRawState.readTime); + out += cookAndDispatch(mCurrentRawState.when, mCurrentRawState.readTime); } if (count != 0) { mRawStatesPending.erase(mRawStatesPending.begin(), mRawStatesPending.begin() + count); @@ -1566,20 +1535,21 @@ void TouchInputMapper::processRawTouches(bool timeout) { if (timeout) { nsecs_t when = mExternalStylusFusionTimeout - STYLUS_DATA_LATENCY; clearStylusDataPendingFlags(); - mCurrentRawState.copyFrom(mLastRawState); - if (DEBUG_STYLUS_FUSION) { - ALOGD("Timeout expired, synthesizing event with new stylus data"); - } + mCurrentRawState = mLastRawState; + ALOGD_IF(DEBUG_STYLUS_FUSION, + "Timeout expired, synthesizing event with new stylus data"); const nsecs_t readTime = when; // consider this synthetic event to be zero latency - cookAndDispatch(when, readTime); + out += cookAndDispatch(when, readTime); } else if (mExternalStylusFusionTimeout == LLONG_MAX) { mExternalStylusFusionTimeout = mExternalStylusState.when + TOUCH_DATA_TIMEOUT; getContext()->requestTimeoutAtTime(mExternalStylusFusionTimeout); } } + return out; } -void TouchInputMapper::cookAndDispatch(nsecs_t when, nsecs_t readTime) { +std::list TouchInputMapper::cookAndDispatch(nsecs_t when, nsecs_t readTime) { + std::list out; // Always start with a clean state. mCurrentCookedState.clear(); @@ -1605,7 +1575,9 @@ void TouchInputMapper::cookAndDispatch(nsecs_t when, nsecs_t readTime) { // Consume raw off-screen touches before cooking pointer data. // If touches are consumed, subsequent code will not receive any pointer data. - if (consumeRawTouches(when, readTime, policyFlags)) { + bool consumed; + out += consumeRawTouches(when, readTime, policyFlags, consumed /*byref*/); + if (consumed) { mCurrentRawState.rawPointerData.clear(); } @@ -1618,9 +1590,9 @@ void TouchInputMapper::cookAndDispatch(nsecs_t when, nsecs_t readTime) { applyExternalStylusTouchState(when); // Synthesize key down from raw buttons if needed. - synthesizeButtonKeys(getContext(), AKEY_EVENT_ACTION_DOWN, when, readTime, getDeviceId(), - mSource, mViewport.displayId, policyFlags, mLastCookedState.buttonState, - mCurrentCookedState.buttonState); + out += synthesizeButtonKeys(getContext(), AKEY_EVENT_ACTION_DOWN, when, readTime, getDeviceId(), + mSource, mViewport.displayId, policyFlags, + mLastCookedState.buttonState, mCurrentCookedState.buttonState); // Dispatch the touches either directly or by translation through a pointer on screen. if (mDeviceMode == DeviceMode::POINTER) { @@ -1628,13 +1600,12 @@ void TouchInputMapper::cookAndDispatch(nsecs_t when, nsecs_t readTime) { uint32_t id = idBits.clearFirstMarkedBit(); const RawPointerData::Pointer& pointer = mCurrentRawState.rawPointerData.pointerForId(id); - if (pointer.toolType == AMOTION_EVENT_TOOL_TYPE_STYLUS || - pointer.toolType == AMOTION_EVENT_TOOL_TYPE_ERASER) { + if (isStylusToolType(pointer.toolType)) { mCurrentCookedState.stylusIdBits.markBit(id); - } else if (pointer.toolType == AMOTION_EVENT_TOOL_TYPE_FINGER || - pointer.toolType == AMOTION_EVENT_TOOL_TYPE_UNKNOWN) { + } else if (pointer.toolType == ToolType::FINGER || + pointer.toolType == ToolType::UNKNOWN) { mCurrentCookedState.fingerIdBits.markBit(id); - } else if (pointer.toolType == AMOTION_EVENT_TOOL_TYPE_MOUSE) { + } else if (pointer.toolType == ToolType::MOUSE) { mCurrentCookedState.mouseIdBits.markBit(id); } } @@ -1642,8 +1613,7 @@ void TouchInputMapper::cookAndDispatch(nsecs_t when, nsecs_t readTime) { uint32_t id = idBits.clearFirstMarkedBit(); const RawPointerData::Pointer& pointer = mCurrentRawState.rawPointerData.pointerForId(id); - if (pointer.toolType == AMOTION_EVENT_TOOL_TYPE_STYLUS || - pointer.toolType == AMOTION_EVENT_TOOL_TYPE_ERASER) { + if (isStylusToolType(pointer.toolType)) { mCurrentCookedState.stylusIdBits.markBit(id); } } @@ -1662,15 +1632,15 @@ void TouchInputMapper::cookAndDispatch(nsecs_t when, nsecs_t readTime) { pointerUsage = PointerUsage::GESTURES; } - dispatchPointerUsage(when, readTime, policyFlags, pointerUsage); + out += dispatchPointerUsage(when, readTime, policyFlags, pointerUsage); } else { if (!mCurrentMotionAborted) { updateTouchSpots(); - dispatchButtonRelease(when, readTime, policyFlags); - dispatchHoverExit(when, readTime, policyFlags); - dispatchTouches(when, readTime, policyFlags); - dispatchHoverEnterAndMove(when, readTime, policyFlags); - dispatchButtonPress(when, readTime, policyFlags); + out += dispatchButtonRelease(when, readTime, policyFlags); + out += dispatchHoverExit(when, readTime, policyFlags); + out += dispatchTouches(when, readTime, policyFlags); + out += dispatchHoverEnterAndMove(when, readTime, policyFlags); + out += dispatchButtonPress(when, readTime, policyFlags); } if (mCurrentCookedState.cookedPointerData.pointerCount == 0) { @@ -1679,17 +1649,18 @@ void TouchInputMapper::cookAndDispatch(nsecs_t when, nsecs_t readTime) { } // Synthesize key up from raw buttons if needed. - synthesizeButtonKeys(getContext(), AKEY_EVENT_ACTION_UP, when, readTime, getDeviceId(), mSource, - mViewport.displayId, policyFlags, mLastCookedState.buttonState, - mCurrentCookedState.buttonState); + out += synthesizeButtonKeys(getContext(), AKEY_EVENT_ACTION_UP, when, readTime, getDeviceId(), + mSource, mViewport.displayId, policyFlags, + mLastCookedState.buttonState, mCurrentCookedState.buttonState); // Clear some transient state. mCurrentRawState.rawVScroll = 0; mCurrentRawState.rawHScroll = 0; // Copy current touch to last touch in preparation for the next cycle. - mLastRawState.copyFrom(mCurrentRawState); - mLastCookedState.copyFrom(mCurrentCookedState); + mLastRawState = mCurrentRawState; + mLastCookedState = mCurrentCookedState; + return out; } void TouchInputMapper::updateTouchSpots() { @@ -1707,10 +1678,10 @@ void TouchInputMapper::updateTouchSpots() { mPointerController->setPresentation(PointerControllerInterface::Presentation::SPOT); mPointerController->fade(PointerControllerInterface::Transition::GRADUAL); - mPointerController->setButtonState(mCurrentRawState.buttonState); - mPointerController->setSpots(mCurrentCookedState.cookedPointerData.pointerCoords, - mCurrentCookedState.cookedPointerData.idToIndex, - mCurrentCookedState.cookedPointerData.touchingIdBits, + mPointerController->setSpots(mCurrentCookedState.cookedPointerData.pointerCoords.cbegin(), + mCurrentCookedState.cookedPointerData.idToIndex.cbegin(), + mCurrentCookedState.cookedPointerData.touchingIdBits | + mCurrentCookedState.cookedPointerData.hoveringIdBits, mViewport.displayId); } @@ -1720,29 +1691,43 @@ bool TouchInputMapper::isTouchScreen() { } void TouchInputMapper::applyExternalStylusButtonState(nsecs_t when) { - if (mDeviceMode == DeviceMode::DIRECT && hasExternalStylus() && mExternalStylusId != -1) { - mCurrentRawState.buttonState |= mExternalStylusState.buttons; + if (mDeviceMode == DeviceMode::DIRECT && hasExternalStylus()) { + // If any of the external buttons are already pressed by the touch device, ignore them. + const int32_t pressedButtons = + filterButtonState(mConfig, + ~mCurrentRawState.buttonState & mExternalStylusState.buttons); + const int32_t releasedButtons = + mExternalStylusButtonsApplied & ~mExternalStylusState.buttons; + + mCurrentRawState.buttonState |= pressedButtons; + mCurrentRawState.buttonState &= ~releasedButtons; + + mExternalStylusButtonsApplied |= pressedButtons; + mExternalStylusButtonsApplied &= ~releasedButtons; } } void TouchInputMapper::applyExternalStylusTouchState(nsecs_t when) { CookedPointerData& currentPointerData = mCurrentCookedState.cookedPointerData; const CookedPointerData& lastPointerData = mLastCookedState.cookedPointerData; + if (!mFusedStylusPointerId || !currentPointerData.isTouching(*mFusedStylusPointerId)) { + return; + } - if (mExternalStylusId != -1 && currentPointerData.isTouching(mExternalStylusId)) { - float pressure = mExternalStylusState.pressure; - if (pressure == 0.0f && lastPointerData.isTouching(mExternalStylusId)) { - const PointerCoords& coords = lastPointerData.pointerCoordsForId(mExternalStylusId); - pressure = coords.getAxisValue(AMOTION_EVENT_AXIS_PRESSURE); - } - PointerCoords& coords = currentPointerData.editPointerCoordsWithId(mExternalStylusId); - coords.setAxisValue(AMOTION_EVENT_AXIS_PRESSURE, pressure); + float pressure = lastPointerData.isTouching(*mFusedStylusPointerId) + ? lastPointerData.pointerCoordsForId(*mFusedStylusPointerId) + .getAxisValue(AMOTION_EVENT_AXIS_PRESSURE) + : 0.f; + if (mExternalStylusState.pressure && *mExternalStylusState.pressure > 0.f) { + pressure = *mExternalStylusState.pressure; + } + PointerCoords& coords = currentPointerData.editPointerCoordsWithId(*mFusedStylusPointerId); + coords.setAxisValue(AMOTION_EVENT_AXIS_PRESSURE, pressure); + if (mExternalStylusState.toolType != ToolType::UNKNOWN) { PointerProperties& properties = - currentPointerData.editPointerPropertiesWithId(mExternalStylusId); - if (mExternalStylusState.toolType != AMOTION_EVENT_TOOL_TYPE_UNKNOWN) { - properties.toolType = mExternalStylusState.toolType; - } + currentPointerData.editPointerPropertiesWithId(*mFusedStylusPointerId); + properties.toolType = mExternalStylusState.toolType; } } @@ -1751,86 +1736,103 @@ bool TouchInputMapper::assignExternalStylusId(const RawState& state, bool timeou return false; } + // Check if the stylus pointer has gone up. + if (mFusedStylusPointerId && + !state.rawPointerData.touchingIdBits.hasBit(*mFusedStylusPointerId)) { + ALOGD_IF(DEBUG_STYLUS_FUSION, "Stylus pointer is going up"); + mFusedStylusPointerId.reset(); + return false; + } + const bool initialDown = mLastRawState.rawPointerData.pointerCount == 0 && state.rawPointerData.pointerCount != 0; - if (initialDown) { - if (mExternalStylusState.pressure != 0.0f) { - if (DEBUG_STYLUS_FUSION) { - ALOGD("Have both stylus and touch data, beginning fusion"); - } - mExternalStylusId = state.rawPointerData.touchingIdBits.firstMarkedBit(); - } else if (timeout) { - if (DEBUG_STYLUS_FUSION) { - ALOGD("Timeout expired, assuming touch is not a stylus."); - } - resetExternalStylus(); - } else { - if (mExternalStylusFusionTimeout == LLONG_MAX) { - mExternalStylusFusionTimeout = state.when + EXTERNAL_STYLUS_DATA_TIMEOUT; - } - if (DEBUG_STYLUS_FUSION) { - ALOGD("No stylus data but stylus is connected, requesting timeout " - "(%" PRId64 "ms)", - mExternalStylusFusionTimeout); - } - getContext()->requestTimeoutAtTime(mExternalStylusFusionTimeout); - return true; - } + if (!initialDown) { + return false; } - // Check if the stylus pointer has gone up. - if (mExternalStylusId != -1 && !state.rawPointerData.touchingIdBits.hasBit(mExternalStylusId)) { - if (DEBUG_STYLUS_FUSION) { - ALOGD("Stylus pointer is going up"); - } - mExternalStylusId = -1; + if (!mExternalStylusState.pressure) { + ALOGD_IF(DEBUG_STYLUS_FUSION, "Stylus does not support pressure, no pointer fusion needed"); + return false; + } + + if (*mExternalStylusState.pressure != 0.0f) { + ALOGD_IF(DEBUG_STYLUS_FUSION, "Have both stylus and touch data, beginning fusion"); + mFusedStylusPointerId = state.rawPointerData.touchingIdBits.firstMarkedBit(); + return false; + } + + if (timeout) { + ALOGD_IF(DEBUG_STYLUS_FUSION, "Timeout expired, assuming touch is not a stylus."); + mFusedStylusPointerId.reset(); + mExternalStylusFusionTimeout = LLONG_MAX; + return false; } - return false; + // We are waiting for the external stylus to report a pressure value. Withhold touches from + // being processed until we either get pressure data or timeout. + if (mExternalStylusFusionTimeout == LLONG_MAX) { + mExternalStylusFusionTimeout = state.when + EXTERNAL_STYLUS_DATA_TIMEOUT; + } + ALOGD_IF(DEBUG_STYLUS_FUSION, + "No stylus data but stylus is connected, requesting timeout (%" PRId64 "ms)", + mExternalStylusFusionTimeout); + getContext()->requestTimeoutAtTime(mExternalStylusFusionTimeout); + return true; } -void TouchInputMapper::timeoutExpired(nsecs_t when) { +std::list TouchInputMapper::timeoutExpired(nsecs_t when) { + std::list out; if (mDeviceMode == DeviceMode::POINTER) { if (mPointerUsage == PointerUsage::GESTURES) { // Since this is a synthetic event, we can consider its latency to be zero const nsecs_t readTime = when; - dispatchPointerGestures(when, readTime, 0 /*policyFlags*/, true /*isTimeout*/); + out += dispatchPointerGestures(when, readTime, /*policyFlags=*/0, /*isTimeout=*/true); } } else if (mDeviceMode == DeviceMode::DIRECT) { - if (mExternalStylusFusionTimeout < when) { - processRawTouches(true /*timeout*/); + if (mExternalStylusFusionTimeout <= when) { + out += processRawTouches(/*timeout=*/true); } else if (mExternalStylusFusionTimeout != LLONG_MAX) { getContext()->requestTimeoutAtTime(mExternalStylusFusionTimeout); } } + return out; } -void TouchInputMapper::updateExternalStylusState(const StylusState& state) { - mExternalStylusState.copyFrom(state); - if (mExternalStylusId != -1 || mExternalStylusFusionTimeout != LLONG_MAX) { - // We're either in the middle of a fused stream of data or we're waiting on data before - // dispatching the initial down, so go ahead and dispatch now that we have fresh stylus - // data. +std::list TouchInputMapper::updateExternalStylusState(const StylusState& state) { + std::list out; + const bool buttonsChanged = mExternalStylusState.buttons != state.buttons; + mExternalStylusState = state; + if (mFusedStylusPointerId || mExternalStylusFusionTimeout != LLONG_MAX || buttonsChanged) { + // The following three cases are handled here: + // - We're in the middle of a fused stream of data; + // - We're waiting on external stylus data before dispatching the initial down; or + // - Only the button state, which is not reported through a specific pointer, has changed. + // Go ahead and dispatch now that we have fresh stylus data. mExternalStylusDataPending = true; - processRawTouches(false /*timeout*/); + out += processRawTouches(/*timeout=*/false); } + return out; } -bool TouchInputMapper::consumeRawTouches(nsecs_t when, nsecs_t readTime, uint32_t policyFlags) { +std::list TouchInputMapper::consumeRawTouches(nsecs_t when, nsecs_t readTime, + uint32_t policyFlags, bool& outConsumed) { + outConsumed = false; + std::list out; // Check for release of a virtual key. if (mCurrentVirtualKey.down) { if (mCurrentRawState.rawPointerData.touchingIdBits.isEmpty()) { // Pointer went up while virtual key was down. mCurrentVirtualKey.down = false; if (!mCurrentVirtualKey.ignored) { - if (DEBUG_VIRTUAL_KEYS) { - ALOGD("VirtualKeys: Generating key up: keyCode=%d, scanCode=%d", - mCurrentVirtualKey.keyCode, mCurrentVirtualKey.scanCode); - } - dispatchVirtualKey(when, readTime, policyFlags, AKEY_EVENT_ACTION_UP, - AKEY_EVENT_FLAG_FROM_SYSTEM | AKEY_EVENT_FLAG_VIRTUAL_HARD_KEY); + ALOGD_IF(DEBUG_VIRTUAL_KEYS, + "VirtualKeys: Generating key up: keyCode=%d, scanCode=%d", + mCurrentVirtualKey.keyCode, mCurrentVirtualKey.scanCode); + out.push_back(dispatchVirtualKey(when, readTime, policyFlags, AKEY_EVENT_ACTION_UP, + AKEY_EVENT_FLAG_FROM_SYSTEM | + AKEY_EVENT_FLAG_VIRTUAL_HARD_KEY)); } - return true; + outConsumed = true; + return out; } if (mCurrentRawState.rawPointerData.touchingIdBits.count() == 1) { @@ -1840,7 +1842,8 @@ bool TouchInputMapper::consumeRawTouches(nsecs_t when, nsecs_t readTime, uint32_ const VirtualKey* virtualKey = findVirtualKeyHit(pointer.x, pointer.y); if (virtualKey && virtualKey->keyCode == mCurrentVirtualKey.keyCode) { // Pointer is still within the space of the virtual key. - return true; + outConsumed = true; + return out; } } @@ -1850,13 +1853,33 @@ bool TouchInputMapper::consumeRawTouches(nsecs_t when, nsecs_t readTime, uint32_ // into the main display surface. mCurrentVirtualKey.down = false; if (!mCurrentVirtualKey.ignored) { - if (DEBUG_VIRTUAL_KEYS) { - ALOGD("VirtualKeys: Canceling key: keyCode=%d, scanCode=%d", - mCurrentVirtualKey.keyCode, mCurrentVirtualKey.scanCode); + ALOGD_IF(DEBUG_VIRTUAL_KEYS, "VirtualKeys: Canceling key: keyCode=%d, scanCode=%d", + mCurrentVirtualKey.keyCode, mCurrentVirtualKey.scanCode); + out.push_back(dispatchVirtualKey(when, readTime, policyFlags, AKEY_EVENT_ACTION_UP, + AKEY_EVENT_FLAG_FROM_SYSTEM | + AKEY_EVENT_FLAG_VIRTUAL_HARD_KEY | + AKEY_EVENT_FLAG_CANCELED)); + } + } + + if (!mCurrentRawState.rawPointerData.hoveringIdBits.isEmpty() && + mCurrentRawState.rawPointerData.touchingIdBits.isEmpty() && + mDeviceMode != DeviceMode::UNSCALED) { + // We have hovering pointers, and there are no touching pointers. + bool hoveringPointersInFrame = false; + auto hoveringIds = mCurrentRawState.rawPointerData.hoveringIdBits; + while (!hoveringIds.isEmpty()) { + uint32_t id = hoveringIds.clearFirstMarkedBit(); + const auto& pointer = mCurrentRawState.rawPointerData.pointerForId(id); + if (isPointInsidePhysicalFrame(pointer.x, pointer.y)) { + hoveringPointersInFrame = true; + break; } - dispatchVirtualKey(when, readTime, policyFlags, AKEY_EVENT_ACTION_UP, - AKEY_EVENT_FLAG_FROM_SYSTEM | AKEY_EVENT_FLAG_VIRTUAL_HARD_KEY | - AKEY_EVENT_FLAG_CANCELED); + } + if (!hoveringPointersInFrame) { + // All hovering pointers are outside the physical frame. + outConsumed = true; + return out; } } @@ -1866,11 +1889,11 @@ bool TouchInputMapper::consumeRawTouches(nsecs_t when, nsecs_t readTime, uint32_ uint32_t id = mCurrentRawState.rawPointerData.touchingIdBits.firstMarkedBit(); const RawPointerData::Pointer& pointer = mCurrentRawState.rawPointerData.pointerForId(id); // Skip checking whether the pointer is inside the physical frame if the device is in - // unscaled mode. + // unscaled or pointer mode. if (!isPointInsidePhysicalFrame(pointer.x, pointer.y) && - mDeviceMode != DeviceMode::UNSCALED) { + mDeviceMode != DeviceMode::UNSCALED && mDeviceMode != DeviceMode::POINTER) { // If exactly one pointer went down, check for virtual key hit. - // Otherwise we will drop the entire stroke. + // Otherwise, we will drop the entire stroke. if (mCurrentRawState.rawPointerData.touchingIdBits.count() == 1) { const VirtualKey* virtualKey = findVirtualKeyHit(pointer.x, pointer.y); if (virtualKey) { @@ -1883,17 +1906,18 @@ bool TouchInputMapper::consumeRawTouches(nsecs_t when, nsecs_t readTime, uint32_ virtualKey->scanCode); if (!mCurrentVirtualKey.ignored) { - if (DEBUG_VIRTUAL_KEYS) { - ALOGD("VirtualKeys: Generating key down: keyCode=%d, scanCode=%d", - mCurrentVirtualKey.keyCode, mCurrentVirtualKey.scanCode); - } - dispatchVirtualKey(when, readTime, policyFlags, AKEY_EVENT_ACTION_DOWN, - AKEY_EVENT_FLAG_FROM_SYSTEM | - AKEY_EVENT_FLAG_VIRTUAL_HARD_KEY); + ALOGD_IF(DEBUG_VIRTUAL_KEYS, + "VirtualKeys: Generating key down: keyCode=%d, scanCode=%d", + mCurrentVirtualKey.keyCode, mCurrentVirtualKey.scanCode); + out.push_back(dispatchVirtualKey(when, readTime, policyFlags, + AKEY_EVENT_ACTION_DOWN, + AKEY_EVENT_FLAG_FROM_SYSTEM | + AKEY_EVENT_FLAG_VIRTUAL_HARD_KEY)); } } } - return true; + outConsumed = true; + return out; } } @@ -1915,43 +1939,80 @@ bool TouchInputMapper::consumeRawTouches(nsecs_t when, nsecs_t readTime, uint32_ !mCurrentRawState.rawPointerData.touchingIdBits.isEmpty()) { getContext()->disableVirtualKeysUntil(when + mConfig.virtualKeyQuietTime); } - return false; + return out; } -void TouchInputMapper::dispatchVirtualKey(nsecs_t when, nsecs_t readTime, uint32_t policyFlags, - int32_t keyEventAction, int32_t keyEventFlags) { +NotifyKeyArgs TouchInputMapper::dispatchVirtualKey(nsecs_t when, nsecs_t readTime, + uint32_t policyFlags, int32_t keyEventAction, + int32_t keyEventFlags) { int32_t keyCode = mCurrentVirtualKey.keyCode; int32_t scanCode = mCurrentVirtualKey.scanCode; nsecs_t downTime = mCurrentVirtualKey.downTime; int32_t metaState = getContext()->getGlobalMetaState(); policyFlags |= POLICY_FLAG_VIRTUAL; - NotifyKeyArgs args(getContext()->getNextId(), when, readTime, getDeviceId(), - AINPUT_SOURCE_KEYBOARD, mViewport.displayId, policyFlags, keyEventAction, - keyEventFlags, keyCode, scanCode, metaState, downTime); - getListener().notifyKey(&args); + return NotifyKeyArgs(getContext()->getNextId(), when, readTime, getDeviceId(), + AINPUT_SOURCE_KEYBOARD, mViewport.displayId, policyFlags, keyEventAction, + keyEventFlags, keyCode, scanCode, metaState, downTime); } -void TouchInputMapper::abortTouches(nsecs_t when, nsecs_t readTime, uint32_t policyFlags) { +std::list TouchInputMapper::abortTouches(nsecs_t when, nsecs_t readTime, + uint32_t policyFlags) { + std::list out; if (mCurrentMotionAborted) { // Current motion event was already aborted. - return; + return out; } BitSet32 currentIdBits = mCurrentCookedState.cookedPointerData.touchingIdBits; if (!currentIdBits.isEmpty()) { int32_t metaState = getContext()->getGlobalMetaState(); int32_t buttonState = mCurrentCookedState.buttonState; - dispatchMotion(when, readTime, policyFlags, mSource, AMOTION_EVENT_ACTION_CANCEL, 0, 0, - metaState, buttonState, AMOTION_EVENT_EDGE_FLAG_NONE, - mCurrentCookedState.cookedPointerData.pointerProperties, - mCurrentCookedState.cookedPointerData.pointerCoords, - mCurrentCookedState.cookedPointerData.idToIndex, currentIdBits, -1, - mOrientedXPrecision, mOrientedYPrecision, mDownTime); + out.push_back(dispatchMotion(when, readTime, policyFlags, mSource, + AMOTION_EVENT_ACTION_CANCEL, 0, AMOTION_EVENT_FLAG_CANCELED, + metaState, buttonState, AMOTION_EVENT_EDGE_FLAG_NONE, + mCurrentCookedState.cookedPointerData.pointerProperties, + mCurrentCookedState.cookedPointerData.pointerCoords, + mCurrentCookedState.cookedPointerData.idToIndex, currentIdBits, + -1, mOrientedXPrecision, mOrientedYPrecision, mDownTime, + MotionClassification::NONE)); mCurrentMotionAborted = true; } + return out; } -void TouchInputMapper::dispatchTouches(nsecs_t when, nsecs_t readTime, uint32_t policyFlags) { +// Updates pointer coords and properties for pointers with specified ids that have moved. +// Returns true if any of them changed. +static bool updateMovedPointers(const PropertiesArray& inProperties, CoordsArray& inCoords, + const IdToIndexArray& inIdToIndex, PropertiesArray& outProperties, + CoordsArray& outCoords, IdToIndexArray& outIdToIndex, + BitSet32 idBits) { + bool changed = false; + while (!idBits.isEmpty()) { + uint32_t id = idBits.clearFirstMarkedBit(); + uint32_t inIndex = inIdToIndex[id]; + uint32_t outIndex = outIdToIndex[id]; + + const PointerProperties& curInProperties = inProperties[inIndex]; + const PointerCoords& curInCoords = inCoords[inIndex]; + PointerProperties& curOutProperties = outProperties[outIndex]; + PointerCoords& curOutCoords = outCoords[outIndex]; + + if (curInProperties != curOutProperties) { + curOutProperties.copyFrom(curInProperties); + changed = true; + } + + if (curInCoords != curOutCoords) { + curOutCoords.copyFrom(curInCoords); + changed = true; + } + } + return changed; +} + +std::list TouchInputMapper::dispatchTouches(nsecs_t when, nsecs_t readTime, + uint32_t policyFlags) { + std::list out; BitSet32 currentIdBits = mCurrentCookedState.cookedPointerData.touchingIdBits; BitSet32 lastIdBits = mLastCookedState.cookedPointerData.touchingIdBits; int32_t metaState = getContext()->getGlobalMetaState(); @@ -1961,12 +2022,14 @@ void TouchInputMapper::dispatchTouches(nsecs_t when, nsecs_t readTime, uint32_t if (!currentIdBits.isEmpty()) { // No pointer id changes so this is a move event. // The listener takes care of batching moves so we don't have to deal with that here. - dispatchMotion(when, readTime, policyFlags, mSource, AMOTION_EVENT_ACTION_MOVE, 0, 0, - metaState, buttonState, AMOTION_EVENT_EDGE_FLAG_NONE, - mCurrentCookedState.cookedPointerData.pointerProperties, - mCurrentCookedState.cookedPointerData.pointerCoords, - mCurrentCookedState.cookedPointerData.idToIndex, currentIdBits, -1, - mOrientedXPrecision, mOrientedYPrecision, mDownTime); + out.push_back( + dispatchMotion(when, readTime, policyFlags, mSource, AMOTION_EVENT_ACTION_MOVE, + 0, 0, metaState, buttonState, AMOTION_EVENT_EDGE_FLAG_NONE, + mCurrentCookedState.cookedPointerData.pointerProperties, + mCurrentCookedState.cookedPointerData.pointerCoords, + mCurrentCookedState.cookedPointerData.idToIndex, currentIdBits, + -1, mOrientedXPrecision, mOrientedYPrecision, mDownTime, + MotionClassification::NONE)); } } else { // There may be pointers going up and pointers going down and pointers moving @@ -1996,12 +2059,16 @@ void TouchInputMapper::dispatchTouches(nsecs_t when, nsecs_t readTime, uint32_t if (isCanceled) { ALOGI("Canceling pointer %d for the palm event was detected.", upId); } - dispatchMotion(when, readTime, policyFlags, mSource, AMOTION_EVENT_ACTION_POINTER_UP, 0, - isCanceled ? AMOTION_EVENT_FLAG_CANCELED : 0, metaState, buttonState, 0, - mLastCookedState.cookedPointerData.pointerProperties, - mLastCookedState.cookedPointerData.pointerCoords, - mLastCookedState.cookedPointerData.idToIndex, dispatchedIdBits, upId, - mOrientedXPrecision, mOrientedYPrecision, mDownTime); + out.push_back(dispatchMotion(when, readTime, policyFlags, mSource, + AMOTION_EVENT_ACTION_POINTER_UP, 0, + isCanceled ? AMOTION_EVENT_FLAG_CANCELED : 0, metaState, + buttonState, 0, + mLastCookedState.cookedPointerData.pointerProperties, + mLastCookedState.cookedPointerData.pointerCoords, + mLastCookedState.cookedPointerData.idToIndex, + dispatchedIdBits, upId, mOrientedXPrecision, + mOrientedYPrecision, mDownTime, + MotionClassification::NONE)); dispatchedIdBits.clearBit(upId); mCurrentCookedState.cookedPointerData.canceledIdBits.clearBit(upId); } @@ -2011,12 +2078,14 @@ void TouchInputMapper::dispatchTouches(nsecs_t when, nsecs_t readTime, uint32_t // events, they do not generally handle them except when presented in a move event. if (moveNeeded && !moveIdBits.isEmpty()) { ALOG_ASSERT(moveIdBits.value == dispatchedIdBits.value); - dispatchMotion(when, readTime, policyFlags, mSource, AMOTION_EVENT_ACTION_MOVE, 0, 0, - metaState, buttonState, 0, - mCurrentCookedState.cookedPointerData.pointerProperties, - mCurrentCookedState.cookedPointerData.pointerCoords, - mCurrentCookedState.cookedPointerData.idToIndex, dispatchedIdBits, -1, - mOrientedXPrecision, mOrientedYPrecision, mDownTime); + out.push_back(dispatchMotion(when, readTime, policyFlags, mSource, + AMOTION_EVENT_ACTION_MOVE, 0, 0, metaState, buttonState, 0, + mCurrentCookedState.cookedPointerData.pointerProperties, + mCurrentCookedState.cookedPointerData.pointerCoords, + mCurrentCookedState.cookedPointerData.idToIndex, + dispatchedIdBits, -1, mOrientedXPrecision, + mOrientedYPrecision, mDownTime, + MotionClassification::NONE)); } // Dispatch pointer down events using the new pointer locations. @@ -2029,59 +2098,75 @@ void TouchInputMapper::dispatchTouches(nsecs_t when, nsecs_t readTime, uint32_t mDownTime = when; } - dispatchMotion(when, readTime, policyFlags, mSource, AMOTION_EVENT_ACTION_POINTER_DOWN, - 0, 0, metaState, buttonState, 0, - mCurrentCookedState.cookedPointerData.pointerProperties, - mCurrentCookedState.cookedPointerData.pointerCoords, - mCurrentCookedState.cookedPointerData.idToIndex, dispatchedIdBits, - downId, mOrientedXPrecision, mOrientedYPrecision, mDownTime); + out.push_back( + dispatchMotion(when, readTime, policyFlags, mSource, + AMOTION_EVENT_ACTION_POINTER_DOWN, 0, 0, metaState, buttonState, + 0, mCurrentCookedState.cookedPointerData.pointerProperties, + mCurrentCookedState.cookedPointerData.pointerCoords, + mCurrentCookedState.cookedPointerData.idToIndex, + dispatchedIdBits, downId, mOrientedXPrecision, + mOrientedYPrecision, mDownTime, MotionClassification::NONE)); } } + return out; } -void TouchInputMapper::dispatchHoverExit(nsecs_t when, nsecs_t readTime, uint32_t policyFlags) { +std::list TouchInputMapper::dispatchHoverExit(nsecs_t when, nsecs_t readTime, + uint32_t policyFlags) { + std::list out; if (mSentHoverEnter && (mCurrentCookedState.cookedPointerData.hoveringIdBits.isEmpty() || !mCurrentCookedState.cookedPointerData.touchingIdBits.isEmpty())) { int32_t metaState = getContext()->getGlobalMetaState(); - dispatchMotion(when, readTime, policyFlags, mSource, AMOTION_EVENT_ACTION_HOVER_EXIT, 0, 0, - metaState, mLastCookedState.buttonState, 0, - mLastCookedState.cookedPointerData.pointerProperties, - mLastCookedState.cookedPointerData.pointerCoords, - mLastCookedState.cookedPointerData.idToIndex, - mLastCookedState.cookedPointerData.hoveringIdBits, -1, mOrientedXPrecision, - mOrientedYPrecision, mDownTime); + out.push_back(dispatchMotion(when, readTime, policyFlags, mSource, + AMOTION_EVENT_ACTION_HOVER_EXIT, 0, 0, metaState, + mLastCookedState.buttonState, 0, + mLastCookedState.cookedPointerData.pointerProperties, + mLastCookedState.cookedPointerData.pointerCoords, + mLastCookedState.cookedPointerData.idToIndex, + mLastCookedState.cookedPointerData.hoveringIdBits, -1, + mOrientedXPrecision, mOrientedYPrecision, mDownTime, + MotionClassification::NONE)); mSentHoverEnter = false; } + return out; } -void TouchInputMapper::dispatchHoverEnterAndMove(nsecs_t when, nsecs_t readTime, - uint32_t policyFlags) { +std::list TouchInputMapper::dispatchHoverEnterAndMove(nsecs_t when, nsecs_t readTime, + uint32_t policyFlags) { + std::list out; if (mCurrentCookedState.cookedPointerData.touchingIdBits.isEmpty() && !mCurrentCookedState.cookedPointerData.hoveringIdBits.isEmpty()) { int32_t metaState = getContext()->getGlobalMetaState(); if (!mSentHoverEnter) { - dispatchMotion(when, readTime, policyFlags, mSource, AMOTION_EVENT_ACTION_HOVER_ENTER, - 0, 0, metaState, mCurrentRawState.buttonState, 0, - mCurrentCookedState.cookedPointerData.pointerProperties, - mCurrentCookedState.cookedPointerData.pointerCoords, - mCurrentCookedState.cookedPointerData.idToIndex, - mCurrentCookedState.cookedPointerData.hoveringIdBits, -1, - mOrientedXPrecision, mOrientedYPrecision, mDownTime); + out.push_back(dispatchMotion(when, readTime, policyFlags, mSource, + AMOTION_EVENT_ACTION_HOVER_ENTER, 0, 0, metaState, + mCurrentRawState.buttonState, 0, + mCurrentCookedState.cookedPointerData.pointerProperties, + mCurrentCookedState.cookedPointerData.pointerCoords, + mCurrentCookedState.cookedPointerData.idToIndex, + mCurrentCookedState.cookedPointerData.hoveringIdBits, -1, + mOrientedXPrecision, mOrientedYPrecision, mDownTime, + MotionClassification::NONE)); mSentHoverEnter = true; } - dispatchMotion(when, readTime, policyFlags, mSource, AMOTION_EVENT_ACTION_HOVER_MOVE, 0, 0, - metaState, mCurrentRawState.buttonState, 0, - mCurrentCookedState.cookedPointerData.pointerProperties, - mCurrentCookedState.cookedPointerData.pointerCoords, - mCurrentCookedState.cookedPointerData.idToIndex, - mCurrentCookedState.cookedPointerData.hoveringIdBits, -1, - mOrientedXPrecision, mOrientedYPrecision, mDownTime); + out.push_back(dispatchMotion(when, readTime, policyFlags, mSource, + AMOTION_EVENT_ACTION_HOVER_MOVE, 0, 0, metaState, + mCurrentRawState.buttonState, 0, + mCurrentCookedState.cookedPointerData.pointerProperties, + mCurrentCookedState.cookedPointerData.pointerCoords, + mCurrentCookedState.cookedPointerData.idToIndex, + mCurrentCookedState.cookedPointerData.hoveringIdBits, -1, + mOrientedXPrecision, mOrientedYPrecision, mDownTime, + MotionClassification::NONE)); } + return out; } -void TouchInputMapper::dispatchButtonRelease(nsecs_t when, nsecs_t readTime, uint32_t policyFlags) { +std::list TouchInputMapper::dispatchButtonRelease(nsecs_t when, nsecs_t readTime, + uint32_t policyFlags) { + std::list out; BitSet32 releasedButtons(mLastCookedState.buttonState & ~mCurrentCookedState.buttonState); const BitSet32& idBits = findActiveIdBits(mLastCookedState.cookedPointerData); const int32_t metaState = getContext()->getGlobalMetaState(); @@ -2089,16 +2174,21 @@ void TouchInputMapper::dispatchButtonRelease(nsecs_t when, nsecs_t readTime, uin while (!releasedButtons.isEmpty()) { int32_t actionButton = BitSet32::valueForBit(releasedButtons.clearFirstMarkedBit()); buttonState &= ~actionButton; - dispatchMotion(when, readTime, policyFlags, mSource, AMOTION_EVENT_ACTION_BUTTON_RELEASE, - actionButton, 0, metaState, buttonState, 0, - mCurrentCookedState.cookedPointerData.pointerProperties, - mCurrentCookedState.cookedPointerData.pointerCoords, - mCurrentCookedState.cookedPointerData.idToIndex, idBits, -1, - mOrientedXPrecision, mOrientedYPrecision, mDownTime); - } + out.push_back(dispatchMotion(when, readTime, policyFlags, mSource, + AMOTION_EVENT_ACTION_BUTTON_RELEASE, actionButton, 0, + metaState, buttonState, 0, + mLastCookedState.cookedPointerData.pointerProperties, + mLastCookedState.cookedPointerData.pointerCoords, + mLastCookedState.cookedPointerData.idToIndex, idBits, -1, + mOrientedXPrecision, mOrientedYPrecision, mDownTime, + MotionClassification::NONE)); + } + return out; } -void TouchInputMapper::dispatchButtonPress(nsecs_t when, nsecs_t readTime, uint32_t policyFlags) { +std::list TouchInputMapper::dispatchButtonPress(nsecs_t when, nsecs_t readTime, + uint32_t policyFlags) { + std::list out; BitSet32 pressedButtons(mCurrentCookedState.buttonState & ~mLastCookedState.buttonState); const BitSet32& idBits = findActiveIdBits(mCurrentCookedState.cookedPointerData); const int32_t metaState = getContext()->getGlobalMetaState(); @@ -2106,13 +2196,63 @@ void TouchInputMapper::dispatchButtonPress(nsecs_t when, nsecs_t readTime, uint3 while (!pressedButtons.isEmpty()) { int32_t actionButton = BitSet32::valueForBit(pressedButtons.clearFirstMarkedBit()); buttonState |= actionButton; - dispatchMotion(when, readTime, policyFlags, mSource, AMOTION_EVENT_ACTION_BUTTON_PRESS, - actionButton, 0, metaState, buttonState, 0, - mCurrentCookedState.cookedPointerData.pointerProperties, - mCurrentCookedState.cookedPointerData.pointerCoords, - mCurrentCookedState.cookedPointerData.idToIndex, idBits, -1, - mOrientedXPrecision, mOrientedYPrecision, mDownTime); - } + out.push_back(dispatchMotion(when, readTime, policyFlags, mSource, + AMOTION_EVENT_ACTION_BUTTON_PRESS, actionButton, 0, metaState, + buttonState, 0, + mCurrentCookedState.cookedPointerData.pointerProperties, + mCurrentCookedState.cookedPointerData.pointerCoords, + mCurrentCookedState.cookedPointerData.idToIndex, idBits, -1, + mOrientedXPrecision, mOrientedYPrecision, mDownTime, + MotionClassification::NONE)); + } + return out; +} + +std::list TouchInputMapper::dispatchGestureButtonRelease(nsecs_t when, + uint32_t policyFlags, + BitSet32 idBits, + nsecs_t readTime) { + std::list out; + BitSet32 releasedButtons(mLastCookedState.buttonState & ~mCurrentCookedState.buttonState); + const int32_t metaState = getContext()->getGlobalMetaState(); + int32_t buttonState = mLastCookedState.buttonState; + + while (!releasedButtons.isEmpty()) { + int32_t actionButton = BitSet32::valueForBit(releasedButtons.clearFirstMarkedBit()); + buttonState &= ~actionButton; + out.push_back(dispatchMotion(when, readTime, policyFlags, mSource, + AMOTION_EVENT_ACTION_BUTTON_RELEASE, actionButton, 0, + metaState, buttonState, 0, + mPointerGesture.lastGestureProperties, + mPointerGesture.lastGestureCoords, + mPointerGesture.lastGestureIdToIndex, idBits, -1, + mOrientedXPrecision, mOrientedYPrecision, + mPointerGesture.downTime, MotionClassification::NONE)); + } + return out; +} + +std::list TouchInputMapper::dispatchGestureButtonPress(nsecs_t when, + uint32_t policyFlags, + BitSet32 idBits, + nsecs_t readTime) { + std::list out; + BitSet32 pressedButtons(mCurrentCookedState.buttonState & ~mLastCookedState.buttonState); + const int32_t metaState = getContext()->getGlobalMetaState(); + int32_t buttonState = mLastCookedState.buttonState; + + while (!pressedButtons.isEmpty()) { + int32_t actionButton = BitSet32::valueForBit(pressedButtons.clearFirstMarkedBit()); + buttonState |= actionButton; + out.push_back(dispatchMotion(when, readTime, policyFlags, mSource, + AMOTION_EVENT_ACTION_BUTTON_PRESS, actionButton, 0, metaState, + buttonState, 0, mPointerGesture.currentGestureProperties, + mPointerGesture.currentGestureCoords, + mPointerGesture.currentGestureIdToIndex, idBits, -1, + mOrientedXPrecision, mOrientedYPrecision, + mPointerGesture.downTime, MotionClassification::NONE)); + } + return out; } const BitSet32& TouchInputMapper::findActiveIdBits(const CookedPointerData& cookedPointerData) { @@ -2182,7 +2322,7 @@ void TouchInputMapper::cookPointerData() { size = 0; } - if (mCalibration.haveSizeIsSummed && mCalibration.sizeIsSummed) { + if (mCalibration.sizeIsSummed && *mCalibration.sizeIsSummed) { uint32_t touchingCount = mCurrentRawState.rawPointerData.touchingIdBits.count(); if (touchingCount > 1) { touchMajor /= touchingCount; @@ -2208,13 +2348,16 @@ void TouchInputMapper::cookPointerData() { toolMinor = toolMajor; } - mCalibration.applySizeScaleAndBias(&touchMajor); - mCalibration.applySizeScaleAndBias(&touchMinor); - mCalibration.applySizeScaleAndBias(&toolMajor); - mCalibration.applySizeScaleAndBias(&toolMinor); + mCalibration.applySizeScaleAndBias(touchMajor); + mCalibration.applySizeScaleAndBias(touchMinor); + mCalibration.applySizeScaleAndBias(toolMajor); + mCalibration.applySizeScaleAndBias(toolMinor); size *= mSizeScale; break; - default: + case Calibration::SizeCalibration::DEFAULT: + LOG_ALWAYS_FATAL("Resolution should not be 'DEFAULT' at this point"); + break; + case Calibration::SizeCalibration::NONE: touchMajor = 0; touchMinor = 0; toolMajor = 0; @@ -2241,20 +2384,20 @@ void TouchInputMapper::cookPointerData() { if (mHaveTilt) { float tiltXAngle = (in.tiltX - mTiltXCenter) * mTiltXScale; float tiltYAngle = (in.tiltY - mTiltYCenter) * mTiltYScale; - orientation = atan2f(-sinf(tiltXAngle), sinf(tiltYAngle)); + orientation = transformAngle(mRawRotation, atan2f(-sinf(tiltXAngle), sinf(tiltYAngle))); tilt = acosf(cosf(tiltXAngle) * cosf(tiltYAngle)); } else { tilt = 0; switch (mCalibration.orientationCalibration) { case Calibration::OrientationCalibration::INTERPOLATED: - orientation = in.orientation * mOrientationScale; + orientation = transformAngle(mRawRotation, in.orientation * mOrientationScale); 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 = atan2f(c1, c2) * 0.5f; + orientation = transformAngle(mRawRotation, atan2f(c1, c2) * 0.5f); float confidence = hypotf(c1, c2); float scale = 1.0f + confidence / 16.0f; touchMajor *= scale; @@ -2281,79 +2424,16 @@ void TouchInputMapper::cookPointerData() { distance = 0; } - // Coverage - int32_t rawLeft, rawTop, rawRight, rawBottom; - switch (mCalibration.coverageCalibration) { - case Calibration::CoverageCalibration::BOX: - rawLeft = (in.toolMinor & 0xffff0000) >> 16; - rawRight = in.toolMinor & 0x0000ffff; - rawBottom = in.toolMajor & 0x0000ffff; - rawTop = (in.toolMajor & 0xffff0000) >> 16; - break; - default: - rawLeft = rawTop = rawRight = rawBottom = 0; - break; - } - - // Adjust X,Y coords for device calibration - // TODO: Adjust coverage coords? - float xTransformed = in.x, yTransformed = in.y; - mAffineTransform.applyTo(xTransformed, yTransformed); - rotateAndScale(xTransformed, yTransformed); - - // Adjust X, Y, and coverage coords for input device orientation. - float left, top, right, bottom; - - switch (mInputDeviceOrientation) { - case DISPLAY_ORIENTATION_90: - left = float(rawTop - mRawPointerAxes.y.minValue) * mYScale; - right = float(rawBottom - mRawPointerAxes.y.minValue) * mYScale; - bottom = float(mRawPointerAxes.x.maxValue - rawLeft) * mXScale; - top = float(mRawPointerAxes.x.maxValue - rawRight) * mXScale; - orientation -= M_PI_2; - if (mOrientedRanges.haveOrientation && - orientation < mOrientedRanges.orientation.min) { - orientation += - (mOrientedRanges.orientation.max - mOrientedRanges.orientation.min); - } - break; - case DISPLAY_ORIENTATION_180: - left = float(mRawPointerAxes.x.maxValue - rawRight) * mXScale; - right = float(mRawPointerAxes.x.maxValue - rawLeft) * mXScale; - bottom = float(mRawPointerAxes.y.maxValue - rawTop) * mYScale; - top = float(mRawPointerAxes.y.maxValue - rawBottom) * mYScale; - orientation -= M_PI; - if (mOrientedRanges.haveOrientation && - orientation < mOrientedRanges.orientation.min) { - orientation += - (mOrientedRanges.orientation.max - mOrientedRanges.orientation.min); - } - break; - case DISPLAY_ORIENTATION_270: - left = float(mRawPointerAxes.y.maxValue - rawBottom) * mYScale; - right = float(mRawPointerAxes.y.maxValue - rawTop) * mYScale; - bottom = float(rawRight - mRawPointerAxes.x.minValue) * mXScale; - top = float(rawLeft - mRawPointerAxes.x.minValue) * mXScale; - orientation += M_PI_2; - if (mOrientedRanges.haveOrientation && - orientation > mOrientedRanges.orientation.max) { - orientation -= - (mOrientedRanges.orientation.max - mOrientedRanges.orientation.min); - } - break; - default: - left = float(rawLeft - mRawPointerAxes.x.minValue) * mXScale; - right = float(rawRight - mRawPointerAxes.x.minValue) * mXScale; - bottom = float(rawBottom - mRawPointerAxes.y.minValue) * mYScale; - top = float(rawTop - mRawPointerAxes.y.minValue) * mYScale; - break; - } + // Adjust X,Y coords for device calibration and convert to the natural display coordinates. + vec2 transformed = {in.x, in.y}; + mAffineTransform.applyTo(transformed.x /*byRef*/, transformed.y /*byRef*/); + transformed = mRawToDisplay.transform(transformed); // Write output coords. PointerCoords& out = mCurrentCookedState.cookedPointerData.pointerCoords[i]; out.clear(); - out.setAxisValue(AMOTION_EVENT_AXIS_X, xTransformed); - out.setAxisValue(AMOTION_EVENT_AXIS_Y, yTransformed); + out.setAxisValue(AMOTION_EVENT_AXIS_X, transformed.x); + out.setAxisValue(AMOTION_EVENT_AXIS_Y, transformed.y); out.setAxisValue(AMOTION_EVENT_AXIS_PRESSURE, pressure); out.setAxisValue(AMOTION_EVENT_AXIS_SIZE, size); out.setAxisValue(AMOTION_EVENT_AXIS_TOUCH_MAJOR, touchMajor); @@ -2361,23 +2441,16 @@ void TouchInputMapper::cookPointerData() { out.setAxisValue(AMOTION_EVENT_AXIS_ORIENTATION, orientation); out.setAxisValue(AMOTION_EVENT_AXIS_TILT, tilt); out.setAxisValue(AMOTION_EVENT_AXIS_DISTANCE, distance); - if (mCalibration.coverageCalibration == Calibration::CoverageCalibration::BOX) { - out.setAxisValue(AMOTION_EVENT_AXIS_GENERIC_1, left); - out.setAxisValue(AMOTION_EVENT_AXIS_GENERIC_2, top); - out.setAxisValue(AMOTION_EVENT_AXIS_GENERIC_3, right); - out.setAxisValue(AMOTION_EVENT_AXIS_GENERIC_4, bottom); - } else { - out.setAxisValue(AMOTION_EVENT_AXIS_TOOL_MAJOR, toolMajor); - out.setAxisValue(AMOTION_EVENT_AXIS_TOOL_MINOR, toolMinor); - } + out.setAxisValue(AMOTION_EVENT_AXIS_TOOL_MAJOR, toolMajor); + out.setAxisValue(AMOTION_EVENT_AXIS_TOOL_MINOR, toolMinor); // Write output relative fields if applicable. uint32_t id = in.id; if (mSource == AINPUT_SOURCE_TOUCHPAD && mLastCookedState.cookedPointerData.hasPointerCoordsForId(id)) { const PointerCoords& p = mLastCookedState.cookedPointerData.pointerCoordsForId(id); - float dx = xTransformed - p.getAxisValue(AMOTION_EVENT_AXIS_X); - float dy = yTransformed - p.getAxisValue(AMOTION_EVENT_AXIS_Y); + float dx = transformed.x - p.getAxisValue(AMOTION_EVENT_AXIS_X); + float dy = transformed.y - p.getAxisValue(AMOTION_EVENT_AXIS_Y); out.setAxisValue(AMOTION_EVENT_AXIS_RELATIVE_X, dx); out.setAxisValue(AMOTION_EVENT_AXIS_RELATIVE_Y, dy); } @@ -2394,54 +2467,62 @@ void TouchInputMapper::cookPointerData() { } } -void TouchInputMapper::dispatchPointerUsage(nsecs_t when, nsecs_t readTime, uint32_t policyFlags, - PointerUsage pointerUsage) { +std::list TouchInputMapper::dispatchPointerUsage(nsecs_t when, nsecs_t readTime, + uint32_t policyFlags, + PointerUsage pointerUsage) { + std::list out; if (pointerUsage != mPointerUsage) { - abortPointerUsage(when, readTime, policyFlags); + out += abortPointerUsage(when, readTime, policyFlags); mPointerUsage = pointerUsage; } switch (mPointerUsage) { case PointerUsage::GESTURES: - dispatchPointerGestures(when, readTime, policyFlags, false /*isTimeout*/); + out += dispatchPointerGestures(when, readTime, policyFlags, /*isTimeout=*/false); break; case PointerUsage::STYLUS: - dispatchPointerStylus(when, readTime, policyFlags); + out += dispatchPointerStylus(when, readTime, policyFlags); break; case PointerUsage::MOUSE: - dispatchPointerMouse(when, readTime, policyFlags); + out += dispatchPointerMouse(when, readTime, policyFlags); break; case PointerUsage::NONE: break; } + return out; } -void TouchInputMapper::abortPointerUsage(nsecs_t when, nsecs_t readTime, uint32_t policyFlags) { +std::list TouchInputMapper::abortPointerUsage(nsecs_t when, nsecs_t readTime, + uint32_t policyFlags) { + std::list out; switch (mPointerUsage) { case PointerUsage::GESTURES: - abortPointerGestures(when, readTime, policyFlags); + out += abortPointerGestures(when, readTime, policyFlags); break; case PointerUsage::STYLUS: - abortPointerStylus(when, readTime, policyFlags); + out += abortPointerStylus(when, readTime, policyFlags); break; case PointerUsage::MOUSE: - abortPointerMouse(when, readTime, policyFlags); + out += abortPointerMouse(when, readTime, policyFlags); break; case PointerUsage::NONE: break; } mPointerUsage = PointerUsage::NONE; + return out; } -void TouchInputMapper::dispatchPointerGestures(nsecs_t when, nsecs_t readTime, uint32_t policyFlags, - bool isTimeout) { +std::list TouchInputMapper::dispatchPointerGestures(nsecs_t when, nsecs_t readTime, + uint32_t policyFlags, + bool isTimeout) { + std::list out; // Update current gesture coordinates. bool cancelPreviousGesture, finishPreviousGesture; bool sendEvents = preparePointerGestures(when, &cancelPreviousGesture, &finishPreviousGesture, isTimeout); if (!sendEvents) { - return; + return {}; } if (finishPreviousGesture) { cancelPreviousGesture = false; @@ -2455,8 +2536,8 @@ void TouchInputMapper::dispatchPointerGestures(nsecs_t when, nsecs_t readTime, u } if (mPointerGesture.currentGestureMode == PointerGesture::Mode::FREEFORM) { - mPointerController->setSpots(mPointerGesture.currentGestureCoords, - mPointerGesture.currentGestureIdToIndex, + mPointerController->setSpots(mPointerGesture.currentGestureCoords.cbegin(), + mPointerGesture.currentGestureIdToIndex.cbegin(), mPointerGesture.currentGestureIdBits, mPointerController->getDisplayId()); } @@ -2498,6 +2579,10 @@ void TouchInputMapper::dispatchPointerGestures(nsecs_t when, nsecs_t readTime, u // Send events! int32_t metaState = getContext()->getGlobalMetaState(); int32_t buttonState = mCurrentCookedState.buttonState; + const MotionClassification classification = + mPointerGesture.currentGestureMode == PointerGesture::Mode::SWIPE + ? MotionClassification::TWO_FINGER_SWIPE + : MotionClassification::NONE; uint32_t flags = 0; @@ -2534,11 +2619,15 @@ void TouchInputMapper::dispatchPointerGestures(nsecs_t when, nsecs_t readTime, u BitSet32 dispatchedGestureIdBits(mPointerGesture.lastGestureIdBits); if (!dispatchedGestureIdBits.isEmpty()) { if (cancelPreviousGesture) { - dispatchMotion(when, readTime, policyFlags, mSource, AMOTION_EVENT_ACTION_CANCEL, 0, - flags, metaState, buttonState, AMOTION_EVENT_EDGE_FLAG_NONE, - mPointerGesture.lastGestureProperties, mPointerGesture.lastGestureCoords, - mPointerGesture.lastGestureIdToIndex, dispatchedGestureIdBits, -1, 0, 0, - mPointerGesture.downTime); + const uint32_t cancelFlags = flags | AMOTION_EVENT_FLAG_CANCELED; + out.push_back(dispatchMotion(when, readTime, policyFlags, mSource, + AMOTION_EVENT_ACTION_CANCEL, 0, cancelFlags, metaState, + buttonState, AMOTION_EVENT_EDGE_FLAG_NONE, + mPointerGesture.lastGestureProperties, + mPointerGesture.lastGestureCoords, + mPointerGesture.lastGestureIdToIndex, + dispatchedGestureIdBits, -1, 0, 0, + mPointerGesture.downTime, classification)); dispatchedGestureIdBits.clear(); } else { @@ -2550,14 +2639,21 @@ void TouchInputMapper::dispatchPointerGestures(nsecs_t when, nsecs_t readTime, u dispatchedGestureIdBits.value & ~mPointerGesture.currentGestureIdBits.value; } while (!upGestureIdBits.isEmpty()) { - uint32_t id = upGestureIdBits.clearFirstMarkedBit(); - - dispatchMotion(when, readTime, policyFlags, mSource, - AMOTION_EVENT_ACTION_POINTER_UP, 0, flags, metaState, buttonState, - AMOTION_EVENT_EDGE_FLAG_NONE, mPointerGesture.lastGestureProperties, - mPointerGesture.lastGestureCoords, - mPointerGesture.lastGestureIdToIndex, dispatchedGestureIdBits, id, 0, - 0, mPointerGesture.downTime); + if (((mLastCookedState.buttonState & AMOTION_EVENT_BUTTON_PRIMARY) != 0 || + (mLastCookedState.buttonState & AMOTION_EVENT_BUTTON_SECONDARY) != 0) && + mPointerGesture.lastGestureMode == PointerGesture::Mode::BUTTON_CLICK_OR_DRAG) { + out += dispatchGestureButtonRelease(when, policyFlags, dispatchedGestureIdBits, + readTime); + } + const uint32_t id = upGestureIdBits.clearFirstMarkedBit(); + out.push_back(dispatchMotion(when, readTime, policyFlags, mSource, + AMOTION_EVENT_ACTION_POINTER_UP, 0, flags, metaState, + buttonState, AMOTION_EVENT_EDGE_FLAG_NONE, + mPointerGesture.lastGestureProperties, + mPointerGesture.lastGestureCoords, + mPointerGesture.lastGestureIdToIndex, + dispatchedGestureIdBits, id, 0, 0, + mPointerGesture.downTime, classification)); dispatchedGestureIdBits.clearBit(id); } @@ -2566,12 +2662,13 @@ void TouchInputMapper::dispatchPointerGestures(nsecs_t when, nsecs_t readTime, u // Send motion events for all pointers that moved. if (moveNeeded) { - dispatchMotion(when, readTime, policyFlags, mSource, AMOTION_EVENT_ACTION_MOVE, 0, flags, - metaState, buttonState, AMOTION_EVENT_EDGE_FLAG_NONE, - mPointerGesture.currentGestureProperties, - mPointerGesture.currentGestureCoords, - mPointerGesture.currentGestureIdToIndex, dispatchedGestureIdBits, -1, 0, 0, - mPointerGesture.downTime); + out.push_back( + dispatchMotion(when, readTime, policyFlags, mSource, AMOTION_EVENT_ACTION_MOVE, 0, + flags, metaState, buttonState, AMOTION_EVENT_EDGE_FLAG_NONE, + mPointerGesture.currentGestureProperties, + mPointerGesture.currentGestureCoords, + mPointerGesture.currentGestureIdToIndex, dispatchedGestureIdBits, -1, + 0, 0, mPointerGesture.downTime, classification)); } // Send motion events for all pointers that went down. @@ -2586,35 +2683,43 @@ void TouchInputMapper::dispatchPointerGestures(nsecs_t when, nsecs_t readTime, u mPointerGesture.downTime = when; } - dispatchMotion(when, readTime, policyFlags, mSource, AMOTION_EVENT_ACTION_POINTER_DOWN, - 0, flags, metaState, buttonState, 0, - mPointerGesture.currentGestureProperties, - mPointerGesture.currentGestureCoords, - mPointerGesture.currentGestureIdToIndex, dispatchedGestureIdBits, id, 0, - 0, mPointerGesture.downTime); + out.push_back(dispatchMotion(when, readTime, policyFlags, mSource, + AMOTION_EVENT_ACTION_POINTER_DOWN, 0, flags, metaState, + buttonState, 0, mPointerGesture.currentGestureProperties, + mPointerGesture.currentGestureCoords, + mPointerGesture.currentGestureIdToIndex, + dispatchedGestureIdBits, id, 0, 0, + mPointerGesture.downTime, classification)); + if (((buttonState & AMOTION_EVENT_BUTTON_PRIMARY) != 0 || + (buttonState & AMOTION_EVENT_BUTTON_SECONDARY) != 0) && + mPointerGesture.currentGestureMode == PointerGesture::Mode::BUTTON_CLICK_OR_DRAG) { + out += dispatchGestureButtonPress(when, policyFlags, dispatchedGestureIdBits, + readTime); + } } } // Send motion events for hover. if (mPointerGesture.currentGestureMode == PointerGesture::Mode::HOVER) { - dispatchMotion(when, readTime, policyFlags, mSource, AMOTION_EVENT_ACTION_HOVER_MOVE, 0, - flags, metaState, buttonState, AMOTION_EVENT_EDGE_FLAG_NONE, - mPointerGesture.currentGestureProperties, - mPointerGesture.currentGestureCoords, - mPointerGesture.currentGestureIdToIndex, - mPointerGesture.currentGestureIdBits, -1, 0, 0, mPointerGesture.downTime); + out.push_back(dispatchMotion(when, readTime, policyFlags, mSource, + AMOTION_EVENT_ACTION_HOVER_MOVE, 0, flags, metaState, + buttonState, AMOTION_EVENT_EDGE_FLAG_NONE, + mPointerGesture.currentGestureProperties, + mPointerGesture.currentGestureCoords, + mPointerGesture.currentGestureIdToIndex, + mPointerGesture.currentGestureIdBits, -1, 0, 0, + mPointerGesture.downTime, MotionClassification::NONE)); } else if (dispatchedGestureIdBits.isEmpty() && !mPointerGesture.lastGestureIdBits.isEmpty()) { // Synthesize a hover move event after all pointers go up to indicate that // 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. - float x, y; - mPointerController->getPosition(&x, &y); + const auto [x, y] = mPointerController->getPosition(); PointerProperties pointerProperties; pointerProperties.clear(); pointerProperties.id = 0; - pointerProperties.toolType = AMOTION_EVENT_TOOL_TYPE_FINGER; + pointerProperties.toolType = ToolType::FINGER; PointerCoords pointerCoords; pointerCoords.clear(); @@ -2622,12 +2727,13 @@ void TouchInputMapper::dispatchPointerGestures(nsecs_t when, nsecs_t readTime, u pointerCoords.setAxisValue(AMOTION_EVENT_AXIS_Y, y); const int32_t displayId = mPointerController->getDisplayId(); - NotifyMotionArgs args(getContext()->getNextId(), when, readTime, getDeviceId(), mSource, - displayId, 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, /* videoFrames */ {}); - getListener().notifyMotion(&args); + out.push_back(NotifyMotionArgs(getContext()->getNextId(), when, readTime, getDeviceId(), + mSource, displayId, 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, + /* videoFrames */ {})); } // Update state. @@ -2646,18 +2752,28 @@ void TouchInputMapper::dispatchPointerGestures(nsecs_t when, nsecs_t readTime, u mPointerGesture.lastGestureIdToIndex[id] = index; } } + return out; } -void TouchInputMapper::abortPointerGestures(nsecs_t when, nsecs_t readTime, uint32_t policyFlags) { +std::list TouchInputMapper::abortPointerGestures(nsecs_t when, nsecs_t readTime, + uint32_t policyFlags) { + const MotionClassification classification = + mPointerGesture.lastGestureMode == PointerGesture::Mode::SWIPE + ? MotionClassification::TWO_FINGER_SWIPE + : MotionClassification::NONE; + std::list out; // Cancel previously dispatches pointers. if (!mPointerGesture.lastGestureIdBits.isEmpty()) { int32_t metaState = getContext()->getGlobalMetaState(); int32_t buttonState = mCurrentRawState.buttonState; - dispatchMotion(when, readTime, policyFlags, mSource, AMOTION_EVENT_ACTION_CANCEL, 0, 0, - metaState, buttonState, AMOTION_EVENT_EDGE_FLAG_NONE, - mPointerGesture.lastGestureProperties, mPointerGesture.lastGestureCoords, - mPointerGesture.lastGestureIdToIndex, mPointerGesture.lastGestureIdBits, -1, - 0, 0, mPointerGesture.downTime); + out.push_back(dispatchMotion(when, readTime, policyFlags, mSource, + AMOTION_EVENT_ACTION_CANCEL, 0, AMOTION_EVENT_FLAG_CANCELED, + metaState, buttonState, AMOTION_EVENT_EDGE_FLAG_NONE, + mPointerGesture.lastGestureProperties, + mPointerGesture.lastGestureCoords, + mPointerGesture.lastGestureIdToIndex, + mPointerGesture.lastGestureIdBits, -1, 0, 0, + mPointerGesture.downTime, classification)); } // Reset the current pointer gesture. @@ -2669,6 +2785,7 @@ void TouchInputMapper::abortPointerGestures(nsecs_t when, nsecs_t readTime, uint mPointerController->fade(PointerControllerInterface::Transition::GRADUAL); mPointerController->clearSpots(); } + return out; } bool TouchInputMapper::preparePointerGestures(nsecs_t when, bool* outCancelPreviousGesture, @@ -2678,9 +2795,7 @@ bool TouchInputMapper::preparePointerGestures(nsecs_t when, bool* outCancelPrevi // Handle TAP timeout. if (isTimeout) { - if (DEBUG_GESTURES) { - ALOGD("Gestures: Processing timeout"); - } + ALOGD_IF(DEBUG_GESTURES, "Gestures: Processing timeout"); if (mPointerGesture.lastGestureMode == PointerGesture::Mode::TAP) { if (when <= mPointerGesture.tapUpTime + mConfig.pointerGestureTapDragInterval) { @@ -2689,9 +2804,7 @@ bool TouchInputMapper::preparePointerGestures(nsecs_t when, bool* outCancelPrevi mConfig.pointerGestureTapDragInterval); } else { // The tap is finished. - if (DEBUG_GESTURES) { - ALOGD("Gestures: TAP finished"); - } + ALOGD_IF(DEBUG_GESTURES, "Gestures: TAP finished"); *outFinishPreviousGesture = true; mPointerGesture.activeGestureId = -1; @@ -2712,17 +2825,15 @@ bool TouchInputMapper::preparePointerGestures(nsecs_t when, bool* outCancelPrevi // Update the velocity tracker. { - std::vector positions; for (BitSet32 idBits(mCurrentCookedState.fingerIdBits); !idBits.isEmpty();) { uint32_t id = idBits.clearFirstMarkedBit(); const RawPointerData::Pointer& pointer = mCurrentRawState.rawPointerData.pointerForId(id); - float x = pointer.x * mPointerXMovementScale; - float y = pointer.y * mPointerYMovementScale; - positions.push_back({x, y}); + const float x = pointer.x * mPointerXMovementScale; + const float y = pointer.y * mPointerYMovementScale; + mPointerGesture.velocityTracker.addMovement(when, id, AMOTION_EVENT_AXIS_X, x); + mPointerGesture.velocityTracker.addMovement(when, id, AMOTION_EVENT_AXIS_Y, y); } - mPointerGesture.velocityTracker.addMovement(when, mCurrentCookedState.fingerIdBits, - positions); } // If the gesture ever enters a mode other than TAP, HOVER or TAP_DRAG, without first returning @@ -2738,60 +2849,24 @@ bool TouchInputMapper::preparePointerGestures(nsecs_t when, bool* outCancelPrevi // Otherwise choose an arbitrary remaining pointer. // This guarantees we always have an active touch id when there is at least one pointer. // We keep the same active touch id for as long as possible. - int32_t lastActiveTouchId = mPointerGesture.activeTouchId; - int32_t activeTouchId = lastActiveTouchId; - if (activeTouchId < 0) { + if (mPointerGesture.activeTouchId < 0) { if (!mCurrentCookedState.fingerIdBits.isEmpty()) { - activeTouchId = mPointerGesture.activeTouchId = - mCurrentCookedState.fingerIdBits.firstMarkedBit(); + mPointerGesture.activeTouchId = mCurrentCookedState.fingerIdBits.firstMarkedBit(); mPointerGesture.firstTouchTime = when; } - } else if (!mCurrentCookedState.fingerIdBits.hasBit(activeTouchId)) { - if (!mCurrentCookedState.fingerIdBits.isEmpty()) { - activeTouchId = mPointerGesture.activeTouchId = - mCurrentCookedState.fingerIdBits.firstMarkedBit(); - } else { - activeTouchId = mPointerGesture.activeTouchId = -1; - } - } - - // Determine whether we are in quiet time. - bool isQuietTime = false; - if (activeTouchId < 0) { - mPointerGesture.resetQuietTime(); - } else { - isQuietTime = when < mPointerGesture.quietTime + mConfig.pointerGestureQuietInterval; - if (!isQuietTime) { - if ((mPointerGesture.lastGestureMode == PointerGesture::Mode::PRESS || - mPointerGesture.lastGestureMode == PointerGesture::Mode::SWIPE || - mPointerGesture.lastGestureMode == PointerGesture::Mode::FREEFORM) && - currentFingerCount < 2) { - // Enter quiet time when exiting swipe or freeform state. - // This is to prevent accidentally entering the hover state and flinging the - // pointer when finishing a swipe and there is still one pointer left onscreen. - isQuietTime = true; - } else if (mPointerGesture.lastGestureMode == - PointerGesture::Mode::BUTTON_CLICK_OR_DRAG && - currentFingerCount >= 2 && !isPointerDown(mCurrentRawState.buttonState)) { - // Enter quiet time when releasing the button and there are still two or more - // fingers down. This may indicate that one finger was used to press the button - // but it has not gone up yet. - isQuietTime = true; - } - if (isQuietTime) { - mPointerGesture.quietTime = when; - } - } + } else if (!mCurrentCookedState.fingerIdBits.hasBit(mPointerGesture.activeTouchId)) { + mPointerGesture.activeTouchId = !mCurrentCookedState.fingerIdBits.isEmpty() + ? mCurrentCookedState.fingerIdBits.firstMarkedBit() + : -1; } + const int32_t& activeTouchId = mPointerGesture.activeTouchId; // Switch states based on button and pointer state. - if (isQuietTime) { + if (checkForTouchpadQuietTime(when)) { // Case 1: Quiet time. (QUIET) - if (DEBUG_GESTURES) { - ALOGD("Gestures: QUIET for next %0.3fms", - (mPointerGesture.quietTime + mConfig.pointerGestureQuietInterval - when) * - 0.000001f); - } + ALOGD_IF(DEBUG_GESTURES, "Gestures: QUIET for next %0.3fms", + (mPointerGesture.quietTime + mConfig.pointerGestureQuietInterval - when) * + 0.000001f); if (mPointerGesture.lastGestureMode != PointerGesture::Mode::QUIET) { *outFinishPreviousGesture = true; } @@ -2815,11 +2890,9 @@ bool TouchInputMapper::preparePointerGestures(nsecs_t when, bool* outCancelPrevi // active. If the user first puts one finger down to click then adds another // finger to drag then the active pointer should switch to the finger that is // being dragged. - if (DEBUG_GESTURES) { - ALOGD("Gestures: BUTTON_CLICK_OR_DRAG activeTouchId=%d, " - "currentFingerCount=%d", - activeTouchId, currentFingerCount); - } + ALOGD_IF(DEBUG_GESTURES, + "Gestures: BUTTON_CLICK_OR_DRAG activeTouchId=%d, currentFingerCount=%d", + activeTouchId, currentFingerCount); // Reset state when just starting. if (mPointerGesture.lastGestureMode != PointerGesture::Mode::BUTTON_CLICK_OR_DRAG) { *outFinishPreviousGesture = true; @@ -2829,51 +2902,25 @@ bool TouchInputMapper::preparePointerGestures(nsecs_t when, bool* outCancelPrevi // Switch pointers if needed. // Find the fastest pointer and follow it. if (activeTouchId >= 0 && currentFingerCount > 1) { - int32_t bestId = -1; - float bestSpeed = mConfig.pointerGestureDragMinSwitchSpeed; - for (BitSet32 idBits(mCurrentCookedState.fingerIdBits); !idBits.isEmpty();) { - uint32_t id = idBits.clearFirstMarkedBit(); - float vx, vy; - if (mPointerGesture.velocityTracker.getVelocity(id, &vx, &vy)) { - float speed = hypotf(vx, vy); - if (speed > bestSpeed) { - bestId = id; - bestSpeed = speed; - } - } - } + const auto [bestId, bestSpeed] = getFastestFinger(); if (bestId >= 0 && bestId != activeTouchId) { - mPointerGesture.activeTouchId = activeTouchId = bestId; - if (DEBUG_GESTURES) { - ALOGD("Gestures: BUTTON_CLICK_OR_DRAG switched pointers, " - "bestId=%d, bestSpeed=%0.3f", - bestId, bestSpeed); - } + mPointerGesture.activeTouchId = bestId; + ALOGD_IF(DEBUG_GESTURES, + "Gestures: BUTTON_CLICK_OR_DRAG switched pointers, bestId=%d, " + "bestSpeed=%0.3f", + bestId, bestSpeed); } } - float deltaX = 0, deltaY = 0; if (activeTouchId >= 0 && mLastCookedState.fingerIdBits.hasBit(activeTouchId)) { - const RawPointerData::Pointer& currentPointer = - mCurrentRawState.rawPointerData.pointerForId(activeTouchId); - const RawPointerData::Pointer& lastPointer = - mLastRawState.rawPointerData.pointerForId(activeTouchId); - deltaX = (currentPointer.x - lastPointer.x) * mPointerXMovementScale; - deltaY = (currentPointer.y - lastPointer.y) * mPointerYMovementScale; - - rotateDelta(mInputDeviceOrientation, &deltaX, &deltaY); - mPointerVelocityControl.move(when, &deltaX, &deltaY); - - // Move the pointer using a relative motion. // When using spots, the click will occur at the position of the anchor // spot and all other spots will move there. - mPointerController->move(deltaX, deltaY); + moveMousePointerFromPointerDelta(when, activeTouchId); } else { mPointerVelocityControl.reset(); } - float x, y; - mPointerController->getPosition(&x, &y); + const auto [x, y] = mPointerController->getPosition(); mPointerGesture.currentGestureMode = PointerGesture::Mode::BUTTON_CLICK_OR_DRAG; mPointerGesture.currentGestureIdBits.clear(); @@ -2881,7 +2928,7 @@ bool TouchInputMapper::preparePointerGestures(nsecs_t when, bool* outCancelPrevi mPointerGesture.currentGestureIdToIndex[mPointerGesture.activeGestureId] = 0; mPointerGesture.currentGestureProperties[0].clear(); mPointerGesture.currentGestureProperties[0].id = mPointerGesture.activeGestureId; - mPointerGesture.currentGestureProperties[0].toolType = AMOTION_EVENT_TOOL_TYPE_FINGER; + 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); @@ -2899,13 +2946,10 @@ bool TouchInputMapper::preparePointerGestures(nsecs_t when, bool* outCancelPrevi mPointerGesture.lastGestureMode == PointerGesture::Mode::TAP_DRAG) && lastFingerCount == 1) { if (when <= mPointerGesture.tapDownTime + mConfig.pointerGestureTapInterval) { - float x, y; - mPointerController->getPosition(&x, &y); + const auto [x, y] = mPointerController->getPosition(); if (fabs(x - mPointerGesture.tapX) <= mConfig.pointerGestureTapSlop && fabs(y - mPointerGesture.tapY) <= mConfig.pointerGestureTapSlop) { - if (DEBUG_GESTURES) { - ALOGD("Gestures: TAP"); - } + ALOGD_IF(DEBUG_GESTURES, "Gestures: TAP"); mPointerGesture.tapUpTime = when; getContext()->requestTimeoutAtTime(when + @@ -2919,8 +2963,7 @@ bool TouchInputMapper::preparePointerGestures(nsecs_t when, bool* outCancelPrevi mPointerGesture.currentGestureProperties[0].clear(); mPointerGesture.currentGestureProperties[0].id = mPointerGesture.activeGestureId; - mPointerGesture.currentGestureProperties[0].toolType = - AMOTION_EVENT_TOOL_TYPE_FINGER; + mPointerGesture.currentGestureProperties[0].toolType = ToolType::FINGER; mPointerGesture.currentGestureCoords[0].clear(); mPointerGesture.currentGestureCoords[0].setAxisValue(AMOTION_EVENT_AXIS_X, mPointerGesture.tapX); @@ -2931,10 +2974,8 @@ bool TouchInputMapper::preparePointerGestures(nsecs_t when, bool* outCancelPrevi tapped = true; } else { - if (DEBUG_GESTURES) { - ALOGD("Gestures: Not a TAP, deltaX=%f, deltaY=%f", x - mPointerGesture.tapX, - y - mPointerGesture.tapY); - } + ALOGD_IF(DEBUG_GESTURES, "Gestures: Not a TAP, deltaX=%f, deltaY=%f", + x - mPointerGesture.tapX, y - mPointerGesture.tapY); } } else { if (DEBUG_GESTURES) { @@ -2951,9 +2992,7 @@ bool TouchInputMapper::preparePointerGestures(nsecs_t when, bool* outCancelPrevi mPointerVelocityControl.reset(); if (!tapped) { - if (DEBUG_GESTURES) { - ALOGD("Gestures: NEUTRAL"); - } + ALOGD_IF(DEBUG_GESTURES, "Gestures: NEUTRAL"); mPointerGesture.activeGestureId = -1; mPointerGesture.currentGestureMode = PointerGesture::Mode::NEUTRAL; mPointerGesture.currentGestureIdBits.clear(); @@ -2968,56 +3007,35 @@ 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) { - float x, y; - mPointerController->getPosition(&x, &y); + const auto [x, y] = mPointerController->getPosition(); if (fabs(x - mPointerGesture.tapX) <= mConfig.pointerGestureTapSlop && fabs(y - mPointerGesture.tapY) <= mConfig.pointerGestureTapSlop) { mPointerGesture.currentGestureMode = PointerGesture::Mode::TAP_DRAG; } else { - if (DEBUG_GESTURES) { - ALOGD("Gestures: Not a TAP_DRAG, deltaX=%f, deltaY=%f", - x - mPointerGesture.tapX, y - mPointerGesture.tapY); - } + ALOGD_IF(DEBUG_GESTURES, "Gestures: Not a TAP_DRAG, deltaX=%f, deltaY=%f", + x - mPointerGesture.tapX, y - mPointerGesture.tapY); } } else { - if (DEBUG_GESTURES) { - ALOGD("Gestures: Not a TAP_DRAG, %0.3fms time since up", - (when - mPointerGesture.tapUpTime) * 0.000001f); - } + ALOGD_IF(DEBUG_GESTURES, "Gestures: Not a TAP_DRAG, %0.3fms time since up", + (when - mPointerGesture.tapUpTime) * 0.000001f); } } else if (mPointerGesture.lastGestureMode == PointerGesture::Mode::TAP_DRAG) { mPointerGesture.currentGestureMode = PointerGesture::Mode::TAP_DRAG; } - float deltaX = 0, deltaY = 0; if (mLastCookedState.fingerIdBits.hasBit(activeTouchId)) { - const RawPointerData::Pointer& currentPointer = - mCurrentRawState.rawPointerData.pointerForId(activeTouchId); - const RawPointerData::Pointer& lastPointer = - mLastRawState.rawPointerData.pointerForId(activeTouchId); - deltaX = (currentPointer.x - lastPointer.x) * mPointerXMovementScale; - deltaY = (currentPointer.y - lastPointer.y) * mPointerYMovementScale; - - rotateDelta(mInputDeviceOrientation, &deltaX, &deltaY); - mPointerVelocityControl.move(when, &deltaX, &deltaY); - - // Move the pointer using a relative motion. // When using spots, the hover or drag will occur at the position of the anchor spot. - mPointerController->move(deltaX, deltaY); + moveMousePointerFromPointerDelta(when, activeTouchId); } else { mPointerVelocityControl.reset(); } bool down; if (mPointerGesture.currentGestureMode == PointerGesture::Mode::TAP_DRAG) { - if (DEBUG_GESTURES) { - ALOGD("Gestures: TAP_DRAG"); - } + ALOGD_IF(DEBUG_GESTURES, "Gestures: TAP_DRAG"); down = true; } else { - if (DEBUG_GESTURES) { - ALOGD("Gestures: HOVER"); - } + ALOGD_IF(DEBUG_GESTURES, "Gestures: HOVER"); if (mPointerGesture.lastGestureMode != PointerGesture::Mode::HOVER) { *outFinishPreviousGesture = true; } @@ -3025,15 +3043,14 @@ bool TouchInputMapper::preparePointerGestures(nsecs_t when, bool* outCancelPrevi down = false; } - float x, y; - mPointerController->getPosition(&x, &y); + const auto [x, y] = mPointerController->getPosition(); mPointerGesture.currentGestureIdBits.clear(); mPointerGesture.currentGestureIdBits.markBit(mPointerGesture.activeGestureId); mPointerGesture.currentGestureIdToIndex[mPointerGesture.activeGestureId] = 0; mPointerGesture.currentGestureProperties[0].clear(); mPointerGesture.currentGestureProperties[0].id = mPointerGesture.activeGestureId; - mPointerGesture.currentGestureProperties[0].toolType = AMOTION_EVENT_TOOL_TYPE_FINGER; + 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); @@ -3048,391 +3065,453 @@ bool TouchInputMapper::preparePointerGestures(nsecs_t when, bool* outCancelPrevi } } else { // Case 5. At least two fingers down, button is not pressed. (PRESS, SWIPE or FREEFORM) - // We need to provide feedback for each finger that goes down so we cannot wait - // for the fingers to move before deciding what to do. - // - // The ambiguous case is deciding what to do when there are two fingers down but they - // have not moved enough to determine whether they are part of a drag or part of a - // freeform gesture, or just a press or long-press at the pointer location. - // - // When there are two fingers we start with the PRESS hypothesis and we generate a - // down at the pointer location. - // - // When the two fingers move enough or when additional fingers are added, we make - // a decision to transition into SWIPE or FREEFORM mode accordingly. - ALOG_ASSERT(activeTouchId >= 0); + prepareMultiFingerPointerGestures(when, outCancelPreviousGesture, outFinishPreviousGesture); + } - bool settled = when >= - mPointerGesture.firstTouchTime + mConfig.pointerGestureMultitouchSettleInterval; - if (mPointerGesture.lastGestureMode != PointerGesture::Mode::PRESS && - mPointerGesture.lastGestureMode != PointerGesture::Mode::SWIPE && - mPointerGesture.lastGestureMode != PointerGesture::Mode::FREEFORM) { - *outFinishPreviousGesture = true; - } else if (!settled && currentFingerCount > lastFingerCount) { - // Additional pointers have gone down but not yet settled. - // Reset the gesture. - if (DEBUG_GESTURES) { - ALOGD("Gestures: Resetting gesture since additional pointers went down for " - "MULTITOUCH, settle time remaining %0.3fms", - (mPointerGesture.firstTouchTime + - mConfig.pointerGestureMultitouchSettleInterval - when) * - 0.000001f); - } - *outCancelPreviousGesture = true; - } else { - // Continue previous gesture. - mPointerGesture.currentGestureMode = mPointerGesture.lastGestureMode; + if (DEBUG_GESTURES) { + ALOGD("Gestures: finishPreviousGesture=%s, cancelPreviousGesture=%s, " + "currentGestureMode=%d, currentGestureIdBits=0x%08x, " + "lastGestureMode=%d, lastGestureIdBits=0x%08x", + toString(*outFinishPreviousGesture), toString(*outCancelPreviousGesture), + mPointerGesture.currentGestureMode, mPointerGesture.currentGestureIdBits.value, + mPointerGesture.lastGestureMode, mPointerGesture.lastGestureIdBits.value); + for (BitSet32 idBits = mPointerGesture.currentGestureIdBits; !idBits.isEmpty();) { + uint32_t id = idBits.clearFirstMarkedBit(); + uint32_t index = mPointerGesture.currentGestureIdToIndex[id]; + const PointerProperties& properties = mPointerGesture.currentGestureProperties[index]; + const PointerCoords& coords = mPointerGesture.currentGestureCoords[index]; + ALOGD(" currentGesture[%d]: index=%d, toolType=%s, " + "x=%0.3f, y=%0.3f, pressure=%0.3f", + id, index, ftl::enum_string(properties.toolType).c_str(), + coords.getAxisValue(AMOTION_EVENT_AXIS_X), + coords.getAxisValue(AMOTION_EVENT_AXIS_Y), + coords.getAxisValue(AMOTION_EVENT_AXIS_PRESSURE)); } + for (BitSet32 idBits = mPointerGesture.lastGestureIdBits; !idBits.isEmpty();) { + uint32_t id = idBits.clearFirstMarkedBit(); + uint32_t index = mPointerGesture.lastGestureIdToIndex[id]; + const PointerProperties& properties = mPointerGesture.lastGestureProperties[index]; + const PointerCoords& coords = mPointerGesture.lastGestureCoords[index]; + ALOGD(" lastGesture[%d]: index=%d, toolType=%s, " + "x=%0.3f, y=%0.3f, pressure=%0.3f", + id, index, ftl::enum_string(properties.toolType).c_str(), + coords.getAxisValue(AMOTION_EVENT_AXIS_X), + coords.getAxisValue(AMOTION_EVENT_AXIS_Y), + coords.getAxisValue(AMOTION_EVENT_AXIS_PRESSURE)); + } + } + return true; +} - if (*outFinishPreviousGesture || *outCancelPreviousGesture) { - mPointerGesture.currentGestureMode = PointerGesture::Mode::PRESS; - mPointerGesture.activeGestureId = 0; - mPointerGesture.referenceIdBits.clear(); - mPointerVelocityControl.reset(); +bool TouchInputMapper::checkForTouchpadQuietTime(nsecs_t when) { + if (mPointerGesture.activeTouchId < 0) { + mPointerGesture.resetQuietTime(); + return false; + } + + if (when < mPointerGesture.quietTime + mConfig.pointerGestureQuietInterval) { + return true; + } + + const uint32_t currentFingerCount = mCurrentCookedState.fingerIdBits.count(); + bool isQuietTime = false; + if ((mPointerGesture.lastGestureMode == PointerGesture::Mode::PRESS || + mPointerGesture.lastGestureMode == PointerGesture::Mode::SWIPE || + mPointerGesture.lastGestureMode == PointerGesture::Mode::FREEFORM) && + currentFingerCount < 2) { + // Enter quiet time when exiting swipe or freeform state. + // This is to prevent accidentally entering the hover state and flinging the + // pointer when finishing a swipe and there is still one pointer left onscreen. + isQuietTime = true; + } else if (mPointerGesture.lastGestureMode == PointerGesture::Mode::BUTTON_CLICK_OR_DRAG && + currentFingerCount >= 2 && !isPointerDown(mCurrentRawState.buttonState)) { + // Enter quiet time when releasing the button and there are still two or more + // fingers down. This may indicate that one finger was used to press the button + // but it has not gone up yet. + isQuietTime = true; + } + if (isQuietTime) { + mPointerGesture.quietTime = when; + } + return isQuietTime; +} - // Use the centroid and pointer location as the reference points for the gesture. - if (DEBUG_GESTURES) { - ALOGD("Gestures: Using centroid as reference for MULTITOUCH, " - "settle time remaining %0.3fms", - (mPointerGesture.firstTouchTime + - mConfig.pointerGestureMultitouchSettleInterval - when) * - 0.000001f); +std::pair TouchInputMapper::getFastestFinger() { + int32_t bestId = -1; + float bestSpeed = mConfig.pointerGestureDragMinSwitchSpeed; + for (BitSet32 idBits(mCurrentCookedState.fingerIdBits); !idBits.isEmpty();) { + uint32_t id = idBits.clearFirstMarkedBit(); + std::optional vx = + mPointerGesture.velocityTracker.getVelocity(AMOTION_EVENT_AXIS_X, id); + std::optional vy = + mPointerGesture.velocityTracker.getVelocity(AMOTION_EVENT_AXIS_Y, id); + if (vx && vy) { + float speed = hypotf(*vx, *vy); + if (speed > bestSpeed) { + bestId = id; + bestSpeed = speed; } - mCurrentRawState.rawPointerData - .getCentroidOfTouchingPointers(&mPointerGesture.referenceTouchX, - &mPointerGesture.referenceTouchY); - mPointerController->getPosition(&mPointerGesture.referenceGestureX, - &mPointerGesture.referenceGestureY); } + } + return std::make_pair(bestId, bestSpeed); +} - // Clear the reference deltas for fingers not yet included in the reference calculation. - for (BitSet32 idBits(mCurrentCookedState.fingerIdBits.value & - ~mPointerGesture.referenceIdBits.value); - !idBits.isEmpty();) { - uint32_t id = idBits.clearFirstMarkedBit(); - mPointerGesture.referenceDeltas[id].dx = 0; - mPointerGesture.referenceDeltas[id].dy = 0; +void TouchInputMapper::prepareMultiFingerPointerGestures(nsecs_t when, bool* cancelPreviousGesture, + bool* finishPreviousGesture) { + // We need to provide feedback for each finger that goes down so we cannot wait for the fingers + // to move before deciding what to do. + // + // The ambiguous case is deciding what to do when there are two fingers down but they have not + // moved enough to determine whether they are part of a drag or part of a freeform gesture, or + // just a press or long-press at the pointer location. + // + // When there are two fingers we start with the PRESS hypothesis and we generate a down at the + // pointer location. + // + // When the two fingers move enough or when additional fingers are added, we make a decision to + // transition into SWIPE or FREEFORM mode accordingly. + const int32_t activeTouchId = mPointerGesture.activeTouchId; + ALOG_ASSERT(activeTouchId >= 0); + + const uint32_t currentFingerCount = mCurrentCookedState.fingerIdBits.count(); + const uint32_t lastFingerCount = mLastCookedState.fingerIdBits.count(); + bool settled = + when >= mPointerGesture.firstTouchTime + mConfig.pointerGestureMultitouchSettleInterval; + if (mPointerGesture.lastGestureMode != PointerGesture::Mode::PRESS && + mPointerGesture.lastGestureMode != PointerGesture::Mode::SWIPE && + mPointerGesture.lastGestureMode != PointerGesture::Mode::FREEFORM) { + *finishPreviousGesture = true; + } else if (!settled && currentFingerCount > lastFingerCount) { + // Additional pointers have gone down but not yet settled. + // Reset the gesture. + ALOGD_IF(DEBUG_GESTURES, + "Gestures: Resetting gesture since additional pointers went down for " + "MULTITOUCH, settle time remaining %0.3fms", + (mPointerGesture.firstTouchTime + mConfig.pointerGestureMultitouchSettleInterval - + when) * 0.000001f); + *cancelPreviousGesture = true; + } else { + // Continue previous gesture. + mPointerGesture.currentGestureMode = mPointerGesture.lastGestureMode; + } + + if (*finishPreviousGesture || *cancelPreviousGesture) { + mPointerGesture.currentGestureMode = PointerGesture::Mode::PRESS; + mPointerGesture.activeGestureId = 0; + mPointerGesture.referenceIdBits.clear(); + mPointerVelocityControl.reset(); + + // Use the centroid and pointer location as the reference points for the gesture. + ALOGD_IF(DEBUG_GESTURES, + "Gestures: Using centroid as reference for MULTITOUCH, settle time remaining " + "%0.3fms", + (mPointerGesture.firstTouchTime + mConfig.pointerGestureMultitouchSettleInterval - + when) * 0.000001f); + mCurrentRawState.rawPointerData + .getCentroidOfTouchingPointers(&mPointerGesture.referenceTouchX, + &mPointerGesture.referenceTouchY); + std::tie(mPointerGesture.referenceGestureX, mPointerGesture.referenceGestureY) = + mPointerController->getPosition(); + } + + // Clear the reference deltas for fingers not yet included in the reference calculation. + for (BitSet32 idBits(mCurrentCookedState.fingerIdBits.value & + ~mPointerGesture.referenceIdBits.value); + !idBits.isEmpty();) { + uint32_t id = idBits.clearFirstMarkedBit(); + mPointerGesture.referenceDeltas[id].dx = 0; + mPointerGesture.referenceDeltas[id].dy = 0; + } + mPointerGesture.referenceIdBits = mCurrentCookedState.fingerIdBits; + + // Add delta for all fingers and calculate a common movement delta. + int32_t commonDeltaRawX = 0, commonDeltaRawY = 0; + BitSet32 commonIdBits(mLastCookedState.fingerIdBits.value & + mCurrentCookedState.fingerIdBits.value); + for (BitSet32 idBits(commonIdBits); !idBits.isEmpty();) { + bool first = (idBits == commonIdBits); + uint32_t id = idBits.clearFirstMarkedBit(); + const RawPointerData::Pointer& cpd = mCurrentRawState.rawPointerData.pointerForId(id); + const RawPointerData::Pointer& lpd = mLastRawState.rawPointerData.pointerForId(id); + PointerGesture::Delta& delta = mPointerGesture.referenceDeltas[id]; + delta.dx += cpd.x - lpd.x; + delta.dy += cpd.y - lpd.y; + + if (first) { + commonDeltaRawX = delta.dx; + commonDeltaRawY = delta.dy; + } else { + commonDeltaRawX = calculateCommonVector(commonDeltaRawX, delta.dx); + commonDeltaRawY = calculateCommonVector(commonDeltaRawY, delta.dy); } - mPointerGesture.referenceIdBits = mCurrentCookedState.fingerIdBits; + } - // Add delta for all fingers and calculate a common movement delta. - float commonDeltaX = 0, commonDeltaY = 0; - BitSet32 commonIdBits(mLastCookedState.fingerIdBits.value & - mCurrentCookedState.fingerIdBits.value); - for (BitSet32 idBits(commonIdBits); !idBits.isEmpty();) { - bool first = (idBits == commonIdBits); + // Consider transitions from PRESS to SWIPE or MULTITOUCH. + if (mPointerGesture.currentGestureMode == PointerGesture::Mode::PRESS) { + float dist[MAX_POINTER_ID + 1]; + int32_t distOverThreshold = 0; + for (BitSet32 idBits(mPointerGesture.referenceIdBits); !idBits.isEmpty();) { uint32_t id = idBits.clearFirstMarkedBit(); - const RawPointerData::Pointer& cpd = mCurrentRawState.rawPointerData.pointerForId(id); - const RawPointerData::Pointer& lpd = mLastRawState.rawPointerData.pointerForId(id); PointerGesture::Delta& delta = mPointerGesture.referenceDeltas[id]; - delta.dx += cpd.x - lpd.x; - delta.dy += cpd.y - lpd.y; - - if (first) { - commonDeltaX = delta.dx; - commonDeltaY = delta.dy; - } else { - commonDeltaX = calculateCommonVector(commonDeltaX, delta.dx); - commonDeltaY = calculateCommonVector(commonDeltaY, delta.dy); + dist[id] = hypotf(delta.dx * mPointerXZoomScale, delta.dy * mPointerYZoomScale); + if (dist[id] > mConfig.pointerGestureMultitouchMinDistance) { + distOverThreshold += 1; } } - // Consider transitions from PRESS to SWIPE or MULTITOUCH. - if (mPointerGesture.currentGestureMode == PointerGesture::Mode::PRESS) { - float dist[MAX_POINTER_ID + 1]; - int32_t distOverThreshold = 0; - for (BitSet32 idBits(mPointerGesture.referenceIdBits); !idBits.isEmpty();) { - uint32_t id = idBits.clearFirstMarkedBit(); - PointerGesture::Delta& delta = mPointerGesture.referenceDeltas[id]; - dist[id] = hypotf(delta.dx * mPointerXZoomScale, delta.dy * mPointerYZoomScale); - if (dist[id] > mConfig.pointerGestureMultitouchMinDistance) { - distOverThreshold += 1; - } - } - - // Only transition when at least two pointers have moved further than - // the minimum distance threshold. - if (distOverThreshold >= 2) { - if (currentFingerCount > 2) { - // There are more than two pointers, switch to FREEFORM. - if (DEBUG_GESTURES) { - ALOGD("Gestures: PRESS transitioned to FREEFORM, number of pointers %d > 2", - currentFingerCount); - } - *outCancelPreviousGesture = true; + // Only transition when at least two pointers have moved further than + // the minimum distance threshold. + if (distOverThreshold >= 2) { + if (currentFingerCount > 2) { + // There are more than two pointers, switch to FREEFORM. + ALOGD_IF(DEBUG_GESTURES, + "Gestures: PRESS transitioned to FREEFORM, number of pointers %d > 2", + currentFingerCount); + *cancelPreviousGesture = true; + mPointerGesture.currentGestureMode = PointerGesture::Mode::FREEFORM; + } else { + // There are exactly two pointers. + BitSet32 idBits(mCurrentCookedState.fingerIdBits); + uint32_t id1 = idBits.clearFirstMarkedBit(); + uint32_t id2 = idBits.firstMarkedBit(); + const RawPointerData::Pointer& p1 = + mCurrentRawState.rawPointerData.pointerForId(id1); + const RawPointerData::Pointer& p2 = + mCurrentRawState.rawPointerData.pointerForId(id2); + float mutualDistance = distance(p1.x, p1.y, p2.x, p2.y); + if (mutualDistance > mPointerGestureMaxSwipeWidth) { + // There are two pointers but they are too far apart for a SWIPE, + // switch to FREEFORM. + ALOGD_IF(DEBUG_GESTURES, + "Gestures: PRESS transitioned to FREEFORM, distance %0.3f > %0.3f", + mutualDistance, mPointerGestureMaxSwipeWidth); + *cancelPreviousGesture = true; mPointerGesture.currentGestureMode = PointerGesture::Mode::FREEFORM; } else { - // There are exactly two pointers. - BitSet32 idBits(mCurrentCookedState.fingerIdBits); - uint32_t id1 = idBits.clearFirstMarkedBit(); - uint32_t id2 = idBits.firstMarkedBit(); - const RawPointerData::Pointer& p1 = - mCurrentRawState.rawPointerData.pointerForId(id1); - const RawPointerData::Pointer& p2 = - mCurrentRawState.rawPointerData.pointerForId(id2); - float mutualDistance = distance(p1.x, p1.y, p2.x, p2.y); - if (mutualDistance > mPointerGestureMaxSwipeWidth) { - // There are two pointers but they are too far apart for a SWIPE, - // switch to FREEFORM. - if (DEBUG_GESTURES) { - ALOGD("Gestures: PRESS transitioned to FREEFORM, distance %0.3f > " - "%0.3f", - mutualDistance, mPointerGestureMaxSwipeWidth); - } - *outCancelPreviousGesture = true; - mPointerGesture.currentGestureMode = PointerGesture::Mode::FREEFORM; - } else { - // There are two pointers. Wait for both pointers to start moving - // before deciding whether this is a SWIPE or FREEFORM gesture. - float dist1 = dist[id1]; - float dist2 = dist[id2]; - if (dist1 >= mConfig.pointerGestureMultitouchMinDistance && - dist2 >= mConfig.pointerGestureMultitouchMinDistance) { - // Calculate the dot product of the displacement vectors. - // When the vectors are oriented in approximately the same direction, - // the angle betweeen them is near zero and the cosine of the angle - // approches 1.0. Recall that dot(v1, v2) = cos(angle) * mag(v1) * - // mag(v2). - PointerGesture::Delta& delta1 = mPointerGesture.referenceDeltas[id1]; - PointerGesture::Delta& delta2 = mPointerGesture.referenceDeltas[id2]; - float dx1 = delta1.dx * mPointerXZoomScale; - float dy1 = delta1.dy * mPointerYZoomScale; - float dx2 = delta2.dx * mPointerXZoomScale; - float dy2 = delta2.dy * mPointerYZoomScale; - float dot = dx1 * dx2 + dy1 * dy2; - float cosine = dot / (dist1 * dist2); // denominator always > 0 - if (cosine >= mConfig.pointerGestureSwipeTransitionAngleCosine) { - // Pointers are moving in the same direction. Switch to SWIPE. - if (DEBUG_GESTURES) { - ALOGD("Gestures: PRESS transitioned to SWIPE, " - "dist1 %0.3f >= %0.3f, dist2 %0.3f >= %0.3f, " - "cosine %0.3f >= %0.3f", - dist1, mConfig.pointerGestureMultitouchMinDistance, dist2, - mConfig.pointerGestureMultitouchMinDistance, cosine, - mConfig.pointerGestureSwipeTransitionAngleCosine); - } - mPointerGesture.currentGestureMode = PointerGesture::Mode::SWIPE; - } else { - // Pointers are moving in different directions. Switch to FREEFORM. - if (DEBUG_GESTURES) { - ALOGD("Gestures: PRESS transitioned to FREEFORM, " - "dist1 %0.3f >= %0.3f, dist2 %0.3f >= %0.3f, " - "cosine %0.3f < %0.3f", - dist1, mConfig.pointerGestureMultitouchMinDistance, dist2, - mConfig.pointerGestureMultitouchMinDistance, cosine, - mConfig.pointerGestureSwipeTransitionAngleCosine); - } - *outCancelPreviousGesture = true; - mPointerGesture.currentGestureMode = PointerGesture::Mode::FREEFORM; - } + // There are two pointers. Wait for both pointers to start moving + // before deciding whether this is a SWIPE or FREEFORM gesture. + float dist1 = dist[id1]; + float dist2 = dist[id2]; + if (dist1 >= mConfig.pointerGestureMultitouchMinDistance && + dist2 >= mConfig.pointerGestureMultitouchMinDistance) { + // Calculate the dot product of the displacement vectors. + // When the vectors are oriented in approximately the same direction, + // the angle betweeen them is near zero and the cosine of the angle + // approaches 1.0. Recall that dot(v1, v2) = cos(angle) * mag(v1) * + // mag(v2). + PointerGesture::Delta& delta1 = mPointerGesture.referenceDeltas[id1]; + PointerGesture::Delta& delta2 = mPointerGesture.referenceDeltas[id2]; + float dx1 = delta1.dx * mPointerXZoomScale; + float dy1 = delta1.dy * mPointerYZoomScale; + float dx2 = delta2.dx * mPointerXZoomScale; + float dy2 = delta2.dy * mPointerYZoomScale; + float dot = dx1 * dx2 + dy1 * dy2; + float cosine = dot / (dist1 * dist2); // denominator always > 0 + if (cosine >= mConfig.pointerGestureSwipeTransitionAngleCosine) { + // Pointers are moving in the same direction. Switch to SWIPE. + ALOGD_IF(DEBUG_GESTURES, + "Gestures: PRESS transitioned to SWIPE, " + "dist1 %0.3f >= %0.3f, dist2 %0.3f >= %0.3f, " + "cosine %0.3f >= %0.3f", + dist1, mConfig.pointerGestureMultitouchMinDistance, dist2, + mConfig.pointerGestureMultitouchMinDistance, cosine, + mConfig.pointerGestureSwipeTransitionAngleCosine); + mPointerGesture.currentGestureMode = PointerGesture::Mode::SWIPE; + } else { + // Pointers are moving in different directions. Switch to FREEFORM. + ALOGD_IF(DEBUG_GESTURES, + "Gestures: PRESS transitioned to FREEFORM, " + "dist1 %0.3f >= %0.3f, dist2 %0.3f >= %0.3f, " + "cosine %0.3f < %0.3f", + dist1, mConfig.pointerGestureMultitouchMinDistance, dist2, + mConfig.pointerGestureMultitouchMinDistance, cosine, + mConfig.pointerGestureSwipeTransitionAngleCosine); + *cancelPreviousGesture = true; + mPointerGesture.currentGestureMode = PointerGesture::Mode::FREEFORM; } } } } - } else if (mPointerGesture.currentGestureMode == PointerGesture::Mode::SWIPE) { - // Switch from SWIPE to FREEFORM if additional pointers go down. - // Cancel previous gesture. - if (currentFingerCount > 2) { - if (DEBUG_GESTURES) { - ALOGD("Gestures: SWIPE transitioned to FREEFORM, number of pointers %d > 2", - currentFingerCount); - } - *outCancelPreviousGesture = true; - mPointerGesture.currentGestureMode = PointerGesture::Mode::FREEFORM; - } } + } else if (mPointerGesture.currentGestureMode == PointerGesture::Mode::SWIPE) { + // Switch from SWIPE to FREEFORM if additional pointers go down. + // Cancel previous gesture. + if (currentFingerCount > 2) { + ALOGD_IF(DEBUG_GESTURES, + "Gestures: SWIPE transitioned to FREEFORM, number of pointers %d > 2", + currentFingerCount); + *cancelPreviousGesture = true; + mPointerGesture.currentGestureMode = PointerGesture::Mode::FREEFORM; + } + } - // Move the reference points based on the overall group motion of the fingers - // except in PRESS mode while waiting for a transition to occur. - if (mPointerGesture.currentGestureMode != PointerGesture::Mode::PRESS && - (commonDeltaX || commonDeltaY)) { - for (BitSet32 idBits(mPointerGesture.referenceIdBits); !idBits.isEmpty();) { - uint32_t id = idBits.clearFirstMarkedBit(); - PointerGesture::Delta& delta = mPointerGesture.referenceDeltas[id]; - delta.dx = 0; - delta.dy = 0; - } + // Move the reference points based on the overall group motion of the fingers + // except in PRESS mode while waiting for a transition to occur. + if (mPointerGesture.currentGestureMode != PointerGesture::Mode::PRESS && + (commonDeltaRawX || commonDeltaRawY)) { + for (BitSet32 idBits(mPointerGesture.referenceIdBits); !idBits.isEmpty();) { + uint32_t id = idBits.clearFirstMarkedBit(); + PointerGesture::Delta& delta = mPointerGesture.referenceDeltas[id]; + delta.dx = 0; + delta.dy = 0; + } - mPointerGesture.referenceTouchX += commonDeltaX; - mPointerGesture.referenceTouchY += commonDeltaY; + mPointerGesture.referenceTouchX += commonDeltaRawX; + mPointerGesture.referenceTouchY += commonDeltaRawY; - commonDeltaX *= mPointerXMovementScale; - commonDeltaY *= mPointerYMovementScale; + float commonDeltaX = commonDeltaRawX * mPointerXMovementScale; + float commonDeltaY = commonDeltaRawY * mPointerYMovementScale; - rotateDelta(mInputDeviceOrientation, &commonDeltaX, &commonDeltaY); - mPointerVelocityControl.move(when, &commonDeltaX, &commonDeltaY); + rotateDelta(mInputDeviceOrientation, &commonDeltaX, &commonDeltaY); + mPointerVelocityControl.move(when, &commonDeltaX, &commonDeltaY); - mPointerGesture.referenceGestureX += commonDeltaX; - mPointerGesture.referenceGestureY += commonDeltaY; - } + mPointerGesture.referenceGestureX += commonDeltaX; + mPointerGesture.referenceGestureY += commonDeltaY; + } - // Report gestures. - if (mPointerGesture.currentGestureMode == PointerGesture::Mode::PRESS || - mPointerGesture.currentGestureMode == PointerGesture::Mode::SWIPE) { - // PRESS or SWIPE mode. - if (DEBUG_GESTURES) { - ALOGD("Gestures: PRESS or SWIPE activeTouchId=%d," - "activeGestureId=%d, currentTouchPointerCount=%d", - activeTouchId, mPointerGesture.activeGestureId, currentFingerCount); - } - ALOG_ASSERT(mPointerGesture.activeGestureId >= 0); + // Report gestures. + if (mPointerGesture.currentGestureMode == PointerGesture::Mode::PRESS || + mPointerGesture.currentGestureMode == PointerGesture::Mode::SWIPE) { + // PRESS or SWIPE mode. + ALOGD_IF(DEBUG_GESTURES, + "Gestures: PRESS or SWIPE activeTouchId=%d, activeGestureId=%d, " + "currentTouchPointerCount=%d", + activeTouchId, mPointerGesture.activeGestureId, currentFingerCount); + ALOG_ASSERT(mPointerGesture.activeGestureId >= 0); - mPointerGesture.currentGestureIdBits.clear(); - mPointerGesture.currentGestureIdBits.markBit(mPointerGesture.activeGestureId); - mPointerGesture.currentGestureIdToIndex[mPointerGesture.activeGestureId] = 0; - mPointerGesture.currentGestureProperties[0].clear(); - mPointerGesture.currentGestureProperties[0].id = mPointerGesture.activeGestureId; - mPointerGesture.currentGestureProperties[0].toolType = AMOTION_EVENT_TOOL_TYPE_FINGER; - mPointerGesture.currentGestureCoords[0].clear(); - mPointerGesture.currentGestureCoords[0].setAxisValue(AMOTION_EVENT_AXIS_X, - mPointerGesture.referenceGestureX); - mPointerGesture.currentGestureCoords[0].setAxisValue(AMOTION_EVENT_AXIS_Y, - mPointerGesture.referenceGestureY); - mPointerGesture.currentGestureCoords[0].setAxisValue(AMOTION_EVENT_AXIS_PRESSURE, 1.0f); - } else if (mPointerGesture.currentGestureMode == PointerGesture::Mode::FREEFORM) { - // FREEFORM mode. - if (DEBUG_GESTURES) { - ALOGD("Gestures: FREEFORM activeTouchId=%d," - "activeGestureId=%d, currentTouchPointerCount=%d", - activeTouchId, mPointerGesture.activeGestureId, currentFingerCount); - } - ALOG_ASSERT(mPointerGesture.activeGestureId >= 0); + mPointerGesture.currentGestureIdBits.clear(); + mPointerGesture.currentGestureIdBits.markBit(mPointerGesture.activeGestureId); + mPointerGesture.currentGestureIdToIndex[mPointerGesture.activeGestureId] = 0; + mPointerGesture.currentGestureProperties[0].clear(); + mPointerGesture.currentGestureProperties[0].id = mPointerGesture.activeGestureId; + mPointerGesture.currentGestureProperties[0].toolType = ToolType::FINGER; + mPointerGesture.currentGestureCoords[0].clear(); + mPointerGesture.currentGestureCoords[0].setAxisValue(AMOTION_EVENT_AXIS_X, + mPointerGesture.referenceGestureX); + mPointerGesture.currentGestureCoords[0].setAxisValue(AMOTION_EVENT_AXIS_Y, + mPointerGesture.referenceGestureY); + mPointerGesture.currentGestureCoords[0].setAxisValue(AMOTION_EVENT_AXIS_PRESSURE, 1.0f); + if (mPointerGesture.currentGestureMode == PointerGesture::Mode::SWIPE) { + float xOffset = static_cast(commonDeltaRawX) / + (mRawPointerAxes.x.maxValue - mRawPointerAxes.x.minValue); + float yOffset = static_cast(commonDeltaRawY) / + (mRawPointerAxes.y.maxValue - mRawPointerAxes.y.minValue); + mPointerGesture.currentGestureCoords[0] + .setAxisValue(AMOTION_EVENT_AXIS_GESTURE_X_OFFSET, xOffset); + mPointerGesture.currentGestureCoords[0] + .setAxisValue(AMOTION_EVENT_AXIS_GESTURE_Y_OFFSET, yOffset); + } + } else if (mPointerGesture.currentGestureMode == PointerGesture::Mode::FREEFORM) { + // FREEFORM mode. + ALOGD_IF(DEBUG_GESTURES, + "Gestures: FREEFORM activeTouchId=%d, activeGestureId=%d, " + "currentTouchPointerCount=%d", + activeTouchId, mPointerGesture.activeGestureId, currentFingerCount); + ALOG_ASSERT(mPointerGesture.activeGestureId >= 0); - mPointerGesture.currentGestureIdBits.clear(); + mPointerGesture.currentGestureIdBits.clear(); - BitSet32 mappedTouchIdBits; - BitSet32 usedGestureIdBits; - if (mPointerGesture.lastGestureMode != PointerGesture::Mode::FREEFORM) { - // Initially, assign the active gesture id to the active touch point - // if there is one. No other touch id bits are mapped yet. - if (!*outCancelPreviousGesture) { - mappedTouchIdBits.markBit(activeTouchId); - usedGestureIdBits.markBit(mPointerGesture.activeGestureId); - mPointerGesture.freeformTouchToGestureIdMap[activeTouchId] = - mPointerGesture.activeGestureId; - } else { - mPointerGesture.activeGestureId = -1; - } + BitSet32 mappedTouchIdBits; + BitSet32 usedGestureIdBits; + if (mPointerGesture.lastGestureMode != PointerGesture::Mode::FREEFORM) { + // Initially, assign the active gesture id to the active touch point + // if there is one. No other touch id bits are mapped yet. + if (!*cancelPreviousGesture) { + mappedTouchIdBits.markBit(activeTouchId); + usedGestureIdBits.markBit(mPointerGesture.activeGestureId); + mPointerGesture.freeformTouchToGestureIdMap[activeTouchId] = + mPointerGesture.activeGestureId; } else { - // Otherwise, assume we mapped all touches from the previous frame. - // Reuse all mappings that are still applicable. - mappedTouchIdBits.value = mLastCookedState.fingerIdBits.value & - mCurrentCookedState.fingerIdBits.value; - usedGestureIdBits = mPointerGesture.lastGestureIdBits; - - // Check whether we need to choose a new active gesture id because the - // current went went up. - for (BitSet32 upTouchIdBits(mLastCookedState.fingerIdBits.value & - ~mCurrentCookedState.fingerIdBits.value); - !upTouchIdBits.isEmpty();) { - uint32_t upTouchId = upTouchIdBits.clearFirstMarkedBit(); - uint32_t upGestureId = mPointerGesture.freeformTouchToGestureIdMap[upTouchId]; - if (upGestureId == uint32_t(mPointerGesture.activeGestureId)) { - mPointerGesture.activeGestureId = -1; - break; - } - } - } - - if (DEBUG_GESTURES) { - ALOGD("Gestures: FREEFORM follow up " - "mappedTouchIdBits=0x%08x, usedGestureIdBits=0x%08x, " - "activeGestureId=%d", - mappedTouchIdBits.value, usedGestureIdBits.value, - mPointerGesture.activeGestureId); + mPointerGesture.activeGestureId = -1; } - - BitSet32 idBits(mCurrentCookedState.fingerIdBits); - for (uint32_t i = 0; i < currentFingerCount; i++) { - uint32_t touchId = idBits.clearFirstMarkedBit(); - uint32_t gestureId; - if (!mappedTouchIdBits.hasBit(touchId)) { - gestureId = usedGestureIdBits.markFirstUnmarkedBit(); - mPointerGesture.freeformTouchToGestureIdMap[touchId] = gestureId; - if (DEBUG_GESTURES) { - ALOGD("Gestures: FREEFORM " - "new mapping for touch id %d -> gesture id %d", - touchId, gestureId); - } - } else { - gestureId = mPointerGesture.freeformTouchToGestureIdMap[touchId]; - if (DEBUG_GESTURES) { - ALOGD("Gestures: FREEFORM " - "existing mapping for touch id %d -> gesture id %d", - touchId, gestureId); - } + } else { + // Otherwise, assume we mapped all touches from the previous frame. + // Reuse all mappings that are still applicable. + mappedTouchIdBits.value = + mLastCookedState.fingerIdBits.value & mCurrentCookedState.fingerIdBits.value; + usedGestureIdBits = mPointerGesture.lastGestureIdBits; + + // Check whether we need to choose a new active gesture id because the + // current went went up. + for (BitSet32 upTouchIdBits(mLastCookedState.fingerIdBits.value & + ~mCurrentCookedState.fingerIdBits.value); + !upTouchIdBits.isEmpty();) { + uint32_t upTouchId = upTouchIdBits.clearFirstMarkedBit(); + uint32_t upGestureId = mPointerGesture.freeformTouchToGestureIdMap[upTouchId]; + if (upGestureId == uint32_t(mPointerGesture.activeGestureId)) { + mPointerGesture.activeGestureId = -1; + break; } - mPointerGesture.currentGestureIdBits.markBit(gestureId); - mPointerGesture.currentGestureIdToIndex[gestureId] = i; - - const RawPointerData::Pointer& pointer = - mCurrentRawState.rawPointerData.pointerForId(touchId); - float deltaX = (pointer.x - mPointerGesture.referenceTouchX) * mPointerXZoomScale; - float deltaY = (pointer.y - mPointerGesture.referenceTouchY) * mPointerYZoomScale; - rotateDelta(mInputDeviceOrientation, &deltaX, &deltaY); - - mPointerGesture.currentGestureProperties[i].clear(); - mPointerGesture.currentGestureProperties[i].id = gestureId; - mPointerGesture.currentGestureProperties[i].toolType = - AMOTION_EVENT_TOOL_TYPE_FINGER; - mPointerGesture.currentGestureCoords[i].clear(); - mPointerGesture.currentGestureCoords[i] - .setAxisValue(AMOTION_EVENT_AXIS_X, - mPointerGesture.referenceGestureX + deltaX); - mPointerGesture.currentGestureCoords[i] - .setAxisValue(AMOTION_EVENT_AXIS_Y, - mPointerGesture.referenceGestureY + deltaY); - mPointerGesture.currentGestureCoords[i].setAxisValue(AMOTION_EVENT_AXIS_PRESSURE, - 1.0f); } + } - if (mPointerGesture.activeGestureId < 0) { - mPointerGesture.activeGestureId = - mPointerGesture.currentGestureIdBits.firstMarkedBit(); - if (DEBUG_GESTURES) { - ALOGD("Gestures: FREEFORM new activeGestureId=%d", - mPointerGesture.activeGestureId); - } + ALOGD_IF(DEBUG_GESTURES, + "Gestures: FREEFORM follow up mappedTouchIdBits=0x%08x, usedGestureIdBits=0x%08x, " + "activeGestureId=%d", + mappedTouchIdBits.value, usedGestureIdBits.value, mPointerGesture.activeGestureId); + + BitSet32 idBits(mCurrentCookedState.fingerIdBits); + for (uint32_t i = 0; i < currentFingerCount; i++) { + uint32_t touchId = idBits.clearFirstMarkedBit(); + uint32_t gestureId; + if (!mappedTouchIdBits.hasBit(touchId)) { + gestureId = usedGestureIdBits.markFirstUnmarkedBit(); + mPointerGesture.freeformTouchToGestureIdMap[touchId] = gestureId; + ALOGD_IF(DEBUG_GESTURES, + "Gestures: FREEFORM new mapping for touch id %d -> gesture id %d", touchId, + gestureId); + } else { + gestureId = mPointerGesture.freeformTouchToGestureIdMap[touchId]; + ALOGD_IF(DEBUG_GESTURES, + "Gestures: FREEFORM existing mapping for touch id %d -> gesture id %d", + touchId, gestureId); } - } - } + mPointerGesture.currentGestureIdBits.markBit(gestureId); + mPointerGesture.currentGestureIdToIndex[gestureId] = i; - mPointerController->setButtonState(mCurrentRawState.buttonState); + const RawPointerData::Pointer& pointer = + mCurrentRawState.rawPointerData.pointerForId(touchId); + float deltaX = (pointer.x - mPointerGesture.referenceTouchX) * mPointerXZoomScale; + float deltaY = (pointer.y - mPointerGesture.referenceTouchY) * mPointerYZoomScale; + rotateDelta(mInputDeviceOrientation, &deltaX, &deltaY); - if (DEBUG_GESTURES) { - ALOGD("Gestures: finishPreviousGesture=%s, cancelPreviousGesture=%s, " - "currentGestureMode=%d, currentGestureIdBits=0x%08x, " - "lastGestureMode=%d, lastGestureIdBits=0x%08x", - toString(*outFinishPreviousGesture), toString(*outCancelPreviousGesture), - mPointerGesture.currentGestureMode, mPointerGesture.currentGestureIdBits.value, - mPointerGesture.lastGestureMode, mPointerGesture.lastGestureIdBits.value); - for (BitSet32 idBits = mPointerGesture.currentGestureIdBits; !idBits.isEmpty();) { - uint32_t id = idBits.clearFirstMarkedBit(); - uint32_t index = mPointerGesture.currentGestureIdToIndex[id]; - const PointerProperties& properties = mPointerGesture.currentGestureProperties[index]; - const PointerCoords& coords = mPointerGesture.currentGestureCoords[index]; - ALOGD(" currentGesture[%d]: index=%d, toolType=%d, " - "x=%0.3f, y=%0.3f, pressure=%0.3f", - id, index, properties.toolType, coords.getAxisValue(AMOTION_EVENT_AXIS_X), - coords.getAxisValue(AMOTION_EVENT_AXIS_Y), - coords.getAxisValue(AMOTION_EVENT_AXIS_PRESSURE)); + mPointerGesture.currentGestureProperties[i].clear(); + mPointerGesture.currentGestureProperties[i].id = gestureId; + mPointerGesture.currentGestureProperties[i].toolType = ToolType::FINGER; + mPointerGesture.currentGestureCoords[i].clear(); + mPointerGesture.currentGestureCoords[i].setAxisValue(AMOTION_EVENT_AXIS_X, + mPointerGesture.referenceGestureX + + deltaX); + mPointerGesture.currentGestureCoords[i].setAxisValue(AMOTION_EVENT_AXIS_Y, + mPointerGesture.referenceGestureY + + deltaY); + mPointerGesture.currentGestureCoords[i].setAxisValue(AMOTION_EVENT_AXIS_PRESSURE, 1.0f); } - for (BitSet32 idBits = mPointerGesture.lastGestureIdBits; !idBits.isEmpty();) { - uint32_t id = idBits.clearFirstMarkedBit(); - uint32_t index = mPointerGesture.lastGestureIdToIndex[id]; - const PointerProperties& properties = mPointerGesture.lastGestureProperties[index]; - const PointerCoords& coords = mPointerGesture.lastGestureCoords[index]; - ALOGD(" lastGesture[%d]: index=%d, toolType=%d, " - "x=%0.3f, y=%0.3f, pressure=%0.3f", - id, index, properties.toolType, coords.getAxisValue(AMOTION_EVENT_AXIS_X), - coords.getAxisValue(AMOTION_EVENT_AXIS_Y), - coords.getAxisValue(AMOTION_EVENT_AXIS_PRESSURE)); + + if (mPointerGesture.activeGestureId < 0) { + mPointerGesture.activeGestureId = mPointerGesture.currentGestureIdBits.firstMarkedBit(); + ALOGD_IF(DEBUG_GESTURES, "Gestures: FREEFORM new activeGestureId=%d", + mPointerGesture.activeGestureId); } } - return true; } -void TouchInputMapper::dispatchPointerStylus(nsecs_t when, nsecs_t readTime, uint32_t policyFlags) { +void TouchInputMapper::moveMousePointerFromPointerDelta(nsecs_t when, uint32_t pointerId) { + const RawPointerData::Pointer& currentPointer = + mCurrentRawState.rawPointerData.pointerForId(pointerId); + const RawPointerData::Pointer& lastPointer = + mLastRawState.rawPointerData.pointerForId(pointerId); + float deltaX = (currentPointer.x - lastPointer.x) * mPointerXMovementScale; + float deltaY = (currentPointer.y - lastPointer.y) * mPointerYMovementScale; + + rotateDelta(mInputDeviceOrientation, &deltaX, &deltaY); + mPointerVelocityControl.move(when, &deltaX, &deltaY); + + mPointerController->move(deltaX, deltaY); +} + +std::list TouchInputMapper::dispatchPointerStylus(nsecs_t when, nsecs_t readTime, + uint32_t policyFlags) { mPointerSimple.currentCoords.clear(); mPointerSimple.currentProperties.clear(); @@ -3440,15 +3519,19 @@ void TouchInputMapper::dispatchPointerStylus(nsecs_t when, nsecs_t readTime, uin if (!mCurrentCookedState.stylusIdBits.isEmpty()) { uint32_t id = mCurrentCookedState.stylusIdBits.firstMarkedBit(); uint32_t index = mCurrentCookedState.cookedPointerData.idToIndex[id]; - mPointerController - ->setPosition(mCurrentCookedState.cookedPointerData.pointerCoords[index].getX(), - mCurrentCookedState.cookedPointerData.pointerCoords[index].getY()); - hovering = mCurrentCookedState.cookedPointerData.hoveringIdBits.hasBit(id); down = !hovering; - float x, y; - mPointerController->getPosition(&x, &y); + 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.copyFrom( mCurrentCookedState.cookedPointerData.pointerCoords[index]); mPointerSimple.currentCoords.setAxisValue(AMOTION_EVENT_AXIS_X, x); @@ -3461,35 +3544,24 @@ void TouchInputMapper::dispatchPointerStylus(nsecs_t when, nsecs_t readTime, uin hovering = false; } - dispatchPointerSimple(when, readTime, policyFlags, down, hovering); + return dispatchPointerSimple(when, readTime, policyFlags, down, hovering, mViewport.displayId); } -void TouchInputMapper::abortPointerStylus(nsecs_t when, nsecs_t readTime, uint32_t policyFlags) { - abortPointerSimple(when, readTime, policyFlags); +std::list TouchInputMapper::abortPointerStylus(nsecs_t when, nsecs_t readTime, + uint32_t policyFlags) { + return abortPointerSimple(when, readTime, policyFlags); } -void TouchInputMapper::dispatchPointerMouse(nsecs_t when, nsecs_t readTime, uint32_t policyFlags) { +std::list TouchInputMapper::dispatchPointerMouse(nsecs_t when, nsecs_t readTime, + uint32_t policyFlags) { mPointerSimple.currentCoords.clear(); mPointerSimple.currentProperties.clear(); bool down, hovering; if (!mCurrentCookedState.mouseIdBits.isEmpty()) { uint32_t id = mCurrentCookedState.mouseIdBits.firstMarkedBit(); - uint32_t currentIndex = mCurrentRawState.rawPointerData.idToIndex[id]; - float deltaX = 0, deltaY = 0; if (mLastCookedState.mouseIdBits.hasBit(id)) { - uint32_t lastIndex = mCurrentRawState.rawPointerData.idToIndex[id]; - deltaX = (mCurrentRawState.rawPointerData.pointers[currentIndex].x - - mLastRawState.rawPointerData.pointers[lastIndex].x) * - mPointerXMovementScale; - deltaY = (mCurrentRawState.rawPointerData.pointers[currentIndex].y - - mLastRawState.rawPointerData.pointers[lastIndex].y) * - mPointerYMovementScale; - - rotateDelta(mInputDeviceOrientation, &deltaX, &deltaY); - mPointerVelocityControl.move(when, &deltaX, &deltaY); - - mPointerController->move(deltaX, deltaY); + moveMousePointerFromPointerDelta(when, id); } else { mPointerVelocityControl.reset(); } @@ -3497,8 +3569,8 @@ void TouchInputMapper::dispatchPointerMouse(nsecs_t when, nsecs_t readTime, uint down = isPointerDown(mCurrentRawState.buttonState); hovering = !down; - float x, y; - mPointerController->getPosition(&x, &y); + const auto [x, y] = mPointerController->getPosition(); + const uint32_t currentIndex = mCurrentRawState.rawPointerData.idToIndex[id]; mPointerSimple.currentCoords.copyFrom( mCurrentCookedState.cookedPointerData.pointerCoords[currentIndex]); mPointerSimple.currentCoords.setAxisValue(AMOTION_EVENT_AXIS_X, x); @@ -3515,60 +3587,67 @@ void TouchInputMapper::dispatchPointerMouse(nsecs_t when, nsecs_t readTime, uint hovering = false; } - dispatchPointerSimple(when, readTime, policyFlags, down, hovering); + const int32_t displayId = mPointerController->getDisplayId(); + return dispatchPointerSimple(when, readTime, policyFlags, down, hovering, displayId); } -void TouchInputMapper::abortPointerMouse(nsecs_t when, nsecs_t readTime, uint32_t policyFlags) { - abortPointerSimple(when, readTime, policyFlags); +std::list TouchInputMapper::abortPointerMouse(nsecs_t when, nsecs_t readTime, + uint32_t policyFlags) { + std::list out = abortPointerSimple(when, readTime, policyFlags); mPointerVelocityControl.reset(); + + return out; } -void TouchInputMapper::dispatchPointerSimple(nsecs_t when, nsecs_t readTime, uint32_t policyFlags, - bool down, bool hovering) { +std::list TouchInputMapper::dispatchPointerSimple(nsecs_t when, nsecs_t readTime, + uint32_t policyFlags, bool down, + bool hovering, int32_t displayId) { LOG_ALWAYS_FATAL_IF(mDeviceMode != DeviceMode::POINTER, "%s cannot be used when the device is not in POINTER mode.", __func__); + std::list out; int32_t metaState = getContext()->getGlobalMetaState(); + auto cursorPosition = mPointerSimple.currentCoords.getXYValue(); - if (down || hovering) { - mPointerController->setPresentation(PointerControllerInterface::Presentation::POINTER); - mPointerController->clearSpots(); - mPointerController->setButtonState(mCurrentRawState.buttonState); - mPointerController->unfade(PointerControllerInterface::Transition::IMMEDIATE); - } else if (!down && !hovering && (mPointerSimple.down || mPointerSimple.hovering)) { - mPointerController->fade(PointerControllerInterface::Transition::GRADUAL); + 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); + } } - int32_t displayId = mPointerController->getDisplayId(); - - float xCursorPosition, yCursorPosition; - mPointerController->getPosition(&xCursorPosition, &yCursorPosition); if (mPointerSimple.down && !down) { mPointerSimple.down = false; // Send up. - NotifyMotionArgs args(getContext()->getNextId(), when, readTime, getDeviceId(), mSource, - displayId, policyFlags, AMOTION_EVENT_ACTION_UP, 0, 0, metaState, - mLastRawState.buttonState, MotionClassification::NONE, - AMOTION_EVENT_EDGE_FLAG_NONE, 1, &mPointerSimple.lastProperties, - &mPointerSimple.lastCoords, mOrientedXPrecision, mOrientedYPrecision, - xCursorPosition, yCursorPosition, mPointerSimple.downTime, - /* videoFrames */ {}); - getListener().notifyMotion(&args); + out.push_back(NotifyMotionArgs(getContext()->getNextId(), when, readTime, getDeviceId(), + mSource, displayId, policyFlags, AMOTION_EVENT_ACTION_UP, 0, + 0, metaState, mLastRawState.buttonState, + MotionClassification::NONE, AMOTION_EVENT_EDGE_FLAG_NONE, 1, + &mPointerSimple.lastProperties, &mPointerSimple.lastCoords, + mOrientedXPrecision, mOrientedYPrecision, + mPointerSimple.lastCursorX, mPointerSimple.lastCursorY, + mPointerSimple.downTime, + /* videoFrames */ {})); } if (mPointerSimple.hovering && !hovering) { mPointerSimple.hovering = false; // Send hover exit. - NotifyMotionArgs args(getContext()->getNextId(), when, readTime, getDeviceId(), mSource, - displayId, policyFlags, AMOTION_EVENT_ACTION_HOVER_EXIT, 0, 0, - metaState, mLastRawState.buttonState, MotionClassification::NONE, - AMOTION_EVENT_EDGE_FLAG_NONE, 1, &mPointerSimple.lastProperties, - &mPointerSimple.lastCoords, mOrientedXPrecision, mOrientedYPrecision, - xCursorPosition, yCursorPosition, mPointerSimple.downTime, - /* videoFrames */ {}); - getListener().notifyMotion(&args); + out.push_back( + NotifyMotionArgs(getContext()->getNextId(), when, readTime, getDeviceId(), mSource, + displayId, policyFlags, AMOTION_EVENT_ACTION_HOVER_EXIT, 0, 0, + metaState, mLastRawState.buttonState, MotionClassification::NONE, + AMOTION_EVENT_EDGE_FLAG_NONE, 1, &mPointerSimple.lastProperties, + &mPointerSimple.lastCoords, mOrientedXPrecision, + mOrientedYPrecision, mPointerSimple.lastCursorX, + mPointerSimple.lastCursorY, mPointerSimple.downTime, + /* videoFrames */ {})); } if (down) { @@ -3577,25 +3656,26 @@ void TouchInputMapper::dispatchPointerSimple(nsecs_t when, nsecs_t readTime, uin mPointerSimple.downTime = when; // Send down. - NotifyMotionArgs args(getContext()->getNextId(), when, readTime, getDeviceId(), mSource, - displayId, policyFlags, AMOTION_EVENT_ACTION_DOWN, 0, 0, - metaState, mCurrentRawState.buttonState, - MotionClassification::NONE, AMOTION_EVENT_EDGE_FLAG_NONE, 1, - &mPointerSimple.currentProperties, &mPointerSimple.currentCoords, - mOrientedXPrecision, mOrientedYPrecision, xCursorPosition, - yCursorPosition, mPointerSimple.downTime, /* videoFrames */ {}); - getListener().notifyMotion(&args); + out.push_back(NotifyMotionArgs(getContext()->getNextId(), when, readTime, getDeviceId(), + mSource, displayId, policyFlags, + AMOTION_EVENT_ACTION_DOWN, 0, 0, metaState, + mCurrentRawState.buttonState, MotionClassification::NONE, + AMOTION_EVENT_EDGE_FLAG_NONE, 1, + &mPointerSimple.currentProperties, + &mPointerSimple.currentCoords, mOrientedXPrecision, + mOrientedYPrecision, cursorPosition.x, cursorPosition.y, + mPointerSimple.downTime, /* videoFrames */ {})); } // Send move. - NotifyMotionArgs args(getContext()->getNextId(), when, readTime, getDeviceId(), mSource, - displayId, policyFlags, AMOTION_EVENT_ACTION_MOVE, 0, 0, metaState, - mCurrentRawState.buttonState, MotionClassification::NONE, - AMOTION_EVENT_EDGE_FLAG_NONE, 1, &mPointerSimple.currentProperties, - &mPointerSimple.currentCoords, mOrientedXPrecision, - mOrientedYPrecision, xCursorPosition, yCursorPosition, - mPointerSimple.downTime, /* videoFrames */ {}); - getListener().notifyMotion(&args); + out.push_back(NotifyMotionArgs(getContext()->getNextId(), when, readTime, getDeviceId(), + mSource, displayId, policyFlags, AMOTION_EVENT_ACTION_MOVE, + 0, 0, metaState, mCurrentRawState.buttonState, + MotionClassification::NONE, AMOTION_EVENT_EDGE_FLAG_NONE, 1, + &mPointerSimple.currentProperties, + &mPointerSimple.currentCoords, mOrientedXPrecision, + mOrientedYPrecision, cursorPosition.x, cursorPosition.y, + mPointerSimple.downTime, /* videoFrames */ {})); } if (hovering) { @@ -3603,25 +3683,26 @@ void TouchInputMapper::dispatchPointerSimple(nsecs_t when, nsecs_t readTime, uin mPointerSimple.hovering = true; // Send hover enter. - NotifyMotionArgs args(getContext()->getNextId(), when, readTime, getDeviceId(), mSource, - displayId, policyFlags, AMOTION_EVENT_ACTION_HOVER_ENTER, 0, 0, - metaState, mCurrentRawState.buttonState, - MotionClassification::NONE, AMOTION_EVENT_EDGE_FLAG_NONE, 1, - &mPointerSimple.currentProperties, &mPointerSimple.currentCoords, - mOrientedXPrecision, mOrientedYPrecision, xCursorPosition, - yCursorPosition, mPointerSimple.downTime, /* videoFrames */ {}); - getListener().notifyMotion(&args); + out.push_back(NotifyMotionArgs(getContext()->getNextId(), when, readTime, getDeviceId(), + mSource, displayId, policyFlags, + AMOTION_EVENT_ACTION_HOVER_ENTER, 0, 0, metaState, + mCurrentRawState.buttonState, MotionClassification::NONE, + AMOTION_EVENT_EDGE_FLAG_NONE, 1, + &mPointerSimple.currentProperties, + &mPointerSimple.currentCoords, mOrientedXPrecision, + mOrientedYPrecision, cursorPosition.x, cursorPosition.y, + mPointerSimple.downTime, /* videoFrames */ {})); } // Send hover move. - NotifyMotionArgs args(getContext()->getNextId(), when, readTime, getDeviceId(), mSource, - displayId, policyFlags, AMOTION_EVENT_ACTION_HOVER_MOVE, 0, 0, - metaState, mCurrentRawState.buttonState, MotionClassification::NONE, - AMOTION_EVENT_EDGE_FLAG_NONE, 1, &mPointerSimple.currentProperties, - &mPointerSimple.currentCoords, mOrientedXPrecision, - mOrientedYPrecision, xCursorPosition, yCursorPosition, - mPointerSimple.downTime, /* videoFrames */ {}); - getListener().notifyMotion(&args); + out.push_back( + NotifyMotionArgs(getContext()->getNextId(), when, readTime, getDeviceId(), mSource, + displayId, policyFlags, AMOTION_EVENT_ACTION_HOVER_MOVE, 0, 0, + metaState, mCurrentRawState.buttonState, + MotionClassification::NONE, AMOTION_EVENT_EDGE_FLAG_NONE, 1, + &mPointerSimple.currentProperties, &mPointerSimple.currentCoords, + mOrientedXPrecision, mOrientedYPrecision, cursorPosition.x, + cursorPosition.y, mPointerSimple.downTime, /* videoFrames */ {})); } if (mCurrentRawState.rawVScroll || mCurrentRawState.rawHScroll) { @@ -3636,14 +3717,14 @@ void TouchInputMapper::dispatchPointerSimple(nsecs_t when, nsecs_t readTime, uin pointerCoords.setAxisValue(AMOTION_EVENT_AXIS_VSCROLL, vscroll); pointerCoords.setAxisValue(AMOTION_EVENT_AXIS_HSCROLL, hscroll); - NotifyMotionArgs args(getContext()->getNextId(), when, readTime, getDeviceId(), mSource, - displayId, policyFlags, AMOTION_EVENT_ACTION_SCROLL, 0, 0, metaState, - mCurrentRawState.buttonState, MotionClassification::NONE, - AMOTION_EVENT_EDGE_FLAG_NONE, 1, &mPointerSimple.currentProperties, - &pointerCoords, mOrientedXPrecision, mOrientedYPrecision, - xCursorPosition, yCursorPosition, mPointerSimple.downTime, - /* videoFrames */ {}); - getListener().notifyMotion(&args); + out.push_back(NotifyMotionArgs(getContext()->getNextId(), when, readTime, getDeviceId(), + mSource, displayId, policyFlags, AMOTION_EVENT_ACTION_SCROLL, + 0, 0, metaState, mCurrentRawState.buttonState, + MotionClassification::NONE, AMOTION_EVENT_EDGE_FLAG_NONE, 1, + &mPointerSimple.currentProperties, &pointerCoords, + mOrientedXPrecision, mOrientedYPrecision, cursorPosition.x, + cursorPosition.y, mPointerSimple.downTime, + /* videoFrames */ {})); } // Save state. @@ -3652,43 +3733,51 @@ void TouchInputMapper::dispatchPointerSimple(nsecs_t when, nsecs_t readTime, uin mPointerSimple.lastProperties.copyFrom(mPointerSimple.currentProperties); mPointerSimple.displayId = displayId; mPointerSimple.source = mSource; - mPointerSimple.lastCursorX = xCursorPosition; - mPointerSimple.lastCursorY = yCursorPosition; + mPointerSimple.lastCursorX = cursorPosition.x; + mPointerSimple.lastCursorY = cursorPosition.y; } else { mPointerSimple.reset(); } + return out; } -void TouchInputMapper::abortPointerSimple(nsecs_t when, nsecs_t readTime, uint32_t policyFlags) { - mPointerSimple.currentCoords.clear(); - mPointerSimple.currentProperties.clear(); - +std::list TouchInputMapper::abortPointerSimple(nsecs_t when, nsecs_t readTime, + uint32_t policyFlags) { + std::list out; if (mPointerSimple.down || mPointerSimple.hovering) { int32_t metaState = getContext()->getGlobalMetaState(); - NotifyMotionArgs args(getContext()->getNextId(), when, readTime, getDeviceId(), - mPointerSimple.source, mPointerSimple.displayId, policyFlags, - AMOTION_EVENT_ACTION_CANCEL, 0, AMOTION_EVENT_FLAG_CANCELED, - metaState, mLastRawState.buttonState, MotionClassification::NONE, - AMOTION_EVENT_EDGE_FLAG_NONE, 1, &mPointerSimple.lastProperties, - &mPointerSimple.lastCoords, mOrientedXPrecision, mOrientedYPrecision, - mPointerSimple.lastCursorX, mPointerSimple.lastCursorY, - mPointerSimple.downTime, - /* videoFrames */ {}); - getListener().notifyMotion(&args); + out.push_back(NotifyMotionArgs(getContext()->getNextId(), when, readTime, getDeviceId(), + mPointerSimple.source, mPointerSimple.displayId, policyFlags, + AMOTION_EVENT_ACTION_CANCEL, 0, AMOTION_EVENT_FLAG_CANCELED, + metaState, mLastRawState.buttonState, + MotionClassification::NONE, AMOTION_EVENT_EDGE_FLAG_NONE, 1, + &mPointerSimple.lastProperties, &mPointerSimple.lastCoords, + mOrientedXPrecision, mOrientedYPrecision, + mPointerSimple.lastCursorX, mPointerSimple.lastCursorY, + mPointerSimple.downTime, + /* videoFrames */ {})); if (mPointerController != nullptr) { mPointerController->fade(PointerControllerInterface::Transition::GRADUAL); } } mPointerSimple.reset(); + return out; } -void TouchInputMapper::dispatchMotion(nsecs_t when, nsecs_t readTime, uint32_t policyFlags, - uint32_t source, int32_t action, int32_t actionButton, - int32_t flags, int32_t metaState, int32_t buttonState, - int32_t edgeFlags, const PointerProperties* properties, - const PointerCoords* coords, const uint32_t* idToIndex, - BitSet32 idBits, int32_t changedId, float xPrecision, - float yPrecision, nsecs_t downTime) { +static bool isStylusEvent(uint32_t source, int32_t action, const PointerProperties* properties) { + if (!isFromSource(source, AINPUT_SOURCE_STYLUS)) { + return false; + } + const auto actionIndex = action >> AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT; + return isStylusToolType(properties[actionIndex].toolType); +} + +NotifyMotionArgs TouchInputMapper::dispatchMotion( + nsecs_t when, nsecs_t readTime, uint32_t policyFlags, uint32_t source, int32_t action, + int32_t actionButton, int32_t flags, int32_t metaState, int32_t buttonState, + int32_t edgeFlags, const PropertiesArray& properties, const CoordsArray& coords, + const IdToIndexArray& idToIndex, BitSet32 idBits, int32_t changedId, float xPrecision, + float yPrecision, nsecs_t downTime, MotionClassification classification) { PointerCoords pointerCoords[MAX_POINTERS]; PointerProperties pointerProperties[MAX_POINTERS]; uint32_t pointerCount = 0; @@ -3724,112 +3813,66 @@ void TouchInputMapper::dispatchMotion(nsecs_t when, nsecs_t readTime, uint32_t p ALOG_ASSERT(false); } } + + const int32_t displayId = getAssociatedDisplayId().value_or(ADISPLAY_ID_NONE); + const bool showDirectStylusPointer = mConfig.stylusPointerIconEnabled && + mDeviceMode == DeviceMode::DIRECT && isStylusEvent(source, action, 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) { - mPointerController->getPosition(&xCursorPosition, &yCursorPosition); + std::tie(xCursorPosition, yCursorPosition) = mPointerController->getPosition(); } - const int32_t displayId = getAssociatedDisplayId().value_or(ADISPLAY_ID_NONE); const int32_t deviceId = getDeviceId(); std::vector frames = getDeviceContext().getVideoFrames(); std::for_each(frames.begin(), frames.end(), [this](TouchVideoFrame& frame) { frame.rotate(this->mInputDeviceOrientation); }); - NotifyMotionArgs args(getContext()->getNextId(), when, readTime, deviceId, source, displayId, - policyFlags, action, actionButton, flags, metaState, buttonState, - MotionClassification::NONE, edgeFlags, pointerCount, pointerProperties, - pointerCoords, xPrecision, yPrecision, xCursorPosition, yCursorPosition, - downTime, std::move(frames)); - getListener().notifyMotion(&args); -} - -bool TouchInputMapper::updateMovedPointers(const PointerProperties* inProperties, - const PointerCoords* inCoords, - const uint32_t* inIdToIndex, - PointerProperties* outProperties, - PointerCoords* outCoords, const uint32_t* outIdToIndex, - BitSet32 idBits) const { - bool changed = false; - while (!idBits.isEmpty()) { - uint32_t id = idBits.clearFirstMarkedBit(); - uint32_t inIndex = inIdToIndex[id]; - uint32_t outIndex = outIdToIndex[id]; - - const PointerProperties& curInProperties = inProperties[inIndex]; - const PointerCoords& curInCoords = inCoords[inIndex]; - PointerProperties& curOutProperties = outProperties[outIndex]; - PointerCoords& curOutCoords = outCoords[outIndex]; - - if (curInProperties != curOutProperties) { - curOutProperties.copyFrom(curInProperties); - changed = true; - } - - if (curInCoords != curOutCoords) { - curOutCoords.copyFrom(curInCoords); - changed = true; - } - } - return changed; + return NotifyMotionArgs(getContext()->getNextId(), when, readTime, deviceId, source, displayId, + policyFlags, action, actionButton, flags, metaState, buttonState, + classification, edgeFlags, pointerCount, pointerProperties, + pointerCoords, xPrecision, yPrecision, xCursorPosition, yCursorPosition, + downTime, std::move(frames)); } -void TouchInputMapper::cancelTouch(nsecs_t when, nsecs_t readTime) { - abortPointerUsage(when, readTime, 0 /*policyFlags*/); - abortTouches(when, readTime, 0 /* policyFlags*/); -} - -// Transform input device coordinates to display panel coordinates. -void TouchInputMapper::rotateAndScale(float& x, float& y) const { - const float xScaled = float(x - mRawPointerAxes.x.minValue) * mXScale; - const float yScaled = float(y - mRawPointerAxes.y.minValue) * mYScale; - - const float xScaledMax = float(mRawPointerAxes.x.maxValue - x) * mXScale; - const float yScaledMax = float(mRawPointerAxes.y.maxValue - y) * mYScale; - - // Rotate to display coordinate. - // 0 - no swap and reverse. - // 90 - swap x/y and reverse y. - // 180 - reverse x, y. - // 270 - swap x/y and reverse x. - switch (mInputDeviceOrientation) { - case DISPLAY_ORIENTATION_0: - x = xScaled; - y = yScaled; - break; - case DISPLAY_ORIENTATION_90: - y = xScaledMax; - x = yScaled; - break; - case DISPLAY_ORIENTATION_180: - x = xScaledMax; - y = yScaledMax; - break; - case DISPLAY_ORIENTATION_270: - y = xScaled; - x = yScaledMax; - break; - default: - assert(false); - } +std::list TouchInputMapper::cancelTouch(nsecs_t when, nsecs_t readTime) { + std::list out; + out += abortPointerUsage(when, readTime, /*policyFlags=*/0); + out += abortTouches(when, readTime, /* policyFlags=*/0); + return out; } bool TouchInputMapper::isPointInsidePhysicalFrame(int32_t x, int32_t y) const { - const float xScaled = (x - mRawPointerAxes.x.minValue) * mXScale; - const float yScaled = (y - mRawPointerAxes.y.minValue) * mYScale; - return x >= mRawPointerAxes.x.minValue && x <= mRawPointerAxes.x.maxValue && - xScaled >= mPhysicalLeft && xScaled <= (mPhysicalLeft + mPhysicalWidth) && y >= mRawPointerAxes.y.minValue && y <= mRawPointerAxes.y.maxValue && - yScaled >= mPhysicalTop && yScaled <= (mPhysicalTop + mPhysicalHeight); + isPointInRect(mPhysicalFrameInRotatedDisplay, mRawToRotatedDisplay.transform(x, y)); } const TouchInputMapper::VirtualKey* TouchInputMapper::findVirtualKeyHit(int32_t x, int32_t y) { for (const VirtualKey& virtualKey : mVirtualKeys) { - if (DEBUG_VIRTUAL_KEYS) { - ALOGD("VirtualKeys: Hit test (%d, %d): keyCode=%d, scanCode=%d, " - "left=%d, top=%d, right=%d, bottom=%d", - x, y, virtualKey.keyCode, virtualKey.scanCode, virtualKey.hitLeft, - virtualKey.hitTop, virtualKey.hitRight, virtualKey.hitBottom); - } + ALOGD_IF(DEBUG_VIRTUAL_KEYS, + "VirtualKeys: Hit test (%d, %d): keyCode=%d, scanCode=%d, " + "left=%d, top=%d, right=%d, bottom=%d", + x, y, virtualKey.keyCode, virtualKey.scanCode, virtualKey.hitLeft, + virtualKey.hitTop, virtualKey.hitRight, virtualKey.hitBottom); if (virtualKey.isHit(x, y)) { return &virtualKey; @@ -4000,11 +4043,10 @@ void TouchInputMapper::assignPointerIds(const RawState& last, RawState& current) currentPointerIndex)); usedIdBits.markBit(id); - if (DEBUG_POINTER_ASSIGNMENT) { - ALOGD("assignPointerIds - matched: cur=%" PRIu32 ", last=%" PRIu32 ", id=%" PRIu32 - ", distance=%" PRIu64, - lastPointerIndex, currentPointerIndex, id, heap[0].distance); - } + ALOGD_IF(DEBUG_POINTER_ASSIGNMENT, + "assignPointerIds - matched: cur=%" PRIu32 ", last=%" PRIu32 ", id=%" PRIu32 + ", distance=%" PRIu64, + lastPointerIndex, currentPointerIndex, id, heap[0].distance); break; } } @@ -4019,10 +4061,9 @@ void TouchInputMapper::assignPointerIds(const RawState& last, RawState& current) current.rawPointerData.markIdBit(id, current.rawPointerData.isHovering(currentPointerIndex)); - if (DEBUG_POINTER_ASSIGNMENT) { - ALOGD("assignPointerIds - assigned: cur=%" PRIu32 ", id=%" PRIu32, currentPointerIndex, - id); - } + ALOGD_IF(DEBUG_POINTER_ASSIGNMENT, + "assignPointerIds - assigned: cur=%" PRIu32 ", id=%" PRIu32, currentPointerIndex, + id); } } @@ -4054,10 +4095,11 @@ int32_t TouchInputMapper::getScanCodeState(uint32_t sourceMask, int32_t scanCode return AKEY_STATE_UNKNOWN; } -bool TouchInputMapper::markSupportedKeyCodes(uint32_t sourceMask, size_t numCodes, - const int32_t* keyCodes, uint8_t* outFlags) { +bool TouchInputMapper::markSupportedKeyCodes(uint32_t sourceMask, + const std::vector& keyCodes, + uint8_t* outFlags) { for (const VirtualKey& virtualKey : mVirtualKeys) { - for (size_t i = 0; i < numCodes; i++) { + for (size_t i = 0; i < keyCodes.size(); i++) { if (virtualKey.keyCode == keyCodes[i]) { outFlags[i] = 1; } diff --git a/services/inputflinger/reader/mapper/TouchInputMapper.h b/services/inputflinger/reader/mapper/TouchInputMapper.h index 2937bf88d44911442f1a353f0ffbd1656bec09f8..d8b59ca39b395f1d2a5c7ca793908ad79af63db3 100644 --- a/services/inputflinger/reader/mapper/TouchInputMapper.h +++ b/services/inputflinger/reader/mapper/TouchInputMapper.h @@ -14,10 +14,13 @@ * limitations under the License. */ -#ifndef _UI_INPUTREADER_TOUCH_INPUT_MAPPER_H -#define _UI_INPUTREADER_TOUCH_INPUT_MAPPER_H +#pragma once + +#include +#include #include +#include #include "CursorButtonAccumulator.h" #include "CursorScrollAccumulator.h" @@ -28,55 +31,65 @@ namespace android { +// Maximum amount of latency to add to touch events while waiting for data from an +// external stylus. +static constexpr nsecs_t EXTERNAL_STYLUS_DATA_TIMEOUT = ms2ns(72); + +// Maximum amount of time to wait on touch data before pushing out new pressure data. +static constexpr nsecs_t TOUCH_DATA_TIMEOUT = ms2ns(20); + /* Raw axis information from the driver. */ struct RawPointerAxes { - RawAbsoluteAxisInfo x; - RawAbsoluteAxisInfo y; - RawAbsoluteAxisInfo pressure; - RawAbsoluteAxisInfo touchMajor; - RawAbsoluteAxisInfo touchMinor; - RawAbsoluteAxisInfo toolMajor; - RawAbsoluteAxisInfo toolMinor; - RawAbsoluteAxisInfo orientation; - RawAbsoluteAxisInfo distance; - RawAbsoluteAxisInfo tiltX; - RawAbsoluteAxisInfo tiltY; - RawAbsoluteAxisInfo trackingId; - RawAbsoluteAxisInfo slot; - - RawPointerAxes(); + RawAbsoluteAxisInfo x{}; + RawAbsoluteAxisInfo y{}; + RawAbsoluteAxisInfo pressure{}; + RawAbsoluteAxisInfo touchMajor{}; + RawAbsoluteAxisInfo touchMinor{}; + RawAbsoluteAxisInfo toolMajor{}; + RawAbsoluteAxisInfo toolMinor{}; + RawAbsoluteAxisInfo orientation{}; + RawAbsoluteAxisInfo distance{}; + RawAbsoluteAxisInfo tiltX{}; + RawAbsoluteAxisInfo tiltY{}; + RawAbsoluteAxisInfo trackingId{}; + RawAbsoluteAxisInfo slot{}; + inline int32_t getRawWidth() const { return x.maxValue - x.minValue + 1; } inline int32_t getRawHeight() const { return y.maxValue - y.minValue + 1; } - void clear(); + inline void clear() { *this = RawPointerAxes(); } }; +using PropertiesArray = std::array; +using CoordsArray = std::array; +using IdToIndexArray = std::array; + /* Raw data for a collection of pointers including a pointer id mapping table. */ struct RawPointerData { struct Pointer { - uint32_t id; - int32_t x; - int32_t y; - int32_t pressure; - int32_t touchMajor; - int32_t touchMinor; - int32_t toolMajor; - int32_t toolMinor; - int32_t orientation; - int32_t distance; - int32_t tiltX; - int32_t tiltY; - int32_t toolType; // a fully decoded AMOTION_EVENT_TOOL_TYPE constant - bool isHovering; + uint32_t id{0xFFFFFFFF}; + int32_t x{}; + int32_t y{}; + int32_t pressure{}; + int32_t touchMajor{}; + int32_t touchMinor{}; + int32_t toolMajor{}; + int32_t toolMinor{}; + int32_t orientation{}; + int32_t distance{}; + int32_t tiltX{}; + int32_t tiltY{}; + // A fully decoded ToolType constant. + ToolType toolType{ToolType::UNKNOWN}; + bool isHovering{false}; }; - uint32_t pointerCount; - Pointer pointers[MAX_POINTERS]; - BitSet32 hoveringIdBits, touchingIdBits, canceledIdBits; - uint32_t idToIndex[MAX_POINTER_ID + 1]; + uint32_t pointerCount{}; + std::array pointers{}; + BitSet32 hoveringIdBits{}, touchingIdBits{}, canceledIdBits{}; + IdToIndexArray idToIndex{}; + + inline void clear() { *this = RawPointerData(); } - RawPointerData(); - void clear(); - void copyFrom(const RawPointerData& other); void getCentroidOfTouchingPointers(float* outX, float* outY) const; inline void markIdBit(uint32_t id, bool isHovering) { @@ -100,15 +113,13 @@ struct RawPointerData { /* Cooked data for a collection of pointers including a pointer id mapping table. */ struct CookedPointerData { - uint32_t pointerCount; - PointerProperties pointerProperties[MAX_POINTERS]; - PointerCoords pointerCoords[MAX_POINTERS]; - BitSet32 hoveringIdBits, touchingIdBits, canceledIdBits, validIdBits; - uint32_t idToIndex[MAX_POINTER_ID + 1]; + uint32_t pointerCount{}; + PropertiesArray pointerProperties{}; + CoordsArray pointerCoords{}; + BitSet32 hoveringIdBits{}, touchingIdBits{}, canceledIdBits{}, validIdBits{}; + IdToIndexArray idToIndex{}; - CookedPointerData(); - void clear(); - void copyFrom(const CookedPointerData& other); + inline void clear() { *this = CookedPointerData(); } inline const PointerCoords& pointerCoordsForId(uint32_t id) const { return pointerCoords[idToIndex[id]]; @@ -135,24 +146,26 @@ struct CookedPointerData { class TouchInputMapper : public InputMapper { public: - explicit TouchInputMapper(InputDeviceContext& deviceContext); ~TouchInputMapper() override; uint32_t getSources() const override; - void populateDeviceInfo(InputDeviceInfo* deviceInfo) override; + void populateDeviceInfo(InputDeviceInfo& deviceInfo) override; void dump(std::string& dump) override; - void configure(nsecs_t when, const InputReaderConfiguration* config, uint32_t changes) override; - void reset(nsecs_t when) override; - void process(const RawEvent* rawEvent) override; + [[nodiscard]] std::list reconfigure(nsecs_t when, + const InputReaderConfiguration& config, + ConfigurationChanges changes) override; + [[nodiscard]] std::list reset(nsecs_t when) 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; - bool markSupportedKeyCodes(uint32_t sourceMask, size_t numCodes, const int32_t* keyCodes, + bool markSupportedKeyCodes(uint32_t sourceMask, const std::vector& keyCodes, uint8_t* outFlags) override; - void cancelTouch(nsecs_t when, nsecs_t readTime) override; - void timeoutExpired(nsecs_t when) override; - void updateExternalStylusState(const StylusState& state) override; + [[nodiscard]] std::list cancelTouch(nsecs_t when, nsecs_t readTime) override; + [[nodiscard]] std::list timeoutExpired(nsecs_t when) override; + [[nodiscard]] std::list updateExternalStylusState( + const StylusState& state) override; std::optional getAssociatedDisplayId() override; protected: @@ -177,7 +190,7 @@ protected: }; // Input sources and device mode. - uint32_t mSource; + uint32_t mSource{0}; enum class DeviceMode { DISABLED, // input is disabled @@ -188,7 +201,7 @@ protected: ftl_last = POINTER }; - DeviceMode mDeviceMode; + DeviceMode mDeviceMode{DeviceMode::DISABLED}; // The reader's configuration. InputReaderConfiguration mConfig; @@ -197,7 +210,6 @@ protected: struct Parameters { enum class DeviceType { TOUCH_SCREEN, - TOUCH_PAD, TOUCH_NAVIGATION, POINTER, @@ -209,15 +221,7 @@ protected: bool associatedDisplayIsExternal; bool orientationAware; - enum class Orientation : int32_t { - ORIENTATION_0 = DISPLAY_ORIENTATION_0, - ORIENTATION_90 = DISPLAY_ORIENTATION_90, - ORIENTATION_180 = DISPLAY_ORIENTATION_180, - ORIENTATION_270 = DISPLAY_ORIENTATION_270, - - ftl_last = ORIENTATION_270 - }; - Orientation orientation; + ui::Rotation orientation; bool hasButtonUnderPad; std::string uniqueDisplayId; @@ -231,6 +235,12 @@ protected: GestureMode gestureMode; bool wake; + + // The Universal Stylus Initiative (USI) protocol version supported by this device. + std::optional usiVersion; + + // Allows touches while the display is off. + bool enableForInactiveViewport; } mParameters; // Immutable calibration parameters in parsed form. @@ -248,12 +258,9 @@ protected: SizeCalibration sizeCalibration; - bool haveSizeScale; - float sizeScale; - bool haveSizeBias; - float sizeBias; - bool haveSizeIsSummed; - bool sizeIsSummed; + std::optional sizeScale; + std::optional sizeBias; + std::optional sizeIsSummed; // Pressure enum class PressureCalibration { @@ -264,8 +271,7 @@ protected: }; PressureCalibration pressureCalibration; - bool havePressureScale; - float pressureScale; + std::optional pressureScale; // Orientation enum class OrientationCalibration { @@ -285,26 +291,17 @@ protected: }; DistanceCalibration distanceCalibration; - bool haveDistanceScale; - float distanceScale; + std::optional distanceScale; - enum class CoverageCalibration { - DEFAULT, - NONE, - BOX, - }; - - CoverageCalibration coverageCalibration; - - inline void applySizeScaleAndBias(float* outSize) const { - if (haveSizeScale) { - *outSize *= sizeScale; + inline void applySizeScaleAndBias(float& outSize) const { + if (sizeScale) { + outSize *= *sizeScale; } - if (haveSizeBias) { - *outSize += sizeBias; + if (sizeBias) { + outSize += *sizeBias; } - if (*outSize < 0) { - *outSize = 0; + if (outSize < 0) { + outSize = 0; } } } mCalibration; @@ -315,65 +312,33 @@ protected: RawPointerAxes mRawPointerAxes; struct RawState { - nsecs_t when; - nsecs_t readTime; + nsecs_t when{std::numeric_limits::min()}; + nsecs_t readTime{}; // Raw pointer sample data. - RawPointerData rawPointerData; + RawPointerData rawPointerData{}; - int32_t buttonState; + int32_t buttonState{}; // Scroll state. - int32_t rawVScroll; - int32_t rawHScroll; - - explicit inline RawState() { clear(); } - - void copyFrom(const RawState& other) { - when = other.when; - readTime = other.readTime; - rawPointerData.copyFrom(other.rawPointerData); - buttonState = other.buttonState; - rawVScroll = other.rawVScroll; - rawHScroll = other.rawHScroll; - } + int32_t rawVScroll{}; + int32_t rawHScroll{}; - void clear() { - when = 0; - readTime = 0; - rawPointerData.clear(); - buttonState = 0; - rawVScroll = 0; - rawHScroll = 0; - } + inline void clear() { *this = RawState(); } }; struct CookedState { // Cooked pointer sample data. - CookedPointerData cookedPointerData; + CookedPointerData cookedPointerData{}; // Id bits used to differentiate fingers, stylus and mouse tools. - BitSet32 fingerIdBits; - BitSet32 stylusIdBits; - BitSet32 mouseIdBits; - - int32_t buttonState; - - void copyFrom(const CookedState& other) { - cookedPointerData.copyFrom(other.cookedPointerData); - fingerIdBits = other.fingerIdBits; - stylusIdBits = other.stylusIdBits; - mouseIdBits = other.mouseIdBits; - buttonState = other.buttonState; - } + BitSet32 fingerIdBits{}; + BitSet32 stylusIdBits{}; + BitSet32 mouseIdBits{}; - void clear() { - cookedPointerData.clear(); - fingerIdBits.clear(); - stylusIdBits.clear(); - mouseIdBits.clear(); - buttonState = 0; - } + int32_t buttonState{}; + + inline void clear() { *this = CookedState(); } }; std::vector mRawStatesPending; @@ -384,28 +349,35 @@ protected: // State provided by an external stylus StylusState mExternalStylusState; - int64_t mExternalStylusId; + // If an external stylus is capable of reporting pointer-specific data like pressure, we will + // attempt to fuse the pointer data reported by the stylus to the first touch pointer. This is + // the id of the pointer to which the external stylus data is fused. + std::optional mFusedStylusPointerId; nsecs_t mExternalStylusFusionTimeout; bool mExternalStylusDataPending; + // A subset of the buttons in mCurrentRawState that came from an external stylus. + int32_t mExternalStylusButtonsApplied{0}; // True if we sent a HOVER_ENTER event. - bool mSentHoverEnter; + bool mSentHoverEnter{false}; // Have we assigned pointer IDs for this stream - bool mHavePointerIds; + bool mHavePointerIds{false}; // Is the current stream of direct touch events aborted - bool mCurrentMotionAborted; + bool mCurrentMotionAborted{false}; // The time the primary pointer last went down. - nsecs_t mDownTime; + nsecs_t mDownTime{0}; // The pointer controller, or null if the device is not a pointer. std::shared_ptr mPointerController; std::vector mVirtualKeys; - virtual void configureParameters(); + explicit TouchInputMapper(InputDeviceContext& deviceContext, + const InputReaderConfiguration& readerConfig); + virtual void dumpParameters(std::string& dump); virtual void configureRawPointerAxes(); virtual void dumpRawPointerAxes(std::string& dump); @@ -429,29 +401,31 @@ private: // The components of the viewport are specified in the display's rotated orientation. DisplayViewport mViewport; - // The width and height are obtained from the viewport and are specified - // in the natural orientation. - int32_t mDisplayWidth; - int32_t mDisplayHeight; + // We refer to the display as being in the "natural orientation" when there is no rotation + // applied. The display size obtained from the viewport in the natural orientation. + // Always starts at (0, 0). + ui::Size mDisplayBounds{ui::kInvalidSize}; - // The physical frame is the rectangle in the display's coordinate space that maps to the + // The physical frame is the rectangle in the rotated display's coordinate space that maps to // the logical display frame. - int32_t mPhysicalWidth; - int32_t mPhysicalHeight; - int32_t mPhysicalLeft; - int32_t mPhysicalTop; + Rect mPhysicalFrameInRotatedDisplay{Rect::INVALID_RECT}; // The orientation of the input device relative to that of the display panel. It specifies // the rotation of the input device coordinates required to produce the display panel // orientation, so it will depend on whether the device is orientation aware. - int32_t mInputDeviceOrientation; + ui::Rotation mInputDeviceOrientation{ui::ROTATION_0}; + + // The transform that maps the input device's raw coordinate space to the un-rotated display's + // coordinate space. InputReader generates events in the un-rotated display's coordinate space. + ui::Transform mRawToDisplay; - // Translation and scaling factors, orientation-independent. - float mXScale; - float mXPrecision; + // The transform that maps the input device's raw coordinate space to the rotated display's + // coordinate space. This used to perform hit-testing of raw events with the physical frame in + // the rotated coordinate space. See mPhysicalFrameInRotatedDisplay. + ui::Transform mRawToRotatedDisplay; - float mYScale; - float mYPrecision; + // The transform used for non-planar raw axes, such as orientation and tilt. + ui::Transform mRawRotation; float mGeometricScale; @@ -477,35 +451,29 @@ private: InputDeviceInfo::MotionRange y; InputDeviceInfo::MotionRange pressure; - bool haveSize; - InputDeviceInfo::MotionRange size; + std::optional size; - bool haveTouchSize; - InputDeviceInfo::MotionRange touchMajor; - InputDeviceInfo::MotionRange touchMinor; + std::optional touchMajor; + std::optional touchMinor; - bool haveToolSize; - InputDeviceInfo::MotionRange toolMajor; - InputDeviceInfo::MotionRange toolMinor; + std::optional toolMajor; + std::optional toolMinor; - bool haveOrientation; - InputDeviceInfo::MotionRange orientation; + std::optional orientation; - bool haveDistance; - InputDeviceInfo::MotionRange distance; + std::optional distance; - bool haveTilt; - InputDeviceInfo::MotionRange tilt; - - OrientedRanges() { clear(); } + std::optional tilt; void clear() { - haveSize = false; - haveTouchSize = false; - haveToolSize = false; - haveOrientation = false; - haveDistance = false; - haveTilt = false; + size = std::nullopt; + touchMajor = std::nullopt; + touchMinor = std::nullopt; + toolMajor = std::nullopt; + toolMinor = std::nullopt; + orientation = std::nullopt; + distance = std::nullopt; + tilt = std::nullopt; } } mOrientedRanges; @@ -529,13 +497,15 @@ private: float mPointerXZoomScale; float mPointerYZoomScale; - // The maximum swipe width. + // The maximum swipe width between pointers to detect a swipe gesture + // in the number of pixels.Touches that are wider than this are translated + // into freeform gestures. float mPointerGestureMaxSwipeWidth; struct PointerDistanceHeapElement { - uint32_t currentPointerIndex : 8; - uint32_t lastPointerIndex : 8; - uint64_t distance : 48; // squared distance + uint32_t currentPointerIndex : 8 {}; + uint32_t lastPointerIndex : 8 {}; + uint64_t distance : 48 {}; // squared distance }; enum class PointerUsage { @@ -544,7 +514,7 @@ private: STYLUS, MOUSE, }; - PointerUsage mPointerUsage; + PointerUsage mPointerUsage{PointerUsage::NONE}; struct PointerGesture { enum class Mode { @@ -632,15 +602,15 @@ private: // Pointer coords and ids for the current and previous pointer gesture. Mode currentGestureMode; BitSet32 currentGestureIdBits; - uint32_t currentGestureIdToIndex[MAX_POINTER_ID + 1]; - PointerProperties currentGestureProperties[MAX_POINTERS]; - PointerCoords currentGestureCoords[MAX_POINTERS]; + IdToIndexArray currentGestureIdToIndex{}; + PropertiesArray currentGestureProperties{}; + CoordsArray currentGestureCoords{}; Mode lastGestureMode; BitSet32 lastGestureIdBits; - uint32_t lastGestureIdToIndex[MAX_POINTER_ID + 1]; - PointerProperties lastGestureProperties[MAX_POINTERS]; - PointerCoords lastGestureCoords[MAX_POINTERS]; + IdToIndexArray lastGestureIdToIndex{}; + PropertiesArray lastGestureProperties{}; + CoordsArray lastGestureCoords{}; // Time the pointer gesture last went down. nsecs_t downTime; @@ -749,43 +719,83 @@ private: void initializeOrientedRanges(); void initializeSizeRanges(); - void sync(nsecs_t when, nsecs_t readTime); - - bool consumeRawTouches(nsecs_t when, nsecs_t readTime, uint32_t policyFlags); - void processRawTouches(bool timeout); - void cookAndDispatch(nsecs_t when, nsecs_t readTime); - void dispatchVirtualKey(nsecs_t when, nsecs_t readTime, uint32_t policyFlags, - int32_t keyEventAction, int32_t keyEventFlags); - - void dispatchTouches(nsecs_t when, nsecs_t readTime, uint32_t policyFlags); - void dispatchHoverExit(nsecs_t when, nsecs_t readTime, uint32_t policyFlags); - void dispatchHoverEnterAndMove(nsecs_t when, nsecs_t readTime, uint32_t policyFlags); - void dispatchButtonRelease(nsecs_t when, nsecs_t readTime, uint32_t policyFlags); - void dispatchButtonPress(nsecs_t when, nsecs_t readTime, uint32_t policyFlags); + [[nodiscard]] std::list sync(nsecs_t when, nsecs_t readTime); + + [[nodiscard]] std::list consumeRawTouches(nsecs_t when, nsecs_t readTime, + uint32_t policyFlags, bool& outConsumed); + [[nodiscard]] std::list processRawTouches(bool timeout); + [[nodiscard]] std::list cookAndDispatch(nsecs_t when, nsecs_t readTime); + [[nodiscard]] NotifyKeyArgs dispatchVirtualKey(nsecs_t when, nsecs_t readTime, + uint32_t policyFlags, int32_t keyEventAction, + int32_t keyEventFlags); + + [[nodiscard]] std::list dispatchTouches(nsecs_t when, nsecs_t readTime, + uint32_t policyFlags); + [[nodiscard]] std::list dispatchHoverExit(nsecs_t when, nsecs_t readTime, + uint32_t policyFlags); + [[nodiscard]] std::list dispatchHoverEnterAndMove(nsecs_t when, nsecs_t readTime, + uint32_t policyFlags); + [[nodiscard]] std::list dispatchButtonRelease(nsecs_t when, nsecs_t readTime, + uint32_t policyFlags); + [[nodiscard]] std::list dispatchButtonPress(nsecs_t when, nsecs_t readTime, + uint32_t policyFlags); + [[nodiscard]] std::list dispatchGestureButtonPress(nsecs_t when, + uint32_t policyFlags, + BitSet32 idBits, + nsecs_t readTime); + [[nodiscard]] std::list dispatchGestureButtonRelease(nsecs_t when, + uint32_t policyFlags, + BitSet32 idBits, + nsecs_t readTime); const BitSet32& findActiveIdBits(const CookedPointerData& cookedPointerData); void cookPointerData(); - void abortTouches(nsecs_t when, nsecs_t readTime, uint32_t policyFlags); - - void dispatchPointerUsage(nsecs_t when, nsecs_t readTime, uint32_t policyFlags, - PointerUsage pointerUsage); - void abortPointerUsage(nsecs_t when, nsecs_t readTime, uint32_t policyFlags); - - void dispatchPointerGestures(nsecs_t when, nsecs_t readTime, uint32_t policyFlags, - bool isTimeout); - void abortPointerGestures(nsecs_t when, nsecs_t readTime, uint32_t policyFlags); + [[nodiscard]] std::list abortTouches(nsecs_t when, nsecs_t readTime, + uint32_t policyFlags); + + [[nodiscard]] std::list dispatchPointerUsage(nsecs_t when, nsecs_t readTime, + uint32_t policyFlags, + PointerUsage pointerUsage); + [[nodiscard]] std::list abortPointerUsage(nsecs_t when, nsecs_t readTime, + uint32_t policyFlags); + + [[nodiscard]] std::list dispatchPointerGestures(nsecs_t when, nsecs_t readTime, + uint32_t policyFlags, + bool isTimeout); + [[nodiscard]] std::list abortPointerGestures(nsecs_t when, nsecs_t readTime, + uint32_t policyFlags); bool preparePointerGestures(nsecs_t when, bool* outCancelPreviousGesture, bool* outFinishPreviousGesture, bool isTimeout); - void dispatchPointerStylus(nsecs_t when, nsecs_t readTime, uint32_t policyFlags); - void abortPointerStylus(nsecs_t when, nsecs_t readTime, uint32_t policyFlags); + // Returns true if we're in a period of "quiet time" when touchpad gestures should be ignored. + bool checkForTouchpadQuietTime(nsecs_t when); + + std::pair getFastestFinger(); - void dispatchPointerMouse(nsecs_t when, nsecs_t readTime, uint32_t policyFlags); - void abortPointerMouse(nsecs_t when, nsecs_t readTime, uint32_t policyFlags); + void prepareMultiFingerPointerGestures(nsecs_t when, bool* outCancelPreviousGesture, + bool* outFinishPreviousGesture); - void dispatchPointerSimple(nsecs_t when, nsecs_t readTime, uint32_t policyFlags, bool down, - bool hovering); - void abortPointerSimple(nsecs_t when, nsecs_t readTime, uint32_t policyFlags); + // Moves the on-screen mouse pointer based on the movement of the pointer of the given ID + // between the last and current events. Uses a relative motion. + void moveMousePointerFromPointerDelta(nsecs_t when, uint32_t pointerId); + [[nodiscard]] std::list dispatchPointerStylus(nsecs_t when, nsecs_t readTime, + uint32_t policyFlags); + [[nodiscard]] std::list abortPointerStylus(nsecs_t when, nsecs_t readTime, + uint32_t policyFlags); + + [[nodiscard]] std::list dispatchPointerMouse(nsecs_t when, nsecs_t readTime, + uint32_t policyFlags); + [[nodiscard]] std::list abortPointerMouse(nsecs_t when, nsecs_t readTime, + uint32_t policyFlags); + + [[nodiscard]] std::list dispatchPointerSimple(nsecs_t when, nsecs_t readTime, + uint32_t policyFlags, bool down, + bool hovering, int32_t displayId); + [[nodiscard]] std::list abortPointerSimple(nsecs_t when, nsecs_t readTime, + uint32_t policyFlags); + + // Attempts to assign a pointer id to the external stylus. Returns true if the state should be + // withheld from further processing while waiting for data from the stylus. bool assignExternalStylusId(const RawState& state, bool timeout); void applyExternalStylusButtonState(nsecs_t when); void applyExternalStylusTouchState(nsecs_t when); @@ -794,18 +804,12 @@ private: // If the changedId is >= 0 and the action is POINTER_DOWN or POINTER_UP, the // method will take care of setting the index and transmuting the action to DOWN or UP // it is the first / last pointer to go down / up. - void dispatchMotion(nsecs_t when, nsecs_t readTime, uint32_t policyFlags, uint32_t source, - int32_t action, int32_t actionButton, int32_t flags, int32_t metaState, - int32_t buttonState, int32_t edgeFlags, const PointerProperties* properties, - const PointerCoords* coords, const uint32_t* idToIndex, BitSet32 idBits, - int32_t changedId, float xPrecision, float yPrecision, nsecs_t downTime); - - // Updates pointer coords and properties for pointers with specified ids that have moved. - // Returns true if any of them changed. - bool updateMovedPointers(const PointerProperties* inProperties, const PointerCoords* inCoords, - const uint32_t* inIdToIndex, PointerProperties* outProperties, - PointerCoords* outCoords, const uint32_t* outIdToIndex, - BitSet32 idBits) const; + [[nodiscard]] NotifyMotionArgs dispatchMotion( + nsecs_t when, nsecs_t readTime, uint32_t policyFlags, uint32_t source, int32_t action, + int32_t actionButton, int32_t flags, int32_t metaState, int32_t buttonState, + int32_t edgeFlags, const PropertiesArray& properties, const CoordsArray& coords, + const IdToIndexArray& idToIndex, BitSet32 idBits, int32_t changedId, float xPrecision, + float yPrecision, nsecs_t downTime, MotionClassification classification); // Returns if this touch device is a touch screen with an associated display. bool isTouchScreen(); @@ -818,10 +822,10 @@ private: static void assignPointerIds(const RawState& last, RawState& current); - const char* modeToString(DeviceMode deviceMode); - void rotateAndScale(float& x, float& y) const; + // Compute input transforms for DIRECT and POINTER modes. + void computeInputTransforms(); + static Parameters::DeviceType computeDeviceType(const InputDeviceContext& deviceContext); + static Parameters computeParameters(const InputDeviceContext& deviceContext); }; } // namespace android - -#endif // _UI_INPUTREADER_TOUCH_INPUT_MAPPER_H diff --git a/services/inputflinger/reader/mapper/TouchpadInputMapper.cpp b/services/inputflinger/reader/mapper/TouchpadInputMapper.cpp new file mode 100644 index 0000000000000000000000000000000000000000..c72425a90b25b6a2222b6d48595b698cc82e56bd --- /dev/null +++ b/services/inputflinger/reader/mapper/TouchpadInputMapper.cpp @@ -0,0 +1,373 @@ +/* + * 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 "../Macros.h" + +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include "TouchCursorInputMapperCommon.h" +#include "TouchpadInputMapper.h" +#include "ui/Rotation.h" + +namespace android { + +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); + +// Describes a segment of the acceleration curve. +struct CurveSegment { + // The maximum pointer speed which this segment should apply. The last segment in a curve should + // always set this to infinity. + double maxPointerSpeedMmPerS; + double slope; + double intercept; +}; + +const std::vector segments = { + {10.922, 3.19, 0}, + {31.750, 4.79, -17.526}, + {98.044, 7.28, -96.52}, + {std::numeric_limits::infinity(), 15.04, -857.758}, +}; + +const std::vector sensitivityFactors = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 14, 16, 18}; + +std::vector createAccelerationCurveForSensitivity(int32_t sensitivity, + size_t propertySize) { + LOG_ALWAYS_FATAL_IF(propertySize < 4 * segments.size()); + std::vector output(propertySize, 0); + + // The Gestures library uses functions of the following form to define curve segments, where a, + // b, and c can be specified by us: + // output_speed(input_speed_mm) = a * input_speed_mm ^ 2 + b * input_speed_mm + c + // + // (a, b, and c are also called sqr_, mul_, and int_ in the Gestures library code.) + // + // We are trying to implement the following function, where slope and intercept are the + // parameters specified in the `segments` array above: + // gain(input_speed_mm) = + // 0.64 * (sensitivityFactor / 10) * (slope + intercept / input_speed_mm) + // Where "gain" is a multiplier applied to the input speed to produce the output speed: + // output_speed(input_speed_mm) = input_speed_mm * gain(input_speed_mm) + // + // To put our function in the library's form, we substitute it into the function above: + // output_speed(input_speed_mm) = + // input_speed_mm * (0.64 * (sensitivityFactor / 10) * + // (slope + 25.4 * intercept / input_speed_mm)) + // then expand the brackets so that input_speed_mm cancels out for the intercept term: + // gain(input_speed_mm) = + // 0.64 * (sensitivityFactor / 10) * slope * input_speed_mm + + // 0.64 * (sensitivityFactor / 10) * intercept + // + // This gives us the following parameters for the Gestures library function form: + // a = 0 + // b = 0.64 * (sensitivityFactor / 10) * slope + // c = 0.64 * (sensitivityFactor / 10) * intercept + + double commonFactor = 0.64 * sensitivityFactors[sensitivity + 7] / 10; + + size_t i = 0; + for (CurveSegment seg : segments) { + // The library's curve format consists of four doubles per segment: + // * maximum pointer speed for the segment (mm/s) + // * multiplier for the x² term (a.k.a. "a" or "sqr") + // * multiplier for the x term (a.k.a. "b" or "mul") + // * the intercept (a.k.a. "c" or "int") + // (see struct CurveSegment in the library's AccelFilterInterpreter) + output[i + 0] = seg.maxPointerSpeedMmPerS; + output[i + 1] = 0; + output[i + 2] = commonFactor * seg.slope; + output[i + 3] = commonFactor * seg.intercept; + i += 4; + } + + return output; +} + +short getMaxTouchCount(const InputDeviceContext& context) { + if (context.hasScanCode(BTN_TOOL_QUINTTAP)) return 5; + if (context.hasScanCode(BTN_TOOL_QUADTAP)) return 4; + if (context.hasScanCode(BTN_TOOL_TRIPLETAP)) return 3; + if (context.hasScanCode(BTN_TOOL_DOUBLETAP)) return 2; + if (context.hasScanCode(BTN_TOOL_FINGER)) return 1; + return 0; +} + +HardwareProperties createHardwareProperties(const InputDeviceContext& context) { + HardwareProperties props; + RawAbsoluteAxisInfo absMtPositionX; + context.getAbsoluteAxisInfo(ABS_MT_POSITION_X, &absMtPositionX); + props.left = absMtPositionX.minValue; + props.right = absMtPositionX.maxValue; + props.res_x = absMtPositionX.resolution; + + RawAbsoluteAxisInfo absMtPositionY; + context.getAbsoluteAxisInfo(ABS_MT_POSITION_Y, &absMtPositionY); + props.top = absMtPositionY.minValue; + props.bottom = absMtPositionY.maxValue; + props.res_y = absMtPositionY.resolution; + + RawAbsoluteAxisInfo absMtOrientation; + context.getAbsoluteAxisInfo(ABS_MT_ORIENTATION, &absMtOrientation); + props.orientation_minimum = absMtOrientation.minValue; + props.orientation_maximum = absMtOrientation.maxValue; + + RawAbsoluteAxisInfo absMtSlot; + context.getAbsoluteAxisInfo(ABS_MT_SLOT, &absMtSlot); + props.max_finger_cnt = absMtSlot.maxValue - absMtSlot.minValue + 1; + props.max_touch_cnt = getMaxTouchCount(context); + + // T5R2 ("Track 5, Report 2") is a feature of some old Synaptics touchpads that could track 5 + // fingers but only report the coordinates of 2 of them. We don't know of any external touchpads + // that did this, so assume false. + props.supports_t5r2 = false; + + props.support_semi_mt = context.hasInputProperty(INPUT_PROP_SEMI_MT); + props.is_button_pad = context.hasInputProperty(INPUT_PROP_BUTTONPAD); + + // Mouse-only properties, which will always be false. + props.has_wheel = false; + props.wheel_is_hi_res = false; + + // Linux Kernel haptic touchpad support isn't merged yet, so for now assume that no touchpads + // are haptic. + props.is_haptic_pad = false; + return props; +} + +void gestureInterpreterCallback(void* clientData, const Gesture* gesture) { + TouchpadInputMapper* mapper = static_cast(clientData); + mapper->consumeGesture(gesture); +} + +} // namespace + +TouchpadInputMapper::TouchpadInputMapper(InputDeviceContext& deviceContext, + const InputReaderConfiguration& readerConfig) + : InputMapper(deviceContext, readerConfig), + mGestureInterpreter(NewGestureInterpreter(), DeleteGestureInterpreter), + mPointerController(getContext()->getPointerController(getDeviceId())), + mStateConverter(deviceContext, mMotionAccumulator), + mGestureConverter(*getContext(), deviceContext, getDeviceId()), + mCapturedEventConverter(*getContext(), deviceContext, mMotionAccumulator, getDeviceId()) { + 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()); + } + mMotionAccumulator.configure(deviceContext, slotAxisInfo.maxValue + 1, true); + + mGestureInterpreter->Initialize(GESTURES_DEVCLASS_TOUCHPAD); + mGestureInterpreter->SetHardwareProperties(createHardwareProperties(deviceContext)); + // Even though we don't explicitly delete copy/move semantics, it's safe to + // give away pointers to TouchpadInputMapper and its members here because + // 1) mGestureInterpreter's lifecycle is determined by TouchpadInputMapper, and + // 2) TouchpadInputMapper is stored as a unique_ptr and not moved. + mGestureInterpreter->SetPropProvider(const_cast(&gesturePropProvider), + &mPropertyProvider); + mGestureInterpreter->SetCallback(gestureInterpreterCallback, this); + // TODO(b/251196347): set a timer provider, so the library can use timers. +} + +TouchpadInputMapper::~TouchpadInputMapper() { + if (mPointerController != nullptr) { + mPointerController->fade(PointerControllerInterface::Transition::IMMEDIATE); + } + + // The gesture interpreter's destructor will call its property provider's free function for all + // gesture properties, in this case calling PropertyProvider::freeProperty using a raw pointer + // to mPropertyProvider. Depending on the declaration order in TouchpadInputMapper.h, this may + // happen after mPropertyProvider has been destructed, causing allocation errors. Depending on + // declaration order to avoid crashes seems rather fragile, so explicitly clear the property + // provider here to ensure all the freeProperty calls happen before mPropertyProvider is + // destructed. + mGestureInterpreter->SetPropProvider(nullptr, nullptr); +} + +uint32_t TouchpadInputMapper::getSources() const { + return AINPUT_SOURCE_MOUSE | AINPUT_SOURCE_TOUCHPAD; +} + +void TouchpadInputMapper::populateDeviceInfo(InputDeviceInfo& info) { + InputMapper::populateDeviceInfo(info); + if (mPointerCaptured) { + mCapturedEventConverter.populateMotionRanges(info); + } else { + mGestureConverter.populateMotionRanges(info); + } +} + +void TouchpadInputMapper::dump(std::string& dump) { + dump += INDENT2 "Touchpad Input Mapper:\n"; + if (mProcessing) { + dump += INDENT3 "Currently processing a hardware state\n"; + } + if (mResettingInterpreter) { + dump += INDENT3 "Currently resetting gesture interpreter\n"; + } + dump += StringPrintf(INDENT3 "Pointer captured: %s\n", toString(mPointerCaptured)); + dump += INDENT3 "Gesture converter:\n"; + dump += addLinePrefix(mGestureConverter.dump(), INDENT4); + dump += INDENT3 "Gesture properties:\n"; + dump += addLinePrefix(mPropertyProvider.dump(), INDENT4); + dump += INDENT3 "Captured event converter:\n"; + dump += addLinePrefix(mCapturedEventConverter.dump(), INDENT4); +} + +std::list TouchpadInputMapper::reconfigure(nsecs_t when, + const InputReaderConfiguration& config, + ConfigurationChanges changes) { + if (!changes.any()) { + // First time configuration + mPropertyProvider.loadPropertiesFromIdcFile(getDeviceContext().getConfiguration()); + } + + if (!changes.any() || changes.test(InputReaderConfiguration::Change::DISPLAY_INFO)) { + std::optional displayId = mPointerController->getDisplayId(); + ui::Rotation orientation = ui::ROTATION_0; + if (displayId.has_value()) { + if (auto viewport = config.getDisplayViewportById(*displayId); viewport) { + orientation = getInverseRotation(viewport->orientation); + } + } + mGestureConverter.setOrientation(orientation); + } + if (!changes.any() || changes.test(InputReaderConfiguration::Change::TOUCHPAD_SETTINGS)) { + mPropertyProvider.getProperty("Use Custom Touchpad Pointer Accel Curve") + .setBoolValues({true}); + GesturesProp accelCurveProp = mPropertyProvider.getProperty("Pointer Accel Curve"); + accelCurveProp.setRealValues( + createAccelerationCurveForSensitivity(config.touchpadPointerSpeed, + accelCurveProp.getCount())); + mPropertyProvider.getProperty("Use Custom Touchpad Scroll Accel Curve") + .setBoolValues({true}); + GesturesProp scrollCurveProp = mPropertyProvider.getProperty("Scroll Accel Curve"); + scrollCurveProp.setRealValues( + createAccelerationCurveForSensitivity(config.touchpadPointerSpeed, + scrollCurveProp.getCount())); + mPropertyProvider.getProperty("Scroll X Out Scale").setRealValues({1.0}); + mPropertyProvider.getProperty("Scroll Y Out Scale").setRealValues({1.0}); + mPropertyProvider.getProperty("Invert Scrolling") + .setBoolValues({config.touchpadNaturalScrollingEnabled}); + mPropertyProvider.getProperty("Tap Enable") + .setBoolValues({config.touchpadTapToClickEnabled}); + mPropertyProvider.getProperty("Button Right Click Zone Enable") + .setBoolValues({config.touchpadRightClickZoneEnabled}); + } + std::list out; + if ((!changes.any() && config.pointerCaptureRequest.enable) || + changes.test(InputReaderConfiguration::Change::POINTER_CAPTURE)) { + mPointerCaptured = config.pointerCaptureRequest.enable; + // The motion ranges are going to change, so bump the generation to clear the cached ones. + bumpGeneration(); + if (mPointerCaptured) { + // 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(); + } + if (changes.any()) { + out.push_back(NotifyDeviceResetArgs(getContext()->getNextId(), when, getDeviceId())); + } + } + return out; +} + +std::list TouchpadInputMapper::reset(nsecs_t when) { + mStateConverter.reset(); + resetGestureInterpreter(when); + std::list out = mGestureConverter.reset(when); + out += InputMapper::reset(when); + return out; +} + +void TouchpadInputMapper::resetGestureInterpreter(nsecs_t when) { + // The GestureInterpreter has no official reset method, but sending a HardwareState with no + // fingers down or buttons pressed should get it into a clean state. + HardwareState state; + state.timestamp = std::chrono::duration(std::chrono::nanoseconds(when)).count(); + mResettingInterpreter = true; + mGestureInterpreter->PushHardwareState(&state); + mResettingInterpreter = false; +} + +std::list TouchpadInputMapper::process(const RawEvent* rawEvent) { + if (mPointerCaptured) { + return mCapturedEventConverter.process(*rawEvent); + } + std::optional state = mStateConverter.processRawEvent(rawEvent); + if (state) { + return sendHardwareState(rawEvent->when, rawEvent->readTime, *state); + } else { + return {}; + } +} + +std::list TouchpadInputMapper::sendHardwareState(nsecs_t when, nsecs_t readTime, + SelfContainedHardwareState schs) { + ALOGD_IF(DEBUG_TOUCHPAD_GESTURES, "New hardware state: %s", schs.state.String().c_str()); + mProcessing = true; + mGestureInterpreter->PushHardwareState(&schs.state); + mProcessing = false; + + return processGestures(when, readTime); +} + +void TouchpadInputMapper::consumeGesture(const Gesture* gesture) { + ALOGD_IF(DEBUG_TOUCHPAD_GESTURES, "Gesture ready: %s", gesture->String().c_str()); + if (mResettingInterpreter) { + // We already handle tidying up fake fingers etc. in GestureConverter::reset, so we should + // ignore any gestures produced from the interpreter while we're resetting it. + return; + } + if (!mProcessing) { + ALOGE("Received gesture outside of the normal processing flow; ignoring it."); + return; + } + mGesturesToProcess.push_back(*gesture); +} + +std::list TouchpadInputMapper::processGestures(nsecs_t when, nsecs_t readTime) { + std::list out = {}; + for (Gesture& gesture : mGesturesToProcess) { + out += mGestureConverter.handleGesture(when, readTime, gesture); + } + mGesturesToProcess.clear(); + return out; +} + +} // namespace android diff --git a/services/inputflinger/reader/mapper/TouchpadInputMapper.h b/services/inputflinger/reader/mapper/TouchpadInputMapper.h new file mode 100644 index 0000000000000000000000000000000000000000..23d0fd3cf3b58c0cb4da1c0c2ccc5c76d0c2a327 --- /dev/null +++ b/services/inputflinger/reader/mapper/TouchpadInputMapper.h @@ -0,0 +1,91 @@ +/* + * 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 +#include + +#include +#include + +#include "CapturedTouchpadEventConverter.h" +#include "EventHub.h" +#include "InputDevice.h" +#include "InputMapper.h" +#include "InputReaderBase.h" +#include "NotifyArgs.h" +#include "accumulator/MultiTouchMotionAccumulator.h" +#include "gestures/GestureConverter.h" +#include "gestures/HardwareStateConverter.h" +#include "gestures/PropertyProvider.h" + +#include "include/gestures.h" + +namespace android { + +class TouchpadInputMapper : public InputMapper { +public: + template + friend std::unique_ptr createInputMapper(InputDeviceContext& deviceContext, + const InputReaderConfiguration& readerConfig, + Args... args); + ~TouchpadInputMapper(); + + uint32_t getSources() const override; + void populateDeviceInfo(InputDeviceInfo& deviceInfo) override; + void dump(std::string& dump) override; + + [[nodiscard]] std::list reconfigure(nsecs_t when, + const InputReaderConfiguration& config, + ConfigurationChanges changes) override; + [[nodiscard]] std::list reset(nsecs_t when) override; + [[nodiscard]] std::list process(const RawEvent* rawEvent) override; + + void consumeGesture(const Gesture* gesture); + +private: + void resetGestureInterpreter(nsecs_t when); + explicit TouchpadInputMapper(InputDeviceContext& deviceContext, + const InputReaderConfiguration& readerConfig); + [[nodiscard]] std::list sendHardwareState(nsecs_t when, nsecs_t readTime, + SelfContainedHardwareState schs); + [[nodiscard]] std::list processGestures(nsecs_t when, nsecs_t readTime); + + std::unique_ptr + mGestureInterpreter; + std::shared_ptr mPointerController; + + PropertyProvider mPropertyProvider; + + // The MultiTouchMotionAccumulator is shared between the HardwareStateConverter and + // CapturedTouchpadEventConverter, so that if the touchpad is captured or released while touches + // are down, the relevant converter can still benefit from the current axis values stored in the + // accumulator. + MultiTouchMotionAccumulator mMotionAccumulator; + + HardwareStateConverter mStateConverter; + GestureConverter mGestureConverter; + CapturedTouchpadEventConverter mCapturedEventConverter; + + bool mPointerCaptured = false; + bool mProcessing = false; + bool mResettingInterpreter = false; + std::vector mGesturesToProcess; +}; + +} // namespace android diff --git a/services/inputflinger/reader/mapper/VibratorInputMapper.cpp b/services/inputflinger/reader/mapper/VibratorInputMapper.cpp index 33db527db14c67b063c9dc3b1ed9d1f55ff40c98..8d78d0fd80ce641faac8b8ce596d8be0a5b564e4 100644 --- a/services/inputflinger/reader/mapper/VibratorInputMapper.cpp +++ b/services/inputflinger/reader/mapper/VibratorInputMapper.cpp @@ -20,8 +20,9 @@ namespace android { -VibratorInputMapper::VibratorInputMapper(InputDeviceContext& deviceContext) - : InputMapper(deviceContext), mVibrating(false), mSequence(0) {} +VibratorInputMapper::VibratorInputMapper(InputDeviceContext& deviceContext, + const InputReaderConfiguration& readerConfig) + : InputMapper(deviceContext, readerConfig), mVibrating(false), mSequence(0) {} VibratorInputMapper::~VibratorInputMapper() {} @@ -29,22 +30,24 @@ uint32_t VibratorInputMapper::getSources() const { return 0; } -void VibratorInputMapper::populateDeviceInfo(InputDeviceInfo* info) { +void VibratorInputMapper::populateDeviceInfo(InputDeviceInfo& info) { InputMapper::populateDeviceInfo(info); - info->setVibrator(true); + info.setVibrator(true); } -void 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 {}; } -void VibratorInputMapper::vibrate(const VibrationSequence& sequence, ssize_t repeat, - int32_t token) { +std::list VibratorInputMapper::vibrate(const VibrationSequence& sequence, + ssize_t repeat, int32_t token) { if (DEBUG_VIBRATOR) { ALOGD("vibrate: deviceId=%d, pattern=[%s], repeat=%zd, token=%d", getDeviceId(), sequence.toString().c_str(), repeat, token); } + std::list out; mVibrating = true; mSequence = sequence; @@ -53,19 +56,22 @@ void VibratorInputMapper::vibrate(const VibrationSequence& sequence, ssize_t rep mIndex = -1; // Request InputReader to notify InputManagerService for vibration started. - NotifyVibratorStateArgs args(getContext()->getNextId(), systemTime(), getDeviceId(), true); - getListener().notifyVibratorState(&args); - nextStep(); + out.push_back( + NotifyVibratorStateArgs(getContext()->getNextId(), systemTime(), getDeviceId(), true)); + out += nextStep(); + return out; } -void VibratorInputMapper::cancelVibrate(int32_t token) { +std::list VibratorInputMapper::cancelVibrate(int32_t token) { if (DEBUG_VIBRATOR) { ALOGD("cancelVibrate: deviceId=%d, token=%d", getDeviceId(), token); } + std::list out; if (mVibrating && mToken == token) { - stopVibrating(); + out.push_back(stopVibrating()); } + return out; } bool VibratorInputMapper::isVibrating() { @@ -76,26 +82,29 @@ std::vector VibratorInputMapper::getVibratorIds() { return getDeviceContext().getVibratorIds(); } -void VibratorInputMapper::timeoutExpired(nsecs_t when) { +std::list VibratorInputMapper::timeoutExpired(nsecs_t when) { + std::list out; if (mVibrating) { if (when >= mNextStepTime) { - nextStep(); + out += nextStep(); } else { getContext()->requestTimeoutAtTime(mNextStepTime); } } + return out; } -void VibratorInputMapper::nextStep() { +std::list VibratorInputMapper::nextStep() { if (DEBUG_VIBRATOR) { ALOGD("nextStep: index=%d, vibrate deviceId=%d", (int)mIndex, getDeviceId()); } + std::list out; mIndex += 1; if (size_t(mIndex) >= mSequence.pattern.size()) { if (mRepeat < 0) { // We are done. - stopVibrating(); - return; + out.push_back(stopVibrating()); + return out; } mIndex = mRepeat; } @@ -122,9 +131,10 @@ void VibratorInputMapper::nextStep() { if (DEBUG_VIBRATOR) { ALOGD("nextStep: scheduled timeout in %lldms", element.duration.count()); } + return out; } -void VibratorInputMapper::stopVibrating() { +NotifyVibratorStateArgs VibratorInputMapper::stopVibrating() { mVibrating = false; if (DEBUG_VIBRATOR) { ALOGD("stopVibrating: sending cancel vibrate deviceId=%d", getDeviceId()); @@ -132,8 +142,7 @@ void VibratorInputMapper::stopVibrating() { getDeviceContext().cancelVibrate(); // Request InputReader to notify InputManagerService for vibration complete. - NotifyVibratorStateArgs args(getContext()->getNextId(), systemTime(), getDeviceId(), false); - getListener().notifyVibratorState(&args); + return NotifyVibratorStateArgs(getContext()->getNextId(), systemTime(), getDeviceId(), false); } void VibratorInputMapper::dump(std::string& dump) { diff --git a/services/inputflinger/reader/mapper/VibratorInputMapper.h b/services/inputflinger/reader/mapper/VibratorInputMapper.h index d3c22b60a812e42d27b12f99fb6c1706536da0d2..9079c73f8e020edf9cb70ef091633d8a6c189c3f 100644 --- a/services/inputflinger/reader/mapper/VibratorInputMapper.h +++ b/services/inputflinger/reader/mapper/VibratorInputMapper.h @@ -14,8 +14,7 @@ * limitations under the License. */ -#ifndef _UI_INPUTREADER_VIBRATOR_INPUT_MAPPER_H -#define _UI_INPUTREADER_VIBRATOR_INPUT_MAPPER_H +#pragma once #include "InputMapper.h" @@ -23,18 +22,22 @@ namespace android { class VibratorInputMapper : public InputMapper { public: - explicit VibratorInputMapper(InputDeviceContext& deviceContext); + template + friend std::unique_ptr createInputMapper(InputDeviceContext& deviceContext, + const InputReaderConfiguration& readerConfig, + Args... args); virtual ~VibratorInputMapper(); virtual uint32_t getSources() const override; - virtual void populateDeviceInfo(InputDeviceInfo* deviceInfo) override; - virtual void process(const RawEvent* rawEvent) override; + virtual void populateDeviceInfo(InputDeviceInfo& deviceInfo) override; + [[nodiscard]] std::list process(const RawEvent* rawEvent) override; - virtual void vibrate(const VibrationSequence& sequence, ssize_t repeat, int32_t token) override; - virtual void cancelVibrate(int32_t token) override; + [[nodiscard]] std::list vibrate(const VibrationSequence& sequence, ssize_t repeat, + int32_t token) override; + [[nodiscard]] std::list cancelVibrate(int32_t token) override; virtual bool isVibrating() override; virtual std::vector getVibratorIds() override; - virtual void timeoutExpired(nsecs_t when) override; + [[nodiscard]] std::list timeoutExpired(nsecs_t when) override; virtual void dump(std::string& dump) override; private: @@ -45,10 +48,10 @@ private: ssize_t mIndex; nsecs_t mNextStepTime; - void nextStep(); - void stopVibrating(); + explicit VibratorInputMapper(InputDeviceContext& deviceContext, + const InputReaderConfiguration& readerConfig); + [[nodiscard]] std::list nextStep(); + [[nodiscard]] NotifyVibratorStateArgs stopVibrating(); }; } // namespace android - -#endif // _UI_INPUTREADER_VIBRATOR_INPUT_MAPPER_H \ No newline at end of file diff --git a/services/inputflinger/reader/mapper/accumulator/CursorButtonAccumulator.cpp b/services/inputflinger/reader/mapper/accumulator/CursorButtonAccumulator.cpp index 2d7d73b4a3cf342dd1e2d45e775a5e0a6709ce75..153236c177fcaa98635db1744ff7214a9299f91d 100644 --- a/services/inputflinger/reader/mapper/accumulator/CursorButtonAccumulator.cpp +++ b/services/inputflinger/reader/mapper/accumulator/CursorButtonAccumulator.cpp @@ -25,7 +25,7 @@ CursorButtonAccumulator::CursorButtonAccumulator() { clearButtons(); } -void CursorButtonAccumulator::reset(InputDeviceContext& deviceContext) { +void CursorButtonAccumulator::reset(const InputDeviceContext& deviceContext) { mBtnLeft = deviceContext.isKeyPressed(BTN_LEFT); mBtnRight = deviceContext.isKeyPressed(BTN_RIGHT); mBtnMiddle = deviceContext.isKeyPressed(BTN_MIDDLE); diff --git a/services/inputflinger/reader/mapper/accumulator/CursorButtonAccumulator.h b/services/inputflinger/reader/mapper/accumulator/CursorButtonAccumulator.h index 9e159064fa55c7cd6708dfc0a9be27f82213525a..6960644a1d49da08efb42620ac1d2e7bf3a8cb59 100644 --- a/services/inputflinger/reader/mapper/accumulator/CursorButtonAccumulator.h +++ b/services/inputflinger/reader/mapper/accumulator/CursorButtonAccumulator.h @@ -14,8 +14,7 @@ * limitations under the License. */ -#ifndef _UI_INPUTREADER_CURSOR_BUTTON_ACCUMULATOR_H -#define _UI_INPUTREADER_CURSOR_BUTTON_ACCUMULATOR_H +#pragma once #include @@ -28,11 +27,19 @@ struct RawEvent; class CursorButtonAccumulator { public: CursorButtonAccumulator(); - void reset(InputDeviceContext& deviceContext); + void reset(const InputDeviceContext& deviceContext); void process(const RawEvent* rawEvent); uint32_t getButtonState() const; + inline bool isLeftPressed() const { return mBtnLeft; } + inline bool isRightPressed() const { return mBtnRight; } + inline bool isMiddlePressed() const { return mBtnMiddle; } + inline bool isBackPressed() const { return mBtnBack; } + inline bool isSidePressed() const { return mBtnSide; } + inline bool isForwardPressed() const { return mBtnForward; } + inline bool isExtraPressed() const { return mBtnExtra; } + inline bool isTaskPressed() const { return mBtnTask; } private: bool mBtnLeft; @@ -48,5 +55,3 @@ private: }; } // namespace android - -#endif // _UI_INPUTREADER_CURSOR_BUTTON_ACCUMULATOR_H \ No newline at end of file diff --git a/services/inputflinger/reader/mapper/accumulator/CursorScrollAccumulator.h b/services/inputflinger/reader/mapper/accumulator/CursorScrollAccumulator.h index 16495599d7dc3ec6656460146e10cbaa3cf7d5b6..ae1b7a32f4affa984e437287825636f3400f0a7a 100644 --- a/services/inputflinger/reader/mapper/accumulator/CursorScrollAccumulator.h +++ b/services/inputflinger/reader/mapper/accumulator/CursorScrollAccumulator.h @@ -14,8 +14,7 @@ * limitations under the License. */ -#ifndef _UI_INPUTREADER_CURSOR_SCROLL_ACCUMULATOR_H -#define _UI_INPUTREADER_CURSOR_SCROLL_ACCUMULATOR_H +#pragma once #include @@ -56,5 +55,3 @@ private: }; } // namespace android - -#endif // _UI_INPUTREADER_CURSOR_SCROLL_ACCUMULATOR_H \ No newline at end of file diff --git a/services/inputflinger/reader/mapper/accumulator/HidUsageAccumulator.cpp b/services/inputflinger/reader/mapper/accumulator/HidUsageAccumulator.cpp new file mode 100644 index 0000000000000000000000000000000000000000..2da1d814fa0d52eb0025d335e087d0fc4aff966b --- /dev/null +++ b/services/inputflinger/reader/mapper/accumulator/HidUsageAccumulator.cpp @@ -0,0 +1,39 @@ +/* + * 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. + */ + +#include "HidUsageAccumulator.h" + +namespace android { + +void HidUsageAccumulator::process(const RawEvent& rawEvent) { + if (rawEvent.type == EV_MSC && rawEvent.code == MSC_SCAN) { + mCurrentHidUsage = rawEvent.value; + return; + } + + if (rawEvent.type == EV_SYN && rawEvent.code == SYN_REPORT) { + reset(); + return; + } +} + +int32_t HidUsageAccumulator::consumeCurrentHidUsage() { + const int32_t currentHidUsage = mCurrentHidUsage; + reset(); + return currentHidUsage; +} + +} // namespace android diff --git a/services/surfaceflinger/ContainerLayer.h b/services/inputflinger/reader/mapper/accumulator/HidUsageAccumulator.h similarity index 56% rename from services/surfaceflinger/ContainerLayer.h rename to services/inputflinger/reader/mapper/accumulator/HidUsageAccumulator.h index 9b7bab1e1dbfbdfb48ba428bf80e007ddf940f08..740a71048326725c69233705ee6254c22ef5b637 100644 --- a/services/surfaceflinger/ContainerLayer.h +++ b/services/inputflinger/reader/mapper/accumulator/HidUsageAccumulator.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2018 The Android Open Source Project + * 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. @@ -13,29 +13,28 @@ * See the License for the specific language governing permissions and * limitations under the License. */ + #pragma once -#include +#include "EventHub.h" #include -#include "Layer.h" - namespace android { -class ContainerLayer : public Layer { +/* Keeps track of the state of currently reported HID usage code. */ +class HidUsageAccumulator { public: - explicit ContainerLayer(const LayerCreationArgs&); - ~ContainerLayer() override; + explicit HidUsageAccumulator() = default; + inline void reset() { *this = HidUsageAccumulator(); } - const char* getType() const override { return "ContainerLayer"; } - bool isVisible() const override; + void process(const RawEvent& rawEvent); - bool isCreatedFromMainThread() const override { return true; } + /* This must be called when processing the `EV_KEY` event. Returns 0 if invalid. */ + int32_t consumeCurrentHidUsage(); -protected: - bool canDrawShadows() const override { return false; } - sp createClone() override; +private: + int32_t mCurrentHidUsage{}; }; } // namespace android diff --git a/services/inputflinger/reader/mapper/accumulator/MultiTouchMotionAccumulator.cpp b/services/inputflinger/reader/mapper/accumulator/MultiTouchMotionAccumulator.cpp new file mode 100644 index 0000000000000000000000000000000000000000..f70be727418e22f5f0eb8db7803a0222b6a99aa4 --- /dev/null +++ b/services/inputflinger/reader/mapper/accumulator/MultiTouchMotionAccumulator.cpp @@ -0,0 +1,171 @@ +/* + * 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. + */ + +// clang-format off +#include "../Macros.h" +// clang-format on +#include "MultiTouchMotionAccumulator.h" + +namespace android { + +// --- MultiTouchMotionAccumulator --- + +MultiTouchMotionAccumulator::MultiTouchMotionAccumulator() + : mCurrentSlot(-1), mUsingSlotsProtocol(false) {} + +void MultiTouchMotionAccumulator::configure(const InputDeviceContext& deviceContext, + size_t slotCount, bool usingSlotsProtocol) { + mUsingSlotsProtocol = usingSlotsProtocol; + mSlots = std::vector(slotCount); + + mCurrentSlot = -1; + if (mUsingSlotsProtocol) { + // Query the driver for the current slot index and use it as the initial slot before we + // start reading events from the device. It is possible that the current slot index will + // not be the same as it was when the first event was written into the evdev buffer, which + // means the input mapper could start out of sync with the initial state of the events in + // the evdev buffer. In the extremely unlikely case that this happens, the data from two + // slots will be confused until the next ABS_MT_SLOT event is received. This can cause the + // touch point to "jump", but at least there will be no stuck touches. + int32_t initialSlot; + if (const auto status = deviceContext.getAbsoluteAxisValue(ABS_MT_SLOT, &initialSlot); + status == OK) { + mCurrentSlot = initialSlot; + } else { + ALOGD("Could not retrieve current multi-touch slot index. status=%d", status); + } + } +} + +void MultiTouchMotionAccumulator::resetSlots() { + for (Slot& slot : mSlots) { + slot.clear(); + } + mCurrentSlot = -1; +} + +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; + newSlot = true; + } + } else if (mCurrentSlot < 0) { + mCurrentSlot = 0; + } + + if (mCurrentSlot < 0 || size_t(mCurrentSlot) >= mSlots.size()) { + if (newSlot) { + ALOGW_IF(DEBUG_POINTERS, + "MultiTouch device emitted invalid slot index %d but it " + "should be between 0 and %zd; ignoring this slot.", + mCurrentSlot, mSlots.size() - 1); + } + } else { + Slot& slot = mSlots[mCurrentSlot]; + // If mUsingSlotsProtocol is true, it means the raw pointer has axis info of + // ABS_MT_TRACKING_ID and ABS_MT_SLOT, so driver should send a valid trackingId while + // updating the slot. + if (!mUsingSlotsProtocol) { + slot.mInUse = true; + } + + switch (rawEvent->code) { + case ABS_MT_POSITION_X: + slot.mAbsMtPositionX = rawEvent->value; + warnIfNotInUse(*rawEvent, slot); + break; + case ABS_MT_POSITION_Y: + slot.mAbsMtPositionY = rawEvent->value; + warnIfNotInUse(*rawEvent, slot); + break; + case ABS_MT_TOUCH_MAJOR: + slot.mAbsMtTouchMajor = rawEvent->value; + break; + case ABS_MT_TOUCH_MINOR: + slot.mAbsMtTouchMinor = rawEvent->value; + slot.mHaveAbsMtTouchMinor = true; + break; + case ABS_MT_WIDTH_MAJOR: + slot.mAbsMtWidthMajor = rawEvent->value; + break; + case ABS_MT_WIDTH_MINOR: + slot.mAbsMtWidthMinor = rawEvent->value; + slot.mHaveAbsMtWidthMinor = true; + break; + case ABS_MT_ORIENTATION: + slot.mAbsMtOrientation = rawEvent->value; + break; + case ABS_MT_TRACKING_ID: + if (mUsingSlotsProtocol && rawEvent->value < 0) { + // The slot is no longer in use but it retains its previous contents, + // which may be reused for subsequent touches. + slot.mInUse = false; + } else { + slot.mInUse = true; + slot.mAbsMtTrackingId = rawEvent->value; + } + break; + case ABS_MT_PRESSURE: + slot.mAbsMtPressure = rawEvent->value; + break; + case ABS_MT_DISTANCE: + slot.mAbsMtDistance = rawEvent->value; + break; + case ABS_MT_TOOL_TYPE: + slot.mAbsMtToolType = rawEvent->value; + slot.mHaveAbsMtToolType = true; + break; + } + } + } 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; + } +} + +void MultiTouchMotionAccumulator::finishSync() { + if (!mUsingSlotsProtocol) { + resetSlots(); + } +} + +void MultiTouchMotionAccumulator::warnIfNotInUse(const RawEvent& event, const Slot& slot) { + if (!slot.mInUse) { + ALOGW("Received unexpected event (0x%0x, 0x%0x) for slot %i with tracking id %i", + event.code, event.value, mCurrentSlot, slot.mAbsMtTrackingId); + } +} + +// --- MultiTouchMotionAccumulator::Slot --- + +ToolType MultiTouchMotionAccumulator::Slot::getToolType() const { + if (mHaveAbsMtToolType) { + switch (mAbsMtToolType) { + case MT_TOOL_FINGER: + return ToolType::FINGER; + case MT_TOOL_PEN: + return ToolType::STYLUS; + case MT_TOOL_PALM: + return ToolType::PALM; + } + } + return ToolType::UNKNOWN; +} + +} // namespace android diff --git a/services/inputflinger/reader/mapper/accumulator/MultiTouchMotionAccumulator.h b/services/inputflinger/reader/mapper/accumulator/MultiTouchMotionAccumulator.h new file mode 100644 index 0000000000000000000000000000000000000000..943dde5ca2af3586943a2197745e56924dc22922 --- /dev/null +++ b/services/inputflinger/reader/mapper/accumulator/MultiTouchMotionAccumulator.h @@ -0,0 +1,95 @@ +/* + * 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. + */ + +#pragma once + +#include +#include +#include + +#include "EventHub.h" +#include "InputDevice.h" + +namespace android { + +/* Keeps track of the state of multi-touch protocol. */ +class MultiTouchMotionAccumulator { +public: + class Slot { + public: + inline bool isInUse() const { return mInUse; } + inline int32_t getX() const { return mAbsMtPositionX; } + inline int32_t getY() const { return mAbsMtPositionY; } + inline int32_t getTouchMajor() const { return mAbsMtTouchMajor; } + inline int32_t getTouchMinor() const { + return mHaveAbsMtTouchMinor ? mAbsMtTouchMinor : mAbsMtTouchMajor; + } + inline int32_t getToolMajor() const { return mAbsMtWidthMajor; } + inline int32_t getToolMinor() const { + return mHaveAbsMtWidthMinor ? mAbsMtWidthMinor : mAbsMtWidthMajor; + } + inline int32_t getOrientation() const { return mAbsMtOrientation; } + inline int32_t getTrackingId() const { return mAbsMtTrackingId; } + inline int32_t getPressure() const { return mAbsMtPressure; } + inline int32_t getDistance() const { return mAbsMtDistance; } + ToolType getToolType() const; + + private: + friend class MultiTouchMotionAccumulator; + + bool mInUse = false; + bool mHaveAbsMtTouchMinor = false; + bool mHaveAbsMtWidthMinor = false; + bool mHaveAbsMtToolType = false; + + int32_t mAbsMtPositionX = 0; + int32_t mAbsMtPositionY = 0; + int32_t mAbsMtTouchMajor = 0; + int32_t mAbsMtTouchMinor = 0; + int32_t mAbsMtWidthMajor = 0; + int32_t mAbsMtWidthMinor = 0; + int32_t mAbsMtOrientation = 0; + int32_t mAbsMtTrackingId = -1; + int32_t mAbsMtPressure = 0; + int32_t mAbsMtDistance = 0; + int32_t mAbsMtToolType = 0; + + void clear() { *this = Slot(); } + }; + + MultiTouchMotionAccumulator(); + + void configure(const InputDeviceContext& deviceContext, size_t slotCount, + bool usingSlotsProtocol); + void process(const RawEvent* rawEvent); + void finishSync(); + + inline size_t getSlotCount() const { return mSlots.size(); } + inline const Slot& getSlot(size_t index) const { + LOG_ALWAYS_FATAL_IF(index < 0 || index >= mSlots.size(), "Invalid index: %zu", index); + return mSlots[index]; + } + +private: + int32_t mCurrentSlot; + std::vector mSlots; + bool mUsingSlotsProtocol; + + void resetSlots(); + void warnIfNotInUse(const RawEvent& event, const Slot& slot); +}; + +} // namespace android diff --git a/services/inputflinger/reader/mapper/accumulator/SingleTouchMotionAccumulator.h b/services/inputflinger/reader/mapper/accumulator/SingleTouchMotionAccumulator.h index 4c011f16a743161ade7e8a73e75fad7c40733d46..93056f06e61c80a994dbacaffb51295b3f7af0eb 100644 --- a/services/inputflinger/reader/mapper/accumulator/SingleTouchMotionAccumulator.h +++ b/services/inputflinger/reader/mapper/accumulator/SingleTouchMotionAccumulator.h @@ -14,8 +14,7 @@ * limitations under the License. */ -#ifndef _UI_INPUTREADER_SINGLE_TOUCH_MOTION_ACCUMULATOR_H -#define _UI_INPUTREADER_SINGLE_TOUCH_MOTION_ACCUMULATOR_H +#pragma once #include @@ -53,5 +52,3 @@ private: }; } // namespace android - -#endif // _UI_INPUTREADER_SINGLE_TOUCH_MOTION_ACCUMULATOR_H \ No newline at end of file diff --git a/services/inputflinger/reader/mapper/accumulator/TouchButtonAccumulator.cpp b/services/inputflinger/reader/mapper/accumulator/TouchButtonAccumulator.cpp index 86153d3f5e1a4bab7c57c3db8978d23c0e350074..8c4bed32677597c84eb5cd3e80e352ae8c95d64c 100644 --- a/services/inputflinger/reader/mapper/accumulator/TouchButtonAccumulator.cpp +++ b/services/inputflinger/reader/mapper/accumulator/TouchButtonAccumulator.cpp @@ -21,55 +21,40 @@ namespace android { -TouchButtonAccumulator::TouchButtonAccumulator() : mHaveBtnTouch(false), mHaveStylus(false) { - clearButtons(); +void TouchButtonAccumulator::configure() { + mHaveBtnTouch = mDeviceContext.hasScanCode(BTN_TOUCH); + mHaveStylus = mDeviceContext.hasScanCode(BTN_TOOL_PEN) || + mDeviceContext.hasScanCode(BTN_TOOL_RUBBER) || + mDeviceContext.hasScanCode(BTN_TOOL_BRUSH) || + mDeviceContext.hasScanCode(BTN_TOOL_PENCIL) || + mDeviceContext.hasScanCode(BTN_TOOL_AIRBRUSH); } -void TouchButtonAccumulator::configure(InputDeviceContext& deviceContext) { - mHaveBtnTouch = deviceContext.hasScanCode(BTN_TOUCH); - mHaveStylus = deviceContext.hasScanCode(BTN_TOOL_PEN) || - deviceContext.hasScanCode(BTN_TOOL_RUBBER) || - deviceContext.hasScanCode(BTN_TOOL_BRUSH) || - deviceContext.hasScanCode(BTN_TOOL_PENCIL) || - deviceContext.hasScanCode(BTN_TOOL_AIRBRUSH); -} - -void TouchButtonAccumulator::reset(InputDeviceContext& deviceContext) { - mBtnTouch = deviceContext.isKeyPressed(BTN_TOUCH); - mBtnStylus = deviceContext.isKeyPressed(BTN_STYLUS); +void TouchButtonAccumulator::reset() { + mBtnTouch = mDeviceContext.isKeyPressed(BTN_TOUCH); + mBtnStylus = mDeviceContext.isKeyPressed(BTN_STYLUS) || + mDeviceContext.isKeyCodePressed(AKEYCODE_STYLUS_BUTTON_PRIMARY); // BTN_0 is what gets mapped for the HID usage Digitizers.SecondaryBarrelSwitch - mBtnStylus2 = deviceContext.isKeyPressed(BTN_STYLUS2) || deviceContext.isKeyPressed(BTN_0); - mBtnToolFinger = deviceContext.isKeyPressed(BTN_TOOL_FINGER); - mBtnToolPen = deviceContext.isKeyPressed(BTN_TOOL_PEN); - mBtnToolRubber = deviceContext.isKeyPressed(BTN_TOOL_RUBBER); - mBtnToolBrush = deviceContext.isKeyPressed(BTN_TOOL_BRUSH); - mBtnToolPencil = deviceContext.isKeyPressed(BTN_TOOL_PENCIL); - mBtnToolAirbrush = deviceContext.isKeyPressed(BTN_TOOL_AIRBRUSH); - mBtnToolMouse = deviceContext.isKeyPressed(BTN_TOOL_MOUSE); - mBtnToolLens = deviceContext.isKeyPressed(BTN_TOOL_LENS); - mBtnToolDoubleTap = deviceContext.isKeyPressed(BTN_TOOL_DOUBLETAP); - mBtnToolTripleTap = deviceContext.isKeyPressed(BTN_TOOL_TRIPLETAP); - mBtnToolQuadTap = deviceContext.isKeyPressed(BTN_TOOL_QUADTAP); -} - -void TouchButtonAccumulator::clearButtons() { - mBtnTouch = 0; - mBtnStylus = 0; - mBtnStylus2 = 0; - mBtnToolFinger = 0; - mBtnToolPen = 0; - mBtnToolRubber = 0; - mBtnToolBrush = 0; - mBtnToolPencil = 0; - mBtnToolAirbrush = 0; - mBtnToolMouse = 0; - mBtnToolLens = 0; - mBtnToolDoubleTap = 0; - mBtnToolTripleTap = 0; - mBtnToolQuadTap = 0; + mBtnStylus2 = mDeviceContext.isKeyPressed(BTN_STYLUS2) || mDeviceContext.isKeyPressed(BTN_0) || + mDeviceContext.isKeyCodePressed(AKEYCODE_STYLUS_BUTTON_SECONDARY); + mBtnToolFinger = mDeviceContext.isKeyPressed(BTN_TOOL_FINGER); + mBtnToolPen = mDeviceContext.isKeyPressed(BTN_TOOL_PEN); + mBtnToolRubber = mDeviceContext.isKeyPressed(BTN_TOOL_RUBBER); + mBtnToolBrush = mDeviceContext.isKeyPressed(BTN_TOOL_BRUSH); + mBtnToolPencil = mDeviceContext.isKeyPressed(BTN_TOOL_PENCIL); + mBtnToolAirbrush = mDeviceContext.isKeyPressed(BTN_TOOL_AIRBRUSH); + mBtnToolMouse = mDeviceContext.isKeyPressed(BTN_TOOL_MOUSE); + mBtnToolLens = mDeviceContext.isKeyPressed(BTN_TOOL_LENS); + mBtnToolDoubleTap = mDeviceContext.isKeyPressed(BTN_TOOL_DOUBLETAP); + mBtnToolTripleTap = mDeviceContext.isKeyPressed(BTN_TOOL_TRIPLETAP); + mBtnToolQuadTap = mDeviceContext.isKeyPressed(BTN_TOOL_QUADTAP); + mBtnToolQuintTap = mDeviceContext.isKeyPressed(BTN_TOOL_QUINTTAP); + mHidUsageAccumulator.reset(); } void TouchButtonAccumulator::process(const RawEvent* rawEvent) { + mHidUsageAccumulator.process(*rawEvent); + if (rawEvent->type == EV_KEY) { switch (rawEvent->code) { case BTN_TOUCH: @@ -116,7 +101,32 @@ void TouchButtonAccumulator::process(const RawEvent* rawEvent) { case BTN_TOOL_QUADTAP: mBtnToolQuadTap = rawEvent->value; break; + case BTN_TOOL_QUINTTAP: + mBtnToolQuintTap = rawEvent->value; + break; + default: + processMappedKey(rawEvent->code, rawEvent->value); } + return; + } +} + +void TouchButtonAccumulator::processMappedKey(int32_t scanCode, bool down) { + int32_t keyCode, metaState; + uint32_t flags; + if (mDeviceContext.mapKey(scanCode, mHidUsageAccumulator.consumeCurrentHidUsage(), + /*metaState=*/0, &keyCode, &metaState, &flags) != OK) { + return; + } + switch (keyCode) { + case AKEYCODE_STYLUS_BUTTON_PRIMARY: + mBtnStylus = down; + break; + case AKEYCODE_STYLUS_BUTTON_SECONDARY: + mBtnStylus2 = down; + break; + default: + break; } } @@ -131,26 +141,27 @@ uint32_t TouchButtonAccumulator::getButtonState() const { return result; } -int32_t TouchButtonAccumulator::getToolType() const { +ToolType TouchButtonAccumulator::getToolType() const { if (mBtnToolMouse || mBtnToolLens) { - return AMOTION_EVENT_TOOL_TYPE_MOUSE; + return ToolType::MOUSE; } if (mBtnToolRubber) { - return AMOTION_EVENT_TOOL_TYPE_ERASER; + return ToolType::ERASER; } if (mBtnToolPen || mBtnToolBrush || mBtnToolPencil || mBtnToolAirbrush) { - return AMOTION_EVENT_TOOL_TYPE_STYLUS; + return ToolType::STYLUS; } - if (mBtnToolFinger || mBtnToolDoubleTap || mBtnToolTripleTap || mBtnToolQuadTap) { - return AMOTION_EVENT_TOOL_TYPE_FINGER; + if (mBtnToolFinger || mBtnToolDoubleTap || mBtnToolTripleTap || mBtnToolQuadTap || + mBtnToolQuintTap) { + return ToolType::FINGER; } - return AMOTION_EVENT_TOOL_TYPE_UNKNOWN; + return ToolType::UNKNOWN; } bool TouchButtonAccumulator::isToolActive() const { return mBtnTouch || mBtnToolFinger || mBtnToolPen || mBtnToolRubber || mBtnToolBrush || mBtnToolPencil || mBtnToolAirbrush || mBtnToolMouse || mBtnToolLens || - mBtnToolDoubleTap || mBtnToolTripleTap || mBtnToolQuadTap; + mBtnToolDoubleTap || mBtnToolTripleTap || mBtnToolQuadTap || mBtnToolQuintTap; } bool TouchButtonAccumulator::isHovering() const { @@ -161,4 +172,19 @@ bool TouchButtonAccumulator::hasStylus() const { return mHaveStylus; } +bool TouchButtonAccumulator::hasButtonTouch() const { + return mHaveBtnTouch; +} + +int TouchButtonAccumulator::getTouchCount() const { + if (mBtnTouch) { + if (mBtnToolQuintTap) return 5; + if (mBtnToolQuadTap) return 4; + if (mBtnToolTripleTap) return 3; + if (mBtnToolDoubleTap) return 2; + if (mBtnToolFinger) return 1; + } + return 0; +} + } // namespace android diff --git a/services/inputflinger/reader/mapper/accumulator/TouchButtonAccumulator.h b/services/inputflinger/reader/mapper/accumulator/TouchButtonAccumulator.h index 22ebb720d55727c452939ac8159a47e5be6b9a0c..e829692206b3680f003180ad8a63463e0f94b1e3 100644 --- a/services/inputflinger/reader/mapper/accumulator/TouchButtonAccumulator.h +++ b/services/inputflinger/reader/mapper/accumulator/TouchButtonAccumulator.h @@ -14,10 +14,10 @@ * limitations under the License. */ -#ifndef _UI_INPUTREADER_TOUCH_BUTTON_ACCUMULATOR_H -#define _UI_INPUTREADER_TOUCH_BUTTON_ACCUMULATOR_H +#pragma once -#include +#include +#include "HidUsageAccumulator.h" namespace android { @@ -27,40 +27,53 @@ struct RawEvent; /* Keeps track of the state of touch, stylus and tool buttons. */ class TouchButtonAccumulator { public: - TouchButtonAccumulator(); - void configure(InputDeviceContext& deviceContext); - void reset(InputDeviceContext& deviceContext); + explicit TouchButtonAccumulator(const InputDeviceContext& deviceContext) + : mDeviceContext(deviceContext){}; + + void configure(); + void reset(); void process(const RawEvent* rawEvent); uint32_t getButtonState() const; - int32_t getToolType() const; + ToolType getToolType() const; bool isToolActive() const; bool isHovering() const; bool hasStylus() const; + bool hasButtonTouch() const; + + /* + * Returns the number of touches reported by the device through its BTN_TOOL_FINGER and + * BTN_TOOL_*TAP "buttons". Note that this count includes touches reported with their + * ABS_MT_TOOL_TYPE set to MT_TOOL_PALM. + */ + int getTouchCount() const; private: - bool mHaveBtnTouch; - bool mHaveStylus; + bool mHaveBtnTouch{}; + bool mHaveStylus{}; + + bool mBtnTouch{}; + bool mBtnStylus{}; + bool mBtnStylus2{}; + bool mBtnToolFinger{}; + bool mBtnToolPen{}; + bool mBtnToolRubber{}; + bool mBtnToolBrush{}; + bool mBtnToolPencil{}; + bool mBtnToolAirbrush{}; + bool mBtnToolMouse{}; + bool mBtnToolLens{}; + bool mBtnToolDoubleTap{}; + bool mBtnToolTripleTap{}; + bool mBtnToolQuadTap{}; + bool mBtnToolQuintTap{}; - bool mBtnTouch; - bool mBtnStylus; - bool mBtnStylus2; - bool mBtnToolFinger; - bool mBtnToolPen; - bool mBtnToolRubber; - bool mBtnToolBrush; - bool mBtnToolPencil; - bool mBtnToolAirbrush; - bool mBtnToolMouse; - bool mBtnToolLens; - bool mBtnToolDoubleTap; - bool mBtnToolTripleTap; - bool mBtnToolQuadTap; + HidUsageAccumulator mHidUsageAccumulator{}; - void clearButtons(); + const InputDeviceContext& mDeviceContext; + + void processMappedKey(int32_t scanCode, bool down); }; } // namespace android - -#endif // _UI_INPUTREADER_TOUCH_BUTTON_ACCUMULATOR_H \ No newline at end of file diff --git a/services/inputflinger/reader/mapper/gestures/GestureConverter.cpp b/services/inputflinger/reader/mapper/gestures/GestureConverter.cpp new file mode 100644 index 0000000000000000000000000000000000000000..7eca6fa0b41f2c9d502c55e67aadc54a275212d4 --- /dev/null +++ b/services/inputflinger/reader/mapper/gestures/GestureConverter.cpp @@ -0,0 +1,556 @@ +/* + * 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 "gestures/GestureConverter.h" + +#include +#include + +#include +#include +#include +#include +#include + +#include "TouchCursorInputMapperCommon.h" +#include "input/Input.h" + +namespace android { + +namespace { + +uint32_t gesturesButtonToMotionEventButton(uint32_t gesturesButton) { + switch (gesturesButton) { + case GESTURES_BUTTON_LEFT: + return AMOTION_EVENT_BUTTON_PRIMARY; + case GESTURES_BUTTON_MIDDLE: + return AMOTION_EVENT_BUTTON_TERTIARY; + case GESTURES_BUTTON_RIGHT: + return AMOTION_EVENT_BUTTON_SECONDARY; + case GESTURES_BUTTON_BACK: + return AMOTION_EVENT_BUTTON_BACK; + case GESTURES_BUTTON_FORWARD: + return AMOTION_EVENT_BUTTON_FORWARD; + default: + return 0; + } +} + +} // namespace + +GestureConverter::GestureConverter(InputReaderContext& readerContext, + const InputDeviceContext& deviceContext, int32_t deviceId) + : mDeviceId(deviceId), + mReaderContext(readerContext), + mPointerController(readerContext.getPointerController(deviceId)) { + deviceContext.getAbsoluteAxisInfo(ABS_MT_POSITION_X, &mXAxisInfo); + deviceContext.getAbsoluteAxisInfo(ABS_MT_POSITION_Y, &mYAxisInfo); +} + +std::string GestureConverter::dump() const { + std::stringstream out; + out << "Orientation: " << ftl::enum_string(mOrientation) << "\n"; + out << "Axis info:\n"; + out << " X: " << mXAxisInfo << "\n"; + out << " Y: " << mYAxisInfo << "\n"; + out << StringPrintf("Button state: 0x%08x\n", mButtonState); + out << "Down time: " << mDownTime << "\n"; + out << "Current classification: " << ftl::enum_string(mCurrentClassification) << "\n"; + return out.str(); +} + +std::list GestureConverter::reset(nsecs_t when) { + std::list out; + switch (mCurrentClassification) { + case MotionClassification::TWO_FINGER_SWIPE: + out.push_back(endScroll(when, when)); + break; + case MotionClassification::MULTI_FINGER_SWIPE: + out += handleMultiFingerSwipeLift(when, when); + break; + case MotionClassification::PINCH: + out += endPinch(when, when); + break; + case MotionClassification::NONE: + // When a button is pressed, the Gestures library always ends the current gesture, + // so we don't have to worry about the case where buttons need to be lifted during a + // pinch or swipe. + if (mButtonState) { + out += releaseAllButtons(when, when); + } + break; + default: + break; + } + mCurrentClassification = MotionClassification::NONE; + mDownTime = 0; + return out; +} + +void GestureConverter::populateMotionRanges(InputDeviceInfo& info) const { + info.addMotionRange(AMOTION_EVENT_AXIS_PRESSURE, SOURCE, 0.0f, 1.0f, 0, 0, 0); + + // TODO(b/259547750): set this using the raw axis ranges from the touchpad when pointer capture + // is enabled. + if (std::optional rect = mPointerController->getBounds(); rect.has_value()) { + info.addMotionRange(AMOTION_EVENT_AXIS_X, SOURCE, rect->left, rect->right, 0, 0, 0); + info.addMotionRange(AMOTION_EVENT_AXIS_Y, SOURCE, rect->top, rect->bottom, 0, 0, 0); + } + + info.addMotionRange(AMOTION_EVENT_AXIS_GESTURE_X_OFFSET, SOURCE, -1.0f, 1.0f, 0, 0, 0); + info.addMotionRange(AMOTION_EVENT_AXIS_GESTURE_Y_OFFSET, SOURCE, -1.0f, 1.0f, 0, 0, 0); + + // The other axes that can be reported don't have ranges that are easy to define. RELATIVE_X/Y + // and GESTURE_SCROLL_X/Y_DISTANCE are the result of acceleration functions being applied to + // finger movements, so their maximum values can't simply be derived from the size of the + // touchpad. GESTURE_PINCH_SCALE_FACTOR's maximum value depends on the minimum finger separation + // that the pad can report, which cannot be determined from its raw axis information. (Assuming + // a minimum finger separation of 1 unit would let us calculate a theoretical maximum, but it + // would be orders of magnitude too high, so probably not very useful.) +} + +std::list GestureConverter::handleGesture(nsecs_t when, nsecs_t readTime, + const Gesture& gesture) { + switch (gesture.type) { + case kGestureTypeMove: + return {handleMove(when, readTime, gesture)}; + case kGestureTypeButtonsChange: + return handleButtonsChange(when, readTime, gesture); + case kGestureTypeScroll: + return handleScroll(when, readTime, gesture); + case kGestureTypeFling: + return handleFling(when, readTime, gesture); + case kGestureTypeSwipe: + return handleMultiFingerSwipe(when, readTime, 3, gesture.details.swipe.dx, + gesture.details.swipe.dy); + case kGestureTypeFourFingerSwipe: + return handleMultiFingerSwipe(when, readTime, 4, gesture.details.four_finger_swipe.dx, + gesture.details.four_finger_swipe.dy); + case kGestureTypeSwipeLift: + case kGestureTypeFourFingerSwipeLift: + return handleMultiFingerSwipeLift(when, readTime); + case kGestureTypePinch: + return handlePinch(when, readTime, gesture); + default: + return {}; + } +} + +NotifyMotionArgs GestureConverter::handleMove(nsecs_t when, nsecs_t readTime, + const Gesture& gesture) { + float deltaX = gesture.details.move.dx; + float deltaY = gesture.details.move.dy; + rotateDelta(mOrientation, &deltaX, &deltaY); + + mPointerController->setPresentation(PointerControllerInterface::Presentation::POINTER); + mPointerController->move(deltaX, deltaY); + 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, deltaX); + coords.setAxisValue(AMOTION_EVENT_AXIS_RELATIVE_Y, deltaY); + const bool down = isPointerDown(mButtonState); + 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; + return makeMotionArgs(when, readTime, action, /* actionButton= */ 0, mButtonState, + /* pointerCount= */ 1, mFingerProps.data(), &coords, xCursorPosition, + yCursorPosition); +} + +std::list GestureConverter::handleButtonsChange(nsecs_t when, nsecs_t readTime, + 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); + const uint32_t buttonsPressed = gesture.details.buttons.down; + bool pointerDown = isPointerDown(mButtonState) || + buttonsPressed & + (GESTURES_BUTTON_LEFT | GESTURES_BUTTON_MIDDLE | GESTURES_BUTTON_RIGHT); + coords.setAxisValue(AMOTION_EVENT_AXIS_PRESSURE, pointerDown ? 1.0f : 0.0f); + + uint32_t newButtonState = mButtonState; + std::list pressEvents = {}; + for (uint32_t button = 1; button <= GESTURES_BUTTON_FORWARD; button <<= 1) { + if (buttonsPressed & button) { + uint32_t actionButton = gesturesButtonToMotionEventButton(button); + newButtonState |= actionButton; + pressEvents.push_back(makeMotionArgs(when, readTime, AMOTION_EVENT_ACTION_BUTTON_PRESS, + actionButton, newButtonState, + /* pointerCount= */ 1, mFingerProps.data(), + &coords, xCursorPosition, yCursorPosition)); + } + } + if (!isPointerDown(mButtonState) && isPointerDown(newButtonState)) { + mDownTime = when; + out.push_back(makeMotionArgs(when, readTime, AMOTION_EVENT_ACTION_DOWN, + /* actionButton= */ 0, newButtonState, /* pointerCount= */ 1, + mFingerProps.data(), &coords, xCursorPosition, + yCursorPosition)); + } + out.splice(out.end(), pressEvents); + + // The same button may be in both down and up in the same gesture, in which case we should treat + // it as having gone down and then up. So, we treat a single button change gesture as two state + // changes: a set of buttons going down, followed by a set of buttons going up. + mButtonState = newButtonState; + + const uint32_t buttonsReleased = gesture.details.buttons.up; + for (uint32_t button = 1; button <= GESTURES_BUTTON_FORWARD; button <<= 1) { + if (buttonsReleased & button) { + uint32_t actionButton = gesturesButtonToMotionEventButton(button); + newButtonState &= ~actionButton; + out.push_back(makeMotionArgs(when, readTime, AMOTION_EVENT_ACTION_BUTTON_RELEASE, + actionButton, newButtonState, /* pointerCount= */ 1, + mFingerProps.data(), &coords, xCursorPosition, + yCursorPosition)); + } + } + 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, mFingerProps.data(), + &coords, xCursorPosition, yCursorPosition)); + } + mButtonState = newButtonState; + return out; +} + +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); + coords.setAxisValue(AMOTION_EVENT_AXIS_PRESSURE, pointerDown ? 1.0f : 0.0f); + uint32_t newButtonState = mButtonState; + for (uint32_t button = AMOTION_EVENT_BUTTON_PRIMARY; button <= AMOTION_EVENT_BUTTON_FORWARD; + button <<= 1) { + if (mButtonState & button) { + newButtonState &= ~button; + out.push_back(makeMotionArgs(when, readTime, AMOTION_EVENT_ACTION_BUTTON_RELEASE, + button, newButtonState, /*pointerCount=*/1, + mFingerProps.data(), &coords, xCursorPosition, + yCursorPosition)); + } + } + if (pointerDown) { + coords.setAxisValue(AMOTION_EVENT_AXIS_PRESSURE, 0.0f); + out.push_back(makeMotionArgs(when, readTime, AMOTION_EVENT_ACTION_UP, /*actionButton=*/0, + newButtonState, /*pointerCount=*/1, mFingerProps.data(), + &coords, xCursorPosition, yCursorPosition)); + } + mButtonState = 0; + return out; +} + +std::list GestureConverter::handleScroll(nsecs_t when, nsecs_t readTime, + const Gesture& gesture) { + std::list out; + PointerCoords& coords = mFakeFingerCoords[0]; + const auto [xCursorPosition, yCursorPosition] = mPointerController->getPosition(); + if (mCurrentClassification != MotionClassification::TWO_FINGER_SWIPE) { + 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, mFingerProps.data(), + mFakeFingerCoords.data(), xCursorPosition, yCursorPosition); + args.flags |= AMOTION_EVENT_FLAG_IS_GENERATED_GESTURE; + out.push_back(args); + } + float deltaX = gesture.details.scroll.dx; + float deltaY = gesture.details.scroll.dy; + rotateDelta(mOrientation, &deltaX, &deltaY); + + coords.setAxisValue(AMOTION_EVENT_AXIS_X, coords.getAxisValue(AMOTION_EVENT_AXIS_X) + deltaX); + coords.setAxisValue(AMOTION_EVENT_AXIS_Y, coords.getAxisValue(AMOTION_EVENT_AXIS_Y) + deltaY); + // TODO(b/262876643): set AXIS_GESTURE_{X,Y}_OFFSET. + coords.setAxisValue(AMOTION_EVENT_AXIS_GESTURE_SCROLL_X_DISTANCE, -gesture.details.scroll.dx); + 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, mFingerProps.data(), + mFakeFingerCoords.data(), xCursorPosition, yCursorPosition); + args.flags |= AMOTION_EVENT_FLAG_IS_GENERATED_GESTURE; + out.push_back(args); + return out; +} + +std::list GestureConverter::handleFling(nsecs_t when, nsecs_t readTime, + const Gesture& gesture) { + switch (gesture.details.fling.fling_state) { + case GESTURES_FLING_START: + if (mCurrentClassification == MotionClassification::TWO_FINGER_SWIPE) { + // We don't actually want to use the gestures library's fling velocity values (to + // ensure consistency between touchscreen and touchpad flings), so we're just using + // the "start fling" gestures as a marker for the end of a two-finger scroll + // gesture. + return {endScroll(when, readTime)}; + } + break; + case GESTURES_FLING_TAP_DOWN: + if (mCurrentClassification == MotionClassification::NONE) { + // Use the tap down state of a fling gesture as an indicator that a contact + // has been initiated with the touchpad. We treat this as a move event with zero + // magnitude, which will also result in the pointer icon being updated. + // TODO(b/282023644): Add a signal in libgestures for when a stable contact has been + // initiated with a touchpad. + return {handleMove(when, readTime, + Gesture(kGestureMove, gesture.start_time, gesture.end_time, + /*dx=*/0.f, + /*dy=*/0.f))}; + } + break; + default: + break; + } + + return {}; +} + +NotifyMotionArgs GestureConverter::endScroll(nsecs_t when, nsecs_t readTime) { + 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, mFingerProps.data(), + mFakeFingerCoords.data(), xCursorPosition, yCursorPosition); + args.flags |= AMOTION_EVENT_FLAG_IS_GENERATED_GESTURE; + mCurrentClassification = MotionClassification::NONE; + return args; +} + +[[nodiscard]] std::list GestureConverter::handleMultiFingerSwipe(nsecs_t when, + nsecs_t readTime, + uint32_t fingerCount, + 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. + mCurrentClassification = MotionClassification::MULTI_FINGER_SWIPE; + mSwipeFingerCount = fingerCount; + + constexpr float FAKE_FINGER_SPACING = 100; + float xCoord = xCursorPosition - FAKE_FINGER_SPACING * (mSwipeFingerCount - 1) / 2; + for (size_t i = 0; i < mSwipeFingerCount; i++) { + PointerCoords& coords = mFakeFingerCoords[i]; + coords.clear(); + coords.setAxisValue(AMOTION_EVENT_AXIS_X, xCoord); + coords.setAxisValue(AMOTION_EVENT_AXIS_Y, yCursorPosition); + coords.setAxisValue(AMOTION_EVENT_AXIS_PRESSURE, 1.0f); + xCoord += FAKE_FINGER_SPACING; + } + + mDownTime = when; + out.push_back(makeMotionArgs(when, readTime, AMOTION_EVENT_ACTION_DOWN, + /* actionButton= */ 0, mButtonState, /* pointerCount= */ 1, + mFingerProps.data(), mFakeFingerCoords.data(), xCursorPosition, + yCursorPosition)); + 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, mFingerProps.data(), + mFakeFingerCoords.data(), xCursorPosition, + yCursorPosition)); + } + } + float rotatedDeltaX = dx, rotatedDeltaY = -dy; + rotateDelta(mOrientation, &rotatedDeltaX, &rotatedDeltaY); + for (size_t i = 0; i < mSwipeFingerCount; i++) { + PointerCoords& coords = mFakeFingerCoords[i]; + coords.setAxisValue(AMOTION_EVENT_AXIS_X, + coords.getAxisValue(AMOTION_EVENT_AXIS_X) + rotatedDeltaX); + coords.setAxisValue(AMOTION_EVENT_AXIS_Y, + coords.getAxisValue(AMOTION_EVENT_AXIS_Y) + rotatedDeltaY); + } + float xOffset = dx / (mXAxisInfo.maxValue - mXAxisInfo.minValue); + float yOffset = -dy / (mYAxisInfo.maxValue - mYAxisInfo.minValue); + mFakeFingerCoords[0].setAxisValue(AMOTION_EVENT_AXIS_GESTURE_X_OFFSET, xOffset); + 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, + mFingerProps.data(), mFakeFingerCoords.data(), xCursorPosition, + yCursorPosition)); + return out; +} + +[[nodiscard]] std::list GestureConverter::handleMultiFingerSwipeLift(nsecs_t when, + nsecs_t readTime) { + std::list out = {}; + 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); + + for (size_t i = mSwipeFingerCount; i > 1; i--) { + out.push_back(makeMotionArgs(when, readTime, + AMOTION_EVENT_ACTION_POINTER_UP | + ((i - 1) << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT), + /* actionButton= */ 0, mButtonState, /* pointerCount= */ i, + mFingerProps.data(), mFakeFingerCoords.data(), xCursorPosition, + yCursorPosition)); + } + out.push_back(makeMotionArgs(when, readTime, AMOTION_EVENT_ACTION_UP, + /* actionButton= */ 0, mButtonState, /* pointerCount= */ 1, + mFingerProps.data(), mFakeFingerCoords.data(), xCursorPosition, + yCursorPosition)); + mCurrentClassification = MotionClassification::NONE; + 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 + // those cases. + + if (mCurrentClassification != MotionClassification::PINCH) { + LOG_ALWAYS_FATAL_IF(gesture.details.pinch.zoom_state != GESTURES_ZOOM_START, + "First pinch gesture does not have the START zoom state (%d instead).", + gesture.details.pinch.zoom_state); + mCurrentClassification = MotionClassification::PINCH; + mPinchFingerSeparation = INITIAL_PINCH_SEPARATION_PX; + 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_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_PRESSURE, 1.0f); + mDownTime = when; + std::list out; + out.push_back(makeMotionArgs(when, readTime, AMOTION_EVENT_ACTION_DOWN, + /* actionButton= */ 0, mButtonState, /* pointerCount= */ 1, + mFingerProps.data(), mFakeFingerCoords.data(), xCursorPosition, + yCursorPosition)); + out.push_back(makeMotionArgs(when, readTime, + AMOTION_EVENT_ACTION_POINTER_DOWN | + 1 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT, + /* actionButton= */ 0, mButtonState, /* pointerCount= */ 2, + mFingerProps.data(), mFakeFingerCoords.data(), xCursorPosition, + yCursorPosition)); + return out; + } + + if (gesture.details.pinch.zoom_state == GESTURES_ZOOM_END) { + return endPinch(when, readTime); + } + + mPinchFingerSeparation *= gesture.details.pinch.dz; + 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); + return {makeMotionArgs(when, readTime, AMOTION_EVENT_ACTION_MOVE, /*actionButton=*/0, + mButtonState, /*pointerCount=*/2, mFingerProps.data(), + mFakeFingerCoords.data(), xCursorPosition, yCursorPosition)}; +} + +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, + mFingerProps.data(), mFakeFingerCoords.data(), xCursorPosition, + yCursorPosition)); + out.push_back(makeMotionArgs(when, readTime, AMOTION_EVENT_ACTION_UP, /*actionButton=*/0, + mButtonState, /*pointerCount=*/1, mFingerProps.data(), + mFakeFingerCoords.data(), xCursorPosition, yCursorPosition)); + mCurrentClassification = MotionClassification::NONE; + mFakeFingerCoords[0].setAxisValue(AMOTION_EVENT_AXIS_GESTURE_PINCH_SCALE_FACTOR, 0); + return out; +} + +NotifyMotionArgs GestureConverter::makeMotionArgs(nsecs_t when, nsecs_t readTime, int32_t action, + int32_t actionButton, int32_t buttonState, + uint32_t pointerCount, + const PointerProperties* pointerProperties, + const PointerCoords* pointerCoords, + float xCursorPosition, float yCursorPosition) { + return {mReaderContext.getNextId(), + when, + readTime, + mDeviceId, + SOURCE, + mPointerController->getDisplayId(), + /* policyFlags= */ POLICY_FLAG_WAKE, + action, + /* actionButton= */ actionButton, + /* flags= */ 0, + mReaderContext.getGlobalMetaState(), + buttonState, + mCurrentClassification, + AMOTION_EVENT_EDGE_FLAG_NONE, + pointerCount, + pointerProperties, + pointerCoords, + /* xPrecision= */ 1.0f, + /* yPrecision= */ 1.0f, + xCursorPosition, + yCursorPosition, + /* downTime= */ mDownTime, + /* videoFrames= */ {}}; +} + +} // namespace android diff --git a/services/inputflinger/reader/mapper/gestures/GestureConverter.h b/services/inputflinger/reader/mapper/gestures/GestureConverter.h new file mode 100644 index 0000000000000000000000000000000000000000..b613b887cd9053dc948fd8cd8e3c6279c7dbcb6a --- /dev/null +++ b/services/inputflinger/reader/mapper/gestures/GestureConverter.h @@ -0,0 +1,115 @@ +/* + * 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 +#include + +#include +#include +#include + +#include "EventHub.h" +#include "InputDevice.h" +#include "InputReaderContext.h" +#include "NotifyArgs.h" +#include "ui/Rotation.h" + +#include "include/gestures.h" + +namespace android { + +// Converts Gesture structs from the gestures library into NotifyArgs and the appropriate +// PointerController calls. +class GestureConverter { +public: + GestureConverter(InputReaderContext& readerContext, const InputDeviceContext& deviceContext, + int32_t deviceId); + + std::string dump() const; + + void setOrientation(ui::Rotation orientation) { mOrientation = orientation; } + [[nodiscard]] std::list reset(nsecs_t when); + + void populateMotionRanges(InputDeviceInfo& info) const; + + [[nodiscard]] std::list handleGesture(nsecs_t when, nsecs_t readTime, + const Gesture& gesture); + +private: + [[nodiscard]] NotifyMotionArgs handleMove(nsecs_t when, nsecs_t readTime, + const Gesture& gesture); + [[nodiscard]] std::list handleButtonsChange(nsecs_t when, nsecs_t readTime, + const Gesture& gesture); + [[nodiscard]] std::list releaseAllButtons(nsecs_t when, nsecs_t readTime); + [[nodiscard]] std::list handleScroll(nsecs_t when, nsecs_t readTime, + const Gesture& gesture); + [[nodiscard]] std::list handleFling(nsecs_t when, nsecs_t readTime, + const Gesture& gesture); + [[nodiscard]] NotifyMotionArgs endScroll(nsecs_t when, nsecs_t readTime); + + [[nodiscard]] std::list handleMultiFingerSwipe(nsecs_t when, nsecs_t readTime, + uint32_t fingerCount, float dx, + float dy); + [[nodiscard]] std::list handleMultiFingerSwipeLift(nsecs_t when, nsecs_t readTime); + [[nodiscard]] std::list handlePinch(nsecs_t when, nsecs_t readTime, + const Gesture& gesture); + [[nodiscard]] std::list endPinch(nsecs_t when, nsecs_t readTime); + + NotifyMotionArgs makeMotionArgs(nsecs_t when, nsecs_t readTime, int32_t action, + int32_t actionButton, int32_t buttonState, + uint32_t pointerCount, + const PointerProperties* pointerProperties, + const PointerCoords* pointerCoords, float xCursorPosition, + float yCursorPosition); + + const int32_t mDeviceId; + InputReaderContext& mReaderContext; + std::shared_ptr mPointerController; + + ui::Rotation mOrientation = ui::ROTATION_0; + RawAbsoluteAxisInfo mXAxisInfo; + RawAbsoluteAxisInfo mYAxisInfo; + + // The current button state according to the gestures library, but converted into MotionEvent + // button values (AMOTION_EVENT_BUTTON_...). + uint32_t mButtonState = 0; + nsecs_t mDownTime = 0; + + MotionClassification mCurrentClassification = MotionClassification::NONE; + // Only used when mCurrentClassification is MULTI_FINGER_SWIPE. + uint32_t mSwipeFingerCount = 0; + static constexpr float INITIAL_PINCH_SEPARATION_PX = 200.0; + // Only used when mCurrentClassification is PINCH. + float mPinchFingerSeparation; + static constexpr size_t MAX_FAKE_FINGERS = 4; + // We never need any PointerProperties other than the finger tool type, so we can just keep a + // const array of them. + const std::array mFingerProps = {{ + {.id = 0, .toolType = ToolType::FINGER}, + {.id = 1, .toolType = ToolType::FINGER}, + {.id = 2, .toolType = ToolType::FINGER}, + {.id = 3, .toolType = ToolType::FINGER}, + }}; + std::array mFakeFingerCoords = {}; + + // TODO(b/260226362): consider what the appropriate source for these events is. + static constexpr uint32_t SOURCE = AINPUT_SOURCE_MOUSE; +}; + +} // namespace android diff --git a/services/inputflinger/reader/mapper/gestures/GesturesLogging.cpp b/services/inputflinger/reader/mapper/gestures/GesturesLogging.cpp new file mode 100644 index 0000000000000000000000000000000000000000..81b4968df8412508e62c221ca5f06cc345a0df9b --- /dev/null +++ b/services/inputflinger/reader/mapper/gestures/GesturesLogging.cpp @@ -0,0 +1,41 @@ +/* + * 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 LOG_TAG +#define LOG_TAG "Gestures" +#endif + +#include + +#include + +#include "include/gestures.h" + +extern "C" { + +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); + } + va_end(args); +} +} diff --git a/services/inputflinger/reader/mapper/gestures/HardwareStateConverter.cpp b/services/inputflinger/reader/mapper/gestures/HardwareStateConverter.cpp new file mode 100644 index 0000000000000000000000000000000000000000..6780dcedecb4590e11e7daf1745ca64e2ec668e8 --- /dev/null +++ b/services/inputflinger/reader/mapper/gestures/HardwareStateConverter.cpp @@ -0,0 +1,116 @@ +/* + * 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. + */ + +// clang-format off +#include "../Macros.h" +// clang-format on +#include "gestures/HardwareStateConverter.h" + +#include +#include + +#include + +namespace android { + +HardwareStateConverter::HardwareStateConverter(const InputDeviceContext& deviceContext, + MultiTouchMotionAccumulator& motionAccumulator) + : mDeviceContext(deviceContext), + mMotionAccumulator(motionAccumulator), + mTouchButtonAccumulator(deviceContext) { + mTouchButtonAccumulator.configure(); +} + +std::optional HardwareStateConverter::processRawEvent( + const RawEvent* rawEvent) { + std::optional out; + 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; + } + mCursorButtonAccumulator.process(rawEvent); + mMotionAccumulator.process(rawEvent); + mTouchButtonAccumulator.process(rawEvent); + return out; +} + +SelfContainedHardwareState HardwareStateConverter::produceHardwareState(nsecs_t when) { + SelfContainedHardwareState schs; + // The gestures library uses doubles to represent timestamps in seconds. + schs.state.timestamp = std::chrono::duration(std::chrono::nanoseconds(when)).count(); + schs.state.msc_timestamp = + std::chrono::duration(std::chrono::microseconds(mMscTimestamp)).count(); + + schs.state.buttons_down = 0; + if (mCursorButtonAccumulator.isLeftPressed()) { + schs.state.buttons_down |= GESTURES_BUTTON_LEFT; + } + if (mCursorButtonAccumulator.isMiddlePressed()) { + schs.state.buttons_down |= GESTURES_BUTTON_MIDDLE; + } + if (mCursorButtonAccumulator.isRightPressed()) { + schs.state.buttons_down |= GESTURES_BUTTON_RIGHT; + } + if (mCursorButtonAccumulator.isBackPressed() || mCursorButtonAccumulator.isSidePressed()) { + schs.state.buttons_down |= GESTURES_BUTTON_BACK; + } + if (mCursorButtonAccumulator.isForwardPressed() || mCursorButtonAccumulator.isExtraPressed()) { + schs.state.buttons_down |= GESTURES_BUTTON_FORWARD; + } + + schs.fingers.clear(); + size_t numPalms = 0; + for (size_t i = 0; i < mMotionAccumulator.getSlotCount(); i++) { + MultiTouchMotionAccumulator::Slot slot = mMotionAccumulator.getSlot(i); + if (!slot.isInUse()) { + continue; + } + // Some touchpads continue to report contacts even after they've identified them as palms. + // We want to exclude these contacts from the HardwareStates. + if (slot.getToolType() == ToolType::PALM) { + numPalms++; + continue; + } + + FingerState& fingerState = schs.fingers.emplace_back(); + fingerState = {}; + fingerState.touch_major = slot.getTouchMajor(); + fingerState.touch_minor = slot.getTouchMinor(); + fingerState.width_major = slot.getToolMajor(); + fingerState.width_minor = slot.getToolMinor(); + fingerState.pressure = slot.getPressure(); + fingerState.orientation = slot.getOrientation(); + fingerState.position_x = slot.getX(); + fingerState.position_y = slot.getY(); + fingerState.tracking_id = slot.getTrackingId(); + } + schs.state.fingers = schs.fingers.data(); + schs.state.finger_cnt = schs.fingers.size(); + schs.state.touch_cnt = mTouchButtonAccumulator.getTouchCount() - numPalms; + return schs; +} + +void HardwareStateConverter::reset() { + mCursorButtonAccumulator.reset(mDeviceContext); + mTouchButtonAccumulator.reset(); + mMscTimestamp = 0; +} + +} // namespace android diff --git a/services/inputflinger/reader/mapper/gestures/HardwareStateConverter.h b/services/inputflinger/reader/mapper/gestures/HardwareStateConverter.h new file mode 100644 index 0000000000000000000000000000000000000000..633448e67e74af5a34ebe81bae343f6215732e2e --- /dev/null +++ b/services/inputflinger/reader/mapper/gestures/HardwareStateConverter.h @@ -0,0 +1,60 @@ +/* + * 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 + +#include + +#include "EventHub.h" +#include "InputDevice.h" +#include "accumulator/CursorButtonAccumulator.h" +#include "accumulator/MultiTouchMotionAccumulator.h" +#include "accumulator/TouchButtonAccumulator.h" + +#include "include/gestures.h" + +namespace android { + +// A HardwareState struct, but bundled with a vector to contain its FingerStates, so you don't have +// to worry about where that memory is allocated. +struct SelfContainedHardwareState { + HardwareState state; + std::vector fingers; +}; + +// Converts RawEvents into the HardwareState structs used by the gestures library. +class HardwareStateConverter { +public: + HardwareStateConverter(const InputDeviceContext& deviceContext, + MultiTouchMotionAccumulator& motionAccumulator); + + std::optional processRawEvent(const RawEvent* event); + void reset(); + +private: + SelfContainedHardwareState produceHardwareState(nsecs_t when); + + const InputDeviceContext& mDeviceContext; + CursorButtonAccumulator mCursorButtonAccumulator; + MultiTouchMotionAccumulator& mMotionAccumulator; + TouchButtonAccumulator mTouchButtonAccumulator; + int32_t mMscTimestamp = 0; +}; + +} // namespace android diff --git a/services/inputflinger/reader/mapper/gestures/PropertyProvider.cpp b/services/inputflinger/reader/mapper/gestures/PropertyProvider.cpp new file mode 100644 index 0000000000000000000000000000000000000000..be2bfed691d53d238b815f1a87e95a0ec6cbd23d --- /dev/null +++ b/services/inputflinger/reader/mapper/gestures/PropertyProvider.cpp @@ -0,0 +1,311 @@ +/* + * 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 "../Macros.h" + +#include "gestures/PropertyProvider.h" + +#include +#include +#include + +#include +#include +#include +#include + +namespace android { + +namespace { + +GesturesProp* createInt(void* data, const char* name, int* loc, size_t count, const int* init) { + return static_cast(data)->createIntArrayProperty(name, loc, count, init); +} + +GesturesProp* createBool(void* data, const char* name, GesturesPropBool* loc, size_t count, + const GesturesPropBool* init) { + return static_cast(data)->createBoolArrayProperty(name, loc, count, init); +} + +GesturesProp* createString(void* data, const char* name, const char** loc, const char* const init) { + return static_cast(data)->createStringProperty(name, loc, init); +} + +GesturesProp* createReal(void* data, const char* name, double* loc, size_t count, + const double* init) { + return static_cast(data)->createRealArrayProperty(name, loc, count, init); +} + +void registerHandlers(void* data, GesturesProp* prop, void* handlerData, + GesturesPropGetHandler getter, GesturesPropSetHandler setter) { + prop->registerHandlers(handlerData, getter, setter); +} + +void freeProperty(void* data, GesturesProp* prop) { + static_cast(data)->freeProperty(prop); +} + +} // namespace + +const GesturesPropProvider gesturePropProvider = { + .create_int_fn = createInt, + .create_bool_fn = createBool, + .create_string_fn = createString, + .create_real_fn = createReal, + .register_handlers_fn = registerHandlers, + .free_fn = freeProperty, +}; + +bool PropertyProvider::hasProperty(const std::string& name) const { + return mProperties.find(name) != mProperties.end(); +} + +GesturesProp& PropertyProvider::getProperty(const std::string& name) { + return mProperties.at(name); +} + +std::string PropertyProvider::dump() const { + std::string dump; + for (const auto& [name, property] : mProperties) { + dump += property.dump() + "\n"; + } + return dump; +} + +void PropertyProvider::loadPropertiesFromIdcFile(const PropertyMap& idcProperties) { + // For compatibility with the configuration file syntax, gesture property names in IDC files are + // 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)) { + std::string propertyName = key.substr(gesturePropPrefix.length()); + for (size_t i = 0; i < propertyName.length(); i++) { + if (propertyName[i] == '_') { + propertyName[i] = ' '; + } + } + + auto it = mProperties.find(propertyName); + if (it != mProperties.end()) { + it->second.trySetFromIdcProperty(idcProperties, key); + } else { + ALOGE("Gesture property \"%s\" specified in IDC file does not exist for this device.", + propertyName.c_str()); + } + } +} + +GesturesProp* PropertyProvider::createIntArrayProperty(const std::string& name, int* loc, + size_t count, const int* init) { + const auto [it, inserted] = + mProperties.insert(std::pair{name, GesturesProp(name, loc, count, init)}); + LOG_ALWAYS_FATAL_IF(!inserted, "Gesture property \"%s\" already exists.", name.c_str()); + return &it->second; +} + +GesturesProp* PropertyProvider::createBoolArrayProperty(const std::string& name, + GesturesPropBool* loc, size_t count, + const GesturesPropBool* init) { + const auto [it, inserted] = + mProperties.insert(std::pair{name, GesturesProp(name, loc, count, init)}); + LOG_ALWAYS_FATAL_IF(!inserted, "Gesture property \"%s\" already exists.", name.c_str()); + return &it->second; +} + +GesturesProp* PropertyProvider::createRealArrayProperty(const std::string& name, double* loc, + size_t count, const double* init) { + const auto [it, inserted] = + mProperties.insert(std::pair{name, GesturesProp(name, loc, count, init)}); + LOG_ALWAYS_FATAL_IF(!inserted, "Gesture property \"%s\" already exists.", name.c_str()); + return &it->second; +} + +GesturesProp* PropertyProvider::createStringProperty(const std::string& name, const char** loc, + const char* const init) { + const auto [it, inserted] = mProperties.insert(std::pair{name, GesturesProp(name, loc, init)}); + LOG_ALWAYS_FATAL_IF(!inserted, "Gesture property \"%s\" already exists.", name.c_str()); + return &it->second; +} + +void PropertyProvider::freeProperty(GesturesProp* prop) { + mProperties.erase(prop->getName()); +} + +} // namespace android + +template +GesturesProp::GesturesProp(std::string name, T* dataPointer, size_t count, const T* initialValues) + : mName(name), mCount(count), mDataPointer(dataPointer) { + std::copy_n(initialValues, count, dataPointer); +} + +GesturesProp::GesturesProp(std::string name, const char** dataPointer, + const char* const initialValue) + : mName(name), mCount(1), mDataPointer(dataPointer) { + *(std::get(mDataPointer)) = initialValue; +} + +std::string GesturesProp::dump() const { + using android::base::StringPrintf; + std::string type, values; + switch (mDataPointer.index()) { + case 0: + type = "integer"; + values = android::dumpVector(getIntValues()); + break; + case 1: + type = "boolean"; + values = android::dumpVector(getBoolValues()); + break; + case 2: + type = "string"; + values = getStringValue(); + break; + case 3: + type = "real"; + values = android::dumpVector(getRealValues()); + break; + } + std::string typeAndSize = mCount == 1 ? type : std::to_string(mCount) + " " + type + "s"; + return StringPrintf("%s (%s): %s", mName.c_str(), typeAndSize.c_str(), values.c_str()); +} + +void GesturesProp::registerHandlers(void* handlerData, GesturesPropGetHandler getter, + GesturesPropSetHandler setter) { + mHandlerData = handlerData; + mGetter = getter; + mSetter = setter; +} + +std::vector GesturesProp::getIntValues() const { + LOG_ALWAYS_FATAL_IF(!std::holds_alternative(mDataPointer), + "Attempt to read ints from \"%s\" gesture property.", mName.c_str()); + return getValues(std::get(mDataPointer)); +} + +std::vector GesturesProp::getBoolValues() const { + LOG_ALWAYS_FATAL_IF(!std::holds_alternative(mDataPointer), + "Attempt to read bools from \"%s\" gesture property.", mName.c_str()); + return getValues(std::get(mDataPointer)); +} + +std::vector GesturesProp::getRealValues() const { + LOG_ALWAYS_FATAL_IF(!std::holds_alternative(mDataPointer), + "Attempt to read reals from \"%s\" gesture property.", mName.c_str()); + return getValues(std::get(mDataPointer)); +} + +std::string GesturesProp::getStringValue() const { + LOG_ALWAYS_FATAL_IF(!std::holds_alternative(mDataPointer), + "Attempt to read a string from \"%s\" gesture property.", mName.c_str()); + if (mGetter != nullptr) { + mGetter(mHandlerData); + } + return std::string(*std::get(mDataPointer)); +} + +void GesturesProp::setBoolValues(const std::vector& values) { + LOG_ALWAYS_FATAL_IF(!std::holds_alternative(mDataPointer), + "Attempt to write bools to \"%s\" gesture property.", mName.c_str()); + setValues(std::get(mDataPointer), values); +} + +void GesturesProp::setIntValues(const std::vector& values) { + LOG_ALWAYS_FATAL_IF(!std::holds_alternative(mDataPointer), + "Attempt to write ints to \"%s\" gesture property.", mName.c_str()); + setValues(std::get(mDataPointer), values); +} + +void GesturesProp::setRealValues(const std::vector& values) { + LOG_ALWAYS_FATAL_IF(!std::holds_alternative(mDataPointer), + "Attempt to write reals to \"%s\" gesture property.", mName.c_str()); + setValues(std::get(mDataPointer), values); +} + +namespace { + +// Helper to std::visit with lambdas. +template +struct Visitor : V... {}; +// explicit deduction guide (not needed as of C++20) +template +Visitor(V...) -> Visitor; + +} // namespace + +void GesturesProp::trySetFromIdcProperty(const android::PropertyMap& idcProperties, + const std::string& propertyName) { + if (mCount != 1) { + ALOGE("Gesture property \"%s\" is an array, and so cannot be set in an IDC file.", + mName.c_str()); + return; + } + bool parsedSuccessfully = false; + Visitor setVisitor{ + [&](int*) { + if (std::optional value = idcProperties.getInt(propertyName); value) { + parsedSuccessfully = true; + setIntValues({*value}); + } + }, + [&](GesturesPropBool*) { + if (std::optional value = idcProperties.getBool(propertyName); value) { + parsedSuccessfully = true; + setBoolValues({*value}); + } + }, + [&](double*) { + if (std::optional value = idcProperties.getDouble(propertyName); value) { + parsedSuccessfully = true; + setRealValues({*value}); + } + }, + [&](const char**) { + ALOGE("Gesture property \"%s\" is a string, and so cannot be set in an IDC file.", + mName.c_str()); + // We've already reported the type mismatch, so set parsedSuccessfully. + parsedSuccessfully = true; + }, + }; + std::visit(setVisitor, mDataPointer); + + ALOGE_IF(!parsedSuccessfully, "Gesture property \"%s\" couldn't be set due to a type mismatch.", + mName.c_str()); +} + +template +const std::vector GesturesProp::getValues(U* dataPointer) const { + if (mGetter != nullptr) { + mGetter(mHandlerData); + } + std::vector values; + values.reserve(mCount); + for (size_t i = 0; i < mCount; i++) { + values.push_back(dataPointer[i]); + } + return values; +} + +template +void GesturesProp::setValues(T* dataPointer, const std::vector& values) { + LOG_ALWAYS_FATAL_IF(values.size() != mCount, + "Attempt to write %zu values to \"%s\" gesture property, which holds %zu.", + values.size(), mName.c_str(), mCount); + std::copy(values.begin(), values.end(), dataPointer); + if (mSetter != nullptr) { + mSetter(mHandlerData); + } +} diff --git a/services/inputflinger/reader/mapper/gestures/PropertyProvider.h b/services/inputflinger/reader/mapper/gestures/PropertyProvider.h new file mode 100644 index 0000000000000000000000000000000000000000..c7e0858c6dffb369b43d5749e084b00c4e37251c --- /dev/null +++ b/services/inputflinger/reader/mapper/gestures/PropertyProvider.h @@ -0,0 +1,106 @@ +/* + * 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. + */ + +#pragma once + +#include +#include +#include +#include + +#include "include/gestures.h" +#include "input/PropertyMap.h" + +namespace android { + +// Struct containing functions that wrap PropertyProvider in a C-compatible interface. +extern const GesturesPropProvider gesturePropProvider; + +// Implementation of a gestures library property provider, which provides configuration parameters. +class PropertyProvider { +public: + bool hasProperty(const std::string& name) const; + GesturesProp& getProperty(const std::string& name); + std::string dump() const; + + void loadPropertiesFromIdcFile(const PropertyMap& idcProperties); + + // Methods to be called by the gestures library: + GesturesProp* createIntArrayProperty(const std::string& name, int* loc, size_t count, + const int* init); + GesturesProp* createBoolArrayProperty(const std::string& name, GesturesPropBool* loc, + size_t count, const GesturesPropBool* init); + GesturesProp* createRealArrayProperty(const std::string& name, double* loc, size_t count, + const double* init); + GesturesProp* createStringProperty(const std::string& name, const char** loc, + const char* const init); + + void freeProperty(GesturesProp* prop); + +private: + std::map mProperties; +}; + +} // namespace android + +// Represents a single gesture property. +// +// Pointers to this struct will be used by the gestures library (though it can never deference +// them). The library's API requires this to be in the top-level namespace. +struct GesturesProp { +public: + template + GesturesProp(std::string name, T* dataPointer, size_t count, const T* initialValues); + GesturesProp(std::string name, const char** dataPointer, const char* const initialValue); + + std::string dump() const; + + std::string getName() const { return mName; } + + size_t getCount() const { return mCount; } + + void registerHandlers(void* handlerData, GesturesPropGetHandler getter, + GesturesPropSetHandler setter); + + std::vector getIntValues() const; + std::vector getBoolValues() const; + std::vector getRealValues() const; + std::string getStringValue() const; + + void setIntValues(const std::vector& values); + void setBoolValues(const std::vector& values); + void setRealValues(const std::vector& values); + // Setting string values isn't supported since we don't have a use case yet and the memory + // management adds additional complexity. + + void trySetFromIdcProperty(const android::PropertyMap& idcProperties, + const std::string& propertyName); + +private: + // Two type parameters are required for these methods, rather than one, due to the gestures + // library using its own bool type. + template + const std::vector getValues(U* dataPointer) const; + template + void setValues(T* dataPointer, const std::vector& values); + + std::string mName; + size_t mCount; + std::variant mDataPointer; + void* mHandlerData = nullptr; + GesturesPropGetHandler mGetter = nullptr; + GesturesPropSetHandler mSetter = nullptr; +}; diff --git a/services/inputflinger/reporter/Android.bp b/services/inputflinger/reporter/Android.bp index 74307310c5a989dd111913021bddf63f43632b84..693ff063b192cfa33d40e05a56df282afe71f977 100644 --- a/services/inputflinger/reporter/Android.bp +++ b/services/inputflinger/reporter/Android.bp @@ -23,13 +23,14 @@ package { cc_library_headers { name: "libinputreporter_headers", + host_supported: true, export_include_dirs: ["."], } filegroup { name: "libinputreporter_sources", srcs: [ - "InputReporter.cpp", + "InputReporter.cpp", ], } diff --git a/services/inputflinger/reporter/InputReporter.cpp b/services/inputflinger/reporter/InputReporter.cpp index b591d3f90946132e2a372f404ff6b6a95c007c1c..6f1eef653cf21dfa5cd87ac7445f933473ed2327 100644 --- a/services/inputflinger/reporter/InputReporter.cpp +++ b/services/inputflinger/reporter/InputReporter.cpp @@ -35,7 +35,7 @@ void InputReporter::reportDroppedKey(uint32_t sequenceNum) { } sp createInputReporter() { - return new InputReporter(); + return sp::make(); } } // namespace android diff --git a/services/inputflinger/reporter/InputReporterInterface.h b/services/inputflinger/reporter/InputReporterInterface.h index e5d360609fba86885af021d569da730fe8da00de..72a4aeb13b5c684d178044d88a4cd6a50477bc4c 100644 --- a/services/inputflinger/reporter/InputReporterInterface.h +++ b/services/inputflinger/reporter/InputReporterInterface.h @@ -14,8 +14,7 @@ * limitations under the License. */ -#ifndef _UI_INPUT_REPORTER_INTERFACE_H -#define _UI_INPUT_REPORTER_INTERFACE_H +#pragma once #include @@ -49,5 +48,3 @@ public: sp createInputReporter(); } // namespace android - -#endif // _UI_INPUT_REPORTER_INTERFACE_H diff --git a/services/inputflinger/tests/Android.bp b/services/inputflinger/tests/Android.bp index 75cd9da782be1af1003c83227f341f6aa277be4b..569690ab785cde70bea01496d2bb36e282fdcaa2 100644 --- a/services/inputflinger/tests/Android.bp +++ b/services/inputflinger/tests/Android.bp @@ -23,6 +23,7 @@ package { cc_test { name: "inputflinger_tests", + host_supported: true, defaults: [ "inputflinger_defaults", // For all targets inside inputflinger, these tests build all of their sources using their @@ -38,17 +39,27 @@ cc_test { srcs: [ "AnrTracker_test.cpp", "BlockingQueue_test.cpp", + "CapturedTouchpadEventConverter_test.cpp", + "CursorInputMapper_test.cpp", "EventHub_test.cpp", + "FakeEventHub.cpp", + "FakeInputReaderPolicy.cpp", + "FakePointerController.cpp", "FocusResolver_test.cpp", - "IInputFlingerQuery.aidl", - "InputClassifier_test.cpp", - "InputClassifierConverter_test.cpp", + "GestureConverter_test.cpp", + "HardwareStateConverter_test.cpp", + "InputMapperTest.cpp", + "InputProcessor_test.cpp", + "InputProcessorConverter_test.cpp", "InputDispatcher_test.cpp", "InputReader_test.cpp", - "InputFlingerService_test.cpp", + "InstrumentedInputReader.cpp", "LatencyTracker_test.cpp", + "NotifyArgs_test.cpp", "PreferStylusOverTouch_test.cpp", + "PropertyProvider_test.cpp", "TestInputListener.cpp", + "TouchpadInputMapper_test.cpp", "UinputDevice.cpp", "UnwantedInteractionBlocker_test.cpp", ], @@ -58,10 +69,40 @@ cc_test { "frameworks/native/libs/input", ], }, + target: { + android: { + shared_libs: [ + "libinput", + "libvintf", + ], + }, + host: { + include_dirs: [ + "bionic/libc/kernel/android/uapi/", + "bionic/libc/kernel/uapi", + ], + cflags: [ + "-D__ANDROID_HOST__", + ], + static_libs: [ + "libinput", + ], + }, + }, + sanitize: { + undefined: true, + all_undefined: true, + diag: { + undefined: true, + }, + }, static_libs: [ "libc++fs", "libgmock", ], require_root: true, + test_options: { + unit_test: true, + }, test_suites: ["device-tests"], } diff --git a/services/inputflinger/tests/AnrTracker_test.cpp b/services/inputflinger/tests/AnrTracker_test.cpp index b561da107d1fa5a6fe69e2bbb62729fa213b3f6b..25adeea48f2393e11a422980fce0eeaee438a3c2 100644 --- a/services/inputflinger/tests/AnrTracker_test.cpp +++ b/services/inputflinger/tests/AnrTracker_test.cpp @@ -40,8 +40,8 @@ TEST(AnrTrackerTest, SingleEntry_First) { TEST(AnrTrackerTest, MultipleEntries_RemoveToken) { AnrTracker tracker; - sp token1 = new BBinder(); - sp token2 = new BBinder(); + sp token1 = sp::make(); + sp token2 = sp::make(); tracker.insert(1, token1); tracker.insert(2, token2); @@ -90,8 +90,8 @@ TEST(AnrTrackerTest, SingleToken_MaintainsOrder) { TEST(AnrTrackerTest, MultipleTokens_MaintainsOrder) { AnrTracker tracker; - sp token1 = new BBinder(); - sp token2 = new BBinder(); + sp token1 = sp::make(); + sp token2 = sp::make(); tracker.insert(2, token1); tracker.insert(5, token2); @@ -104,8 +104,8 @@ TEST(AnrTrackerTest, MultipleTokens_MaintainsOrder) { TEST(AnrTrackerTest, MultipleTokens_IdenticalTimes) { AnrTracker tracker; - sp token1 = new BBinder(); - sp token2 = new BBinder(); + sp token1 = sp::make(); + sp token2 = sp::make(); tracker.insert(2, token1); tracker.insert(2, token2); @@ -119,8 +119,8 @@ TEST(AnrTrackerTest, MultipleTokens_IdenticalTimes) { TEST(AnrTrackerTest, MultipleTokens_IdenticalTimesRemove) { AnrTracker tracker; - sp token1 = new BBinder(); - sp token2 = new BBinder(); + sp token1 = sp::make(); + sp token2 = sp::make(); tracker.insert(2, token1); tracker.insert(2, token2); @@ -137,7 +137,7 @@ TEST(AnrTrackerTest, Empty_DoesntCrash) { ASSERT_TRUE(tracker.empty()); - ASSERT_EQ(LONG_LONG_MAX, tracker.firstTimeout()); + ASSERT_EQ(LLONG_MAX, tracker.firstTimeout()); // Can't call firstToken() if tracker.empty() } @@ -152,12 +152,12 @@ TEST(AnrTrackerTest, RemoveInvalidItem_DoesntCrash) { ASSERT_EQ(nullptr, tracker.firstToken()); // Remove with non-matching token - tracker.erase(1, new BBinder()); + tracker.erase(1, sp::make()); ASSERT_EQ(1, tracker.firstTimeout()); ASSERT_EQ(nullptr, tracker.firstToken()); // Remove with both non-matching - tracker.erase(2, new BBinder()); + tracker.erase(2, sp::make()); ASSERT_EQ(1, tracker.firstTimeout()); ASSERT_EQ(nullptr, tracker.firstToken()); } diff --git a/services/inputflinger/tests/CapturedTouchpadEventConverter_test.cpp b/services/inputflinger/tests/CapturedTouchpadEventConverter_test.cpp new file mode 100644 index 0000000000000000000000000000000000000000..99a6a1f5db1adde8130912164d413d2e1305e0eb --- /dev/null +++ b/services/inputflinger/tests/CapturedTouchpadEventConverter_test.cpp @@ -0,0 +1,955 @@ +/* + * 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 +#include + +#include +#include +#include +#include +#include + +#include "FakeEventHub.h" +#include "FakeInputReaderPolicy.h" +#include "InstrumentedInputReader.h" +#include "TestConstants.h" +#include "TestInputListener.h" +#include "TestInputListenerMatchers.h" + +namespace android { + +using testing::AllOf; + +class CapturedTouchpadEventConverterTest : public testing::Test { +public: + CapturedTouchpadEventConverterTest() + : mFakeEventHub(std::make_unique()), + mFakePolicy(sp::make()), + mReader(mFakeEventHub, mFakePolicy, mFakeListener), + mDevice(newDevice()), + mDeviceContext(*mDevice, EVENTHUB_ID) { + const size_t slotCount = 8; + mFakeEventHub->addAbsoluteAxis(EVENTHUB_ID, ABS_MT_SLOT, 0, slotCount - 1, 0, 0, 0); + mAccumulator.configure(mDeviceContext, slotCount, /*usingSlotsProtocol=*/true); + } + +protected: + static constexpr int32_t DEVICE_ID = END_RESERVED_ID + 1000; + static constexpr int32_t EVENTHUB_ID = 1; + + 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; + } + + void addBasicAxesToEventHub() { + mFakeEventHub->addAbsoluteAxis(EVENTHUB_ID, ABS_MT_POSITION_X, 0, 4000, 0, 0, 45); + mFakeEventHub->addAbsoluteAxis(EVENTHUB_ID, ABS_MT_POSITION_Y, 0, 2500, 0, 0, 40); + mFakeEventHub->addAbsoluteAxis(EVENTHUB_ID, ABS_MT_PRESSURE, 0, 256, 0, 0, 0); + mFakeEventHub->addAbsoluteAxis(EVENTHUB_ID, ABS_MT_TOUCH_MAJOR, 0, 1000, 0, 0, 0); + mFakeEventHub->addAbsoluteAxis(EVENTHUB_ID, ABS_MT_TOUCH_MINOR, 0, 1000, 0, 0, 0); + } + + CapturedTouchpadEventConverter createConverter() { + addBasicAxesToEventHub(); + return CapturedTouchpadEventConverter(*mReader.getContext(), mDeviceContext, mAccumulator, + DEVICE_ID); + } + + void processAxis(CapturedTouchpadEventConverter& conv, int32_t type, int32_t code, + int32_t value) { + RawEvent event; + event.when = ARBITRARY_TIME; + event.readTime = READ_TIME; + event.deviceId = EVENTHUB_ID; + event.type = type; + event.code = code; + event.value = value; + std::list out = conv.process(event); + EXPECT_TRUE(out.empty()); + } + + std::list processSync(CapturedTouchpadEventConverter& conv) { + RawEvent event; + event.when = ARBITRARY_TIME; + event.readTime = READ_TIME; + event.deviceId = EVENTHUB_ID; + event.type = EV_SYN; + event.code = SYN_REPORT; + event.value = 0; + return conv.process(event); + } + + NotifyMotionArgs processSyncAndExpectSingleMotionArg(CapturedTouchpadEventConverter& conv) { + std::list args = processSync(conv); + EXPECT_EQ(1u, args.size()); + return std::get(args.front()); + } + + std::shared_ptr mFakeEventHub; + sp mFakePolicy; + TestInputListener mFakeListener; + InstrumentedInputReader mReader; + std::shared_ptr mDevice; + InputDeviceContext mDeviceContext; + MultiTouchMotionAccumulator mAccumulator; +}; + +TEST_F(CapturedTouchpadEventConverterTest, MotionRanges_allAxesPresent_populatedCorrectly) { + mFakeEventHub->addAbsoluteAxis(EVENTHUB_ID, ABS_MT_POSITION_X, 0, 4000, 0, 0, 45); + mFakeEventHub->addAbsoluteAxis(EVENTHUB_ID, ABS_MT_POSITION_Y, 0, 2500, 0, 0, 40); + mFakeEventHub->addAbsoluteAxis(EVENTHUB_ID, ABS_MT_TOUCH_MAJOR, 0, 1100, 0, 0, 35); + mFakeEventHub->addAbsoluteAxis(EVENTHUB_ID, ABS_MT_TOUCH_MINOR, 0, 1000, 0, 0, 30); + mFakeEventHub->addAbsoluteAxis(EVENTHUB_ID, ABS_MT_WIDTH_MAJOR, 0, 900, 0, 0, 25); + mFakeEventHub->addAbsoluteAxis(EVENTHUB_ID, ABS_MT_WIDTH_MINOR, 0, 800, 0, 0, 20); + mFakeEventHub->addAbsoluteAxis(EVENTHUB_ID, ABS_MT_ORIENTATION, -3, 4, 0, 0, 0); + mFakeEventHub->addAbsoluteAxis(EVENTHUB_ID, ABS_MT_PRESSURE, 0, 256, 0, 0, 0); + CapturedTouchpadEventConverter conv(*mReader.getContext(), mDeviceContext, mAccumulator, + DEVICE_ID); + + InputDeviceInfo info; + conv.populateMotionRanges(info); + + // Most axes should have min, max, and resolution matching the evdev axes. + const InputDeviceInfo::MotionRange* posX = + info.getMotionRange(AMOTION_EVENT_AXIS_X, AINPUT_SOURCE_TOUCHPAD); + ASSERT_NE(nullptr, posX); + EXPECT_NEAR(0, posX->min, EPSILON); + EXPECT_NEAR(4000, posX->max, EPSILON); + EXPECT_NEAR(45, posX->resolution, EPSILON); + + const InputDeviceInfo::MotionRange* posY = + info.getMotionRange(AMOTION_EVENT_AXIS_Y, AINPUT_SOURCE_TOUCHPAD); + ASSERT_NE(nullptr, posY); + EXPECT_NEAR(0, posY->min, EPSILON); + EXPECT_NEAR(2500, posY->max, EPSILON); + EXPECT_NEAR(40, posY->resolution, EPSILON); + + const InputDeviceInfo::MotionRange* touchMajor = + info.getMotionRange(AMOTION_EVENT_AXIS_TOUCH_MAJOR, AINPUT_SOURCE_TOUCHPAD); + ASSERT_NE(nullptr, touchMajor); + EXPECT_NEAR(0, touchMajor->min, EPSILON); + EXPECT_NEAR(1100, touchMajor->max, EPSILON); + EXPECT_NEAR(35, touchMajor->resolution, EPSILON); + + const InputDeviceInfo::MotionRange* touchMinor = + info.getMotionRange(AMOTION_EVENT_AXIS_TOUCH_MINOR, AINPUT_SOURCE_TOUCHPAD); + ASSERT_NE(nullptr, touchMinor); + EXPECT_NEAR(0, touchMinor->min, EPSILON); + EXPECT_NEAR(1000, touchMinor->max, EPSILON); + EXPECT_NEAR(30, touchMinor->resolution, EPSILON); + + const InputDeviceInfo::MotionRange* toolMajor = + info.getMotionRange(AMOTION_EVENT_AXIS_TOOL_MAJOR, AINPUT_SOURCE_TOUCHPAD); + ASSERT_NE(nullptr, toolMajor); + EXPECT_NEAR(0, toolMajor->min, EPSILON); + EXPECT_NEAR(900, toolMajor->max, EPSILON); + EXPECT_NEAR(25, toolMajor->resolution, EPSILON); + + const InputDeviceInfo::MotionRange* toolMinor = + info.getMotionRange(AMOTION_EVENT_AXIS_TOOL_MINOR, AINPUT_SOURCE_TOUCHPAD); + ASSERT_NE(nullptr, toolMinor); + EXPECT_NEAR(0, toolMinor->min, EPSILON); + EXPECT_NEAR(800, toolMinor->max, EPSILON); + EXPECT_NEAR(20, toolMinor->resolution, EPSILON); + + // ...except orientation and pressure, which get scaled, and size, which is generated from other + // values. + const InputDeviceInfo::MotionRange* orientation = + info.getMotionRange(AMOTION_EVENT_AXIS_ORIENTATION, AINPUT_SOURCE_TOUCHPAD); + ASSERT_NE(nullptr, orientation); + EXPECT_NEAR(-M_PI_2, orientation->min, EPSILON); + EXPECT_NEAR(M_PI_2, orientation->max, EPSILON); + EXPECT_NEAR(0, orientation->resolution, EPSILON); + + const InputDeviceInfo::MotionRange* pressure = + info.getMotionRange(AMOTION_EVENT_AXIS_PRESSURE, AINPUT_SOURCE_TOUCHPAD); + ASSERT_NE(nullptr, pressure); + EXPECT_NEAR(0, pressure->min, EPSILON); + EXPECT_NEAR(1, pressure->max, EPSILON); + EXPECT_NEAR(0, pressure->resolution, EPSILON); + + const InputDeviceInfo::MotionRange* size = + info.getMotionRange(AMOTION_EVENT_AXIS_SIZE, AINPUT_SOURCE_TOUCHPAD); + ASSERT_NE(nullptr, size); + EXPECT_NEAR(0, size->min, EPSILON); + EXPECT_NEAR(1, size->max, EPSILON); + EXPECT_NEAR(0, size->resolution, EPSILON); +} + +TEST_F(CapturedTouchpadEventConverterTest, MotionRanges_bareMinimumAxesPresent_populatedCorrectly) { + mFakeEventHub->addAbsoluteAxis(EVENTHUB_ID, ABS_MT_POSITION_X, 0, 4000, 0, 0, 45); + mFakeEventHub->addAbsoluteAxis(EVENTHUB_ID, ABS_MT_POSITION_Y, 0, 2500, 0, 0, 40); + CapturedTouchpadEventConverter conv(*mReader.getContext(), mDeviceContext, mAccumulator, + DEVICE_ID); + + InputDeviceInfo info; + conv.populateMotionRanges(info); + + // Only the bare minimum motion ranges should be reported, and no others (e.g. size shouldn't be + // present, since it's generated from axes that aren't provided by this device). + EXPECT_NE(nullptr, info.getMotionRange(AMOTION_EVENT_AXIS_X, AINPUT_SOURCE_TOUCHPAD)); + EXPECT_NE(nullptr, info.getMotionRange(AMOTION_EVENT_AXIS_Y, AINPUT_SOURCE_TOUCHPAD)); + EXPECT_EQ(2u, info.getMotionRanges().size()); +} + +TEST_F(CapturedTouchpadEventConverterTest, OneFinger_motionReportedCorrectly) { + CapturedTouchpadEventConverter conv = createConverter(); + + processAxis(conv, EV_ABS, ABS_MT_SLOT, 0); + processAxis(conv, EV_ABS, ABS_MT_TRACKING_ID, 1); + processAxis(conv, EV_ABS, ABS_MT_POSITION_X, 50); + processAxis(conv, EV_ABS, ABS_MT_POSITION_Y, 100); + + processAxis(conv, EV_KEY, BTN_TOUCH, 1); + processAxis(conv, EV_KEY, BTN_TOOL_FINGER, 1); + + EXPECT_THAT(processSyncAndExpectSingleMotionArg(conv), + AllOf(WithMotionAction(AMOTION_EVENT_ACTION_DOWN), WithPointerCount(1u), + WithCoords(50, 100), WithToolType(ToolType::FINGER))); + + processAxis(conv, EV_ABS, ABS_MT_POSITION_X, 52); + processAxis(conv, EV_ABS, ABS_MT_POSITION_Y, 99); + + EXPECT_THAT(processSyncAndExpectSingleMotionArg(conv), + AllOf(WithMotionAction(AMOTION_EVENT_ACTION_MOVE), WithPointerCount(1u), + WithCoords(52, 99), WithToolType(ToolType::FINGER))); + + processAxis(conv, EV_ABS, ABS_MT_TRACKING_ID, -1); + processAxis(conv, EV_KEY, BTN_TOUCH, 0); + processAxis(conv, EV_KEY, BTN_TOOL_FINGER, 0); + + std::list args = processSync(conv); + ASSERT_EQ(2u, args.size()); + EXPECT_THAT(std::get(args.front()), + AllOf(WithMotionAction(AMOTION_EVENT_ACTION_MOVE), WithPointerCount(1u), + WithCoords(52, 99), WithToolType(ToolType::FINGER))); + args.pop_front(); + EXPECT_THAT(std::get(args.front()), + AllOf(WithMotionAction(AMOTION_EVENT_ACTION_UP), WithPointerCount(1u), + WithCoords(52, 99), WithToolType(ToolType::FINGER))); +} + +TEST_F(CapturedTouchpadEventConverterTest, OneFinger_touchDimensionsPassedThrough) { + addBasicAxesToEventHub(); + mFakeEventHub->addAbsoluteAxis(EVENTHUB_ID, ABS_MT_WIDTH_MAJOR, 0, 1000, 0, 0, 0); + mFakeEventHub->addAbsoluteAxis(EVENTHUB_ID, ABS_MT_WIDTH_MINOR, 0, 1000, 0, 0, 0); + CapturedTouchpadEventConverter conv(*mReader.getContext(), mDeviceContext, mAccumulator, + DEVICE_ID); + + processAxis(conv, EV_ABS, ABS_MT_SLOT, 0); + processAxis(conv, EV_ABS, ABS_MT_TRACKING_ID, 1); + processAxis(conv, EV_ABS, ABS_MT_TOUCH_MAJOR, 250); + processAxis(conv, EV_ABS, ABS_MT_TOUCH_MINOR, 120); + processAxis(conv, EV_ABS, ABS_MT_WIDTH_MAJOR, 400); + processAxis(conv, EV_ABS, ABS_MT_WIDTH_MINOR, 200); + + processAxis(conv, EV_KEY, BTN_TOUCH, 1); + processAxis(conv, EV_KEY, BTN_TOOL_FINGER, 1); + + EXPECT_THAT(processSyncAndExpectSingleMotionArg(conv), + AllOf(WithMotionAction(AMOTION_EVENT_ACTION_DOWN), WithPointerCount(1u), + WithTouchDimensions(250, 120), WithToolDimensions(400, 200))); +} + +TEST_F(CapturedTouchpadEventConverterTest, OneFinger_orientationCalculatedCorrectly) { + addBasicAxesToEventHub(); + mFakeEventHub->addAbsoluteAxis(EVENTHUB_ID, ABS_MT_ORIENTATION, -3, 4, 0, 0, 0); + CapturedTouchpadEventConverter conv(*mReader.getContext(), mDeviceContext, mAccumulator, + DEVICE_ID); + + processAxis(conv, EV_ABS, ABS_MT_SLOT, 0); + processAxis(conv, EV_ABS, ABS_MT_TRACKING_ID, 1); + processAxis(conv, EV_ABS, ABS_MT_ORIENTATION, -3); + processAxis(conv, EV_KEY, BTN_TOUCH, 1); + processAxis(conv, EV_KEY, BTN_TOOL_FINGER, 1); + + EXPECT_NEAR(-3 * M_PI / 8, + processSyncAndExpectSingleMotionArg(conv).pointerCoords[0].getAxisValue( + AMOTION_EVENT_AXIS_ORIENTATION), + EPSILON); + + processAxis(conv, EV_ABS, ABS_MT_ORIENTATION, 0); + + EXPECT_NEAR(0, + processSyncAndExpectSingleMotionArg(conv).pointerCoords[0].getAxisValue( + AMOTION_EVENT_AXIS_ORIENTATION), + EPSILON); + + processAxis(conv, EV_ABS, ABS_MT_ORIENTATION, 4); + + EXPECT_NEAR(M_PI / 2, + processSyncAndExpectSingleMotionArg(conv).pointerCoords[0].getAxisValue( + AMOTION_EVENT_AXIS_ORIENTATION), + EPSILON); +} + +TEST_F(CapturedTouchpadEventConverterTest, OneFinger_pressureScaledCorrectly) { + mFakeEventHub->addAbsoluteAxis(EVENTHUB_ID, ABS_MT_POSITION_X, 0, 4000, 0, 0, 45); + mFakeEventHub->addAbsoluteAxis(EVENTHUB_ID, ABS_MT_POSITION_Y, 0, 2500, 0, 0, 40); + mFakeEventHub->addAbsoluteAxis(EVENTHUB_ID, ABS_MT_PRESSURE, 0, 256, 0, 0, 0); + CapturedTouchpadEventConverter conv(*mReader.getContext(), mDeviceContext, mAccumulator, + DEVICE_ID); + + processAxis(conv, EV_ABS, ABS_MT_SLOT, 0); + processAxis(conv, EV_ABS, ABS_MT_TRACKING_ID, 1); + processAxis(conv, EV_ABS, ABS_MT_PRESSURE, 128); + processAxis(conv, EV_KEY, BTN_TOUCH, 1); + processAxis(conv, EV_KEY, BTN_TOOL_FINGER, 1); + + EXPECT_THAT(processSyncAndExpectSingleMotionArg(conv), WithPressure(0.5)); +} + +TEST_F(CapturedTouchpadEventConverterTest, + OneFinger_withAllSizeAxes_sizeCalculatedFromTouchMajorMinorAverage) { + mFakeEventHub->addAbsoluteAxis(EVENTHUB_ID, ABS_MT_POSITION_X, 0, 4000, 0, 0, 45); + mFakeEventHub->addAbsoluteAxis(EVENTHUB_ID, ABS_MT_POSITION_Y, 0, 2500, 0, 0, 40); + mFakeEventHub->addAbsoluteAxis(EVENTHUB_ID, ABS_MT_TOUCH_MAJOR, 0, 256, 0, 0, 0); + mFakeEventHub->addAbsoluteAxis(EVENTHUB_ID, ABS_MT_TOUCH_MINOR, 0, 256, 0, 0, 0); + mFakeEventHub->addAbsoluteAxis(EVENTHUB_ID, ABS_MT_WIDTH_MAJOR, 0, 256, 0, 0, 0); + mFakeEventHub->addAbsoluteAxis(EVENTHUB_ID, ABS_MT_WIDTH_MINOR, 0, 256, 0, 0, 0); + CapturedTouchpadEventConverter conv(*mReader.getContext(), mDeviceContext, mAccumulator, + DEVICE_ID); + + processAxis(conv, EV_ABS, ABS_MT_SLOT, 0); + processAxis(conv, EV_ABS, ABS_MT_TRACKING_ID, 1); + processAxis(conv, EV_ABS, ABS_MT_TOUCH_MAJOR, 138); + processAxis(conv, EV_ABS, ABS_MT_TOUCH_MINOR, 118); + processAxis(conv, EV_ABS, ABS_MT_WIDTH_MAJOR, 200); + processAxis(conv, EV_ABS, ABS_MT_WIDTH_MINOR, 210); + processAxis(conv, EV_KEY, BTN_TOUCH, 1); + processAxis(conv, EV_KEY, BTN_TOOL_FINGER, 1); + + EXPECT_NEAR(0.5, + processSyncAndExpectSingleMotionArg(conv).pointerCoords[0].getAxisValue( + AMOTION_EVENT_AXIS_SIZE), + EPSILON); +} + +TEST_F(CapturedTouchpadEventConverterTest, + OneFinger_withMajorDimensionsOnly_sizeCalculatedFromTouchMajor) { + mFakeEventHub->addAbsoluteAxis(EVENTHUB_ID, ABS_MT_POSITION_X, 0, 4000, 0, 0, 45); + mFakeEventHub->addAbsoluteAxis(EVENTHUB_ID, ABS_MT_POSITION_Y, 0, 2500, 0, 0, 40); + mFakeEventHub->addAbsoluteAxis(EVENTHUB_ID, ABS_MT_TOUCH_MAJOR, 0, 256, 0, 0, 0); + mFakeEventHub->addAbsoluteAxis(EVENTHUB_ID, ABS_MT_WIDTH_MAJOR, 0, 256, 0, 0, 0); + CapturedTouchpadEventConverter conv(*mReader.getContext(), mDeviceContext, mAccumulator, + DEVICE_ID); + + processAxis(conv, EV_ABS, ABS_MT_SLOT, 0); + processAxis(conv, EV_ABS, ABS_MT_TRACKING_ID, 1); + processAxis(conv, EV_ABS, ABS_MT_TOUCH_MAJOR, 128); + processAxis(conv, EV_ABS, ABS_MT_WIDTH_MAJOR, 200); + processAxis(conv, EV_KEY, BTN_TOUCH, 1); + processAxis(conv, EV_KEY, BTN_TOOL_FINGER, 1); + + EXPECT_NEAR(0.5, + processSyncAndExpectSingleMotionArg(conv).pointerCoords[0].getAxisValue( + AMOTION_EVENT_AXIS_SIZE), + EPSILON); +} + +TEST_F(CapturedTouchpadEventConverterTest, + OneFinger_withToolDimensionsOnly_sizeCalculatedFromToolMajorMinorAverage) { + mFakeEventHub->addAbsoluteAxis(EVENTHUB_ID, ABS_MT_POSITION_X, 0, 4000, 0, 0, 45); + mFakeEventHub->addAbsoluteAxis(EVENTHUB_ID, ABS_MT_POSITION_Y, 0, 2500, 0, 0, 40); + mFakeEventHub->addAbsoluteAxis(EVENTHUB_ID, ABS_MT_WIDTH_MAJOR, 0, 256, 0, 0, 0); + mFakeEventHub->addAbsoluteAxis(EVENTHUB_ID, ABS_MT_WIDTH_MINOR, 0, 256, 0, 0, 0); + CapturedTouchpadEventConverter conv(*mReader.getContext(), mDeviceContext, mAccumulator, + DEVICE_ID); + + processAxis(conv, EV_ABS, ABS_MT_SLOT, 0); + processAxis(conv, EV_ABS, ABS_MT_TRACKING_ID, 1); + processAxis(conv, EV_ABS, ABS_MT_WIDTH_MAJOR, 138); + processAxis(conv, EV_ABS, ABS_MT_WIDTH_MINOR, 118); + processAxis(conv, EV_KEY, BTN_TOUCH, 1); + processAxis(conv, EV_KEY, BTN_TOOL_FINGER, 1); + + EXPECT_NEAR(0.5, + processSyncAndExpectSingleMotionArg(conv).pointerCoords[0].getAxisValue( + AMOTION_EVENT_AXIS_SIZE), + EPSILON); +} + +TEST_F(CapturedTouchpadEventConverterTest, + OneFinger_withToolMajorOnly_sizeCalculatedFromTouchMajor) { + mFakeEventHub->addAbsoluteAxis(EVENTHUB_ID, ABS_MT_POSITION_X, 0, 4000, 0, 0, 45); + mFakeEventHub->addAbsoluteAxis(EVENTHUB_ID, ABS_MT_POSITION_Y, 0, 2500, 0, 0, 40); + mFakeEventHub->addAbsoluteAxis(EVENTHUB_ID, ABS_MT_WIDTH_MAJOR, 0, 256, 0, 0, 0); + CapturedTouchpadEventConverter conv(*mReader.getContext(), mDeviceContext, mAccumulator, + DEVICE_ID); + + processAxis(conv, EV_ABS, ABS_MT_SLOT, 0); + processAxis(conv, EV_ABS, ABS_MT_TRACKING_ID, 1); + processAxis(conv, EV_ABS, ABS_MT_WIDTH_MAJOR, 128); + processAxis(conv, EV_KEY, BTN_TOUCH, 1); + processAxis(conv, EV_KEY, BTN_TOOL_FINGER, 1); + + EXPECT_NEAR(0.5, + processSyncAndExpectSingleMotionArg(conv).pointerCoords[0].getAxisValue( + AMOTION_EVENT_AXIS_SIZE), + EPSILON); +} + +TEST_F(CapturedTouchpadEventConverterTest, OnePalm_neverReported) { + addBasicAxesToEventHub(); + mFakeEventHub->addAbsoluteAxis(EVENTHUB_ID, ABS_MT_TOOL_TYPE, 0, MT_TOOL_PALM, 0, 0, 0); + CapturedTouchpadEventConverter conv(*mReader.getContext(), mDeviceContext, mAccumulator, + DEVICE_ID); + + processAxis(conv, EV_ABS, ABS_MT_SLOT, 0); + processAxis(conv, EV_ABS, ABS_MT_TRACKING_ID, 1); + processAxis(conv, EV_ABS, ABS_MT_POSITION_X, 50); + processAxis(conv, EV_ABS, ABS_MT_POSITION_Y, 100); + processAxis(conv, EV_ABS, ABS_MT_TOOL_TYPE, MT_TOOL_PALM); + processAxis(conv, EV_KEY, BTN_TOUCH, 1); + processAxis(conv, EV_KEY, BTN_TOOL_FINGER, 1); + + EXPECT_EQ(0u, processSync(conv).size()); + + processAxis(conv, EV_ABS, ABS_MT_POSITION_X, 51); + + EXPECT_EQ(0u, processSync(conv).size()); + + processAxis(conv, EV_ABS, ABS_MT_TRACKING_ID, -1); + processAxis(conv, EV_KEY, BTN_TOUCH, 0); + processAxis(conv, EV_KEY, BTN_TOOL_FINGER, 0); + + EXPECT_EQ(0u, processSync(conv).size()); +} + +TEST_F(CapturedTouchpadEventConverterTest, FingerTurningIntoPalm_cancelled) { + addBasicAxesToEventHub(); + mFakeEventHub->addAbsoluteAxis(EVENTHUB_ID, ABS_MT_TOOL_TYPE, 0, MT_TOOL_PALM, 0, 0, 0); + CapturedTouchpadEventConverter conv(*mReader.getContext(), mDeviceContext, mAccumulator, + DEVICE_ID); + + processAxis(conv, EV_ABS, ABS_MT_SLOT, 0); + processAxis(conv, EV_ABS, ABS_MT_TRACKING_ID, 1); + processAxis(conv, EV_ABS, ABS_MT_POSITION_X, 50); + processAxis(conv, EV_ABS, ABS_MT_POSITION_Y, 100); + processAxis(conv, EV_ABS, ABS_MT_TOOL_TYPE, MT_TOOL_FINGER); + processAxis(conv, EV_KEY, BTN_TOUCH, 1); + processAxis(conv, EV_KEY, BTN_TOOL_FINGER, 1); + + EXPECT_THAT(processSyncAndExpectSingleMotionArg(conv), + AllOf(WithMotionAction(AMOTION_EVENT_ACTION_DOWN), WithToolType(ToolType::FINGER), + WithPointerCount(1u))); + + processAxis(conv, EV_ABS, ABS_MT_POSITION_X, 51); + processAxis(conv, EV_ABS, ABS_MT_TOOL_TYPE, MT_TOOL_PALM); + + std::list args = processSync(conv); + ASSERT_EQ(2u, args.size()); + EXPECT_THAT(std::get(args.front()), + AllOf(WithMotionAction(AMOTION_EVENT_ACTION_MOVE), WithPointerCount(1u))); + args.pop_front(); + EXPECT_THAT(std::get(args.front()), + AllOf(WithMotionAction(AMOTION_EVENT_ACTION_CANCEL), WithPointerCount(1u))); + + processAxis(conv, EV_ABS, ABS_MT_POSITION_X, 52); + + EXPECT_EQ(0u, processSync(conv).size()); + + processAxis(conv, EV_ABS, ABS_MT_TRACKING_ID, -1); + processAxis(conv, EV_KEY, BTN_TOUCH, 0); + processAxis(conv, EV_KEY, BTN_TOOL_FINGER, 0); + + EXPECT_EQ(0u, processSync(conv).size()); +} + +TEST_F(CapturedTouchpadEventConverterTest, PalmTurningIntoFinger_reported) { + addBasicAxesToEventHub(); + mFakeEventHub->addAbsoluteAxis(EVENTHUB_ID, ABS_MT_TOOL_TYPE, 0, MT_TOOL_PALM, 0, 0, 0); + CapturedTouchpadEventConverter conv(*mReader.getContext(), mDeviceContext, mAccumulator, + DEVICE_ID); + + processAxis(conv, EV_ABS, ABS_MT_SLOT, 0); + processAxis(conv, EV_ABS, ABS_MT_TRACKING_ID, 1); + processAxis(conv, EV_ABS, ABS_MT_POSITION_X, 50); + processAxis(conv, EV_ABS, ABS_MT_POSITION_Y, 100); + processAxis(conv, EV_ABS, ABS_MT_TOOL_TYPE, MT_TOOL_PALM); + processAxis(conv, EV_KEY, BTN_TOUCH, 1); + processAxis(conv, EV_KEY, BTN_TOOL_FINGER, 1); + + EXPECT_EQ(0u, processSync(conv).size()); + + processAxis(conv, EV_ABS, ABS_MT_POSITION_X, 51); + processAxis(conv, EV_ABS, ABS_MT_TOOL_TYPE, MT_TOOL_FINGER); + + EXPECT_THAT(processSyncAndExpectSingleMotionArg(conv), + AllOf(WithMotionAction(AMOTION_EVENT_ACTION_DOWN), WithPointerCount(1u), + WithCoords(51, 100))); + + processAxis(conv, EV_ABS, ABS_MT_POSITION_X, 52); + + EXPECT_THAT(processSyncAndExpectSingleMotionArg(conv), + AllOf(WithMotionAction(AMOTION_EVENT_ACTION_MOVE), WithPointerCount(1u), + WithCoords(52, 100))); +} + +TEST_F(CapturedTouchpadEventConverterTest, FingerArrivingAfterPalm_onlyFingerReported) { + addBasicAxesToEventHub(); + mFakeEventHub->addAbsoluteAxis(EVENTHUB_ID, ABS_MT_TOOL_TYPE, 0, MT_TOOL_PALM, 0, 0, 0); + CapturedTouchpadEventConverter conv(*mReader.getContext(), mDeviceContext, mAccumulator, + DEVICE_ID); + + processAxis(conv, EV_ABS, ABS_MT_SLOT, 0); + processAxis(conv, EV_ABS, ABS_MT_TRACKING_ID, 1); + processAxis(conv, EV_ABS, ABS_MT_POSITION_X, 50); + processAxis(conv, EV_ABS, ABS_MT_POSITION_Y, 100); + processAxis(conv, EV_ABS, ABS_MT_TOOL_TYPE, MT_TOOL_PALM); + processAxis(conv, EV_KEY, BTN_TOUCH, 1); + processAxis(conv, EV_KEY, BTN_TOOL_FINGER, 1); + + EXPECT_EQ(0u, processSync(conv).size()); + + processAxis(conv, EV_ABS, ABS_MT_SLOT, 1); + processAxis(conv, EV_ABS, ABS_MT_TRACKING_ID, 2); + processAxis(conv, EV_ABS, ABS_MT_POSITION_X, 100); + processAxis(conv, EV_ABS, ABS_MT_POSITION_Y, 150); + processAxis(conv, EV_ABS, ABS_MT_TOOL_TYPE, MT_TOOL_FINGER); + processAxis(conv, EV_KEY, BTN_TOOL_FINGER, 0); + processAxis(conv, EV_KEY, BTN_TOOL_DOUBLETAP, 1); + + EXPECT_THAT(processSyncAndExpectSingleMotionArg(conv), + AllOf(WithMotionAction(AMOTION_EVENT_ACTION_DOWN), WithPointerCount(1u), + WithCoords(100, 150))); + + processAxis(conv, EV_ABS, ABS_MT_SLOT, 0); + processAxis(conv, EV_ABS, ABS_MT_POSITION_X, 52); + processAxis(conv, EV_ABS, ABS_MT_POSITION_Y, 102); + processAxis(conv, EV_ABS, ABS_MT_SLOT, 1); + processAxis(conv, EV_ABS, ABS_MT_POSITION_X, 98); + processAxis(conv, EV_ABS, ABS_MT_POSITION_Y, 148); + + EXPECT_THAT(processSyncAndExpectSingleMotionArg(conv), + AllOf(WithMotionAction(AMOTION_EVENT_ACTION_MOVE), WithPointerCount(1u), + WithCoords(98, 148))); +} + +TEST_F(CapturedTouchpadEventConverterTest, FingerAndFingerTurningIntoPalm_partiallyCancelled) { + addBasicAxesToEventHub(); + mFakeEventHub->addAbsoluteAxis(EVENTHUB_ID, ABS_MT_TOOL_TYPE, 0, MT_TOOL_PALM, 0, 0, 0); + CapturedTouchpadEventConverter conv(*mReader.getContext(), mDeviceContext, mAccumulator, + DEVICE_ID); + + processAxis(conv, EV_ABS, ABS_MT_SLOT, 0); + processAxis(conv, EV_ABS, ABS_MT_TRACKING_ID, 1); + processAxis(conv, EV_ABS, ABS_MT_POSITION_X, 50); + processAxis(conv, EV_ABS, ABS_MT_TOOL_TYPE, MT_TOOL_FINGER); + + processAxis(conv, EV_ABS, ABS_MT_SLOT, 1); + processAxis(conv, EV_ABS, ABS_MT_TRACKING_ID, 2); + processAxis(conv, EV_ABS, ABS_MT_POSITION_X, 250); + processAxis(conv, EV_ABS, ABS_MT_TOOL_TYPE, MT_TOOL_FINGER); + + processAxis(conv, EV_KEY, BTN_TOUCH, 1); + processAxis(conv, EV_KEY, BTN_TOOL_DOUBLETAP, 1); + + std::list args = processSync(conv); + ASSERT_EQ(2u, args.size()); + EXPECT_THAT(std::get(args.front()), + AllOf(WithMotionAction(AMOTION_EVENT_ACTION_DOWN), WithPointerCount(1u), + WithToolType(ToolType::FINGER))); + args.pop_front(); + EXPECT_THAT(std::get(args.front()), + AllOf(WithMotionAction(AMOTION_EVENT_ACTION_POINTER_DOWN | + 1 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT), + WithPointerCount(2u), WithPointerToolType(0, ToolType::FINGER), + WithPointerToolType(1, ToolType::FINGER))); + + processAxis(conv, EV_ABS, ABS_MT_SLOT, 0); + processAxis(conv, EV_ABS, ABS_MT_POSITION_X, 51); + + processAxis(conv, EV_ABS, ABS_MT_SLOT, 1); + processAxis(conv, EV_ABS, ABS_MT_POSITION_X, 251); + processAxis(conv, EV_ABS, ABS_MT_TOOL_TYPE, MT_TOOL_PALM); + + args = processSync(conv); + ASSERT_EQ(2u, args.size()); + EXPECT_THAT(std::get(args.front()), + AllOf(WithMotionAction(AMOTION_EVENT_ACTION_MOVE), WithPointerCount(2u))); + args.pop_front(); + EXPECT_THAT(std::get(args.front()), + AllOf(WithMotionAction(AMOTION_EVENT_ACTION_POINTER_UP | + 1 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT), + WithFlags(AMOTION_EVENT_FLAG_CANCELED), WithPointerCount(2u))); +} + +TEST_F(CapturedTouchpadEventConverterTest, FingerAndPalmTurningIntoFinger_reported) { + addBasicAxesToEventHub(); + mFakeEventHub->addAbsoluteAxis(EVENTHUB_ID, ABS_MT_TOOL_TYPE, 0, MT_TOOL_PALM, 0, 0, 0); + CapturedTouchpadEventConverter conv(*mReader.getContext(), mDeviceContext, mAccumulator, + DEVICE_ID); + + processAxis(conv, EV_ABS, ABS_MT_SLOT, 0); + processAxis(conv, EV_ABS, ABS_MT_TRACKING_ID, 1); + processAxis(conv, EV_ABS, ABS_MT_POSITION_X, 50); + processAxis(conv, EV_ABS, ABS_MT_TOOL_TYPE, MT_TOOL_FINGER); + + processAxis(conv, EV_ABS, ABS_MT_SLOT, 1); + processAxis(conv, EV_ABS, ABS_MT_TRACKING_ID, 2); + processAxis(conv, EV_ABS, ABS_MT_POSITION_X, 250); + processAxis(conv, EV_ABS, ABS_MT_TOOL_TYPE, MT_TOOL_PALM); + + processAxis(conv, EV_KEY, BTN_TOUCH, 1); + processAxis(conv, EV_KEY, BTN_TOOL_DOUBLETAP, 1); + + EXPECT_THAT(processSyncAndExpectSingleMotionArg(conv), + AllOf(WithMotionAction(AMOTION_EVENT_ACTION_DOWN), WithPointerCount(1u), + WithToolType(ToolType::FINGER))); + + processAxis(conv, EV_ABS, ABS_MT_SLOT, 0); + processAxis(conv, EV_ABS, ABS_MT_POSITION_X, 51); + + processAxis(conv, EV_ABS, ABS_MT_SLOT, 1); + processAxis(conv, EV_ABS, ABS_MT_POSITION_X, 251); + processAxis(conv, EV_ABS, ABS_MT_TOOL_TYPE, MT_TOOL_FINGER); + + std::list args = processSync(conv); + ASSERT_EQ(2u, args.size()); + EXPECT_THAT(std::get(args.front()), + AllOf(WithMotionAction(AMOTION_EVENT_ACTION_MOVE), WithPointerCount(1u))); + args.pop_front(); + EXPECT_THAT(std::get(args.front()), + AllOf(WithMotionAction(AMOTION_EVENT_ACTION_POINTER_DOWN | + 1 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT), + WithPointerCount(2u))); +} + +TEST_F(CapturedTouchpadEventConverterTest, TwoFingers_motionReportedCorrectly) { + CapturedTouchpadEventConverter conv = createConverter(); + + processAxis(conv, EV_ABS, ABS_MT_SLOT, 0); + processAxis(conv, EV_ABS, ABS_MT_TRACKING_ID, 1); + processAxis(conv, EV_ABS, ABS_MT_POSITION_X, 50); + processAxis(conv, EV_ABS, ABS_MT_POSITION_Y, 100); + + processAxis(conv, EV_KEY, BTN_TOUCH, 1); + processAxis(conv, EV_KEY, BTN_TOOL_FINGER, 1); + + EXPECT_THAT(processSyncAndExpectSingleMotionArg(conv), + AllOf(WithMotionAction(AMOTION_EVENT_ACTION_DOWN), WithPointerCount(1u), + WithCoords(50, 100), WithToolType(ToolType::FINGER))); + + processAxis(conv, EV_ABS, ABS_MT_SLOT, 0); + processAxis(conv, EV_ABS, ABS_MT_POSITION_X, 52); + processAxis(conv, EV_ABS, ABS_MT_POSITION_Y, 99); + + processAxis(conv, EV_ABS, ABS_MT_SLOT, 1); + processAxis(conv, EV_ABS, ABS_MT_TRACKING_ID, 2); + processAxis(conv, EV_ABS, ABS_MT_POSITION_X, 250); + processAxis(conv, EV_ABS, ABS_MT_POSITION_Y, 200); + + processAxis(conv, EV_KEY, BTN_TOOL_FINGER, 0); + processAxis(conv, EV_KEY, BTN_TOOL_DOUBLETAP, 1); + + std::list args = processSync(conv); + ASSERT_EQ(2u, args.size()); + EXPECT_THAT(std::get(args.front()), + AllOf(WithMotionAction(AMOTION_EVENT_ACTION_MOVE), WithPointerCount(1u), + WithCoords(52, 99), WithToolType(ToolType::FINGER))); + args.pop_front(); + EXPECT_THAT(std::get(args.front()), + AllOf(WithMotionAction(AMOTION_EVENT_ACTION_POINTER_DOWN | + 1 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT), + WithPointerCount(2u), WithPointerCoords(0, 52, 99), + WithPointerCoords(1, 250, 200), WithPointerToolType(0, ToolType::FINGER), + WithPointerToolType(1, ToolType::FINGER))); + + processAxis(conv, EV_ABS, ABS_MT_SLOT, 0); + processAxis(conv, EV_ABS, ABS_MT_TRACKING_ID, -1); + processAxis(conv, EV_ABS, ABS_MT_SLOT, 1); + processAxis(conv, EV_ABS, ABS_MT_POSITION_X, 255); + processAxis(conv, EV_ABS, ABS_MT_POSITION_Y, 202); + + processAxis(conv, EV_KEY, BTN_TOOL_FINGER, 1); + processAxis(conv, EV_KEY, BTN_TOOL_DOUBLETAP, 0); + + args = processSync(conv); + ASSERT_EQ(2u, args.size()); + EXPECT_THAT(std::get(args.front()), + AllOf(WithMotionAction(AMOTION_EVENT_ACTION_MOVE), WithPointerCount(2u), + WithPointerCoords(0, 52, 99), WithPointerCoords(1, 255, 202), + WithPointerToolType(1, ToolType::FINGER), + WithPointerToolType(0, ToolType::FINGER))); + args.pop_front(); + EXPECT_THAT(std::get(args.front()), + AllOf(WithMotionAction(AMOTION_EVENT_ACTION_POINTER_UP | + 0 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT), + WithPointerCount(2u), WithPointerCoords(0, 52, 99), + WithPointerCoords(1, 255, 202), WithPointerToolType(0, ToolType::FINGER), + WithPointerToolType(1, ToolType::FINGER))); + + processAxis(conv, EV_ABS, ABS_MT_TRACKING_ID, -1); + processAxis(conv, EV_KEY, BTN_TOOL_FINGER, 0); + processAxis(conv, EV_KEY, BTN_TOUCH, 0); + + args = processSync(conv); + ASSERT_EQ(2u, args.size()); + EXPECT_THAT(std::get(args.front()), + AllOf(WithMotionAction(AMOTION_EVENT_ACTION_MOVE), WithPointerCount(1u), + WithCoords(255, 202), WithToolType(ToolType::FINGER))); + args.pop_front(); + EXPECT_THAT(std::get(args.front()), + AllOf(WithMotionAction(AMOTION_EVENT_ACTION_UP), WithPointerCount(1u), + WithCoords(255, 202), WithToolType(ToolType::FINGER))); +} + +// Pointer IDs max out at 31, and so must be reused once a touch is lifted to avoid running out. +TEST_F(CapturedTouchpadEventConverterTest, PointerIdsReusedAfterLift) { + CapturedTouchpadEventConverter conv = createConverter(); + + // Put down two fingers, which should get IDs 0 and 1. + processAxis(conv, EV_ABS, ABS_MT_SLOT, 0); + processAxis(conv, EV_ABS, ABS_MT_TRACKING_ID, 1); + processAxis(conv, EV_ABS, ABS_MT_POSITION_X, 10); + processAxis(conv, EV_ABS, ABS_MT_SLOT, 1); + processAxis(conv, EV_ABS, ABS_MT_TRACKING_ID, 2); + processAxis(conv, EV_ABS, ABS_MT_POSITION_X, 20); + + processAxis(conv, EV_KEY, BTN_TOUCH, 1); + processAxis(conv, EV_KEY, BTN_TOOL_DOUBLETAP, 1); + + std::list args = processSync(conv); + ASSERT_EQ(2u, args.size()); + EXPECT_THAT(std::get(args.front()), + AllOf(WithMotionAction(AMOTION_EVENT_ACTION_DOWN), WithPointerCount(1u), + WithPointerId(/*index=*/0, /*id=*/0))); + args.pop_front(); + EXPECT_THAT(std::get(args.front()), + AllOf(WithMotionAction(AMOTION_EVENT_ACTION_POINTER_DOWN | + 1 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT), + WithPointerCount(2u), WithPointerId(/*index=*/0, /*id=*/0), + WithPointerId(/*index=*/1, /*id=*/1))); + + // Lift the finger in slot 0, freeing up pointer ID 0... + processAxis(conv, EV_ABS, ABS_MT_SLOT, 0); + processAxis(conv, EV_ABS, ABS_MT_TRACKING_ID, -1); + + // ...and simultaneously add a finger in slot 2. + processAxis(conv, EV_ABS, ABS_MT_SLOT, 2); + processAxis(conv, EV_ABS, ABS_MT_TRACKING_ID, 3); + processAxis(conv, EV_ABS, ABS_MT_POSITION_X, 30); + + args = processSync(conv); + ASSERT_EQ(3u, args.size()); + // Slot 1 being present will result in a MOVE event, even though it hasn't actually moved (see + // comments in CapturedTouchpadEventConverter::sync). + EXPECT_THAT(std::get(args.front()), + AllOf(WithMotionAction(AMOTION_EVENT_ACTION_MOVE), WithPointerCount(2u), + WithPointerId(/*index=*/0, /*id=*/0), WithPointerId(/*index=*/1, /*id=*/1))); + args.pop_front(); + EXPECT_THAT(std::get(args.front()), + AllOf(WithMotionAction(AMOTION_EVENT_ACTION_POINTER_UP | + 0 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT), + WithPointerCount(2u), WithPointerId(/*index=*/0, /*id=*/0), + WithPointerId(/*index=*/1, /*id=*/1))); + args.pop_front(); + // Slot 0 being lifted causes the finger from slot 1 to move up to index 0, but keep its + // previous ID. The new finger in slot 2 should take ID 0, which was just freed up. + EXPECT_THAT(std::get(args.front()), + AllOf(WithMotionAction(AMOTION_EVENT_ACTION_POINTER_DOWN | + 1 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT), + WithPointerCount(2u), WithPointerId(/*index=*/0, /*id=*/1), + WithPointerId(/*index=*/1, /*id=*/0))); +} + +// Motion events without any pointers are invalid, so when a button press is reported in the same +// frame as a touch down, the button press must be reported second. Similarly with a button release +// and a touch lift. +TEST_F(CapturedTouchpadEventConverterTest, + ButtonPressedAndReleasedInSameFrameAsTouch_ReportedWithPointers) { + CapturedTouchpadEventConverter conv = createConverter(); + + processAxis(conv, EV_ABS, ABS_MT_SLOT, 0); + processAxis(conv, EV_ABS, ABS_MT_TRACKING_ID, 1); + processAxis(conv, EV_ABS, ABS_MT_POSITION_X, 50); + processAxis(conv, EV_ABS, ABS_MT_POSITION_Y, 100); + processAxis(conv, EV_KEY, BTN_TOUCH, 1); + processAxis(conv, EV_KEY, BTN_TOOL_FINGER, 1); + + processAxis(conv, EV_KEY, BTN_LEFT, 1); + + std::list args = processSync(conv); + ASSERT_EQ(2u, args.size()); + EXPECT_THAT(std::get(args.front()), + WithMotionAction(AMOTION_EVENT_ACTION_DOWN)); + args.pop_front(); + EXPECT_THAT(std::get(args.front()), + AllOf(WithMotionAction(AMOTION_EVENT_ACTION_BUTTON_PRESS), WithPointerCount(1u), + WithCoords(50, 100), WithActionButton(AMOTION_EVENT_BUTTON_PRIMARY), + WithButtonState(AMOTION_EVENT_BUTTON_PRIMARY))); + + processAxis(conv, EV_ABS, ABS_MT_TRACKING_ID, -1); + processAxis(conv, EV_KEY, BTN_TOUCH, 0); + processAxis(conv, EV_KEY, BTN_TOOL_FINGER, 0); + + processAxis(conv, EV_KEY, BTN_LEFT, 0); + args = processSync(conv); + ASSERT_EQ(3u, args.size()); + EXPECT_THAT(std::get(args.front()), + WithMotionAction(AMOTION_EVENT_ACTION_MOVE)); + args.pop_front(); + EXPECT_THAT(std::get(args.front()), + AllOf(WithMotionAction(AMOTION_EVENT_ACTION_BUTTON_RELEASE), WithPointerCount(1u), + WithCoords(50, 100), WithActionButton(AMOTION_EVENT_BUTTON_PRIMARY), + WithButtonState(0))); + args.pop_front(); + EXPECT_THAT(std::get(args.front()), + WithMotionAction(AMOTION_EVENT_ACTION_UP)); +} + +// Some touchpads sometimes report a button press before they report the finger touching the pad. In +// that case we need to wait until the touch comes to report the button press. +TEST_F(CapturedTouchpadEventConverterTest, ButtonPressedBeforeTouch_ReportedOnceTouchOccurs) { + CapturedTouchpadEventConverter conv = createConverter(); + + processAxis(conv, EV_KEY, BTN_LEFT, 1); + ASSERT_EQ(0u, processSync(conv).size()); + + processAxis(conv, EV_ABS, ABS_MT_SLOT, 0); + processAxis(conv, EV_ABS, ABS_MT_TRACKING_ID, 1); + processAxis(conv, EV_ABS, ABS_MT_POSITION_X, 50); + processAxis(conv, EV_ABS, ABS_MT_POSITION_Y, 100); + processAxis(conv, EV_KEY, BTN_TOUCH, 1); + processAxis(conv, EV_KEY, BTN_TOOL_FINGER, 1); + + std::list args = processSync(conv); + ASSERT_EQ(2u, args.size()); + EXPECT_THAT(std::get(args.front()), + WithMotionAction(AMOTION_EVENT_ACTION_DOWN)); + args.pop_front(); + EXPECT_THAT(std::get(args.front()), + AllOf(WithMotionAction(AMOTION_EVENT_ACTION_BUTTON_PRESS), WithPointerCount(1u), + WithCoords(50, 100), WithActionButton(AMOTION_EVENT_BUTTON_PRIMARY), + WithButtonState(AMOTION_EVENT_BUTTON_PRIMARY))); +} + +// When all fingers are lifted from a touchpad, we should release any buttons that are down, since +// we won't be able to report them being lifted later if no pointers are present. +TEST_F(CapturedTouchpadEventConverterTest, ButtonReleasedAfterTouchLifts_ReportedWithLift) { + CapturedTouchpadEventConverter conv = createConverter(); + + processAxis(conv, EV_ABS, ABS_MT_SLOT, 0); + processAxis(conv, EV_ABS, ABS_MT_TRACKING_ID, 1); + processAxis(conv, EV_ABS, ABS_MT_POSITION_X, 50); + processAxis(conv, EV_ABS, ABS_MT_POSITION_Y, 100); + processAxis(conv, EV_KEY, BTN_TOUCH, 1); + processAxis(conv, EV_KEY, BTN_TOOL_FINGER, 1); + + processAxis(conv, EV_KEY, BTN_LEFT, 1); + + std::list args = processSync(conv); + ASSERT_EQ(2u, args.size()); + EXPECT_THAT(std::get(args.front()), + WithMotionAction(AMOTION_EVENT_ACTION_DOWN)); + args.pop_front(); + EXPECT_THAT(std::get(args.front()), + WithMotionAction(AMOTION_EVENT_ACTION_BUTTON_PRESS)); + + processAxis(conv, EV_ABS, ABS_MT_TRACKING_ID, -1); + processAxis(conv, EV_KEY, BTN_TOUCH, 0); + processAxis(conv, EV_KEY, BTN_TOOL_FINGER, 0); + args = processSync(conv); + ASSERT_EQ(3u, args.size()); + EXPECT_THAT(std::get(args.front()), + WithMotionAction(AMOTION_EVENT_ACTION_MOVE)); + args.pop_front(); + EXPECT_THAT(std::get(args.front()), + AllOf(WithMotionAction(AMOTION_EVENT_ACTION_BUTTON_RELEASE), WithPointerCount(1u), + WithCoords(50, 100), WithActionButton(AMOTION_EVENT_BUTTON_PRIMARY), + WithButtonState(0))); + args.pop_front(); + EXPECT_THAT(std::get(args.front()), + WithMotionAction(AMOTION_EVENT_ACTION_UP)); + + processAxis(conv, EV_KEY, BTN_LEFT, 0); + ASSERT_EQ(0u, processSync(conv).size()); +} + +TEST_F(CapturedTouchpadEventConverterTest, MultipleButtonsPressedDuringTouch_ReportedCorrectly) { + CapturedTouchpadEventConverter conv = createConverter(); + + processAxis(conv, EV_ABS, ABS_MT_SLOT, 0); + processAxis(conv, EV_ABS, ABS_MT_TRACKING_ID, 1); + processAxis(conv, EV_ABS, ABS_MT_POSITION_X, 50); + processAxis(conv, EV_ABS, ABS_MT_POSITION_Y, 100); + processAxis(conv, EV_KEY, BTN_TOUCH, 1); + processAxis(conv, EV_KEY, BTN_TOOL_FINGER, 1); + + EXPECT_THAT(processSyncAndExpectSingleMotionArg(conv), + WithMotionAction(AMOTION_EVENT_ACTION_DOWN)); + + processAxis(conv, EV_KEY, BTN_LEFT, 1); + std::list args = processSync(conv); + ASSERT_EQ(2u, args.size()); + EXPECT_THAT(std::get(args.front()), + WithMotionAction(AMOTION_EVENT_ACTION_MOVE)); + args.pop_front(); + EXPECT_THAT(std::get(args.front()), + AllOf(WithMotionAction(AMOTION_EVENT_ACTION_BUTTON_PRESS), + WithActionButton(AMOTION_EVENT_BUTTON_PRIMARY), + WithButtonState(AMOTION_EVENT_BUTTON_PRIMARY))); + + processAxis(conv, EV_KEY, BTN_RIGHT, 1); + args = processSync(conv); + ASSERT_EQ(2u, args.size()); + EXPECT_THAT(std::get(args.front()), + WithMotionAction(AMOTION_EVENT_ACTION_MOVE)); + args.pop_front(); + EXPECT_THAT(std::get(args.front()), + AllOf(WithMotionAction(AMOTION_EVENT_ACTION_BUTTON_PRESS), + WithActionButton(AMOTION_EVENT_BUTTON_SECONDARY), + WithButtonState(AMOTION_EVENT_BUTTON_PRIMARY | + AMOTION_EVENT_BUTTON_SECONDARY))); + + processAxis(conv, EV_KEY, BTN_LEFT, 0); + args = processSync(conv); + ASSERT_EQ(2u, args.size()); + EXPECT_THAT(std::get(args.front()), + WithMotionAction(AMOTION_EVENT_ACTION_MOVE)); + args.pop_front(); + EXPECT_THAT(std::get(args.front()), + AllOf(WithMotionAction(AMOTION_EVENT_ACTION_BUTTON_RELEASE), + WithActionButton(AMOTION_EVENT_BUTTON_PRIMARY), + WithButtonState(AMOTION_EVENT_BUTTON_SECONDARY))); + + processAxis(conv, EV_KEY, BTN_RIGHT, 0); + args = processSync(conv); + ASSERT_EQ(2u, args.size()); + EXPECT_THAT(std::get(args.front()), + WithMotionAction(AMOTION_EVENT_ACTION_MOVE)); + args.pop_front(); + EXPECT_THAT(std::get(args.front()), + AllOf(WithMotionAction(AMOTION_EVENT_ACTION_BUTTON_RELEASE), + WithActionButton(AMOTION_EVENT_BUTTON_SECONDARY), WithButtonState(0))); +} + +} // namespace android diff --git a/services/inputflinger/tests/CursorInputMapper_test.cpp b/services/inputflinger/tests/CursorInputMapper_test.cpp new file mode 100644 index 0000000000000000000000000000000000000000..6774b1793f1c438d06f9d8d5508ac57a02cd8172 --- /dev/null +++ b/services/inputflinger/tests/CursorInputMapper_test.cpp @@ -0,0 +1,105 @@ +/* + * 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 "CursorInputMapper.h" + +#include +#include + +#include "FakePointerController.h" +#include "InputMapperTest.h" +#include "InterfaceMocks.h" +#include "TestInputListenerMatchers.h" + +#define TAG "CursorInputMapper_test" + +namespace android { + +using testing::Return; +using testing::VariantWith; +constexpr auto ACTION_DOWN = AMOTION_EVENT_ACTION_DOWN; +constexpr auto ACTION_MOVE = AMOTION_EVENT_ACTION_MOVE; +constexpr auto ACTION_UP = AMOTION_EVENT_ACTION_UP; +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; + +/** + * Unit tests for CursorInputMapper. + * This class is named 'CursorInputMapperUnitTest' to avoid name collision with the existing + * 'CursorInputMapperTest'. If all of the CursorInputMapper tests are migrated here, the name + * can be simplified to 'CursorInputMapperTest'. + * TODO(b/283812079): move CursorInputMapper tests here. + */ +class CursorInputMapperUnitTest : public InputMapperUnitTest { +protected: + void SetUp() override { + InputMapperUnitTest::SetUp(); + + // Current scan code state - all keys are UP by default + setScanCodeState(KeyState::UP, + {BTN_LEFT, BTN_RIGHT, BTN_MIDDLE, BTN_BACK, BTN_SIDE, BTN_FORWARD, + BTN_EXTRA, BTN_TASK}); + EXPECT_CALL(mMockEventHub, hasRelativeAxis(EVENTHUB_ID, REL_WHEEL)) + .WillRepeatedly(Return(false)); + EXPECT_CALL(mMockEventHub, hasRelativeAxis(EVENTHUB_ID, REL_HWHEEL)) + .WillRepeatedly(Return(false)); + + EXPECT_CALL(mMockInputReaderContext, bumpGeneration()).WillRepeatedly(Return(1)); + + mMapper = createInputMapper(*mDeviceContext, mReaderConfiguration); + } +}; + +/** + * Move the mouse and then click the button. Check whether HOVER_EXIT is generated when hovering + * ends. Currently, it is not. + */ +TEST_F(CursorInputMapperUnitTest, HoverAndLeftButtonPress) { + std::list args; + + // Move the cursor a little + args += process(EV_REL, REL_X, 10); + args += process(EV_REL, REL_Y, 20); + args += process(EV_SYN, SYN_REPORT, 0); + ASSERT_THAT(args, ElementsAre(VariantWith(WithMotionAction(HOVER_MOVE)))); + + // Now click the mouse button + args.clear(); + args += process(EV_KEY, BTN_LEFT, 1); + args += process(EV_SYN, SYN_REPORT, 0); + ASSERT_THAT(args, + ElementsAre(VariantWith(WithMotionAction(ACTION_DOWN)), + VariantWith(WithMotionAction(BUTTON_PRESS)))); + + // Move some more. + args.clear(); + args += process(EV_REL, REL_X, 10); + args += process(EV_REL, REL_Y, 20); + args += process(EV_SYN, SYN_REPORT, 0); + ASSERT_THAT(args, ElementsAre(VariantWith(WithMotionAction(ACTION_MOVE)))); + + // Release the button + args.clear(); + args += process(EV_KEY, BTN_LEFT, 0); + args += process(EV_SYN, SYN_REPORT, 0); + ASSERT_THAT(args, + ElementsAre(VariantWith(WithMotionAction(BUTTON_RELEASE)), + VariantWith(WithMotionAction(ACTION_UP)), + VariantWith(WithMotionAction(HOVER_MOVE)))); +} + +} // namespace android diff --git a/services/inputflinger/tests/EventHub_test.cpp b/services/inputflinger/tests/EventHub_test.cpp index 6ef6e4498cfe8519dd7b8fa742b7ab47d75085b4..2e296daa226bf423bd774e3e9fa4ccc360f0146a 100644 --- a/services/inputflinger/tests/EventHub_test.cpp +++ b/services/inputflinger/tests/EventHub_test.cpp @@ -68,12 +68,18 @@ protected: int32_t mDeviceId; virtual void SetUp() override { +#if !defined(__ANDROID__) + GTEST_SKIP() << "It's only possible to interact with uinput on device"; +#endif mEventHub = std::make_unique(); consumeInitialDeviceAddedEvents(); mKeyboard = createUinputDevice(); ASSERT_NO_FATAL_FAILURE(mDeviceId = waitForDeviceCreation()); } virtual void TearDown() override { +#if !defined(__ANDROID__) + return; +#endif mKeyboard.reset(); waitForDeviceClose(mDeviceId); assertNoMoreEvents(); @@ -99,8 +105,6 @@ protected: }; std::vector EventHubTest::getEvents(std::optional expectedEvents) { - static constexpr size_t EVENT_BUFFER_SIZE = 256; - std::array eventBuffer; std::vector events; while (true) { @@ -108,12 +112,12 @@ std::vector EventHubTest::getEvents(std::optional expectedEven if (expectedEvents) { timeout = 2s; } - const size_t count = - mEventHub->getEvents(timeout.count(), eventBuffer.data(), eventBuffer.size()); - if (count == 0) { + + std::vector newEvents = mEventHub->getEvents(timeout.count()); + if (newEvents.empty()) { break; } - events.insert(events.end(), eventBuffer.begin(), eventBuffer.begin() + count); + events.insert(events.end(), newEvents.begin(), newEvents.end()); if (expectedEvents && events.size() >= *expectedEvents) { break; } diff --git a/services/inputflinger/tests/FakeEventHub.cpp b/services/inputflinger/tests/FakeEventHub.cpp new file mode 100644 index 0000000000000000000000000000000000000000..212fcebecdfd146c2b815a8e9bb1c139e26f0c62 --- /dev/null +++ b/services/inputflinger/tests/FakeEventHub.cpp @@ -0,0 +1,628 @@ +/* + * 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 "FakeEventHub.h" + +#include +#include +#include + +#include "TestConstants.h" + +namespace android { + +const std::string FakeEventHub::BATTERY_DEVPATH = "/sys/devices/mydevice/power_supply/mybattery"; + +FakeEventHub::~FakeEventHub() { + for (size_t i = 0; i < mDevices.size(); i++) { + delete mDevices.valueAt(i); + } +} + +void FakeEventHub::addDevice(int32_t deviceId, const std::string& name, + ftl::Flags classes, int bus) { + Device* device = new Device(classes); + device->identifier.name = name; + device->identifier.bus = bus; + mDevices.add(deviceId, device); + + enqueueEvent(ARBITRARY_TIME, READ_TIME, deviceId, EventHubInterface::DEVICE_ADDED, 0, 0); +} + +void FakeEventHub::removeDevice(int32_t deviceId) { + delete mDevices.valueFor(deviceId); + mDevices.removeItem(deviceId); + + enqueueEvent(ARBITRARY_TIME, READ_TIME, deviceId, EventHubInterface::DEVICE_REMOVED, 0, 0); +} + +bool FakeEventHub::isDeviceEnabled(int32_t deviceId) const { + Device* device = getDevice(deviceId); + if (device == nullptr) { + ALOGE("Incorrect device id=%" PRId32 " provided to %s", deviceId, __func__); + return false; + } + return device->enabled; +} + +status_t FakeEventHub::enableDevice(int32_t deviceId) { + status_t result; + Device* device = getDevice(deviceId); + if (device == nullptr) { + ALOGE("Incorrect device id=%" PRId32 " provided to %s", deviceId, __func__); + return BAD_VALUE; + } + if (device->enabled) { + ALOGW("Duplicate call to %s, device %" PRId32 " already enabled", __func__, deviceId); + return OK; + } + result = device->enable(); + return result; +} + +status_t FakeEventHub::disableDevice(int32_t deviceId) { + Device* device = getDevice(deviceId); + if (device == nullptr) { + ALOGE("Incorrect device id=%" PRId32 " provided to %s", deviceId, __func__); + return BAD_VALUE; + } + if (!device->enabled) { + ALOGW("Duplicate call to %s, device %" PRId32 " already disabled", __func__, deviceId); + return OK; + } + return device->disable(); +} + +void FakeEventHub::finishDeviceScan() { + enqueueEvent(ARBITRARY_TIME, READ_TIME, 0, EventHubInterface::FINISHED_DEVICE_SCAN, 0, 0); +} + +void FakeEventHub::addConfigurationProperty(int32_t deviceId, const char* key, const char* value) { + getDevice(deviceId)->configuration.addProperty(key, value); +} + +void FakeEventHub::addConfigurationMap(int32_t deviceId, const PropertyMap* configuration) { + getDevice(deviceId)->configuration.addAll(configuration); +} + +void FakeEventHub::addAbsoluteAxis(int32_t deviceId, int axis, int32_t minValue, int32_t maxValue, + int flat, int fuzz, int resolution) { + Device* device = getDevice(deviceId); + + RawAbsoluteAxisInfo info; + info.valid = true; + info.minValue = minValue; + info.maxValue = maxValue; + info.flat = flat; + info.fuzz = fuzz; + info.resolution = resolution; + device->absoluteAxes.add(axis, info); +} + +void FakeEventHub::addRelativeAxis(int32_t deviceId, int32_t axis) { + getDevice(deviceId)->relativeAxes.add(axis, true); +} + +void FakeEventHub::setKeyCodeState(int32_t deviceId, int32_t keyCode, int32_t state) { + getDevice(deviceId)->keyCodeStates.replaceValueFor(keyCode, state); +} + +void FakeEventHub::setRawLayoutInfo(int32_t deviceId, RawLayoutInfo info) { + getDevice(deviceId)->layoutInfo = info; +} + +void FakeEventHub::setScanCodeState(int32_t deviceId, int32_t scanCode, int32_t state) { + getDevice(deviceId)->scanCodeStates.replaceValueFor(scanCode, state); +} + +void FakeEventHub::setSwitchState(int32_t deviceId, int32_t switchCode, int32_t state) { + getDevice(deviceId)->switchStates.replaceValueFor(switchCode, state); +} + +void FakeEventHub::setAbsoluteAxisValue(int32_t deviceId, int32_t axis, int32_t value) { + getDevice(deviceId)->absoluteAxisValue.replaceValueFor(axis, value); +} + +void FakeEventHub::addKey(int32_t deviceId, int32_t scanCode, int32_t usageCode, int32_t keyCode, + uint32_t flags) { + Device* device = getDevice(deviceId); + KeyInfo info; + info.keyCode = keyCode; + info.flags = flags; + if (scanCode) { + device->keysByScanCode.add(scanCode, info); + } + if (usageCode) { + device->keysByUsageCode.add(usageCode, info); + } +} + +void FakeEventHub::addKeyCodeMapping(int32_t deviceId, int32_t fromKeyCode, int32_t toKeyCode) { + getDevice(deviceId)->keyCodeMapping.insert_or_assign(fromKeyCode, toKeyCode); +} + +void FakeEventHub::addKeyRemapping(int32_t deviceId, int32_t fromKeyCode, int32_t toKeyCode) const { + Device* device = getDevice(deviceId); + device->keyRemapping.insert_or_assign(fromKeyCode, toKeyCode); +} + +void FakeEventHub::addLed(int32_t deviceId, int32_t led, bool initialState) { + getDevice(deviceId)->leds.add(led, initialState); +} + +void FakeEventHub::addSensorAxis(int32_t deviceId, int32_t absCode, + InputDeviceSensorType sensorType, int32_t sensorDataIndex) { + SensorInfo info; + info.sensorType = sensorType; + info.sensorDataIndex = sensorDataIndex; + getDevice(deviceId)->sensorsByAbsCode.emplace(absCode, info); +} + +void FakeEventHub::setMscEvent(int32_t deviceId, int32_t mscEvent) { + typename BitArray::Buffer buffer; + buffer[mscEvent / 32] = 1 << mscEvent % 32; + getDevice(deviceId)->mscBitmask.loadFromBuffer(buffer); +} + +void FakeEventHub::addRawLightInfo(int32_t rawId, RawLightInfo&& info) { + mRawLightInfos.emplace(rawId, std::move(info)); +} + +void FakeEventHub::fakeLightBrightness(int32_t rawId, int32_t brightness) { + mLightBrightness.emplace(rawId, brightness); +} + +void FakeEventHub::fakeLightIntensities(int32_t rawId, + const std::unordered_map intensities) { + mLightIntensities.emplace(rawId, std::move(intensities)); +} + +bool FakeEventHub::getLedState(int32_t deviceId, int32_t led) { + return getDevice(deviceId)->leds.valueFor(led); +} + +std::vector& FakeEventHub::getExcludedDevices() { + return mExcludedDevices; +} + +void FakeEventHub::addVirtualKeyDefinition(int32_t deviceId, + const VirtualKeyDefinition& definition) { + getDevice(deviceId)->virtualKeys.push_back(definition); +} + +void FakeEventHub::enqueueEvent(nsecs_t when, nsecs_t readTime, int32_t deviceId, int32_t type, + int32_t code, int32_t value) { + std::scoped_lock lock(mLock); + RawEvent event; + event.when = when; + event.readTime = readTime; + event.deviceId = deviceId; + event.type = type; + event.code = code; + event.value = value; + mEvents.push_back(event); + + if (type == EV_ABS) { + setAbsoluteAxisValue(deviceId, code, value); + } +} + +void FakeEventHub::setVideoFrames( + std::unordered_map> videoFrames) { + mVideoFrames = std::move(videoFrames); +} + +void FakeEventHub::assertQueueIsEmpty() { + std::unique_lock lock(mLock); + base::ScopedLockAssertion assumeLocked(mLock); + const bool queueIsEmpty = + mEventsCondition.wait_for(lock, WAIT_TIMEOUT, + [this]() REQUIRES(mLock) { return mEvents.size() == 0; }); + if (!queueIsEmpty) { + FAIL() << "Timed out waiting for EventHub queue to be emptied."; + } +} + +FakeEventHub::Device* FakeEventHub::getDevice(int32_t deviceId) const { + ssize_t index = mDevices.indexOfKey(deviceId); + return index >= 0 ? mDevices.valueAt(index) : nullptr; +} + +ftl::Flags FakeEventHub::getDeviceClasses(int32_t deviceId) const { + Device* device = getDevice(deviceId); + return device ? device->classes : ftl::Flags(0); +} + +InputDeviceIdentifier FakeEventHub::getDeviceIdentifier(int32_t deviceId) const { + Device* device = getDevice(deviceId); + return device ? device->identifier : InputDeviceIdentifier(); +} + +int32_t FakeEventHub::getDeviceControllerNumber(int32_t) const { + return 0; +} + +std::optional FakeEventHub::getConfiguration(int32_t deviceId) const { + Device* device = getDevice(deviceId); + if (device == nullptr) { + return {}; + } + return device->configuration; +} + +status_t FakeEventHub::getAbsoluteAxisInfo(int32_t deviceId, int axis, + RawAbsoluteAxisInfo* outAxisInfo) const { + Device* device = getDevice(deviceId); + if (device) { + ssize_t index = device->absoluteAxes.indexOfKey(axis); + if (index >= 0) { + *outAxisInfo = device->absoluteAxes.valueAt(index); + return OK; + } + } + outAxisInfo->clear(); + return -1; +} + +bool FakeEventHub::hasRelativeAxis(int32_t deviceId, int axis) const { + Device* device = getDevice(deviceId); + if (device) { + return device->relativeAxes.indexOfKey(axis) >= 0; + } + return false; +} + +bool FakeEventHub::hasInputProperty(int32_t, int) const { + return false; +} + +bool FakeEventHub::hasMscEvent(int32_t deviceId, int mscEvent) const { + Device* device = getDevice(deviceId); + if (device) { + return mscEvent >= 0 && mscEvent <= MSC_MAX ? device->mscBitmask.test(mscEvent) : false; + } + return false; +} + +status_t FakeEventHub::mapKey(int32_t deviceId, int32_t scanCode, int32_t usageCode, + int32_t metaState, int32_t* outKeycode, int32_t* outMetaState, + uint32_t* outFlags) const { + Device* device = getDevice(deviceId); + if (device) { + const KeyInfo* key = getKey(device, scanCode, usageCode); + if (key) { + if (outKeycode) { + auto it = device->keyRemapping.find(key->keyCode); + *outKeycode = it != device->keyRemapping.end() ? it->second : key->keyCode; + } + if (outFlags) { + *outFlags = key->flags; + } + if (outMetaState) { + *outMetaState = metaState; + } + return OK; + } + } + return NAME_NOT_FOUND; +} + +const FakeEventHub::KeyInfo* FakeEventHub::getKey(Device* device, int32_t scanCode, + int32_t usageCode) const { + if (usageCode) { + ssize_t index = device->keysByUsageCode.indexOfKey(usageCode); + if (index >= 0) { + return &device->keysByUsageCode.valueAt(index); + } + } + if (scanCode) { + ssize_t index = device->keysByScanCode.indexOfKey(scanCode); + if (index >= 0) { + return &device->keysByScanCode.valueAt(index); + } + } + return nullptr; +} + +status_t FakeEventHub::mapAxis(int32_t, int32_t, AxisInfo*) const { + return NAME_NOT_FOUND; +} + +base::Result> FakeEventHub::mapSensor( + int32_t deviceId, int32_t absCode) const { + Device* device = getDevice(deviceId); + if (!device) { + return Errorf("Sensor device not found."); + } + auto it = device->sensorsByAbsCode.find(absCode); + if (it == device->sensorsByAbsCode.end()) { + return Errorf("Sensor map not found."); + } + const SensorInfo& info = it->second; + return std::make_pair(info.sensorType, info.sensorDataIndex); +} + +void FakeEventHub::setExcludedDevices(const std::vector& devices) { + mExcludedDevices = devices; +} + +std::vector FakeEventHub::getEvents(int) { + std::scoped_lock lock(mLock); + + std::vector buffer; + std::swap(buffer, mEvents); + + mEventsCondition.notify_all(); + return buffer; +} + +std::vector FakeEventHub::getVideoFrames(int32_t deviceId) { + auto it = mVideoFrames.find(deviceId); + if (it != mVideoFrames.end()) { + std::vector frames = std::move(it->second); + mVideoFrames.erase(deviceId); + return frames; + } + return {}; +} + +int32_t FakeEventHub::getScanCodeState(int32_t deviceId, int32_t scanCode) const { + Device* device = getDevice(deviceId); + if (device) { + ssize_t index = device->scanCodeStates.indexOfKey(scanCode); + if (index >= 0) { + return device->scanCodeStates.valueAt(index); + } + } + return AKEY_STATE_UNKNOWN; +} + +std::optional FakeEventHub::getRawLayoutInfo(int32_t deviceId) const { + Device* device = getDevice(deviceId); + return device ? device->layoutInfo : std::nullopt; +} + +int32_t FakeEventHub::getKeyCodeState(int32_t deviceId, int32_t keyCode) const { + Device* device = getDevice(deviceId); + if (device) { + ssize_t index = device->keyCodeStates.indexOfKey(keyCode); + if (index >= 0) { + return device->keyCodeStates.valueAt(index); + } + } + return AKEY_STATE_UNKNOWN; +} + +int32_t FakeEventHub::getSwitchState(int32_t deviceId, int32_t sw) const { + Device* device = getDevice(deviceId); + if (device) { + ssize_t index = device->switchStates.indexOfKey(sw); + if (index >= 0) { + return device->switchStates.valueAt(index); + } + } + return AKEY_STATE_UNKNOWN; +} + +status_t FakeEventHub::getAbsoluteAxisValue(int32_t deviceId, int32_t axis, + int32_t* outValue) const { + Device* device = getDevice(deviceId); + if (device) { + ssize_t index = device->absoluteAxisValue.indexOfKey(axis); + if (index >= 0) { + *outValue = device->absoluteAxisValue.valueAt(index); + return OK; + } + } + *outValue = 0; + return -1; +} + +int32_t FakeEventHub::getKeyCodeForKeyLocation(int32_t deviceId, int32_t locationKeyCode) const { + Device* device = getDevice(deviceId); + if (!device) { + return AKEYCODE_UNKNOWN; + } + auto it = device->keyCodeMapping.find(locationKeyCode); + return it != device->keyCodeMapping.end() ? it->second : locationKeyCode; +} + +// Return true if the device has non-empty key layout. +bool FakeEventHub::markSupportedKeyCodes(int32_t deviceId, const std::vector& keyCodes, + uint8_t* outFlags) const { + Device* device = getDevice(deviceId); + if (!device) return false; + + bool result = device->keysByScanCode.size() > 0 || device->keysByUsageCode.size() > 0; + for (size_t i = 0; i < keyCodes.size(); i++) { + for (size_t j = 0; j < device->keysByScanCode.size(); j++) { + if (keyCodes[i] == device->keysByScanCode.valueAt(j).keyCode) { + outFlags[i] = 1; + } + } + for (size_t j = 0; j < device->keysByUsageCode.size(); j++) { + if (keyCodes[i] == device->keysByUsageCode.valueAt(j).keyCode) { + outFlags[i] = 1; + } + } + } + return result; +} + +bool FakeEventHub::hasScanCode(int32_t deviceId, int32_t scanCode) const { + Device* device = getDevice(deviceId); + if (device) { + ssize_t index = device->keysByScanCode.indexOfKey(scanCode); + return index >= 0; + } + return false; +} + +bool FakeEventHub::hasKeyCode(int32_t deviceId, int32_t keyCode) const { + Device* device = getDevice(deviceId); + if (!device) { + return false; + } + for (size_t i = 0; i < device->keysByScanCode.size(); i++) { + if (keyCode == device->keysByScanCode.valueAt(i).keyCode) { + return true; + } + } + for (size_t j = 0; j < device->keysByUsageCode.size(); j++) { + if (keyCode == device->keysByUsageCode.valueAt(j).keyCode) { + return true; + } + } + return false; +} + +bool FakeEventHub::hasLed(int32_t deviceId, int32_t led) const { + Device* device = getDevice(deviceId); + return device && device->leds.indexOfKey(led) >= 0; +} + +void FakeEventHub::setLedState(int32_t deviceId, int32_t led, bool on) { + Device* device = getDevice(deviceId); + if (device) { + ssize_t index = device->leds.indexOfKey(led); + if (index >= 0) { + device->leds.replaceValueAt(led, on); + } else { + ADD_FAILURE() << "Attempted to set the state of an LED that the EventHub declared " + "was not present. led=" + << led; + } + } +} + +void FakeEventHub::getVirtualKeyDefinitions( + int32_t deviceId, std::vector& outVirtualKeys) const { + outVirtualKeys.clear(); + + Device* device = getDevice(deviceId); + if (device) { + outVirtualKeys = device->virtualKeys; + } +} + +const std::shared_ptr FakeEventHub::getKeyCharacterMap(int32_t) const { + return nullptr; +} + +bool FakeEventHub::setKeyboardLayoutOverlay(int32_t, std::shared_ptr) { + return false; +} + +std::vector FakeEventHub::getVibratorIds(int32_t deviceId) const { + return mVibrators; +} + +std::optional FakeEventHub::getBatteryCapacity(int32_t, int32_t) const { + return BATTERY_CAPACITY; +} + +std::optional FakeEventHub::getBatteryStatus(int32_t, int32_t) const { + return BATTERY_STATUS; +} + +std::vector FakeEventHub::getRawBatteryIds(int32_t deviceId) const { + return {DEFAULT_BATTERY}; +} + +std::optional FakeEventHub::getRawBatteryInfo(int32_t deviceId, + int32_t batteryId) const { + if (batteryId != DEFAULT_BATTERY) return {}; + static const auto BATTERY_INFO = RawBatteryInfo{.id = DEFAULT_BATTERY, + .name = "default battery", + .flags = InputBatteryClass::CAPACITY, + .path = BATTERY_DEVPATH}; + return BATTERY_INFO; +} + +std::vector FakeEventHub::getRawLightIds(int32_t deviceId) const { + std::vector ids; + for (const auto& [rawId, info] : mRawLightInfos) { + ids.push_back(rawId); + } + return ids; +} + +std::optional FakeEventHub::getRawLightInfo(int32_t deviceId, int32_t lightId) const { + auto it = mRawLightInfos.find(lightId); + if (it == mRawLightInfos.end()) { + return std::nullopt; + } + return it->second; +} + +void FakeEventHub::setLightBrightness(int32_t deviceId, int32_t lightId, int32_t brightness) { + mLightBrightness.emplace(lightId, brightness); +} + +void FakeEventHub::setLightIntensities(int32_t deviceId, int32_t lightId, + std::unordered_map intensities) { + mLightIntensities.emplace(lightId, intensities); +}; + +std::optional FakeEventHub::getLightBrightness(int32_t deviceId, int32_t lightId) const { + auto lightIt = mLightBrightness.find(lightId); + if (lightIt == mLightBrightness.end()) { + return std::nullopt; + } + return lightIt->second; +} + +std::optional> FakeEventHub::getLightIntensities( + int32_t deviceId, int32_t lightId) const { + auto lightIt = mLightIntensities.find(lightId); + if (lightIt == mLightIntensities.end()) { + return std::nullopt; + } + return lightIt->second; +}; + +void FakeEventHub::setSysfsRootPath(int32_t deviceId, std::string sysfsRootPath) const { + Device* device = getDevice(deviceId); + if (device == nullptr) { + return; + } + device->sysfsRootPath = sysfsRootPath; +} + +void FakeEventHub::sysfsNodeChanged(const std::string& sysfsNodePath) { + int32_t foundDeviceId = -1; + Device* foundDevice = nullptr; + for (size_t i = 0; i < mDevices.size(); i++) { + Device* d = mDevices.valueAt(i); + if (sysfsNodePath.find(d->sysfsRootPath) != std::string::npos) { + foundDeviceId = mDevices.keyAt(i); + foundDevice = d; + } + } + if (foundDevice == nullptr) { + return; + } + // If device sysfs changed -> reopen the device + if (!mRawLightInfos.empty() && !foundDevice->classes.test(InputDeviceClass::LIGHT)) { + InputDeviceIdentifier identifier = foundDevice->identifier; + ftl::Flags classes = foundDevice->classes; + removeDevice(foundDeviceId); + addDevice(foundDeviceId, identifier.name, classes | InputDeviceClass::LIGHT, + identifier.bus); + } +} + +} // namespace android diff --git a/services/inputflinger/tests/FakeEventHub.h b/services/inputflinger/tests/FakeEventHub.h new file mode 100644 index 0000000000000000000000000000000000000000..8e06940aec80c01f7c4ad5596420faa238852183 --- /dev/null +++ b/services/inputflinger/tests/FakeEventHub.h @@ -0,0 +1,224 @@ +/* + * 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 +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +namespace android { + +class FakeEventHub : public EventHubInterface { + struct KeyInfo { + int32_t keyCode; + uint32_t flags; + }; + + struct SensorInfo { + InputDeviceSensorType sensorType; + int32_t sensorDataIndex; + }; + + struct Device { + InputDeviceIdentifier identifier; + ftl::Flags classes; + PropertyMap configuration; + KeyedVector absoluteAxes; + KeyedVector relativeAxes; + KeyedVector keyCodeStates; + KeyedVector scanCodeStates; + KeyedVector switchStates; + KeyedVector absoluteAxisValue; + KeyedVector keysByScanCode; + KeyedVector keysByUsageCode; + std::unordered_map keyRemapping; + KeyedVector leds; + // fake mapping which would normally come from keyCharacterMap + std::unordered_map keyCodeMapping; + std::unordered_map sensorsByAbsCode; + BitArray mscBitmask; + std::vector virtualKeys; + bool enabled; + std::optional layoutInfo; + std::string sysfsRootPath; + + status_t enable() { + enabled = true; + return OK; + } + + status_t disable() { + enabled = false; + return OK; + } + + explicit Device(ftl::Flags classes) : classes(classes), enabled(true) {} + }; + + std::mutex mLock; + std::condition_variable mEventsCondition; + + KeyedVector mDevices; + std::vector mExcludedDevices; + std::vector mEvents GUARDED_BY(mLock); + std::unordered_map> mVideoFrames; + std::vector mVibrators = {0, 1}; + std::unordered_map mRawLightInfos; + // Simulates a device light brightness, from light id to light brightness. + std::unordered_map mLightBrightness; + // Simulates a device light intensities, from light id to light intensities map. + std::unordered_map> + mLightIntensities; + +public: + static constexpr int32_t DEFAULT_BATTERY = 1; + static constexpr int32_t BATTERY_STATUS = 4; + static constexpr int32_t BATTERY_CAPACITY = 66; + static const std::string BATTERY_DEVPATH; + + virtual ~FakeEventHub(); + FakeEventHub() {} + + void addDevice(int32_t deviceId, const std::string& name, ftl::Flags classes, + int bus = 0); + void removeDevice(int32_t deviceId); + + bool isDeviceEnabled(int32_t deviceId) const override; + status_t enableDevice(int32_t deviceId) override; + status_t disableDevice(int32_t deviceId) override; + + void finishDeviceScan(); + + void addConfigurationProperty(int32_t deviceId, const char* key, const char* value); + void addConfigurationMap(int32_t deviceId, const PropertyMap* configuration); + + void addAbsoluteAxis(int32_t deviceId, int axis, int32_t minValue, int32_t maxValue, int flat, + int fuzz, int resolution = 0); + void addRelativeAxis(int32_t deviceId, int32_t axis); + void setAbsoluteAxisValue(int32_t deviceId, int32_t axis, int32_t value); + + void setRawLayoutInfo(int32_t deviceId, RawLayoutInfo info); + + void setKeyCodeState(int32_t deviceId, int32_t keyCode, int32_t state); + void setScanCodeState(int32_t deviceId, int32_t scanCode, int32_t state); + void setSwitchState(int32_t deviceId, int32_t switchCode, int32_t state); + + void addKey(int32_t deviceId, int32_t scanCode, int32_t usageCode, int32_t keyCode, + uint32_t flags); + void addKeyCodeMapping(int32_t deviceId, int32_t fromKeyCode, int32_t toKeyCode); + void addKeyRemapping(int32_t deviceId, int32_t fromKeyCode, int32_t toKeyCode) const; + void addVirtualKeyDefinition(int32_t deviceId, const VirtualKeyDefinition& definition); + + void addSensorAxis(int32_t deviceId, int32_t absCode, InputDeviceSensorType sensorType, + int32_t sensorDataIndex); + + void setMscEvent(int32_t deviceId, int32_t mscEvent); + + void addLed(int32_t deviceId, int32_t led, bool initialState); + void addRawLightInfo(int32_t rawId, RawLightInfo&& info); + void fakeLightBrightness(int32_t rawId, int32_t brightness); + void fakeLightIntensities(int32_t rawId, + const std::unordered_map intensities); + bool getLedState(int32_t deviceId, int32_t led); + + std::vector& getExcludedDevices(); + + void setVideoFrames( + std::unordered_map> videoFrames); + + void enqueueEvent(nsecs_t when, nsecs_t readTime, int32_t deviceId, int32_t type, int32_t code, + int32_t value); + void assertQueueIsEmpty(); + void setSysfsRootPath(int32_t deviceId, std::string sysfsRootPath) const; + +private: + Device* getDevice(int32_t deviceId) const; + + ftl::Flags getDeviceClasses(int32_t deviceId) const override; + InputDeviceIdentifier getDeviceIdentifier(int32_t deviceId) const override; + int32_t getDeviceControllerNumber(int32_t) const override; + std::optional getConfiguration(int32_t deviceId) const override; + status_t getAbsoluteAxisInfo(int32_t deviceId, int axis, + RawAbsoluteAxisInfo* outAxisInfo) const override; + bool hasRelativeAxis(int32_t deviceId, int axis) const override; + bool hasInputProperty(int32_t, int) const override; + bool hasMscEvent(int32_t deviceId, int mscEvent) const override final; + status_t mapKey(int32_t deviceId, int32_t scanCode, int32_t usageCode, int32_t metaState, + int32_t* outKeycode, int32_t* outMetaState, uint32_t* outFlags) const override; + const KeyInfo* getKey(Device* device, int32_t scanCode, int32_t usageCode) const; + + status_t mapAxis(int32_t, int32_t, AxisInfo*) const override; + base::Result> mapSensor( + int32_t deviceId, int32_t absCode) const override; + void setExcludedDevices(const std::vector& devices) override; + std::vector getEvents(int) override; + std::vector getVideoFrames(int32_t deviceId) override; + int32_t getScanCodeState(int32_t deviceId, int32_t scanCode) const override; + std::optional getRawLayoutInfo(int32_t deviceId) const override; + int32_t getKeyCodeState(int32_t deviceId, int32_t keyCode) const override; + int32_t getSwitchState(int32_t deviceId, int32_t sw) const override; + status_t getAbsoluteAxisValue(int32_t deviceId, int32_t axis, int32_t* outValue) const override; + int32_t getKeyCodeForKeyLocation(int32_t deviceId, int32_t locationKeyCode) const override; + + // Return true if the device has non-empty key layout. + bool markSupportedKeyCodes(int32_t deviceId, const std::vector& keyCodes, + uint8_t* outFlags) const override; + bool hasScanCode(int32_t deviceId, int32_t scanCode) const override; + bool hasKeyCode(int32_t deviceId, int32_t keyCode) const override; + bool hasLed(int32_t deviceId, int32_t led) const override; + void setLedState(int32_t deviceId, int32_t led, bool on) override; + void getVirtualKeyDefinitions(int32_t deviceId, + std::vector& outVirtualKeys) const override; + const std::shared_ptr getKeyCharacterMap(int32_t) const override; + bool setKeyboardLayoutOverlay(int32_t, std::shared_ptr) override; + + void vibrate(int32_t, const VibrationElement&) override {} + void cancelVibrate(int32_t) override {} + std::vector getVibratorIds(int32_t deviceId) const override; + + std::optional getBatteryCapacity(int32_t, int32_t) const override; + std::optional getBatteryStatus(int32_t, int32_t) const override; + std::vector getRawBatteryIds(int32_t deviceId) const override; + std::optional getRawBatteryInfo(int32_t deviceId, + int32_t batteryId) const override; + + std::vector getRawLightIds(int32_t deviceId) const override; + std::optional getRawLightInfo(int32_t deviceId, int32_t lightId) const override; + void setLightBrightness(int32_t deviceId, int32_t lightId, int32_t brightness) override; + void setLightIntensities(int32_t deviceId, int32_t lightId, + std::unordered_map intensities) override; + std::optional getLightBrightness(int32_t deviceId, int32_t lightId) const override; + std::optional> getLightIntensities( + int32_t deviceId, int32_t lightId) const override; + void sysfsNodeChanged(const std::string& sysfsNodePath) override; + void dump(std::string&) const override {} + void monitor() const override {} + void requestReopenDevices() override {} + void wake() override {} +}; + +} // namespace android diff --git a/services/inputflinger/tests/FakeInputReaderPolicy.cpp b/services/inputflinger/tests/FakeInputReaderPolicy.cpp new file mode 100644 index 0000000000000000000000000000000000000000..3486d0f2a42cc745b1ce43225edb68c72ccd373b --- /dev/null +++ b/services/inputflinger/tests/FakeInputReaderPolicy.cpp @@ -0,0 +1,255 @@ +/* + * 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 "FakeInputReaderPolicy.h" + +#include +#include + +#include "TestConstants.h" +#include "ui/Rotation.h" + +namespace android { + +void FakeInputReaderPolicy::assertInputDevicesChanged() { + waitForInputDevices([](bool devicesChanged) { + if (!devicesChanged) { + FAIL() << "Timed out waiting for notifyInputDevicesChanged() to be called."; + } + }); +} + +void FakeInputReaderPolicy::assertInputDevicesNotChanged() { + waitForInputDevices([](bool devicesChanged) { + if (devicesChanged) { + FAIL() << "Expected notifyInputDevicesChanged() to not be called."; + } + }); +} + +void FakeInputReaderPolicy::assertStylusGestureNotified(int32_t deviceId) { + std::scoped_lock lock(mLock); + ASSERT_TRUE(mStylusGestureNotified); + ASSERT_EQ(deviceId, *mStylusGestureNotified); + mStylusGestureNotified.reset(); +} + +void FakeInputReaderPolicy::assertStylusGestureNotNotified() { + std::scoped_lock lock(mLock); + ASSERT_FALSE(mStylusGestureNotified); +} + +void FakeInputReaderPolicy::clearViewports() { + mViewports.clear(); + mConfig.setDisplayViewports(mViewports); +} + +std::optional FakeInputReaderPolicy::getDisplayViewportByUniqueId( + const std::string& uniqueId) const { + return mConfig.getDisplayViewportByUniqueId(uniqueId); +} +std::optional FakeInputReaderPolicy::getDisplayViewportByType( + ViewportType type) const { + return mConfig.getDisplayViewportByType(type); +} + +std::optional FakeInputReaderPolicy::getDisplayViewportByPort( + uint8_t displayPort) const { + return mConfig.getDisplayViewportByPort(displayPort); +} + +void FakeInputReaderPolicy::addDisplayViewport(DisplayViewport viewport) { + mViewports.push_back(std::move(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, + std::optional physicalPort, + ViewportType type) { + const bool isRotated = orientation == ui::ROTATION_90 || orientation == ui::ROTATION_270; + DisplayViewport v; + v.displayId = displayId; + v.orientation = orientation; + v.logicalLeft = 0; + v.logicalTop = 0; + v.logicalRight = isRotated ? height : width; + v.logicalBottom = isRotated ? width : height; + v.physicalLeft = 0; + v.physicalTop = 0; + v.physicalRight = isRotated ? height : width; + v.physicalBottom = isRotated ? width : height; + v.deviceWidth = isRotated ? height : width; + v.deviceHeight = isRotated ? width : height; + v.isActive = isActive; + v.uniqueId = uniqueId; + v.physicalPort = physicalPort; + v.type = type; + + addDisplayViewport(v); +} + +bool FakeInputReaderPolicy::updateViewport(const DisplayViewport& viewport) { + size_t count = mViewports.size(); + for (size_t i = 0; i < count; i++) { + const DisplayViewport& currentViewport = mViewports[i]; + if (currentViewport.displayId == viewport.displayId) { + mViewports[i] = viewport; + mConfig.setDisplayViewports(mViewports); + return true; + } + } + // no viewport found. + return false; +} + +void FakeInputReaderPolicy::addExcludedDeviceName(const std::string& deviceName) { + mConfig.excludedDeviceNames.push_back(deviceName); +} + +void FakeInputReaderPolicy::addInputPortAssociation(const std::string& inputPort, + uint8_t displayPort) { + mConfig.portAssociations.insert({inputPort, displayPort}); +} + +void FakeInputReaderPolicy::addDeviceTypeAssociation(const std::string& inputPort, + const std::string& type) { + mConfig.deviceTypeAssociations.insert({inputPort, type}); +} + +void FakeInputReaderPolicy::addInputUniqueIdAssociation(const std::string& inputUniqueId, + const std::string& displayUniqueId) { + mConfig.uniqueIdAssociations.insert({inputUniqueId, displayUniqueId}); +} + +void FakeInputReaderPolicy::addKeyboardLayoutAssociation(const std::string& inputUniqueId, + const KeyboardLayoutInfo& layoutInfo) { + mConfig.keyboardLayoutAssociations.insert({inputUniqueId, layoutInfo}); +} + +void FakeInputReaderPolicy::addDisabledDevice(int32_t deviceId) { + mConfig.disabledDevices.insert(deviceId); +} + +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; +} + +const std::vector& FakeInputReaderPolicy::getInputDevices() const { + return mInputDevices; +} + +TouchAffineTransformation FakeInputReaderPolicy::getTouchAffineTransformation( + const std::string& inputDeviceDescriptor, ui::Rotation surfaceRotation) { + return transform; +} + +void FakeInputReaderPolicy::setTouchAffineTransformation(const TouchAffineTransformation t) { + transform = t; +} + +PointerCaptureRequest FakeInputReaderPolicy::setPointerCapture(bool enabled) { + mConfig.pointerCaptureRequest = {enabled, mNextPointerCaptureSequenceNumber++}; + return mConfig.pointerCaptureRequest; +} + +void FakeInputReaderPolicy::setShowTouches(bool enabled) { + mConfig.showTouches = enabled; +} + +void FakeInputReaderPolicy::setDefaultPointerDisplayId(int32_t pointerDisplayId) { + mConfig.defaultPointerDisplayId = pointerDisplayId; +} + +void FakeInputReaderPolicy::setPointerGestureEnabled(bool enabled) { + mConfig.pointerGesturesEnabled = enabled; +} + +float FakeInputReaderPolicy::getPointerGestureMovementSpeedRatio() { + return mConfig.pointerGestureMovementSpeedRatio; +} + +float FakeInputReaderPolicy::getPointerGestureZoomSpeedRatio() { + return mConfig.pointerGestureZoomSpeedRatio; +} + +void FakeInputReaderPolicy::setVelocityControlParams(const VelocityControlParameters& params) { + mConfig.pointerVelocityControlParameters = params; + mConfig.wheelVelocityControlParameters = params; +} + +void FakeInputReaderPolicy::setStylusButtonMotionEventsEnabled(bool enabled) { + mConfig.stylusButtonMotionEventsEnabled = enabled; +} + +void FakeInputReaderPolicy::setStylusPointerIconEnabled(bool enabled) { + mConfig.stylusPointerIconEnabled = enabled; +} + +void FakeInputReaderPolicy::getReaderConfiguration(InputReaderConfiguration* outConfig) { + *outConfig = mConfig; +} + +std::shared_ptr FakeInputReaderPolicy::obtainPointerController( + int32_t /*deviceId*/) { + return mPointerController; +} + +void FakeInputReaderPolicy::notifyInputDevicesChanged( + const std::vector& inputDevices) { + std::scoped_lock lock(mLock); + mInputDevices = inputDevices; + mInputDevicesChanged = true; + mDevicesChangedCondition.notify_all(); +} + +std::shared_ptr FakeInputReaderPolicy::getKeyboardLayoutOverlay( + const InputDeviceIdentifier&) { + return nullptr; +} + +std::string FakeInputReaderPolicy::getDeviceAlias(const InputDeviceIdentifier&) { + return ""; +} + +void FakeInputReaderPolicy::waitForInputDevices(std::function processDevicesChanged) { + std::unique_lock lock(mLock); + base::ScopedLockAssertion assumeLocked(mLock); + + const bool devicesChanged = + mDevicesChangedCondition.wait_for(lock, WAIT_TIMEOUT, [this]() REQUIRES(mLock) { + return mInputDevicesChanged; + }); + ASSERT_NO_FATAL_FAILURE(processDevicesChanged(devicesChanged)); + mInputDevicesChanged = false; +} + +void FakeInputReaderPolicy::notifyStylusGestureStarted(int32_t deviceId, nsecs_t eventTime) { + std::scoped_lock lock(mLock); + mStylusGestureNotified = deviceId; +} + +} // namespace android diff --git a/services/inputflinger/tests/FakeInputReaderPolicy.h b/services/inputflinger/tests/FakeInputReaderPolicy.h new file mode 100644 index 0000000000000000000000000000000000000000..85ff01a071249db8c50c4e8dbdf10a97d3c6f3df --- /dev/null +++ b/services/inputflinger/tests/FakeInputReaderPolicy.h @@ -0,0 +1,106 @@ +/* + * 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 +#include +#include +#include +#include + +#include +#include + +#include "FakePointerController.h" +#include "input/DisplayViewport.h" +#include "input/InputDevice.h" + +namespace android { + +class FakeInputReaderPolicy : public InputReaderPolicyInterface { +protected: + virtual ~FakeInputReaderPolicy() {} + +public: + FakeInputReaderPolicy() {} + + void assertInputDevicesChanged(); + void assertInputDevicesNotChanged(); + void assertStylusGestureNotified(int32_t deviceId); + void assertStylusGestureNotNotified(); + + virtual void clearViewports(); + std::optional getDisplayViewportByUniqueId(const std::string& uniqueId) const; + 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, + ui::Rotation orientation, bool isActive, const std::string& uniqueId, + std::optional physicalPort, ViewportType type); + bool updateViewport(const DisplayViewport& viewport); + void addExcludedDeviceName(const std::string& deviceName); + void addInputPortAssociation(const std::string& inputPort, uint8_t displayPort); + void addDeviceTypeAssociation(const std::string& inputPort, const std::string& type); + void addInputUniqueIdAssociation(const std::string& inputUniqueId, + const std::string& displayUniqueId); + void addKeyboardLayoutAssociation(const std::string& inputUniqueId, + 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(bool enabled); + void setShowTouches(bool enabled); + void setDefaultPointerDisplayId(int32_t pointerDisplayId); + void setPointerGestureEnabled(bool enabled); + float getPointerGestureMovementSpeedRatio(); + float getPointerGestureZoomSpeedRatio(); + void setVelocityControlParams(const VelocityControlParameters& params); + void setStylusButtonMotionEventsEnabled(bool enabled); + void setStylusPointerIconEnabled(bool enabled); + +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&) override; + std::string getDeviceAlias(const InputDeviceIdentifier&) override; + void waitForInputDevices(std::function processDevicesChanged); + void notifyStylusGestureStarted(int32_t deviceId, nsecs_t eventTime) override; + + std::mutex mLock; + 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; + TouchAffineTransformation transform; + std::optional mStylusGestureNotified GUARDED_BY(mLock){}; + + uint32_t mNextPointerCaptureSequenceNumber{0}; +}; + +} // namespace android diff --git a/services/inputflinger/tests/FakePointerController.cpp b/services/inputflinger/tests/FakePointerController.cpp new file mode 100644 index 0000000000000000000000000000000000000000..ca517f32c93ad123a486e06bb4a34ba813ca9a96 --- /dev/null +++ b/services/inputflinger/tests/FakePointerController.cpp @@ -0,0 +1,98 @@ +/* + * 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 "FakePointerController.h" + +#include + +namespace android { + +void FakePointerController::setBounds(float minX, float minY, float maxX, float maxY) { + mHaveBounds = true; + mMinX = minX; + mMinY = minY; + mMaxX = maxX; + mMaxY = maxY; +} + +const std::map>& FakePointerController::getSpots() { + return mSpotsByDisplay; +} + +void FakePointerController::setPosition(float x, float y) { + mX = x; + mY = y; +} + +FloatPoint FakePointerController::getPosition() const { + return {mX, mY}; +} + +int32_t FakePointerController::getDisplayId() const { + return mDisplayId; +} + +void FakePointerController::setDisplayViewport(const DisplayViewport& viewport) { + mDisplayId = viewport.displayId; +} + +void FakePointerController::assertPosition(float x, float y) { + const auto [actualX, actualY] = getPosition(); + ASSERT_NEAR(x, actualX, 1); + ASSERT_NEAR(y, actualY, 1); +} + +bool FakePointerController::isPointerShown() { + return mIsPointerShown; +} + +std::optional FakePointerController::getBounds() const { + return mHaveBounds ? std::make_optional(mMinX, mMinY, mMaxX, mMaxY) : std::nullopt; +} + +void FakePointerController::move(float deltaX, float deltaY) { + mX += deltaX; + if (mX < mMinX) mX = mMinX; + if (mX > mMaxX) mX = mMaxX; + mY += deltaY; + if (mY < mMinY) mY = mMinY; + if (mY > mMaxY) mY = mMaxY; +} + +void FakePointerController::fade(Transition) { + mIsPointerShown = false; +} +void FakePointerController::unfade(Transition) { + mIsPointerShown = true; +} + +void FakePointerController::setSpots(const PointerCoords*, const uint32_t*, BitSet32 spotIdBits, + int32_t displayId) { + std::vector newSpots; + // Add spots for fingers that are down. + for (BitSet32 idBits(spotIdBits); !idBits.isEmpty();) { + uint32_t id = idBits.clearFirstMarkedBit(); + newSpots.push_back(id); + } + + mSpotsByDisplay[displayId] = newSpots; +} + +void FakePointerController::clearSpots() { + mSpotsByDisplay.clear(); +} + +} // namespace android diff --git a/services/inputflinger/tests/FakePointerController.h b/services/inputflinger/tests/FakePointerController.h new file mode 100644 index 0000000000000000000000000000000000000000..c374267ff95e29b52b84d8759627f94527b00e02 --- /dev/null +++ b/services/inputflinger/tests/FakePointerController.h @@ -0,0 +1,61 @@ +/* + * 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 +#include +#include +#include + +namespace android { + +class FakePointerController : public PointerControllerInterface { +public: + virtual ~FakePointerController() {} + + void setBounds(float minX, float minY, float maxX, float maxY); + const std::map>& getSpots(); + + void setPosition(float x, float y) override; + FloatPoint getPosition() const override; + int32_t getDisplayId() const override; + void setDisplayViewport(const DisplayViewport& viewport) override; + + void assertPosition(float x, float y); + bool isPointerShown(); + +private: + std::optional getBounds() const override; + void move(float deltaX, float deltaY) override; + void fade(Transition) override; + void unfade(Transition) override; + void setPresentation(Presentation) override {} + void setSpots(const PointerCoords*, const uint32_t*, BitSet32 spotIdBits, + int32_t displayId) override; + void clearSpots() override; + + bool mHaveBounds{false}; + float mMinX{0}, mMinY{0}, mMaxX{0}, mMaxY{0}; + float mX{0}, mY{0}; + int32_t mDisplayId{ADISPLAY_ID_DEFAULT}; + bool mIsPointerShown{false}; + + std::map> mSpotsByDisplay; +}; + +} // namespace android diff --git a/services/inputflinger/tests/FocusResolver_test.cpp b/services/inputflinger/tests/FocusResolver_test.cpp index 91be4a30bf9c47ec298d6d1fd77361a68f953dc8..5440a98db6676318a320c700a26f6891732870dc 100644 --- a/services/inputflinger/tests/FocusResolver_test.cpp +++ b/services/inputflinger/tests/FocusResolver_test.cpp @@ -50,16 +50,16 @@ public: }; TEST(FocusResolverTest, SetFocusedWindow) { - sp focusableWindowToken = new BBinder(); - sp invisibleWindowToken = new BBinder(); - sp unfocusableWindowToken = new BBinder(); + sp focusableWindowToken = sp::make(); + sp invisibleWindowToken = sp::make(); + sp unfocusableWindowToken = sp::make(); std::vector> windows; - windows.push_back(new FakeWindowHandle("Focusable", focusableWindowToken, true /* focusable */, - true /* visible */)); - windows.push_back(new FakeWindowHandle("Invisible", invisibleWindowToken, true /* focusable */, - false /* visible */)); - windows.push_back(new FakeWindowHandle("unfocusable", unfocusableWindowToken, - false /* focusable */, true /* visible */)); + windows.push_back(sp::make("Focusable", focusableWindowToken, + /*focusable=*/true, /*visible=*/true)); + windows.push_back(sp::make("Invisible", invisibleWindowToken, + /*focusable=*/true, /*visible=*/false)); + windows.push_back(sp::make("unfocusable", unfocusableWindowToken, + /*focusable=*/false, /*visible=*/true)); // focusable window can get focused FocusRequest request; @@ -85,10 +85,10 @@ TEST(FocusResolverTest, SetFocusedWindow) { } TEST(FocusResolverTest, RemoveFocusFromFocusedWindow) { - sp focusableWindowToken = new BBinder(); + sp focusableWindowToken = sp::make(); std::vector> windows; - windows.push_back(new FakeWindowHandle("Focusable", focusableWindowToken, true /* focusable */, - true /* visible */)); + windows.push_back(sp::make("Focusable", focusableWindowToken, + /*focusable=*/true, /*visible=*/true)); FocusRequest request; request.displayId = 42; @@ -109,24 +109,24 @@ TEST(FocusResolverTest, RemoveFocusFromFocusedWindow) { } TEST(FocusResolverTest, SetFocusedMirroredWindow) { - sp focusableWindowToken = new BBinder(); - sp invisibleWindowToken = new BBinder(); - sp unfocusableWindowToken = new BBinder(); + sp focusableWindowToken = sp::make(); + sp invisibleWindowToken = sp::make(); + sp unfocusableWindowToken = sp::make(); std::vector> windows; - windows.push_back(new FakeWindowHandle("Mirror1", focusableWindowToken, true /* focusable */, - true /* visible */)); - windows.push_back(new FakeWindowHandle("Mirror1", focusableWindowToken, true /* focusable */, - true /* visible */)); + windows.push_back(sp::make("Mirror1", focusableWindowToken, + /*focusable=*/true, /*visible=*/true)); + windows.push_back(sp::make("Mirror1", focusableWindowToken, + /*focusable=*/true, /*visible=*/true)); - windows.push_back(new FakeWindowHandle("Mirror2Visible", invisibleWindowToken, - true /* focusable */, true /* visible */)); - windows.push_back(new FakeWindowHandle("Mirror2Invisible", invisibleWindowToken, - true /* focusable */, false /* visible */)); + windows.push_back(sp::make("Mirror2Visible", invisibleWindowToken, + /*focusable=*/true, /*visible=*/true)); + windows.push_back(sp::make("Mirror2Invisible", invisibleWindowToken, + /*focusable=*/true, /*visible=*/false)); - windows.push_back(new FakeWindowHandle("Mirror3Focusable", unfocusableWindowToken, - true /* focusable */, true /* visible */)); - windows.push_back(new FakeWindowHandle("Mirror3Unfocusable", unfocusableWindowToken, - false /* focusable */, true /* visible */)); + windows.push_back(sp::make("Mirror3Focusable", unfocusableWindowToken, + /*focusable=*/true, /*visible=*/true)); + windows.push_back(sp::make("Mirror3Unfocusable", unfocusableWindowToken, + /*focusable=*/false, /*visible=*/true)); // mirrored window can get focused FocusRequest request; @@ -149,10 +149,11 @@ TEST(FocusResolverTest, SetFocusedMirroredWindow) { } TEST(FocusResolverTest, SetInputWindows) { - sp focusableWindowToken = new BBinder(); + sp focusableWindowToken = sp::make(); std::vector> windows; - sp window = new FakeWindowHandle("Focusable", focusableWindowToken, - true /* focusable */, true /* visible */); + sp window = + sp::make("Focusable", focusableWindowToken, /*focusable=*/true, + /*visible=*/true); windows.push_back(window); // focusable window can get focused @@ -171,12 +172,12 @@ TEST(FocusResolverTest, SetInputWindows) { } TEST(FocusResolverTest, FocusRequestsCanBePending) { - sp invisibleWindowToken = new BBinder(); + sp invisibleWindowToken = sp::make(); std::vector> windows; sp invisibleWindow = - new FakeWindowHandle("Invisible", invisibleWindowToken, true /* focusable */, - false /* visible */); + sp::make("Invisible", invisibleWindowToken, /*focusable=*/true, + /*visible=*/false); windows.push_back(invisibleWindow); // invisible window cannot get focused @@ -195,11 +196,12 @@ TEST(FocusResolverTest, FocusRequestsCanBePending) { } TEST(FocusResolverTest, FocusRequestsArePersistent) { - sp windowToken = new BBinder(); + sp windowToken = sp::make(); std::vector> windows; - sp window = new FakeWindowHandle("Test Window", windowToken, - false /* focusable */, true /* visible */); + sp window = + sp::make("Test Window", windowToken, /*focusable=*/false, + /*visible=*/true); windows.push_back(window); // non-focusable window cannot get focused @@ -235,63 +237,134 @@ TEST(FocusResolverTest, FocusRequestsArePersistent) { ASSERT_FOCUS_CHANGE(changes, /*from*/ nullptr, /*to*/ windowToken); } -TEST(FocusResolverTest, ConditionalFocusRequestsAreNotPersistent) { - sp hostWindowToken = new BBinder(); +TEST(FocusResolverTest, FocusTransferTarget) { + sp hostWindowToken = sp::make(); std::vector> windows; sp hostWindow = - new FakeWindowHandle("Host Window", hostWindowToken, true /* focusable */, - true /* visible */); + sp::make("Host Window", hostWindowToken, /*focusable=*/true, + /*visible=*/true); windows.push_back(hostWindow); - sp embeddedWindowToken = new BBinder(); + sp embeddedWindowToken = sp::make(); sp embeddedWindow = - new FakeWindowHandle("Embedded Window", embeddedWindowToken, true /* focusable */, - true /* visible */); + sp::make("Embedded Window", embeddedWindowToken, /*focusable=*/false, + /*visible=*/true); windows.push_back(embeddedWindow); FocusRequest request; request.displayId = 42; request.token = hostWindowToken; + + // Host wants to transfer touch to embedded. + hostWindow->editInfo()->focusTransferTarget = embeddedWindowToken; + FocusResolver focusResolver; std::optional changes = focusResolver.setFocusedWindow(request, windows); + // Embedded was not focusable so host gains focus. ASSERT_FOCUS_CHANGE(changes, /*from*/ nullptr, /*to*/ hostWindowToken); - request.focusedToken = hostWindow->getToken(); - request.token = embeddedWindowToken; - changes = focusResolver.setFocusedWindow(request, windows); + // Embedded is now focusable so will gain focus + embeddedWindow->setFocusable(true); + changes = focusResolver.setInputWindows(request.displayId, windows); ASSERT_FOCUS_CHANGE(changes, /*from*/ hostWindowToken, /*to*/ embeddedWindowToken); - embeddedWindow->setFocusable(false); + // Embedded is not visible so host will get focus + embeddedWindow->setVisible(false); changes = focusResolver.setInputWindows(request.displayId, windows); - // The embedded window is no longer focusable, provide focus back to the original focused - // window. ASSERT_FOCUS_CHANGE(changes, /*from*/ embeddedWindowToken, /*to*/ hostWindowToken); - embeddedWindow->setFocusable(true); + // Embedded is now visible so will get focus + embeddedWindow->setVisible(true); changes = focusResolver.setInputWindows(request.displayId, windows); - // The embedded window is focusable again, but we it cannot gain focus unless there is another - // focus request. - ASSERT_FALSE(changes); + ASSERT_FOCUS_CHANGE(changes, /*from*/ hostWindowToken, /*to*/ embeddedWindowToken); - embeddedWindow->setVisible(false); - changes = focusResolver.setFocusedWindow(request, windows); - // If the embedded window is not visible/focusable, then we do not grant it focus and the - // request is dropped. - ASSERT_FALSE(changes); + // Remove focusTransferTarget from host. Host will gain focus. + hostWindow->editInfo()->focusTransferTarget = nullptr; + changes = focusResolver.setInputWindows(request.displayId, windows); + ASSERT_FOCUS_CHANGE(changes, /*from*/ embeddedWindowToken, /*to*/ hostWindowToken); - embeddedWindow->setVisible(true); + // Set invalid token for focusTransferTarget. Host will remain focus + hostWindow->editInfo()->focusTransferTarget = sp::make(); changes = focusResolver.setInputWindows(request.displayId, windows); - // If the embedded window becomes visble/focusable, nothing changes since the request has been - // dropped. ASSERT_FALSE(changes); } + +TEST(FocusResolverTest, FocusTransferMultipleInChain) { + sp hostWindowToken = sp::make(); + std::vector> windows; + + sp hostWindow = + sp::make("Host Window", hostWindowToken, /*focusable=*/true, + /*visible=*/true); + windows.push_back(hostWindow); + sp embeddedWindowToken = sp::make(); + sp embeddedWindow = + sp::make("Embedded Window", embeddedWindowToken, /*focusable=*/true, + /*visible=*/true); + windows.push_back(embeddedWindow); + + sp embeddedWindowToken2 = sp::make(); + sp embeddedWindow2 = + sp::make("Embedded Window2", embeddedWindowToken2, /*focusable=*/true, + /*visible=*/true); + windows.push_back(embeddedWindow2); + + FocusRequest request; + request.displayId = 42; + request.token = hostWindowToken; + + hostWindow->editInfo()->focusTransferTarget = embeddedWindowToken; + embeddedWindow->editInfo()->focusTransferTarget = embeddedWindowToken2; + + FocusResolver focusResolver; + std::optional changes = + focusResolver.setFocusedWindow(request, windows); + ASSERT_FOCUS_CHANGE(changes, /*from*/ nullptr, /*to*/ embeddedWindowToken2); +} + +TEST(FocusResolverTest, FocusTransferTargetCycle) { + sp hostWindowToken = sp::make(); + std::vector> windows; + + sp hostWindow = + sp::make("Host Window", hostWindowToken, /*focusable=*/true, + /*visible=*/true); + windows.push_back(hostWindow); + sp embeddedWindowToken = sp::make(); + sp embeddedWindow = + sp::make("Embedded Window", embeddedWindowToken, /*focusable=*/true, + /*visible=*/true); + windows.push_back(embeddedWindow); + + sp embeddedWindowToken2 = sp::make(); + sp embeddedWindow2 = + sp::make("Embedded Window2", embeddedWindowToken2, /*focusable=*/true, + /*visible=*/true); + windows.push_back(embeddedWindow2); + + FocusRequest request; + request.displayId = 42; + request.token = hostWindowToken; + + hostWindow->editInfo()->focusTransferTarget = embeddedWindowToken; + embeddedWindow->editInfo()->focusTransferTarget = embeddedWindowToken2; + embeddedWindow2->editInfo()->focusTransferTarget = hostWindowToken; + + FocusResolver focusResolver; + std::optional changes = + focusResolver.setFocusedWindow(request, windows); + // Cycle will be detected and stop right before trying to transfer token to host again. + ASSERT_FOCUS_CHANGE(changes, /*from*/ nullptr, /*to*/ embeddedWindowToken2); +} + TEST(FocusResolverTest, FocusRequestsAreClearedWhenWindowIsRemoved) { - sp windowToken = new BBinder(); + sp windowToken = sp::make(); std::vector> windows; - sp window = new FakeWindowHandle("Test Window", windowToken, - true /* focusable */, true /* visible */); + sp window = + sp::make("Test Window", windowToken, /*focusable=*/true, + /*visible=*/true); windows.push_back(window); FocusRequest request; diff --git a/services/inputflinger/tests/GestureConverter_test.cpp b/services/inputflinger/tests/GestureConverter_test.cpp new file mode 100644 index 0000000000000000000000000000000000000000..a723636519e43a7dd2d8d642b8711adbb7111ec8 --- /dev/null +++ b/services/inputflinger/tests/GestureConverter_test.cpp @@ -0,0 +1,908 @@ +/* + * 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 "FakeEventHub.h" +#include "FakeInputReaderPolicy.h" +#include "FakePointerController.h" +#include "InstrumentedInputReader.h" +#include "NotifyArgs.h" +#include "TestConstants.h" +#include "TestInputListener.h" +#include "TestInputListenerMatchers.h" +#include "include/gestures.h" +#include "ui/Rotation.h" + +namespace android { + +using testing::AllOf; + +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(); + mFakePolicy = sp::make(); + mFakeListener = std::make_unique(); + mReader = std::make_unique(mFakeEventHub, mFakePolicy, + *mFakeListener); + 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(); + 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; +}; + +TEST_F(GestureConverterTest, Move) { + InputDeviceContext deviceContext(*mDevice, EVENTHUB_ID); + GestureConverter converter(*mReader->getContext(), deviceContext, DEVICE_ID); + + Gesture moveGesture(kGestureMove, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, -5, 10); + std::list args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, moveGesture); + ASSERT_EQ(1u, args.size()); + + ASSERT_THAT(std::get(args.front()), + AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_MOVE), + WithCoords(POINTER_X - 5, POINTER_Y + 10), WithRelativeMotion(-5, 10), + WithToolType(ToolType::FINGER), WithButtonState(0), + WithPressure(0.0f))); + + ASSERT_NO_FATAL_FAILURE(mFakePointerController->assertPosition(POINTER_X - 5, POINTER_Y + 10)); +} + +TEST_F(GestureConverterTest, Move_Rotated) { + InputDeviceContext deviceContext(*mDevice, EVENTHUB_ID); + GestureConverter converter(*mReader->getContext(), deviceContext, DEVICE_ID); + converter.setOrientation(ui::ROTATION_90); + + Gesture moveGesture(kGestureMove, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, -5, 10); + std::list args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, moveGesture); + ASSERT_EQ(1u, args.size()); + + ASSERT_THAT(std::get(args.front()), + AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_MOVE), + WithCoords(POINTER_X + 10, POINTER_Y + 5), WithRelativeMotion(10, 5), + WithToolType(ToolType::FINGER), WithButtonState(0), + WithPressure(0.0f))); + + 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); + + // 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, downGesture); + ASSERT_EQ(3u, args.size()); + + ASSERT_THAT(std::get(args.front()), + AllOf(WithMotionAction(AMOTION_EVENT_ACTION_DOWN), + WithButtonState(AMOTION_EVENT_BUTTON_PRIMARY | + AMOTION_EVENT_BUTTON_SECONDARY), + WithCoords(POINTER_X, POINTER_Y), + WithToolType(ToolType::FINGER))); + args.pop_front(); + ASSERT_THAT(std::get(args.front()), + AllOf(WithMotionAction(AMOTION_EVENT_ACTION_BUTTON_PRESS), + WithActionButton(AMOTION_EVENT_BUTTON_PRIMARY), + WithButtonState(AMOTION_EVENT_BUTTON_PRIMARY), + WithCoords(POINTER_X, POINTER_Y), + WithToolType(ToolType::FINGER))); + args.pop_front(); + ASSERT_THAT(std::get(args.front()), + AllOf(WithMotionAction(AMOTION_EVENT_ACTION_BUTTON_PRESS), + WithActionButton(AMOTION_EVENT_BUTTON_SECONDARY), + WithButtonState(AMOTION_EVENT_BUTTON_PRIMARY | + AMOTION_EVENT_BUTTON_SECONDARY), + WithCoords(POINTER_X, POINTER_Y), + WithToolType(ToolType::FINGER))); + + // 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, leftUpGesture); + ASSERT_EQ(1u, args.size()); + + ASSERT_THAT(std::get(args.front()), + 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))); + + // 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, rightUpGesture); + ASSERT_EQ(2u, args.size()); + + ASSERT_THAT(std::get(args.front()), + AllOf(WithMotionAction(AMOTION_EVENT_ACTION_BUTTON_RELEASE), + WithActionButton(AMOTION_EVENT_BUTTON_SECONDARY), WithButtonState(0), + WithCoords(POINTER_X, POINTER_Y), + WithToolType(ToolType::FINGER))); + args.pop_front(); + ASSERT_THAT(std::get(args.front()), + AllOf(WithMotionAction(AMOTION_EVENT_ACTION_UP), WithButtonState(0), + WithCoords(POINTER_X, POINTER_Y), + WithToolType(ToolType::FINGER))); +} + +TEST_F(GestureConverterTest, DragWithButton) { + InputDeviceContext deviceContext(*mDevice, EVENTHUB_ID); + GestureConverter converter(*mReader->getContext(), deviceContext, DEVICE_ID); + + // 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, downGesture); + ASSERT_EQ(2u, args.size()); + + ASSERT_THAT(std::get(args.front()), + AllOf(WithMotionAction(AMOTION_EVENT_ACTION_DOWN), + WithButtonState(AMOTION_EVENT_BUTTON_PRIMARY), + WithCoords(POINTER_X, POINTER_Y), + WithToolType(ToolType::FINGER))); + args.pop_front(); + ASSERT_THAT(std::get(args.front()), + AllOf(WithMotionAction(AMOTION_EVENT_ACTION_BUTTON_PRESS), + WithActionButton(AMOTION_EVENT_BUTTON_PRIMARY), + WithButtonState(AMOTION_EVENT_BUTTON_PRIMARY), + WithCoords(POINTER_X, POINTER_Y), + WithToolType(ToolType::FINGER))); + + // Move + Gesture moveGesture(kGestureMove, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, -5, 10); + args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, moveGesture); + ASSERT_EQ(1u, args.size()); + + ASSERT_THAT(std::get(args.front()), + 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))); + + 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, upGesture); + ASSERT_EQ(2u, args.size()); + + ASSERT_THAT(std::get(args.front()), + AllOf(WithMotionAction(AMOTION_EVENT_ACTION_BUTTON_RELEASE), + WithActionButton(AMOTION_EVENT_BUTTON_PRIMARY), WithButtonState(0), + WithCoords(POINTER_X - 5, POINTER_Y + 10), + WithToolType(ToolType::FINGER))); + args.pop_front(); + ASSERT_THAT(std::get(args.front()), + AllOf(WithMotionAction(AMOTION_EVENT_ACTION_UP), WithButtonState(0), + WithCoords(POINTER_X - 5, POINTER_Y + 10), + WithToolType(ToolType::FINGER))); +} + +TEST_F(GestureConverterTest, Scroll) { + const nsecs_t downTime = 12345; + InputDeviceContext deviceContext(*mDevice, EVENTHUB_ID); + GestureConverter converter(*mReader->getContext(), deviceContext, DEVICE_ID); + + Gesture startGesture(kGestureScroll, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, 0, -10); + std::list args = converter.handleGesture(downTime, READ_TIME, startGesture); + ASSERT_EQ(2u, args.size()); + + ASSERT_THAT(std::get(args.front()), + AllOf(WithMotionAction(AMOTION_EVENT_ACTION_DOWN), WithCoords(POINTER_X, POINTER_Y), + WithGestureScrollDistance(0, 0, EPSILON), + WithMotionClassification(MotionClassification::TWO_FINGER_SWIPE), + WithToolType(ToolType::FINGER), WithDownTime(downTime), + WithFlags(AMOTION_EVENT_FLAG_IS_GENERATED_GESTURE))); + args.pop_front(); + ASSERT_THAT(std::get(args.front()), + AllOf(WithMotionAction(AMOTION_EVENT_ACTION_MOVE), + WithCoords(POINTER_X, POINTER_Y - 10), + WithGestureScrollDistance(0, 10, EPSILON), + WithMotionClassification(MotionClassification::TWO_FINGER_SWIPE), + WithToolType(ToolType::FINGER), + WithFlags(AMOTION_EVENT_FLAG_IS_GENERATED_GESTURE))); + + Gesture continueGesture(kGestureScroll, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, 0, -5); + args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, continueGesture); + ASSERT_EQ(1u, args.size()); + ASSERT_THAT(std::get(args.front()), + 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))); + + Gesture flingGesture(kGestureFling, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, 1, 1, + GESTURES_FLING_START); + args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, flingGesture); + ASSERT_EQ(1u, args.size()); + ASSERT_THAT(std::get(args.front()), + AllOf(WithMotionAction(AMOTION_EVENT_ACTION_UP), + WithCoords(POINTER_X, POINTER_Y - 15), + WithGestureScrollDistance(0, 0, EPSILON), + WithMotionClassification(MotionClassification::TWO_FINGER_SWIPE), + WithToolType(ToolType::FINGER), + WithFlags(AMOTION_EVENT_FLAG_IS_GENERATED_GESTURE))); +} + +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); + + Gesture startGesture(kGestureScroll, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, 0, -10); + std::list args = converter.handleGesture(downTime, READ_TIME, startGesture); + ASSERT_EQ(2u, args.size()); + + ASSERT_THAT(std::get(args.front()), + AllOf(WithMotionAction(AMOTION_EVENT_ACTION_DOWN), WithCoords(POINTER_X, POINTER_Y), + WithGestureScrollDistance(0, 0, EPSILON), + WithMotionClassification(MotionClassification::TWO_FINGER_SWIPE), + WithToolType(ToolType::FINGER), WithDownTime(downTime))); + args.pop_front(); + ASSERT_THAT(std::get(args.front()), + AllOf(WithMotionAction(AMOTION_EVENT_ACTION_MOVE), + WithCoords(POINTER_X - 10, POINTER_Y), + WithGestureScrollDistance(0, 10, EPSILON), + WithMotionClassification(MotionClassification::TWO_FINGER_SWIPE), + WithToolType(ToolType::FINGER))); + + Gesture continueGesture(kGestureScroll, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, 0, -5); + args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, continueGesture); + ASSERT_EQ(1u, args.size()); + ASSERT_THAT(std::get(args.front()), + AllOf(WithMotionAction(AMOTION_EVENT_ACTION_MOVE), + WithCoords(POINTER_X - 15, POINTER_Y), + WithGestureScrollDistance(0, 5, EPSILON), + WithMotionClassification(MotionClassification::TWO_FINGER_SWIPE), + WithToolType(ToolType::FINGER))); + + Gesture flingGesture(kGestureFling, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, 1, 1, + GESTURES_FLING_START); + args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, flingGesture); + ASSERT_EQ(1u, args.size()); + ASSERT_THAT(std::get(args.front()), + AllOf(WithMotionAction(AMOTION_EVENT_ACTION_UP), + WithCoords(POINTER_X - 15, POINTER_Y), + WithGestureScrollDistance(0, 0, EPSILON), + WithMotionClassification(MotionClassification::TWO_FINGER_SWIPE), + WithToolType(ToolType::FINGER))); +} + +TEST_F(GestureConverterTest, Scroll_ClearsClassificationAndOffsetsAfterGesture) { + InputDeviceContext deviceContext(*mDevice, EVENTHUB_ID); + GestureConverter converter(*mReader->getContext(), deviceContext, DEVICE_ID); + + Gesture startGesture(kGestureScroll, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, 0, -10); + std::list args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, startGesture); + + Gesture continueGesture(kGestureScroll, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, 0, -5); + args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, continueGesture); + + Gesture flingGesture(kGestureFling, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, 1, 1, + GESTURES_FLING_START); + args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, flingGesture); + + Gesture moveGesture(kGestureMove, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, -5, 10); + args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, moveGesture); + ASSERT_EQ(1u, args.size()); + ASSERT_THAT(std::get(args.front()), + AllOf(WithMotionClassification(MotionClassification::NONE), + WithGestureScrollDistance(0, 0, EPSILON))); +} + +TEST_F(GestureConverterTest, ThreeFingerSwipe_ClearsClassificationAndOffsetsAfterGesture) { + InputDeviceContext deviceContext(*mDevice, EVENTHUB_ID); + GestureConverter converter(*mReader->getContext(), deviceContext, DEVICE_ID); + + Gesture startGesture(kGestureSwipe, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, /* dx= */ 0, + /* dy= */ 0); + std::list args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, startGesture); + + Gesture liftGesture(kGestureSwipeLift, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME); + args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, liftGesture); + + Gesture moveGesture(kGestureMove, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, /* dx= */ -5, + /* dy= */ 10); + args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, moveGesture); + ASSERT_EQ(1u, args.size()); + ASSERT_THAT(std::get(args.front()), + AllOf(WithMotionClassification(MotionClassification::NONE), + WithGestureOffset(0, 0, EPSILON))); +} + +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); + + Gesture startGesture(kGestureSwipe, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, /* dx= */ 0, + /* dy= */ 10); + std::list args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, startGesture); + ASSERT_EQ(4u, args.size()); + + // 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), + WithMotionClassification(MotionClassification::MULTI_FINGER_SWIPE), + WithPointerCount(1u), WithToolType(ToolType::FINGER))); + 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), + WithMotionClassification(MotionClassification::MULTI_FINGER_SWIPE), + WithPointerCount(2u), WithToolType(ToolType::FINGER))); + 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), + WithMotionClassification(MotionClassification::MULTI_FINGER_SWIPE), + WithPointerCount(3u), WithToolType(ToolType::FINGER))); + 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), + WithMotionClassification(MotionClassification::MULTI_FINGER_SWIPE), + WithPointerCount(3u), WithToolType(ToolType::FINGER))); + 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, 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), + WithMotionClassification(MotionClassification::MULTI_FINGER_SWIPE), + WithPointerCount(3u), WithToolType(ToolType::FINGER))); + 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, liftGesture); + ASSERT_EQ(3u, args.size()); + ASSERT_THAT(std::get(args.front()), + 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), WithToolType(ToolType::FINGER))); + args.pop_front(); + ASSERT_THAT(std::get(args.front()), + 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), WithToolType(ToolType::FINGER))); + args.pop_front(); + ASSERT_THAT(std::get(args.front()), + AllOf(WithMotionAction(AMOTION_EVENT_ACTION_UP), WithGestureOffset(0, 0, EPSILON), + WithMotionClassification(MotionClassification::MULTI_FINGER_SWIPE), + WithPointerCount(1u), WithToolType(ToolType::FINGER))); +} + +TEST_F(GestureConverterTest, ThreeFingerSwipe_Rotated) { + InputDeviceContext deviceContext(*mDevice, EVENTHUB_ID); + GestureConverter converter(*mReader->getContext(), deviceContext, DEVICE_ID); + converter.setOrientation(ui::ROTATION_90); + + Gesture startGesture(kGestureSwipe, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, /* dx= */ 0, + /* dy= */ 10); + std::list args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, startGesture); + ASSERT_EQ(4u, args.size()); + + // 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, 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))); + 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, liftGesture); + ASSERT_EQ(3u, args.size()); + ASSERT_THAT(std::get(args.front()), + AllOf(WithMotionAction(AMOTION_EVENT_ACTION_POINTER_UP | + 2 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT), + WithGestureOffset(0, 0, EPSILON), WithPointerCount(3u))); + args.pop_front(); + ASSERT_THAT(std::get(args.front()), + AllOf(WithMotionAction(AMOTION_EVENT_ACTION_POINTER_UP | + 1 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT), + WithGestureOffset(0, 0, EPSILON), WithPointerCount(2u))); + args.pop_front(); + ASSERT_THAT(std::get(args.front()), + AllOf(WithMotionAction(AMOTION_EVENT_ACTION_UP), WithGestureOffset(0, 0, EPSILON), + WithPointerCount(1u))); +} + +TEST_F(GestureConverterTest, FourFingerSwipe_Horizontal) { + InputDeviceContext deviceContext(*mDevice, EVENTHUB_ID); + GestureConverter converter(*mReader->getContext(), deviceContext, DEVICE_ID); + + Gesture startGesture(kGestureFourFingerSwipe, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, + /* dx= */ 10, /* dy= */ 0); + std::list args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, startGesture); + ASSERT_EQ(5u, args.size()); + + // 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), + WithMotionClassification(MotionClassification::MULTI_FINGER_SWIPE), + WithPointerCount(1u), WithToolType(ToolType::FINGER))); + 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), + WithMotionClassification(MotionClassification::MULTI_FINGER_SWIPE), + WithPointerCount(2u), WithToolType(ToolType::FINGER))); + 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), + WithMotionClassification(MotionClassification::MULTI_FINGER_SWIPE), + WithPointerCount(3u), WithToolType(ToolType::FINGER))); + 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), + WithMotionClassification(MotionClassification::MULTI_FINGER_SWIPE), + WithPointerCount(4u), WithToolType(ToolType::FINGER))); + 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), + WithMotionClassification(MotionClassification::MULTI_FINGER_SWIPE), + WithPointerCount(4u), WithToolType(ToolType::FINGER))); + 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, 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), + WithMotionClassification(MotionClassification::MULTI_FINGER_SWIPE), + WithPointerCount(4u), WithToolType(ToolType::FINGER))); + 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, liftGesture); + ASSERT_EQ(4u, args.size()); + ASSERT_THAT(std::get(args.front()), + AllOf(WithMotionAction(AMOTION_EVENT_ACTION_POINTER_UP | + 3 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT), + WithGestureOffset(0, 0, EPSILON), + WithMotionClassification(MotionClassification::MULTI_FINGER_SWIPE), + WithPointerCount(4u), WithToolType(ToolType::FINGER))); + args.pop_front(); + ASSERT_THAT(std::get(args.front()), + 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), WithToolType(ToolType::FINGER))); + args.pop_front(); + ASSERT_THAT(std::get(args.front()), + 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), WithToolType(ToolType::FINGER))); + args.pop_front(); + ASSERT_THAT(std::get(args.front()), + AllOf(WithMotionAction(AMOTION_EVENT_ACTION_UP), WithGestureOffset(0, 0, EPSILON), + WithMotionClassification(MotionClassification::MULTI_FINGER_SWIPE), + WithPointerCount(1u), WithToolType(ToolType::FINGER))); +} + +TEST_F(GestureConverterTest, Pinch_Inwards) { + InputDeviceContext deviceContext(*mDevice, EVENTHUB_ID); + GestureConverter converter(*mReader->getContext(), deviceContext, DEVICE_ID); + + Gesture startGesture(kGesturePinch, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, /* dz= */ 1, + GESTURES_ZOOM_START); + std::list args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, startGesture); + ASSERT_EQ(2u, args.size()); + ASSERT_THAT(std::get(args.front()), + AllOf(WithMotionAction(AMOTION_EVENT_ACTION_DOWN), + WithMotionClassification(MotionClassification::PINCH), + WithGesturePinchScaleFactor(1.0f, EPSILON), + WithCoords(POINTER_X - 100, POINTER_Y), WithPointerCount(1u), + WithToolType(ToolType::FINGER))); + args.pop_front(); + ASSERT_THAT(std::get(args.front()), + AllOf(WithMotionAction(AMOTION_EVENT_ACTION_POINTER_DOWN | + 1 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT), + WithMotionClassification(MotionClassification::PINCH), + WithGesturePinchScaleFactor(1.0f, EPSILON), + WithPointerCoords(1, POINTER_X + 100, POINTER_Y), WithPointerCount(2u), + WithToolType(ToolType::FINGER))); + + Gesture updateGesture(kGesturePinch, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, + /* dz= */ 0.8, GESTURES_ZOOM_UPDATE); + args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, updateGesture); + ASSERT_EQ(1u, args.size()); + ASSERT_THAT(std::get(args.front()), + 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))); + + Gesture endGesture(kGesturePinch, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, /* dz= */ 1, + GESTURES_ZOOM_END); + args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, endGesture); + ASSERT_EQ(2u, args.size()); + ASSERT_THAT(std::get(args.front()), + AllOf(WithMotionAction(AMOTION_EVENT_ACTION_POINTER_UP | + 1 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT), + WithMotionClassification(MotionClassification::PINCH), + WithGesturePinchScaleFactor(1.0f, EPSILON), WithPointerCount(2u), + WithToolType(ToolType::FINGER))); + args.pop_front(); + ASSERT_THAT(std::get(args.front()), + AllOf(WithMotionAction(AMOTION_EVENT_ACTION_UP), + WithMotionClassification(MotionClassification::PINCH), + WithGesturePinchScaleFactor(1.0f, EPSILON), WithPointerCount(1u), + WithToolType(ToolType::FINGER))); +} + +TEST_F(GestureConverterTest, Pinch_Outwards) { + InputDeviceContext deviceContext(*mDevice, EVENTHUB_ID); + GestureConverter converter(*mReader->getContext(), deviceContext, DEVICE_ID); + + Gesture startGesture(kGesturePinch, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, /* dz= */ 1, + GESTURES_ZOOM_START); + std::list args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, startGesture); + ASSERT_EQ(2u, args.size()); + ASSERT_THAT(std::get(args.front()), + AllOf(WithMotionAction(AMOTION_EVENT_ACTION_DOWN), + WithMotionClassification(MotionClassification::PINCH), + WithGesturePinchScaleFactor(1.0f, EPSILON), + WithCoords(POINTER_X - 100, POINTER_Y), WithPointerCount(1u), + WithToolType(ToolType::FINGER))); + args.pop_front(); + ASSERT_THAT(std::get(args.front()), + AllOf(WithMotionAction(AMOTION_EVENT_ACTION_POINTER_DOWN | + 1 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT), + WithMotionClassification(MotionClassification::PINCH), + WithGesturePinchScaleFactor(1.0f, EPSILON), + WithPointerCoords(1, POINTER_X + 100, POINTER_Y), WithPointerCount(2u), + WithToolType(ToolType::FINGER))); + + Gesture updateGesture(kGesturePinch, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, + /* dz= */ 1.2, GESTURES_ZOOM_UPDATE); + args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, updateGesture); + ASSERT_EQ(1u, args.size()); + ASSERT_THAT(std::get(args.front()), + 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))); + + Gesture endGesture(kGesturePinch, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, /* dz= */ 1, + GESTURES_ZOOM_END); + args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, endGesture); + ASSERT_EQ(2u, args.size()); + ASSERT_THAT(std::get(args.front()), + AllOf(WithMotionAction(AMOTION_EVENT_ACTION_POINTER_UP | + 1 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT), + WithMotionClassification(MotionClassification::PINCH), + WithGesturePinchScaleFactor(1.0f, EPSILON), WithPointerCount(2u), + WithToolType(ToolType::FINGER))); + args.pop_front(); + ASSERT_THAT(std::get(args.front()), + AllOf(WithMotionAction(AMOTION_EVENT_ACTION_UP), + WithMotionClassification(MotionClassification::PINCH), + WithGesturePinchScaleFactor(1.0f, EPSILON), WithPointerCount(1u), + WithToolType(ToolType::FINGER))); +} + +TEST_F(GestureConverterTest, Pinch_ClearsClassificationAndScaleFactorAfterGesture) { + InputDeviceContext deviceContext(*mDevice, EVENTHUB_ID); + GestureConverter converter(*mReader->getContext(), deviceContext, DEVICE_ID); + + Gesture startGesture(kGesturePinch, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, /* dz= */ 1, + GESTURES_ZOOM_START); + std::list args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, startGesture); + + Gesture updateGesture(kGesturePinch, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, + /* dz= */ 1.2, GESTURES_ZOOM_UPDATE); + args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, updateGesture); + + Gesture endGesture(kGesturePinch, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, /* dz= */ 1, + GESTURES_ZOOM_END); + args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, endGesture); + + Gesture moveGesture(kGestureMove, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, -5, 10); + args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, moveGesture); + ASSERT_EQ(1u, args.size()); + ASSERT_THAT(std::get(args.front()), + AllOf(WithMotionClassification(MotionClassification::NONE), + WithGesturePinchScaleFactor(0, EPSILON))); +} + +TEST_F(GestureConverterTest, ResetWithButtonPressed) { + InputDeviceContext deviceContext(*mDevice, EVENTHUB_ID); + GestureConverter converter(*mReader->getContext(), deviceContext, DEVICE_ID); + + 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, downGesture); + + std::list args = converter.reset(ARBITRARY_TIME); + ASSERT_EQ(3u, args.size()); + + EXPECT_THAT(std::get(args.front()), + 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))); + args.pop_front(); + EXPECT_THAT(std::get(args.front()), + AllOf(WithMotionAction(AMOTION_EVENT_ACTION_BUTTON_RELEASE), + WithActionButton(AMOTION_EVENT_BUTTON_SECONDARY), WithButtonState(0), + WithCoords(POINTER_X, POINTER_Y), + WithToolType(ToolType::FINGER))); + args.pop_front(); + ASSERT_THAT(std::get(args.front()), + AllOf(WithMotionAction(AMOTION_EVENT_ACTION_UP), WithButtonState(0), + WithCoords(POINTER_X, POINTER_Y), + WithToolType(ToolType::FINGER))); +} + +TEST_F(GestureConverterTest, ResetDuringScroll) { + InputDeviceContext deviceContext(*mDevice, EVENTHUB_ID); + GestureConverter converter(*mReader->getContext(), deviceContext, DEVICE_ID); + + Gesture startGesture(kGestureScroll, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, 0, -10); + (void)converter.handleGesture(ARBITRARY_TIME, READ_TIME, startGesture); + + std::list args = converter.reset(ARBITRARY_TIME); + ASSERT_EQ(1u, args.size()); + ASSERT_THAT(std::get(args.front()), + AllOf(WithMotionAction(AMOTION_EVENT_ACTION_UP), + WithCoords(POINTER_X, POINTER_Y - 10), + WithGestureScrollDistance(0, 0, EPSILON), + WithMotionClassification(MotionClassification::TWO_FINGER_SWIPE), + WithToolType(ToolType::FINGER), + WithFlags(AMOTION_EVENT_FLAG_IS_GENERATED_GESTURE))); +} + +TEST_F(GestureConverterTest, ResetDuringThreeFingerSwipe) { + InputDeviceContext deviceContext(*mDevice, EVENTHUB_ID); + GestureConverter converter(*mReader->getContext(), deviceContext, DEVICE_ID); + + Gesture startGesture(kGestureSwipe, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, /*dx=*/0, + /*dy=*/10); + (void)converter.handleGesture(ARBITRARY_TIME, READ_TIME, startGesture); + + std::list args = converter.reset(ARBITRARY_TIME); + ASSERT_EQ(3u, args.size()); + EXPECT_THAT(std::get(args.front()), + 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), WithToolType(ToolType::FINGER))); + args.pop_front(); + EXPECT_THAT(std::get(args.front()), + 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), WithToolType(ToolType::FINGER))); + args.pop_front(); + EXPECT_THAT(std::get(args.front()), + AllOf(WithMotionAction(AMOTION_EVENT_ACTION_UP), WithGestureOffset(0, 0, EPSILON), + WithMotionClassification(MotionClassification::MULTI_FINGER_SWIPE), + WithPointerCount(1u), WithToolType(ToolType::FINGER))); +} + +TEST_F(GestureConverterTest, ResetDuringPinch) { + InputDeviceContext deviceContext(*mDevice, EVENTHUB_ID); + GestureConverter converter(*mReader->getContext(), deviceContext, DEVICE_ID); + + Gesture startGesture(kGesturePinch, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, /*dz=*/1, + GESTURES_ZOOM_START); + (void)converter.handleGesture(ARBITRARY_TIME, READ_TIME, startGesture); + + std::list args = converter.reset(ARBITRARY_TIME); + ASSERT_EQ(2u, args.size()); + EXPECT_THAT(std::get(args.front()), + AllOf(WithMotionAction(AMOTION_EVENT_ACTION_POINTER_UP | + 1 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT), + WithMotionClassification(MotionClassification::PINCH), + WithGesturePinchScaleFactor(1.0f, EPSILON), WithPointerCount(2u), + WithToolType(ToolType::FINGER))); + args.pop_front(); + EXPECT_THAT(std::get(args.front()), + AllOf(WithMotionAction(AMOTION_EVENT_ACTION_UP), + WithMotionClassification(MotionClassification::PINCH), + WithGesturePinchScaleFactor(1.0f, EPSILON), WithPointerCount(1u), + WithToolType(ToolType::FINGER))); +} + +TEST_F(GestureConverterTest, FlingTapDown) { + InputDeviceContext deviceContext(*mDevice, EVENTHUB_ID); + GestureConverter converter(*mReader->getContext(), deviceContext, DEVICE_ID); + + 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, tapDownGesture); + + ASSERT_THAT(std::get(args.front()), + AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_MOVE), + WithCoords(POINTER_X, POINTER_Y), WithRelativeMotion(0.f, 0.f), + WithToolType(ToolType::FINGER), WithButtonState(0), WithPressure(0.0f))); + + ASSERT_NO_FATAL_FAILURE(mFakePointerController->assertPosition(POINTER_X, POINTER_Y)); + ASSERT_TRUE(mFakePointerController->isPointerShown()); +} + +} // namespace android diff --git a/services/inputflinger/tests/HardwareStateConverter_test.cpp b/services/inputflinger/tests/HardwareStateConverter_test.cpp new file mode 100644 index 0000000000000000000000000000000000000000..5bea2bac1821b35361abc7c33aea3a28a9ea8eb5 --- /dev/null +++ b/services/inputflinger/tests/HardwareStateConverter_test.cpp @@ -0,0 +1,271 @@ +/* + * 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 "FakeEventHub.h" +#include "FakeInputReaderPolicy.h" +#include "InstrumentedInputReader.h" +#include "MultiTouchMotionAccumulator.h" +#include "TestConstants.h" +#include "TestInputListener.h" + +namespace android { + +class HardwareStateConverterTest : public testing::Test { +public: + HardwareStateConverterTest() + : mFakeEventHub(std::make_shared()), + mFakePolicy(sp::make()), + mReader(mFakeEventHub, mFakePolicy, mFakeListener), + mDevice(newDevice()), + mDeviceContext(*mDevice, EVENTHUB_ID) { + const size_t slotCount = 8; + mFakeEventHub->addAbsoluteAxis(EVENTHUB_ID, ABS_MT_SLOT, 0, slotCount - 1, 0, 0, 0); + mAccumulator.configure(mDeviceContext, slotCount, /*usingSlotsProtocol=*/true); + mConverter = std::make_unique(mDeviceContext, mAccumulator); + } + +protected: + static constexpr int32_t DEVICE_ID = END_RESERVED_ID + 1000; + static constexpr int32_t EVENTHUB_ID = 1; + + 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; + } + + void processAxis(nsecs_t when, int32_t type, int32_t code, int32_t value) { + RawEvent event; + event.when = when; + event.readTime = READ_TIME; + event.deviceId = EVENTHUB_ID; + event.type = type; + event.code = code; + event.value = value; + std::optional schs = mConverter->processRawEvent(&event); + EXPECT_FALSE(schs.has_value()); + } + + std::optional processSync(nsecs_t when) { + RawEvent event; + event.when = when; + event.readTime = READ_TIME; + event.deviceId = EVENTHUB_ID; + event.type = EV_SYN; + event.code = SYN_REPORT; + event.value = 0; + return mConverter->processRawEvent(&event); + } + + std::shared_ptr mFakeEventHub; + sp mFakePolicy; + TestInputListener mFakeListener; + InstrumentedInputReader mReader; + std::shared_ptr mDevice; + InputDeviceContext mDeviceContext; + MultiTouchMotionAccumulator mAccumulator; + std::unique_ptr mConverter; +}; + +TEST_F(HardwareStateConverterTest, OneFinger) { + const nsecs_t time = 1500000000; + + processAxis(time, EV_ABS, ABS_MT_SLOT, 0); + processAxis(time, EV_ABS, ABS_MT_TRACKING_ID, 123); + processAxis(time, EV_ABS, ABS_MT_POSITION_X, 50); + processAxis(time, EV_ABS, ABS_MT_POSITION_Y, 100); + processAxis(time, EV_ABS, ABS_MT_TOUCH_MAJOR, 5); + processAxis(time, EV_ABS, ABS_MT_TOUCH_MINOR, 4); + processAxis(time, EV_ABS, ABS_MT_PRESSURE, 42); + processAxis(time, EV_ABS, ABS_MT_ORIENTATION, 2); + + processAxis(time, EV_ABS, ABS_X, 50); + processAxis(time, EV_ABS, ABS_Y, 100); + processAxis(time, EV_ABS, ABS_PRESSURE, 42); + + processAxis(time, EV_KEY, BTN_TOUCH, 1); + processAxis(time, EV_KEY, BTN_TOOL_FINGER, 1); + std::optional schs = processSync(time); + + ASSERT_TRUE(schs.has_value()); + const HardwareState& state = schs->state; + EXPECT_NEAR(1.5, state.timestamp, EPSILON); + EXPECT_EQ(0, state.buttons_down); + EXPECT_EQ(1, state.touch_cnt); + + ASSERT_EQ(1, state.finger_cnt); + const FingerState& finger = state.fingers[0]; + EXPECT_EQ(123, finger.tracking_id); + EXPECT_NEAR(50, finger.position_x, EPSILON); + EXPECT_NEAR(100, finger.position_y, EPSILON); + EXPECT_NEAR(5, finger.touch_major, EPSILON); + EXPECT_NEAR(4, finger.touch_minor, EPSILON); + EXPECT_NEAR(42, finger.pressure, EPSILON); + EXPECT_NEAR(2, finger.orientation, EPSILON); + EXPECT_EQ(0u, finger.flags); + + EXPECT_EQ(0, state.rel_x); + EXPECT_EQ(0, state.rel_y); + EXPECT_EQ(0, state.rel_wheel); + EXPECT_EQ(0, state.rel_wheel_hi_res); + EXPECT_EQ(0, state.rel_hwheel); + EXPECT_NEAR(0.0, state.msc_timestamp, EPSILON); +} + +TEST_F(HardwareStateConverterTest, TwoFingers) { + processAxis(ARBITRARY_TIME, EV_ABS, ABS_MT_SLOT, 0); + processAxis(ARBITRARY_TIME, EV_ABS, ABS_MT_TRACKING_ID, 123); + processAxis(ARBITRARY_TIME, EV_ABS, ABS_MT_POSITION_X, 50); + processAxis(ARBITRARY_TIME, EV_ABS, ABS_MT_POSITION_Y, 100); + processAxis(ARBITRARY_TIME, EV_ABS, ABS_MT_TOUCH_MAJOR, 5); + processAxis(ARBITRARY_TIME, EV_ABS, ABS_MT_TOUCH_MINOR, 4); + processAxis(ARBITRARY_TIME, EV_ABS, ABS_MT_PRESSURE, 42); + processAxis(ARBITRARY_TIME, EV_ABS, ABS_MT_ORIENTATION, 2); + + processAxis(ARBITRARY_TIME, EV_ABS, ABS_MT_SLOT, 1); + processAxis(ARBITRARY_TIME, EV_ABS, ABS_MT_TRACKING_ID, 456); + processAxis(ARBITRARY_TIME, EV_ABS, ABS_MT_POSITION_X, -20); + processAxis(ARBITRARY_TIME, EV_ABS, ABS_MT_POSITION_Y, 40); + processAxis(ARBITRARY_TIME, EV_ABS, ABS_MT_TOUCH_MAJOR, 8); + processAxis(ARBITRARY_TIME, EV_ABS, ABS_MT_TOUCH_MINOR, 7); + processAxis(ARBITRARY_TIME, EV_ABS, ABS_MT_PRESSURE, 21); + processAxis(ARBITRARY_TIME, EV_ABS, ABS_MT_ORIENTATION, 1); + + processAxis(ARBITRARY_TIME, EV_ABS, ABS_X, 50); + processAxis(ARBITRARY_TIME, EV_ABS, ABS_Y, 100); + processAxis(ARBITRARY_TIME, EV_ABS, ABS_PRESSURE, 42); + + processAxis(ARBITRARY_TIME, EV_KEY, BTN_TOUCH, 1); + processAxis(ARBITRARY_TIME, EV_KEY, BTN_TOOL_DOUBLETAP, 1); + std::optional schs = processSync(ARBITRARY_TIME); + + ASSERT_TRUE(schs.has_value()); + ASSERT_EQ(2, schs->state.finger_cnt); + const FingerState& finger1 = schs->state.fingers[0]; + EXPECT_EQ(123, finger1.tracking_id); + EXPECT_NEAR(50, finger1.position_x, EPSILON); + EXPECT_NEAR(100, finger1.position_y, EPSILON); + EXPECT_NEAR(5, finger1.touch_major, EPSILON); + EXPECT_NEAR(4, finger1.touch_minor, EPSILON); + EXPECT_NEAR(42, finger1.pressure, EPSILON); + EXPECT_NEAR(2, finger1.orientation, EPSILON); + EXPECT_EQ(0u, finger1.flags); + + const FingerState& finger2 = schs->state.fingers[1]; + EXPECT_EQ(456, finger2.tracking_id); + EXPECT_NEAR(-20, finger2.position_x, EPSILON); + EXPECT_NEAR(40, finger2.position_y, EPSILON); + EXPECT_NEAR(8, finger2.touch_major, EPSILON); + EXPECT_NEAR(7, finger2.touch_minor, EPSILON); + EXPECT_NEAR(21, finger2.pressure, EPSILON); + EXPECT_NEAR(1, finger2.orientation, EPSILON); + EXPECT_EQ(0u, finger2.flags); +} + +TEST_F(HardwareStateConverterTest, OnePalm) { + processAxis(ARBITRARY_TIME, EV_ABS, ABS_MT_SLOT, 0); + processAxis(ARBITRARY_TIME, EV_ABS, ABS_MT_TOOL_TYPE, MT_TOOL_PALM); + processAxis(ARBITRARY_TIME, EV_ABS, ABS_MT_TRACKING_ID, 123); + processAxis(ARBITRARY_TIME, EV_ABS, ABS_MT_POSITION_X, 50); + processAxis(ARBITRARY_TIME, EV_ABS, ABS_MT_POSITION_Y, 100); + + processAxis(ARBITRARY_TIME, EV_KEY, BTN_TOUCH, 1); + processAxis(ARBITRARY_TIME, EV_KEY, BTN_TOOL_FINGER, 1); + std::optional schs = processSync(ARBITRARY_TIME); + ASSERT_TRUE(schs.has_value()); + EXPECT_EQ(0, schs->state.touch_cnt); + EXPECT_EQ(0, schs->state.finger_cnt); +} + +TEST_F(HardwareStateConverterTest, OneFingerTurningIntoAPalm) { + processAxis(ARBITRARY_TIME, EV_ABS, ABS_MT_SLOT, 0); + processAxis(ARBITRARY_TIME, EV_ABS, ABS_MT_TOOL_TYPE, MT_TOOL_FINGER); + processAxis(ARBITRARY_TIME, EV_ABS, ABS_MT_TRACKING_ID, 123); + processAxis(ARBITRARY_TIME, EV_ABS, ABS_MT_POSITION_X, 50); + processAxis(ARBITRARY_TIME, EV_ABS, ABS_MT_POSITION_Y, 100); + + processAxis(ARBITRARY_TIME, EV_KEY, BTN_TOUCH, 1); + processAxis(ARBITRARY_TIME, EV_KEY, BTN_TOOL_FINGER, 1); + + std::optional schs = processSync(ARBITRARY_TIME); + ASSERT_TRUE(schs.has_value()); + EXPECT_EQ(1, schs->state.touch_cnt); + EXPECT_EQ(1, schs->state.finger_cnt); + + processAxis(ARBITRARY_TIME, EV_ABS, ABS_MT_TOOL_TYPE, MT_TOOL_PALM); + processAxis(ARBITRARY_TIME, EV_ABS, ABS_MT_POSITION_X, 51); + processAxis(ARBITRARY_TIME, EV_ABS, ABS_MT_POSITION_Y, 99); + + schs = processSync(ARBITRARY_TIME); + ASSERT_TRUE(schs.has_value()); + EXPECT_EQ(0, schs->state.touch_cnt); + ASSERT_EQ(0, schs->state.finger_cnt); + + processAxis(ARBITRARY_TIME, EV_ABS, ABS_MT_POSITION_X, 53); + processAxis(ARBITRARY_TIME, EV_ABS, ABS_MT_POSITION_Y, 97); + + schs = processSync(ARBITRARY_TIME); + ASSERT_TRUE(schs.has_value()); + EXPECT_EQ(0, schs->state.touch_cnt); + EXPECT_EQ(0, schs->state.finger_cnt); + + processAxis(ARBITRARY_TIME, EV_ABS, ABS_MT_TOOL_TYPE, MT_TOOL_FINGER); + processAxis(ARBITRARY_TIME, EV_ABS, ABS_MT_POSITION_X, 55); + processAxis(ARBITRARY_TIME, EV_ABS, ABS_MT_POSITION_Y, 95); + schs = processSync(ARBITRARY_TIME); + ASSERT_TRUE(schs.has_value()); + EXPECT_EQ(1, schs->state.touch_cnt); + ASSERT_EQ(1, schs->state.finger_cnt); + const FingerState& newFinger = schs->state.fingers[0]; + EXPECT_EQ(123, newFinger.tracking_id); + EXPECT_NEAR(55, newFinger.position_x, EPSILON); + EXPECT_NEAR(95, newFinger.position_y, EPSILON); +} + +TEST_F(HardwareStateConverterTest, ButtonPressed) { + processAxis(ARBITRARY_TIME, EV_KEY, BTN_LEFT, 1); + std::optional schs = processSync(ARBITRARY_TIME); + + ASSERT_TRUE(schs.has_value()); + EXPECT_EQ(GESTURES_BUTTON_LEFT, schs->state.buttons_down); +} + +TEST_F(HardwareStateConverterTest, MscTimestamp) { + processAxis(ARBITRARY_TIME, EV_MSC, MSC_TIMESTAMP, 1200000); + std::optional schs = processSync(ARBITRARY_TIME); + + ASSERT_TRUE(schs.has_value()); + EXPECT_NEAR(1.2, schs->state.msc_timestamp, EPSILON); +} + +} // namespace android diff --git a/services/inputflinger/tests/InputDispatcher_test.cpp b/services/inputflinger/tests/InputDispatcher_test.cpp index b23b88adb4a091e38f776ba6eb293cf7ea0437b5..6ff420d95104589407a2afc31030dee740374ef8 100644 --- a/services/inputflinger/tests/InputDispatcher_test.cpp +++ b/services/inputflinger/tests/InputDispatcher_test.cpp @@ -22,12 +22,14 @@ #include #include #include +#include #include #include #include #include #include +#include #include #include #include @@ -43,30 +45,58 @@ using android::os::InputEventInjectionSync; namespace android::inputdispatcher { using namespace ftl::flag_operators; +using testing::AllOf; // An arbitrary time value. static constexpr nsecs_t ARBITRARY_TIME = 1234; // An arbitrary device id. static constexpr int32_t DEVICE_ID = 1; +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; +static constexpr int32_t ACTION_DOWN = AMOTION_EVENT_ACTION_DOWN; +static constexpr int32_t ACTION_MOVE = AMOTION_EVENT_ACTION_MOVE; +static constexpr int32_t ACTION_UP = AMOTION_EVENT_ACTION_UP; +static constexpr int32_t ACTION_HOVER_ENTER = AMOTION_EVENT_ACTION_HOVER_ENTER; +static constexpr int32_t ACTION_HOVER_MOVE = AMOTION_EVENT_ACTION_HOVER_MOVE; +static constexpr int32_t ACTION_HOVER_EXIT = AMOTION_EVENT_ACTION_HOVER_EXIT; +static constexpr int32_t ACTION_OUTSIDE = AMOTION_EVENT_ACTION_OUTSIDE; +static constexpr int32_t ACTION_CANCEL = AMOTION_EVENT_ACTION_CANCEL; +/** + * The POINTER_DOWN(0) is an unusual, but valid, action. It just means that the new pointer in the + * MotionEvent is at the index 0 rather than 1 (or later). That is, the pointer id=0 (which is at + * index 0) is the new pointer going down. The same pointer could have been placed at a different + * index, and the action would become POINTER_1_DOWN, 2, etc..; these would all be valid. In + * general, we try to place pointer id = 0 at the index 0. Of course, this is not possible if + * pointer id=0 leaves but the pointer id=1 remains. + */ +static constexpr int32_t POINTER_0_DOWN = + AMOTION_EVENT_ACTION_POINTER_DOWN | (0 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT); 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 constexpr int32_t POINTER_3_DOWN = + AMOTION_EVENT_ACTION_POINTER_DOWN | (3 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT); static constexpr int32_t POINTER_0_UP = AMOTION_EVENT_ACTION_POINTER_UP | (0 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT); static constexpr int32_t POINTER_1_UP = AMOTION_EVENT_ACTION_POINTER_UP | (1 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT); +static constexpr int32_t POINTER_2_UP = + AMOTION_EVENT_ACTION_POINTER_UP | (2 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT); -// The default pid and uid for windows created by the test. +// The default pid and uid for windows created on the primary display by the test. static constexpr int32_t WINDOW_PID = 999; static constexpr int32_t WINDOW_UID = 1001; +// The default pid and uid for the windows created on the secondary display by the test. +static constexpr int32_t SECONDARY_WINDOW_PID = 1010; +static constexpr int32_t SECONDARY_WINDOW_UID = 1012; + // The default policy flags to use for event injection by tests. static constexpr uint32_t DEFAULT_POLICY_FLAGS = POLICY_FLAG_FILTERED | POLICY_FLAG_PASS_TO_USER; @@ -78,9 +108,12 @@ static constexpr std::chrono::duration STALE_EVENT_TIMEOUT = 1000ms; static constexpr int expectedWallpaperFlags = AMOTION_EVENT_FLAG_WINDOW_IS_OBSCURED | AMOTION_EVENT_FLAG_WINDOW_IS_PARTIALLY_OBSCURED; +using ReservedInputDeviceId::VIRTUAL_KEYBOARD_ID; + struct PointF { float x; float y; + auto operator<=>(const PointF&) const = default; }; /** @@ -101,6 +134,73 @@ static void assertMotionAction(int32_t expectedAction, int32_t receivedAction) { << MotionEvent::actionToString(receivedAction); } +MATCHER_P(WithMotionAction, action, "MotionEvent with specified action") { + bool matches = action == arg.getAction(); + if (!matches) { + *result_listener << "expected action " << MotionEvent::actionToString(action) + << ", but got " << MotionEvent::actionToString(arg.getAction()); + } + if (action == AMOTION_EVENT_ACTION_DOWN) { + if (!matches) { + *result_listener << "; "; + } + *result_listener << "downTime should match eventTime for ACTION_DOWN events"; + matches &= arg.getDownTime() == arg.getEventTime(); + } + if (action == AMOTION_EVENT_ACTION_CANCEL) { + if (!matches) { + *result_listener << "; "; + } + *result_listener << "expected FLAG_CANCELED to be set with ACTION_CANCEL, but was not set"; + matches &= (arg.getFlags() & AMOTION_EVENT_FLAG_CANCELED) != 0; + } + return matches; +} + +MATCHER_P(WithDownTime, downTime, "InputEvent with specified downTime") { + return arg.getDownTime() == downTime; +} + +MATCHER_P(WithDisplayId, displayId, "InputEvent with specified displayId") { + return arg.getDisplayId() == displayId; +} + +MATCHER_P(WithDeviceId, deviceId, "InputEvent with specified deviceId") { + return arg.getDeviceId() == deviceId; +} + +MATCHER_P(WithSource, source, "InputEvent with specified source") { + *result_listener << "expected source " << inputEventSourceToString(source) << ", but got " + << inputEventSourceToString(arg.getSource()); + return arg.getSource() == source; +} + +MATCHER_P(WithFlags, flags, "InputEvent with specified flags") { + return arg.getFlags() == flags; +} + +MATCHER_P2(WithCoords, x, y, "MotionEvent with specified coordinates") { + if (arg.getPointerCount() != 1) { + *result_listener << "Expected 1 pointer, got " << arg.getPointerCount(); + return false; + } + return arg.getX(/*pointerIndex=*/0) == x && arg.getY(/*pointerIndex=*/0) == y; +} + +MATCHER_P(WithPointerCount, pointerCount, "MotionEvent with specified number of pointers") { + return arg.getPointerCount() == pointerCount; +} + +MATCHER_P(WithPointers, pointers, "MotionEvent with specified pointers") { + // Build a map for the received pointers, by pointer id + std::map actualPointers; + for (size_t pointerIndex = 0; pointerIndex < arg.getPointerCount(); pointerIndex++) { + const int32_t pointerId = arg.getPointerId(pointerIndex); + actualPointers[pointerId] = {arg.getX(pointerIndex), arg.getY(pointerIndex)}; + } + return pointers == actualPointers; +} + // --- FakeInputDispatcherPolicy --- class FakeInputDispatcherPolicy : public InputDispatcherPolicyInterface { @@ -108,15 +208,13 @@ class FakeInputDispatcherPolicy : public InputDispatcherPolicyInterface { using AnrResult = std::pair, int32_t /*pid*/>; -protected: - virtual ~FakeInputDispatcherPolicy() {} - public: - FakeInputDispatcherPolicy() {} + FakeInputDispatcherPolicy() = default; + virtual ~FakeInputDispatcherPolicy() = default; void assertFilterInputEventWasCalled(const NotifyKeyArgs& args) { assertFilterInputEventWasCalledInternal([&args](const InputEvent& event) { - ASSERT_EQ(event.getType(), AINPUT_EVENT_TYPE_KEY); + ASSERT_EQ(event.getType(), InputEventType::KEY); EXPECT_EQ(event.getDisplayId(), args.displayId); const auto& keyEvent = static_cast(event); @@ -127,7 +225,7 @@ public: void assertFilterInputEventWasCalled(const NotifyMotionArgs& args, vec2 point) { assertFilterInputEventWasCalledInternal([&](const InputEvent& event) { - ASSERT_EQ(event.getType(), AINPUT_EVENT_TYPE_MOTION); + ASSERT_EQ(event.getType(), InputEventType::MOTION); EXPECT_EQ(event.getDisplayId(), args.displayId); const auto& motionEvent = static_cast(event); @@ -307,6 +405,16 @@ public: mInterceptKeyTimeout = timeout; } + void assertUserActivityPoked() { + std::scoped_lock lock(mLock); + ASSERT_TRUE(mPokedUserActivity) << "Expected user activity to have been poked"; + } + + void assertUserActivityNotPoked() { + std::scoped_lock lock(mLock); + ASSERT_FALSE(mPokedUserActivity) << "Expected user activity not to have been poked"; + } + private: std::mutex mLock; std::unique_ptr mFilteredEvent GUARDED_BY(mLock); @@ -328,6 +436,7 @@ private: sp mDropTargetWindowToken GUARDED_BY(mLock); bool mNotifyDropWindowWasCalled GUARDED_BY(mLock) = false; + bool mPokedUserActivity GUARDED_BY(mLock) = false; std::chrono::milliseconds mInterceptKeyTimeout = 0ms; @@ -417,7 +526,6 @@ private: void notifyFocusChanged(const sp&, const sp&) override {} - void notifyUntrustedTouch(const std::string& obscuringPackage) override {} void notifySensorEvent(int32_t deviceId, InputDeviceSensorType sensorType, InputDeviceSensorAccuracy accuracy, nsecs_t timestamp, const std::vector& values) override {} @@ -427,30 +535,32 @@ private: void notifyVibratorState(int32_t deviceId, bool isOn) override {} - void getDispatcherConfiguration(InputDispatcherConfiguration* outConfig) override { - *outConfig = mConfig; - } + InputDispatcherConfiguration getDispatcherConfiguration() override { return mConfig; } - bool filterInputEvent(const InputEvent* inputEvent, uint32_t policyFlags) override { + bool filterInputEvent(const InputEvent& inputEvent, uint32_t policyFlags) override { std::scoped_lock lock(mLock); - switch (inputEvent->getType()) { - case AINPUT_EVENT_TYPE_KEY: { - const KeyEvent* keyEvent = static_cast(inputEvent); - mFilteredEvent = std::make_unique(*keyEvent); + switch (inputEvent.getType()) { + case InputEventType::KEY: { + const KeyEvent& keyEvent = static_cast(inputEvent); + mFilteredEvent = std::make_unique(keyEvent); break; } - case AINPUT_EVENT_TYPE_MOTION: { - const MotionEvent* motionEvent = static_cast(inputEvent); - mFilteredEvent = std::make_unique(*motionEvent); + case InputEventType::MOTION: { + const MotionEvent& motionEvent = static_cast(inputEvent); + mFilteredEvent = std::make_unique(motionEvent); + break; + } + default: { + ADD_FAILURE() << "Should only filter keys or motions"; break; } } return true; } - void interceptKeyBeforeQueueing(const KeyEvent* inputEvent, uint32_t&) override { - if (inputEvent->getAction() == AKEY_EVENT_ACTION_UP) { + void interceptKeyBeforeQueueing(const KeyEvent& inputEvent, uint32_t&) override { + if (inputEvent.getAction() == AKEY_EVENT_ACTION_UP) { // Clear intercept state when we handled the event. mInterceptKeyTimeout = 0ms; } @@ -458,15 +568,16 @@ private: void interceptMotionBeforeQueueing(int32_t, nsecs_t, uint32_t&) override {} - nsecs_t interceptKeyBeforeDispatching(const sp&, const KeyEvent*, uint32_t) override { + nsecs_t interceptKeyBeforeDispatching(const sp&, const KeyEvent&, uint32_t) override { nsecs_t delay = std::chrono::nanoseconds(mInterceptKeyTimeout).count(); // Clear intercept state so we could dispatch the event in next wake. mInterceptKeyTimeout = 0ms; return delay; } - bool dispatchUnhandledKey(const sp&, const KeyEvent*, uint32_t, KeyEvent*) override { - return false; + std::optional dispatchUnhandledKey(const sp&, const KeyEvent&, + uint32_t) override { + return {}; } void notifySwitch(nsecs_t when, uint32_t switchValues, uint32_t switchMask, @@ -475,10 +586,13 @@ private: /** We simply reconstruct NotifySwitchArgs in policy because InputDispatcher is * essentially a passthrough for notifySwitch. */ - mLastNotifySwitch = NotifySwitchArgs(1 /*id*/, when, policyFlags, switchValues, switchMask); + mLastNotifySwitch = NotifySwitchArgs(/*id=*/1, when, policyFlags, switchValues, switchMask); } - void pokeUserActivity(nsecs_t, int32_t, int32_t) override {} + void pokeUserActivity(nsecs_t, int32_t, int32_t) override { + std::scoped_lock lock(mLock); + mPokedUserActivity = true; + } void onPointerDownOutsideFocus(const sp& newToken) override { std::scoped_lock lock(mLock); @@ -510,12 +624,12 @@ private: class InputDispatcherTest : public testing::Test { protected: - sp mFakePolicy; + std::unique_ptr mFakePolicy; std::unique_ptr mDispatcher; void SetUp() override { - mFakePolicy = new FakeInputDispatcherPolicy(); - mDispatcher = std::make_unique(mFakePolicy, STALE_EVENT_TIMEOUT); + mFakePolicy = std::make_unique(); + mDispatcher = std::make_unique(*mFakePolicy, STALE_EVENT_TIMEOUT); mDispatcher->setInputDispatchMode(/*enabled*/ true, /*frozen*/ false); // Start InputDispatcher thread ASSERT_EQ(OK, mDispatcher->start()); @@ -523,7 +637,7 @@ protected: void TearDown() override { ASSERT_EQ(OK, mDispatcher->stop()); - mFakePolicy.clear(); + mFakePolicy.reset(); mDispatcher.reset(); } @@ -541,14 +655,10 @@ protected: } } - void setFocusedWindow(const sp& window, - const sp& focusedWindow = nullptr) { + void setFocusedWindow(const sp& window) { FocusRequest request; request.token = window->getToken(); request.windowName = window->getName(); - if (focusedWindow) { - request.focusedToken = focusedWindow->getToken(); - } request.timestamp = systemTime(SYSTEM_TIME_MONOTONIC); request.displayId = window->getInfo()->displayId; mDispatcher->setFocusedWindow(request); @@ -564,7 +674,7 @@ TEST_F(InputDispatcherTest, InjectInputEvent_ValidatesKeyEvents) { /*action*/ -1, 0, AKEYCODE_A, KEY_A, AMETA_NONE, 0, ARBITRARY_TIME, ARBITRARY_TIME); ASSERT_EQ(InputEventInjectionResult::FAILED, - mDispatcher->injectInputEvent(&event, {} /*targetUid*/, InputEventInjectionSync::NONE, + mDispatcher->injectInputEvent(&event, /*targetUid=*/{}, InputEventInjectionSync::NONE, 0ms, 0)) << "Should reject key events with undefined action."; @@ -573,7 +683,7 @@ TEST_F(InputDispatcherTest, InjectInputEvent_ValidatesKeyEvents) { 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, + mDispatcher->injectInputEvent(&event, /*targetUid=*/{}, InputEventInjectionSync::NONE, 0ms, 0)) << "Should reject key events with ACTION_MULTIPLE."; } @@ -603,7 +713,7 @@ TEST_F(InputDispatcherTest, InjectInputEvent_ValidatesMotionEvents) { ARBITRARY_TIME, /*pointerCount*/ 1, pointerProperties, pointerCoords); ASSERT_EQ(InputEventInjectionResult::FAILED, - mDispatcher->injectInputEvent(&event, {} /*targetUid*/, InputEventInjectionSync::NONE, + mDispatcher->injectInputEvent(&event, /*targetUid=*/{}, InputEventInjectionSync::NONE, 0ms, 0)) << "Should reject motion events with undefined action."; @@ -615,7 +725,7 @@ TEST_F(InputDispatcherTest, InjectInputEvent_ValidatesMotionEvents) { ARBITRARY_TIME, /*pointerCount*/ 1, pointerProperties, pointerCoords); ASSERT_EQ(InputEventInjectionResult::FAILED, - mDispatcher->injectInputEvent(&event, {} /*targetUid*/, InputEventInjectionSync::NONE, + mDispatcher->injectInputEvent(&event, /*targetUid=*/{}, InputEventInjectionSync::NONE, 0ms, 0)) << "Should reject motion events with pointer down index too large."; @@ -627,7 +737,7 @@ TEST_F(InputDispatcherTest, InjectInputEvent_ValidatesMotionEvents) { identityTransform, ARBITRARY_TIME, ARBITRARY_TIME, /*pointerCount*/ 1, pointerProperties, pointerCoords); ASSERT_EQ(InputEventInjectionResult::FAILED, - mDispatcher->injectInputEvent(&event, {} /*targetUid*/, InputEventInjectionSync::NONE, + mDispatcher->injectInputEvent(&event, /*targetUid=*/{}, InputEventInjectionSync::NONE, 0ms, 0)) << "Should reject motion events with pointer down index too small."; @@ -639,7 +749,7 @@ TEST_F(InputDispatcherTest, InjectInputEvent_ValidatesMotionEvents) { ARBITRARY_TIME, /*pointerCount*/ 1, pointerProperties, pointerCoords); ASSERT_EQ(InputEventInjectionResult::FAILED, - mDispatcher->injectInputEvent(&event, {} /*targetUid*/, InputEventInjectionSync::NONE, + mDispatcher->injectInputEvent(&event, /*targetUid=*/{}, InputEventInjectionSync::NONE, 0ms, 0)) << "Should reject motion events with pointer up index too large."; @@ -651,7 +761,7 @@ TEST_F(InputDispatcherTest, InjectInputEvent_ValidatesMotionEvents) { identityTransform, ARBITRARY_TIME, ARBITRARY_TIME, /*pointerCount*/ 1, pointerProperties, pointerCoords); ASSERT_EQ(InputEventInjectionResult::FAILED, - mDispatcher->injectInputEvent(&event, {} /*targetUid*/, InputEventInjectionSync::NONE, + mDispatcher->injectInputEvent(&event, /*targetUid=*/{}, InputEventInjectionSync::NONE, 0ms, 0)) << "Should reject motion events with pointer up index too small."; @@ -663,7 +773,7 @@ TEST_F(InputDispatcherTest, InjectInputEvent_ValidatesMotionEvents) { ARBITRARY_TIME, /*pointerCount*/ 0, pointerProperties, pointerCoords); ASSERT_EQ(InputEventInjectionResult::FAILED, - mDispatcher->injectInputEvent(&event, {} /*targetUid*/, InputEventInjectionSync::NONE, + mDispatcher->injectInputEvent(&event, /*targetUid=*/{}, InputEventInjectionSync::NONE, 0ms, 0)) << "Should reject motion events with 0 pointers."; @@ -674,7 +784,7 @@ TEST_F(InputDispatcherTest, InjectInputEvent_ValidatesMotionEvents) { ARBITRARY_TIME, /*pointerCount*/ MAX_POINTERS + 1, pointerProperties, pointerCoords); ASSERT_EQ(InputEventInjectionResult::FAILED, - mDispatcher->injectInputEvent(&event, {} /*targetUid*/, InputEventInjectionSync::NONE, + mDispatcher->injectInputEvent(&event, /*targetUid=*/{}, InputEventInjectionSync::NONE, 0ms, 0)) << "Should reject motion events with more than MAX_POINTERS pointers."; @@ -687,7 +797,7 @@ TEST_F(InputDispatcherTest, InjectInputEvent_ValidatesMotionEvents) { ARBITRARY_TIME, /*pointerCount*/ 1, pointerProperties, pointerCoords); ASSERT_EQ(InputEventInjectionResult::FAILED, - mDispatcher->injectInputEvent(&event, {} /*targetUid*/, InputEventInjectionSync::NONE, + mDispatcher->injectInputEvent(&event, /*targetUid=*/{}, InputEventInjectionSync::NONE, 0ms, 0)) << "Should reject motion events with pointer ids less than 0."; @@ -699,7 +809,7 @@ TEST_F(InputDispatcherTest, InjectInputEvent_ValidatesMotionEvents) { ARBITRARY_TIME, /*pointerCount*/ 1, pointerProperties, pointerCoords); ASSERT_EQ(InputEventInjectionResult::FAILED, - mDispatcher->injectInputEvent(&event, {} /*targetUid*/, InputEventInjectionSync::NONE, + mDispatcher->injectInputEvent(&event, /*targetUid=*/{}, InputEventInjectionSync::NONE, 0ms, 0)) << "Should reject motion events with pointer ids greater than MAX_POINTER_ID."; @@ -713,7 +823,7 @@ TEST_F(InputDispatcherTest, InjectInputEvent_ValidatesMotionEvents) { ARBITRARY_TIME, /*pointerCount*/ 2, pointerProperties, pointerCoords); ASSERT_EQ(InputEventInjectionResult::FAILED, - mDispatcher->injectInputEvent(&event, {} /*targetUid*/, InputEventInjectionSync::NONE, + mDispatcher->injectInputEvent(&event, /*targetUid=*/{}, InputEventInjectionSync::NONE, 0ms, 0)) << "Should reject motion events with duplicate pointer ids."; } @@ -722,17 +832,16 @@ TEST_F(InputDispatcherTest, InjectInputEvent_ValidatesMotionEvents) { TEST_F(InputDispatcherTest, NotifyConfigurationChanged_CallsPolicy) { constexpr nsecs_t eventTime = 20; - NotifyConfigurationChangedArgs args(10 /*id*/, eventTime); - mDispatcher->notifyConfigurationChanged(&args); + mDispatcher->notifyConfigurationChanged({/*id=*/10, eventTime}); ASSERT_TRUE(mDispatcher->waitForIdle()); mFakePolicy->assertNotifyConfigurationChangedWasCalled(eventTime); } TEST_F(InputDispatcherTest, NotifySwitch_CallsPolicy) { - NotifySwitchArgs args(10 /*id*/, 20 /*eventTime*/, 0 /*policyFlags*/, 1 /*switchValues*/, - 2 /*switchMask*/); - mDispatcher->notifySwitch(&args); + NotifySwitchArgs args(/*id=*/10, /*eventTime=*/20, /*policyFlags=*/0, /*switchValues=*/1, + /*switchMask=*/2); + mDispatcher->notifySwitch(args); // InputDispatcher adds POLICY_FLAG_TRUSTED because the event went through InputListener args.policyFlags |= POLICY_FLAG_TRUSTED; @@ -751,7 +860,7 @@ class FakeApplicationHandle : public InputApplicationHandle { public: FakeApplicationHandle() { mInfo.name = "Fake Application"; - mInfo.token = new BBinder(); + mInfo.token = sp::make(); mInfo.dispatchingTimeoutMillis = std::chrono::duration_cast(DISPATCHING_TIMEOUT).count(); } @@ -792,7 +901,7 @@ public: std::chrono::time_point start = std::chrono::steady_clock::now(); status_t status = WOULD_BLOCK; while (status == WOULD_BLOCK) { - status = mConsumer->consume(&mEventFactory, true /*consumeBatches*/, -1, &consumeSeq, + status = mConsumer->consume(&mEventFactory, /*consumeBatches=*/true, -1, &consumeSeq, &event); std::chrono::duration elapsed = std::chrono::steady_clock::now() - start; if (elapsed > 100ms) { @@ -832,7 +941,7 @@ public: ASSERT_EQ(OK, status); } - void consumeEvent(int32_t expectedEventType, int32_t expectedAction, + void consumeEvent(InputEventType expectedEventType, int32_t expectedAction, std::optional expectedDisplayId, std::optional expectedFlags) { InputEvent* event = consume(); @@ -840,15 +949,15 @@ public: ASSERT_NE(nullptr, event) << mName.c_str() << ": consumer should have returned non-NULL event."; ASSERT_EQ(expectedEventType, event->getType()) - << mName.c_str() << " expected " << inputEventTypeToString(expectedEventType) - << " event, got " << inputEventTypeToString(event->getType()) << " event"; + << mName.c_str() << " expected " << ftl::enum_string(expectedEventType) + << " event, got " << *event; if (expectedDisplayId.has_value()) { EXPECT_EQ(expectedDisplayId, event->getDisplayId()); } switch (expectedEventType) { - case AINPUT_EVENT_TYPE_KEY: { + case InputEventType::KEY: { const KeyEvent& keyEvent = static_cast(*event); EXPECT_EQ(expectedAction, keyEvent.getAction()); if (expectedFlags.has_value()) { @@ -856,7 +965,7 @@ public: } break; } - case AINPUT_EVENT_TYPE_MOTION: { + case InputEventType::MOTION: { const MotionEvent& motionEvent = static_cast(*event); assertMotionAction(expectedAction, motionEvent.getAction()); @@ -865,31 +974,48 @@ public: } break; } - case AINPUT_EVENT_TYPE_FOCUS: { + case InputEventType::FOCUS: { FAIL() << "Use 'consumeFocusEvent' for FOCUS events"; } - case AINPUT_EVENT_TYPE_CAPTURE: { + case InputEventType::CAPTURE: { FAIL() << "Use 'consumeCaptureEvent' for CAPTURE events"; } - case AINPUT_EVENT_TYPE_TOUCH_MODE: { + case InputEventType::TOUCH_MODE: { FAIL() << "Use 'consumeTouchModeEvent' for TOUCH_MODE events"; } - case AINPUT_EVENT_TYPE_DRAG: { + case InputEventType::DRAG: { FAIL() << "Use 'consumeDragEvent' for DRAG events"; } - default: { - FAIL() << mName.c_str() << ": invalid event type: " << expectedEventType; - } } } + MotionEvent* consumeMotion() { + InputEvent* event = consume(); + + if (event == nullptr) { + ADD_FAILURE() << mName << ": expected a MotionEvent, but didn't get one."; + return nullptr; + } + + if (event->getType() != InputEventType::MOTION) { + ADD_FAILURE() << mName << " expected a MotionEvent, got " << *event; + return nullptr; + } + return static_cast(event); + } + + void consumeMotionEvent(const ::testing::Matcher& matcher) { + MotionEvent* motionEvent = consumeMotion(); + ASSERT_NE(nullptr, motionEvent) << "Did not get a motion event, but expected " << matcher; + ASSERT_THAT(*motionEvent, matcher); + } + void consumeFocusEvent(bool hasFocus, bool inTouchMode) { InputEvent* event = consume(); ASSERT_NE(nullptr, event) << mName.c_str() << ": consumer should have returned non-NULL event."; - ASSERT_EQ(AINPUT_EVENT_TYPE_FOCUS, event->getType()) - << "Got " << inputEventTypeToString(event->getType()) - << " event instead of FOCUS event"; + ASSERT_EQ(InputEventType::FOCUS, event->getType()) + << "Instead of FocusEvent, got " << *event; ASSERT_EQ(ADISPLAY_ID_NONE, event->getDisplayId()) << mName.c_str() << ": event displayId should always be NONE."; @@ -902,9 +1028,8 @@ public: const InputEvent* event = consume(); ASSERT_NE(nullptr, event) << mName.c_str() << ": consumer should have returned non-NULL event."; - ASSERT_EQ(AINPUT_EVENT_TYPE_CAPTURE, event->getType()) - << "Got " << inputEventTypeToString(event->getType()) - << " event instead of CAPTURE event"; + ASSERT_EQ(InputEventType::CAPTURE, event->getType()) + << "Instead of CaptureEvent, got " << *event; ASSERT_EQ(ADISPLAY_ID_NONE, event->getDisplayId()) << mName.c_str() << ": event displayId should always be NONE."; @@ -917,9 +1042,7 @@ public: const InputEvent* event = consume(); ASSERT_NE(nullptr, event) << mName.c_str() << ": consumer should have returned non-NULL event."; - ASSERT_EQ(AINPUT_EVENT_TYPE_DRAG, event->getType()) - << "Got " << inputEventTypeToString(event->getType()) - << " event instead of DRAG event"; + ASSERT_EQ(InputEventType::DRAG, event->getType()) << "Instead of DragEvent, got " << *event; EXPECT_EQ(ADISPLAY_ID_NONE, event->getDisplayId()) << mName.c_str() << ": event displayId should always be NONE."; @@ -934,9 +1057,8 @@ public: const InputEvent* event = consume(); ASSERT_NE(nullptr, event) << mName.c_str() << ": consumer should have returned non-NULL event."; - ASSERT_EQ(AINPUT_EVENT_TYPE_TOUCH_MODE, event->getType()) - << "Got " << inputEventTypeToString(event->getType()) - << " event instead of TOUCH_MODE event"; + ASSERT_EQ(InputEventType::TOUCH_MODE, event->getType()) + << "Instead of TouchModeEvent, got " << *event; ASSERT_EQ(ADISPLAY_ID_NONE, event->getDisplayId()) << mName.c_str() << ": event displayId should always be NONE."; @@ -949,23 +1071,23 @@ public: if (event == nullptr) { return; } - if (event->getType() == AINPUT_EVENT_TYPE_KEY) { + if (event->getType() == InputEventType::KEY) { KeyEvent& keyEvent = static_cast(*event); ADD_FAILURE() << "Received key event " << KeyEvent::actionToString(keyEvent.getAction()); - } else if (event->getType() == AINPUT_EVENT_TYPE_MOTION) { + } else if (event->getType() == InputEventType::MOTION) { MotionEvent& motionEvent = static_cast(*event); ADD_FAILURE() << "Received motion event " << MotionEvent::actionToString(motionEvent.getAction()); - } else if (event->getType() == AINPUT_EVENT_TYPE_FOCUS) { + } else if (event->getType() == InputEventType::FOCUS) { FocusEvent& focusEvent = static_cast(*event); ADD_FAILURE() << "Received focus event, hasFocus = " << (focusEvent.getHasFocus() ? "true" : "false"); - } else if (event->getType() == AINPUT_EVENT_TYPE_CAPTURE) { + } else if (event->getType() == InputEventType::CAPTURE) { const auto& captureEvent = static_cast(*event); ADD_FAILURE() << "Received capture event, pointerCaptureEnabled = " << (captureEvent.getPointerCaptureEnabled() ? "true" : "false"); - } else if (event->getType() == AINPUT_EVENT_TYPE_TOUCH_MODE) { + } else if (event->getType() == InputEventType::TOUCH_MODE) { const auto& touchModeEvent = static_cast(*event); ADD_FAILURE() << "Received touch mode event, inTouchMode = " << (touchModeEvent.isInTouchMode() ? "true" : "false"); @@ -1027,8 +1149,8 @@ public: const std::shared_ptr& inputApplicationHandle, const std::unique_ptr& dispatcher, int32_t displayId) { sp handle = - new FakeWindowHandle(inputApplicationHandle, dispatcher, mInfo.name + "(Mirror)", - displayId, mInfo.token); + sp::make(inputApplicationHandle, dispatcher, + mInfo.name + "(Mirror)", displayId, mInfo.token); return handle; } @@ -1082,6 +1204,10 @@ public: mInfo.setInputConfig(WindowInfo::InputConfig::NO_INPUT_CHANNEL, noInputChannel); } + void setDisableUserActivity(bool disableUserActivity) { + mInfo.setInputConfig(WindowInfo::InputConfig::DISABLE_USER_ACTIVITY, disableUserActivity); + } + void setAlpha(float alpha) { mInfo.alpha = alpha; } void setTouchOcclusionMode(TouchOcclusionMode mode) { mInfo.touchOcclusionMode = mode; } @@ -1125,24 +1251,23 @@ public: void setWindowOffset(float offsetX, float offsetY) { mInfo.transform.set(offsetX, offsetY); } void consumeKeyDown(int32_t expectedDisplayId, int32_t expectedFlags = 0) { - consumeEvent(AINPUT_EVENT_TYPE_KEY, AKEY_EVENT_ACTION_DOWN, expectedDisplayId, - expectedFlags); + consumeEvent(InputEventType::KEY, AKEY_EVENT_ACTION_DOWN, expectedDisplayId, expectedFlags); } void consumeKeyUp(int32_t expectedDisplayId, int32_t expectedFlags = 0) { - consumeEvent(AINPUT_EVENT_TYPE_KEY, AKEY_EVENT_ACTION_UP, expectedDisplayId, expectedFlags); + consumeEvent(InputEventType::KEY, AKEY_EVENT_ACTION_UP, expectedDisplayId, expectedFlags); } void consumeMotionCancel(int32_t expectedDisplayId = ADISPLAY_ID_DEFAULT, int32_t expectedFlags = 0) { - consumeEvent(AINPUT_EVENT_TYPE_MOTION, AMOTION_EVENT_ACTION_CANCEL, expectedDisplayId, - expectedFlags); + consumeMotionEvent(AllOf(WithMotionAction(ACTION_CANCEL), WithDisplayId(expectedDisplayId), + WithFlags(expectedFlags | AMOTION_EVENT_FLAG_CANCELED))); } void consumeMotionMove(int32_t expectedDisplayId = ADISPLAY_ID_DEFAULT, int32_t expectedFlags = 0) { - consumeEvent(AINPUT_EVENT_TYPE_MOTION, AMOTION_EVENT_ACTION_MOVE, expectedDisplayId, - expectedFlags); + consumeMotionEvent(AllOf(WithMotionAction(AMOTION_EVENT_ACTION_MOVE), + WithDisplayId(expectedDisplayId), WithFlags(expectedFlags))); } void consumeMotionDown(int32_t expectedDisplayId = ADISPLAY_ID_DEFAULT, @@ -1152,7 +1277,7 @@ public: void consumeAnyMotionDown(std::optional expectedDisplayId = std::nullopt, std::optional expectedFlags = std::nullopt) { - consumeEvent(AINPUT_EVENT_TYPE_MOTION, AMOTION_EVENT_ACTION_DOWN, expectedDisplayId, + consumeEvent(InputEventType::MOTION, AMOTION_EVENT_ACTION_DOWN, expectedDisplayId, expectedFlags); } @@ -1161,32 +1286,33 @@ public: int32_t expectedFlags = 0) { int32_t action = AMOTION_EVENT_ACTION_POINTER_DOWN | (pointerIdx << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT); - consumeEvent(AINPUT_EVENT_TYPE_MOTION, action, expectedDisplayId, expectedFlags); + consumeEvent(InputEventType::MOTION, action, expectedDisplayId, expectedFlags); } void consumeMotionPointerUp(int32_t pointerIdx, int32_t expectedDisplayId = ADISPLAY_ID_DEFAULT, int32_t expectedFlags = 0) { int32_t action = AMOTION_EVENT_ACTION_POINTER_UP | (pointerIdx << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT); - consumeEvent(AINPUT_EVENT_TYPE_MOTION, action, expectedDisplayId, expectedFlags); + consumeEvent(InputEventType::MOTION, action, expectedDisplayId, expectedFlags); } void consumeMotionUp(int32_t expectedDisplayId = ADISPLAY_ID_DEFAULT, int32_t expectedFlags = 0) { - consumeEvent(AINPUT_EVENT_TYPE_MOTION, AMOTION_EVENT_ACTION_UP, expectedDisplayId, + consumeEvent(InputEventType::MOTION, AMOTION_EVENT_ACTION_UP, expectedDisplayId, expectedFlags); } void consumeMotionOutside(int32_t expectedDisplayId = ADISPLAY_ID_DEFAULT, int32_t expectedFlags = 0) { - consumeEvent(AINPUT_EVENT_TYPE_MOTION, AMOTION_EVENT_ACTION_OUTSIDE, expectedDisplayId, + consumeEvent(InputEventType::MOTION, AMOTION_EVENT_ACTION_OUTSIDE, expectedDisplayId, expectedFlags); } void consumeMotionOutsideWithZeroedCoords(int32_t expectedDisplayId = ADISPLAY_ID_DEFAULT, int32_t expectedFlags = 0) { InputEvent* event = consume(); - ASSERT_EQ(AINPUT_EVENT_TYPE_MOTION, event->getType()); + ASSERT_NE(nullptr, event); + ASSERT_EQ(InputEventType::MOTION, event->getType()); const MotionEvent& motionEvent = static_cast(*event); EXPECT_EQ(AMOTION_EVENT_ACTION_OUTSIDE, motionEvent.getActionMasked()); EXPECT_EQ(0.f, motionEvent.getRawPointerCoords(0)->getX()); @@ -1205,7 +1331,13 @@ public: mInputReceiver->consumeCaptureEvent(hasCapture); } - void consumeEvent(int32_t expectedEventType, int32_t expectedAction, + void consumeMotionEvent(const ::testing::Matcher& matcher) { + MotionEvent* motionEvent = consumeMotion(); + ASSERT_NE(nullptr, motionEvent) << "Did not get a motion event, but expected " << matcher; + ASSERT_THAT(*motionEvent, matcher); + } + + void consumeEvent(InputEventType expectedEventType, int32_t expectedAction, std::optional expectedDisplayId, std::optional expectedFlags) { ASSERT_NE(mInputReceiver, nullptr) << "Invalid consume event on window with no receiver"; @@ -1254,9 +1386,8 @@ public: ADD_FAILURE() << "Consume failed : no event"; return nullptr; } - if (event->getType() != AINPUT_EVENT_TYPE_MOTION) { - ADD_FAILURE() << "Instead of motion event, got " - << inputEventTypeToString(event->getType()); + if (event->getType() != InputEventType::MOTION) { + ADD_FAILURE() << "Instead of motion event, got " << *event; return nullptr; } return static_cast(event); @@ -1307,8 +1438,8 @@ static InputEventInjectionResult injectKey( // Define a valid key down event. event.initialize(InputEvent::nextId(), DEVICE_ID, AINPUT_SOURCE_KEYBOARD, displayId, - INVALID_HMAC, action, /* flags */ 0, AKEYCODE_A, KEY_A, AMETA_NONE, - repeatCount, currentTime, currentTime); + INVALID_HMAC, action, /*flags=*/0, AKEYCODE_A, KEY_A, AMETA_NONE, repeatCount, + currentTime, currentTime); if (!allowKeyRepeat) { policyFlags |= POLICY_FLAG_DISABLE_KEY_REPEAT; @@ -1319,7 +1450,7 @@ static InputEventInjectionResult injectKey( static InputEventInjectionResult injectKeyDown(const std::unique_ptr& dispatcher, int32_t displayId = ADISPLAY_ID_NONE) { - return injectKey(dispatcher, AKEY_EVENT_ACTION_DOWN, /* repeatCount */ 0, displayId); + 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 @@ -1327,19 +1458,19 @@ static InputEventInjectionResult injectKeyDown(const std::unique_ptr& dispatcher, int32_t displayId = ADISPLAY_ID_NONE) { - return injectKey(dispatcher, AKEY_EVENT_ACTION_DOWN, /* repeatCount */ 0, displayId, + return injectKey(dispatcher, AKEY_EVENT_ACTION_DOWN, /*repeatCount=*/0, displayId, InputEventInjectionSync::WAIT_FOR_RESULT, INJECT_EVENT_TIMEOUT, - /* allowKeyRepeat */ false); + /*allowKeyRepeat=*/false); } static InputEventInjectionResult injectKeyUp(const std::unique_ptr& dispatcher, int32_t displayId = ADISPLAY_ID_NONE) { - return injectKey(dispatcher, AKEY_EVENT_ACTION_UP, /* repeatCount */ 0, displayId); + return injectKey(dispatcher, AKEY_EVENT_ACTION_UP, /*repeatCount=*/0, displayId); } class PointerBuilder { public: - PointerBuilder(int32_t id, int32_t toolType) { + PointerBuilder(int32_t id, ToolType toolType) { mProperties.clear(); mProperties.id = id; mProperties.toolType = toolType; @@ -1370,6 +1501,17 @@ public: mAction = action; mSource = source; mEventTime = systemTime(SYSTEM_TIME_MONOTONIC); + mDownTime = mEventTime; + } + + MotionEventBuilder& deviceId(int32_t deviceId) { + mDeviceId = deviceId; + return *this; + } + + MotionEventBuilder& downTime(nsecs_t downTime) { + mDownTime = downTime; + return *this; } MotionEventBuilder& eventTime(nsecs_t eventTime) { @@ -1422,19 +1564,18 @@ public: // Set mouse cursor position for the most common cases to avoid boilerplate. if (mSource == AINPUT_SOURCE_MOUSE && - !MotionEvent::isValidCursorPosition(mRawXCursorPosition, mRawYCursorPosition) && - mPointers.size() == 1) { + !MotionEvent::isValidCursorPosition(mRawXCursorPosition, mRawYCursorPosition)) { mRawXCursorPosition = pointerCoords[0].getX(); mRawYCursorPosition = pointerCoords[0].getY(); } MotionEvent event; ui::Transform identityTransform; - event.initialize(InputEvent::nextId(), DEVICE_ID, mSource, mDisplayId, INVALID_HMAC, + event.initialize(InputEvent::nextId(), mDeviceId, mSource, mDisplayId, INVALID_HMAC, mAction, mActionButton, mFlags, /* edgeFlags */ 0, AMETA_NONE, mButtonState, MotionClassification::NONE, identityTransform, /* xPrecision */ 0, /* yPrecision */ 0, mRawXCursorPosition, - mRawYCursorPosition, identityTransform, mEventTime, mEventTime, + mRawYCursorPosition, identityTransform, mDownTime, mEventTime, mPointers.size(), pointerProperties.data(), pointerCoords.data()); return event; @@ -1442,12 +1583,126 @@ public: private: int32_t mAction; + int32_t mDeviceId = DEVICE_ID; + int32_t mSource; + nsecs_t mDownTime; + nsecs_t mEventTime; + int32_t mDisplayId{ADISPLAY_ID_DEFAULT}; + int32_t mActionButton{0}; + int32_t mButtonState{0}; + int32_t mFlags{0}; + float mRawXCursorPosition{AMOTION_EVENT_INVALID_CURSOR_POSITION}; + float mRawYCursorPosition{AMOTION_EVENT_INVALID_CURSOR_POSITION}; + + std::vector mPointers; +}; + +class MotionArgsBuilder { +public: + MotionArgsBuilder(int32_t action, int32_t source) { + mAction = action; + mSource = source; + mEventTime = systemTime(SYSTEM_TIME_MONOTONIC); + mDownTime = mEventTime; + } + + MotionArgsBuilder& deviceId(int32_t deviceId) { + mDeviceId = deviceId; + return *this; + } + + MotionArgsBuilder& downTime(nsecs_t downTime) { + mDownTime = downTime; + return *this; + } + + MotionArgsBuilder& eventTime(nsecs_t eventTime) { + mEventTime = eventTime; + return *this; + } + + MotionArgsBuilder& displayId(int32_t displayId) { + mDisplayId = displayId; + return *this; + } + + MotionArgsBuilder& policyFlags(int32_t policyFlags) { + mPolicyFlags = policyFlags; + return *this; + } + + MotionArgsBuilder& actionButton(int32_t actionButton) { + mActionButton = actionButton; + return *this; + } + + MotionArgsBuilder& buttonState(int32_t buttonState) { + mButtonState = buttonState; + return *this; + } + + MotionArgsBuilder& rawXCursorPosition(float rawXCursorPosition) { + mRawXCursorPosition = rawXCursorPosition; + return *this; + } + + MotionArgsBuilder& rawYCursorPosition(float rawYCursorPosition) { + mRawYCursorPosition = rawYCursorPosition; + return *this; + } + + MotionArgsBuilder& pointer(PointerBuilder pointer) { + mPointers.push_back(pointer); + return *this; + } + + MotionArgsBuilder& addFlag(uint32_t flags) { + mFlags |= flags; + return *this; + } + + MotionArgsBuilder& classification(MotionClassification classification) { + mClassification = classification; + return *this; + } + + NotifyMotionArgs build() { + std::vector pointerProperties; + std::vector pointerCoords; + for (const PointerBuilder& pointer : mPointers) { + pointerProperties.push_back(pointer.buildProperties()); + pointerCoords.push_back(pointer.buildCoords()); + } + + // Set mouse cursor position for the most common cases to avoid boilerplate. + if (mSource == AINPUT_SOURCE_MOUSE && + !MotionEvent::isValidCursorPosition(mRawXCursorPosition, mRawYCursorPosition)) { + mRawXCursorPosition = pointerCoords[0].getX(); + mRawYCursorPosition = pointerCoords[0].getY(); + } + + NotifyMotionArgs args(InputEvent::nextId(), mEventTime, /*readTime=*/mEventTime, mDeviceId, + mSource, mDisplayId, mPolicyFlags, mAction, mActionButton, mFlags, + AMETA_NONE, mButtonState, mClassification, /*edgeFlags=*/0, + mPointers.size(), pointerProperties.data(), pointerCoords.data(), + /*xPrecision=*/0, /*yPrecision=*/0, mRawXCursorPosition, + mRawYCursorPosition, mDownTime, /*videoFrames=*/{}); + + return args; + } + +private: + int32_t mAction; + int32_t mDeviceId = DEVICE_ID; int32_t mSource; + nsecs_t mDownTime; nsecs_t mEventTime; int32_t mDisplayId{ADISPLAY_ID_DEFAULT}; + int32_t mPolicyFlags = DEFAULT_POLICY_FLAGS; int32_t mActionButton{0}; int32_t mButtonState{0}; int32_t mFlags{0}; + MotionClassification mClassification{MotionClassification::NONE}; float mRawXCursorPosition{AMOTION_EVENT_INVALID_CURSOR_POSITION}; float mRawYCursorPosition{AMOTION_EVENT_INVALID_CURSOR_POSITION}; @@ -1477,7 +1732,7 @@ static InputEventInjectionResult injectMotionEvent( .eventTime(eventTime) .rawXCursorPosition(cursorPosition.x) .rawYCursorPosition(cursorPosition.y) - .pointer(PointerBuilder(/* id */ 0, AMOTION_EVENT_TOOL_TYPE_FINGER) + .pointer(PointerBuilder(/*id=*/0, ToolType::FINGER) .x(position.x) .y(position.y)) .build(); @@ -1502,15 +1757,38 @@ static InputEventInjectionResult injectMotionUp(const std::unique_ptr& points) { +static NotifyKeyArgs generateSystemShortcutArgs(int32_t action, + int32_t displayId = ADISPLAY_ID_NONE) { + nsecs_t currentTime = systemTime(SYSTEM_TIME_MONOTONIC); + // Define a valid key event. + NotifyKeyArgs args(/*id=*/0, 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, + int32_t displayId = ADISPLAY_ID_NONE) { + nsecs_t currentTime = systemTime(SYSTEM_TIME_MONOTONIC); + // Define a valid key event. + NotifyKeyArgs args(/*id=*/0, 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, + int32_t displayId, + const std::vector& points) { size_t pointerCount = points.size(); if (action == AMOTION_EVENT_ACTION_DOWN || action == AMOTION_EVENT_ACTION_UP) { EXPECT_EQ(1U, pointerCount) << "Actions DOWN and UP can only contain a single pointer"; @@ -1522,7 +1800,7 @@ static NotifyMotionArgs generateMotionArgs(int32_t action, int32_t source, int32 for (size_t i = 0; i < pointerCount; i++) { pointerProperties[i].clear(); pointerProperties[i].id = i; - pointerProperties[i].toolType = AMOTION_EVENT_TOOL_TYPE_FINGER; + pointerProperties[i].toolType = ToolType::FINGER; pointerCoords[i].clear(); pointerCoords[i].setAxisValue(AMOTION_EVENT_AXIS_X, points[i].x); @@ -1531,7 +1809,7 @@ static NotifyMotionArgs generateMotionArgs(int32_t action, int32_t source, int32 nsecs_t currentTime = systemTime(SYSTEM_TIME_MONOTONIC); // Define a valid motion event. - NotifyMotionArgs args(/* id */ 0, currentTime, 0 /*readTime*/, DEVICE_ID, source, displayId, + NotifyMotionArgs args(/*id=*/0, currentTime, /*readTime=*/0, DEVICE_ID, source, displayId, POLICY_FLAG_PASS_TO_USER, action, /* actionButton */ 0, /* flags */ 0, AMETA_NONE, /* buttonState */ 0, MotionClassification::NONE, AMOTION_EVENT_EDGE_FLAG_NONE, pointerCount, pointerProperties, @@ -1552,7 +1830,7 @@ static NotifyMotionArgs generateMotionArgs(int32_t action, int32_t source, int32 static NotifyPointerCaptureChangedArgs generatePointerCaptureChangedArgs( const PointerCaptureRequest& request) { - return NotifyPointerCaptureChangedArgs(/* id */ 0, systemTime(SYSTEM_TIME_MONOTONIC), request); + return NotifyPointerCaptureChangedArgs(/*id=*/0, systemTime(SYSTEM_TIME_MONOTONIC), request); } /** @@ -1562,8 +1840,8 @@ static NotifyPointerCaptureChangedArgs generatePointerCaptureChangedArgs( TEST_F(InputDispatcherTest, WhenInputChannelBreaks_PolicyIsNotified) { std::shared_ptr application = std::make_shared(); sp window = - new FakeWindowHandle(application, mDispatcher, "Window that breaks its input channel", - ADISPLAY_ID_DEFAULT); + sp::make(application, mDispatcher, + "Window that breaks its input channel", ADISPLAY_ID_DEFAULT); mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {window}}}); @@ -1574,8 +1852,8 @@ TEST_F(InputDispatcherTest, WhenInputChannelBreaks_PolicyIsNotified) { TEST_F(InputDispatcherTest, SetInputWindow_SingleWindowTouch) { std::shared_ptr application = std::make_shared(); - sp window = - new FakeWindowHandle(application, mDispatcher, "Fake Window", ADISPLAY_ID_DEFAULT); + sp window = sp::make(application, mDispatcher, + "Fake Window", ADISPLAY_ID_DEFAULT); mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {window}}}); ASSERT_EQ(InputEventInjectionResult::SUCCEEDED, @@ -1588,8 +1866,8 @@ TEST_F(InputDispatcherTest, SetInputWindow_SingleWindowTouch) { TEST_F(InputDispatcherTest, WhenDisplayNotSpecified_InjectMotionToDefaultDisplay) { std::shared_ptr application = std::make_shared(); - sp window = - new FakeWindowHandle(application, mDispatcher, "Fake Window", ADISPLAY_ID_DEFAULT); + sp window = sp::make(application, mDispatcher, + "Fake Window", ADISPLAY_ID_DEFAULT); mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {window}}}); // Inject a MotionEvent to an unknown display. @@ -1608,8 +1886,8 @@ TEST_F(InputDispatcherTest, WhenDisplayNotSpecified_InjectMotionToDefaultDisplay */ TEST_F(InputDispatcherTest, SetInputWindowOnceWithSingleTouchWindow) { std::shared_ptr application = std::make_shared(); - sp window = - new FakeWindowHandle(application, mDispatcher, "Fake Window", ADISPLAY_ID_DEFAULT); + sp window = sp::make(application, mDispatcher, + "Fake Window", ADISPLAY_ID_DEFAULT); window->setFrame(Rect(0, 0, 100, 100)); mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {window}}}); @@ -1627,8 +1905,8 @@ TEST_F(InputDispatcherTest, SetInputWindowOnceWithSingleTouchWindow) { */ TEST_F(InputDispatcherTest, SetInputWindowTwice_SingleWindowTouch) { std::shared_ptr application = std::make_shared(); - sp window = - new FakeWindowHandle(application, mDispatcher, "Fake Window", ADISPLAY_ID_DEFAULT); + sp window = sp::make(application, mDispatcher, + "Fake Window", ADISPLAY_ID_DEFAULT); window->setFrame(Rect(0, 0, 100, 100)); mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {window}}}); @@ -1646,9 +1924,9 @@ TEST_F(InputDispatcherTest, SetInputWindowTwice_SingleWindowTouch) { TEST_F(InputDispatcherTest, SetInputWindow_MultiWindowsTouch) { std::shared_ptr application = std::make_shared(); sp windowTop = - new FakeWindowHandle(application, mDispatcher, "Top", ADISPLAY_ID_DEFAULT); + sp::make(application, mDispatcher, "Top", ADISPLAY_ID_DEFAULT); sp windowSecond = - new FakeWindowHandle(application, mDispatcher, "Second", ADISPLAY_ID_DEFAULT); + sp::make(application, mDispatcher, "Second", ADISPLAY_ID_DEFAULT); mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {windowTop, windowSecond}}}); ASSERT_EQ(InputEventInjectionResult::SUCCEEDED, @@ -1670,10 +1948,10 @@ TEST_F(InputDispatcherTest, SetInputWindow_MultiWindowsTouch) { TEST_F(InputDispatcherTest, WhenForegroundWindowDisappears_WallpaperTouchIsCanceled) { std::shared_ptr application = std::make_shared(); sp foregroundWindow = - new FakeWindowHandle(application, mDispatcher, "Foreground", ADISPLAY_ID_DEFAULT); + sp::make(application, mDispatcher, "Foreground", ADISPLAY_ID_DEFAULT); foregroundWindow->setDupTouchToWallpaper(true); sp wallpaperWindow = - new FakeWindowHandle(application, mDispatcher, "Wallpaper", ADISPLAY_ID_DEFAULT); + sp::make(application, mDispatcher, "Wallpaper", ADISPLAY_ID_DEFAULT); wallpaperWindow->setIsWallpaper(true); mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {foregroundWindow, wallpaperWindow}}}); @@ -1701,6 +1979,42 @@ TEST_F(InputDispatcherTest, WhenForegroundWindowDisappears_WallpaperTouchIsCance wallpaperWindow->consumeMotionCancel(ADISPLAY_ID_DEFAULT, expectedWallpaperFlags); } +/** + * Two fingers down on the window, and lift off the first finger. + * Next, cancel the gesture to the window by removing the window. Make sure that the CANCEL event + * contains a single pointer. + */ +TEST_F(InputDispatcherTest, CancelAfterPointer0Up) { + std::shared_ptr application = std::make_shared(); + sp window = + sp::make(application, mDispatcher, "Window", ADISPLAY_ID_DEFAULT); + + mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {window}}}); + // First touch pointer down on right window + mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_DOWN, AINPUT_SOURCE_TOUCHSCREEN) + .pointer(PointerBuilder(0, ToolType::FINGER).x(100).y(100)) + .build()); + // Second touch pointer down + mDispatcher->notifyMotion(MotionArgsBuilder(POINTER_1_DOWN, AINPUT_SOURCE_TOUCHSCREEN) + .pointer(PointerBuilder(0, ToolType::FINGER).x(100).y(100)) + .pointer(PointerBuilder(1, ToolType::FINGER).x(110).y(100)) + .build()); + // First touch pointer lifts. The second one remains down + mDispatcher->notifyMotion(MotionArgsBuilder(POINTER_0_UP, AINPUT_SOURCE_TOUCHSCREEN) + .pointer(PointerBuilder(0, ToolType::FINGER).x(100).y(100)) + .pointer(PointerBuilder(1, ToolType::FINGER).x(110).y(100)) + .build()); + window->consumeMotionEvent(WithMotionAction(ACTION_DOWN)); + window->consumeMotionEvent(WithMotionAction(POINTER_1_DOWN)); + window->consumeMotionEvent(WithMotionAction(POINTER_0_UP)); + + // Remove the window. The gesture should be canceled + mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {}}}); + const std::map expectedPointers{{1, PointF{110, 100}}}; + window->consumeMotionEvent( + AllOf(WithMotionAction(ACTION_CANCEL), WithPointers(expectedPointers))); +} + /** * Same test as WhenForegroundWindowDisappears_WallpaperTouchIsCanceled above, * with the following differences: @@ -1712,10 +2026,10 @@ TEST_F(InputDispatcherTest, WhenForegroundWindowDisappears_WallpaperTouchIsCance TEST_F(InputDispatcherTest, WhenWallpaperDisappears_NoCrash) { std::shared_ptr application = std::make_shared(); sp foregroundWindow = - new FakeWindowHandle(application, mDispatcher, "Foreground", ADISPLAY_ID_DEFAULT); + sp::make(application, mDispatcher, "Foreground", ADISPLAY_ID_DEFAULT); foregroundWindow->setDupTouchToWallpaper(true); sp wallpaperWindow = - new FakeWindowHandle(application, mDispatcher, "Wallpaper", ADISPLAY_ID_DEFAULT); + sp::make(application, mDispatcher, "Wallpaper", ADISPLAY_ID_DEFAULT); wallpaperWindow->setIsWallpaper(true); mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {foregroundWindow, wallpaperWindow}}}); @@ -1758,12 +2072,12 @@ INSTANTIATE_TEST_SUITE_P(InputDispatcherTest, ShouldSplitTouchFixture, TEST_P(ShouldSplitTouchFixture, WallpaperWindowReceivesMultiTouch) { std::shared_ptr application = std::make_shared(); sp foregroundWindow = - new FakeWindowHandle(application, mDispatcher, "Foreground", ADISPLAY_ID_DEFAULT); + sp::make(application, mDispatcher, "Foreground", ADISPLAY_ID_DEFAULT); foregroundWindow->setDupTouchToWallpaper(true); foregroundWindow->setPreventSplitting(GetParam()); sp wallpaperWindow = - new FakeWindowHandle(application, mDispatcher, "Wallpaper", ADISPLAY_ID_DEFAULT); + sp::make(application, mDispatcher, "Wallpaper", ADISPLAY_ID_DEFAULT); wallpaperWindow->setIsWallpaper(true); mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {foregroundWindow, wallpaperWindow}}}); @@ -1782,32 +2096,24 @@ TEST_P(ShouldSplitTouchFixture, WallpaperWindowReceivesMultiTouch) { const MotionEvent secondFingerDownEvent = MotionEventBuilder(POINTER_1_DOWN, AINPUT_SOURCE_TOUCHSCREEN) .eventTime(systemTime(SYSTEM_TIME_MONOTONIC)) - .pointer(PointerBuilder(/* id */ 0, AMOTION_EVENT_TOOL_TYPE_FINGER) - .x(100) - .y(100)) - .pointer(PointerBuilder(/* id */ 1, AMOTION_EVENT_TOOL_TYPE_FINGER) - .x(150) - .y(150)) + .pointer(PointerBuilder(/*id=*/0, ToolType::FINGER).x(100).y(100)) + .pointer(PointerBuilder(/*id=*/1, ToolType::FINGER).x(150).y(150)) .build(); ASSERT_EQ(InputEventInjectionResult::SUCCEEDED, injectMotionEvent(mDispatcher, secondFingerDownEvent, INJECT_EVENT_TIMEOUT, InputEventInjectionSync::WAIT_FOR_RESULT)) << "Inject motion event should return InputEventInjectionResult::SUCCEEDED"; - foregroundWindow->consumeMotionPointerDown(1 /* pointerIndex */); - wallpaperWindow->consumeMotionPointerDown(1 /* pointerIndex */, ADISPLAY_ID_DEFAULT, + foregroundWindow->consumeMotionPointerDown(/*pointerIndex=*/1); + wallpaperWindow->consumeMotionPointerDown(/*pointerIndex=*/1, ADISPLAY_ID_DEFAULT, expectedWallpaperFlags); const MotionEvent secondFingerUpEvent = MotionEventBuilder(POINTER_0_UP, AINPUT_SOURCE_TOUCHSCREEN) .displayId(ADISPLAY_ID_DEFAULT) .eventTime(systemTime(SYSTEM_TIME_MONOTONIC)) - .pointer(PointerBuilder(/* id */ 0, AMOTION_EVENT_TOOL_TYPE_FINGER) - .x(100) - .y(100)) - .pointer(PointerBuilder(/* id */ 1, AMOTION_EVENT_TOOL_TYPE_FINGER) - .x(150) - .y(150)) + .pointer(PointerBuilder(/*id=*/0, ToolType::FINGER).x(100).y(100)) + .pointer(PointerBuilder(/*id=*/1, ToolType::FINGER).x(150).y(150)) .build(); ASSERT_EQ(InputEventInjectionResult::SUCCEEDED, injectMotionEvent(mDispatcher, secondFingerUpEvent, INJECT_EVENT_TIMEOUT, @@ -1817,8 +2123,17 @@ TEST_P(ShouldSplitTouchFixture, WallpaperWindowReceivesMultiTouch) { wallpaperWindow->consumeMotionPointerUp(0, ADISPLAY_ID_DEFAULT, expectedWallpaperFlags); ASSERT_EQ(InputEventInjectionResult::SUCCEEDED, - injectMotionUp(mDispatcher, AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT, - {100, 100})) + injectMotionEvent(mDispatcher, + MotionEventBuilder(AMOTION_EVENT_ACTION_UP, + AINPUT_SOURCE_TOUCHSCREEN) + .displayId(ADISPLAY_ID_DEFAULT) + .eventTime(systemTime(SYSTEM_TIME_MONOTONIC)) + .pointer(PointerBuilder(/* id */ 1, + ToolType::FINGER) + .x(100) + .y(100)) + .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, expectedWallpaperFlags); @@ -1835,17 +2150,17 @@ TEST_P(ShouldSplitTouchFixture, WallpaperWindowReceivesMultiTouch) { TEST_F(InputDispatcherTest, TwoWindows_SplitWallpaperTouch) { std::shared_ptr application = std::make_shared(); sp leftWindow = - new FakeWindowHandle(application, mDispatcher, "Left", ADISPLAY_ID_DEFAULT); + sp::make(application, mDispatcher, "Left", ADISPLAY_ID_DEFAULT); leftWindow->setFrame(Rect(0, 0, 200, 200)); leftWindow->setDupTouchToWallpaper(true); sp rightWindow = - new FakeWindowHandle(application, mDispatcher, "Right", ADISPLAY_ID_DEFAULT); + sp::make(application, mDispatcher, "Right", ADISPLAY_ID_DEFAULT); rightWindow->setFrame(Rect(200, 0, 400, 200)); rightWindow->setDupTouchToWallpaper(true); sp wallpaperWindow = - new FakeWindowHandle(application, mDispatcher, "Wallpaper", ADISPLAY_ID_DEFAULT); + sp::make(application, mDispatcher, "Wallpaper", ADISPLAY_ID_DEFAULT); wallpaperWindow->setFrame(Rect(0, 0, 400, 200)); wallpaperWindow->setIsWallpaper(true); @@ -1866,12 +2181,8 @@ TEST_F(InputDispatcherTest, TwoWindows_SplitWallpaperTouch) { const MotionEvent secondFingerDownEvent = MotionEventBuilder(POINTER_1_DOWN, AINPUT_SOURCE_TOUCHSCREEN) .eventTime(systemTime(SYSTEM_TIME_MONOTONIC)) - .pointer(PointerBuilder(/* id */ 0, AMOTION_EVENT_TOOL_TYPE_FINGER) - .x(100) - .y(100)) - .pointer(PointerBuilder(/* id */ 1, AMOTION_EVENT_TOOL_TYPE_FINGER) - .x(300) - .y(100)) + .pointer(PointerBuilder(/*id=*/0, ToolType::FINGER).x(100).y(100)) + .pointer(PointerBuilder(/*id=*/1, ToolType::FINGER).x(300).y(100)) .build(); ASSERT_EQ(InputEventInjectionResult::SUCCEEDED, injectMotionEvent(mDispatcher, secondFingerDownEvent, INJECT_EVENT_TIMEOUT, @@ -1881,7 +2192,7 @@ TEST_F(InputDispatcherTest, TwoWindows_SplitWallpaperTouch) { leftWindow->consumeMotionMove(); // Since the touch is split, right window gets ACTION_DOWN rightWindow->consumeMotionDown(ADISPLAY_ID_DEFAULT); - wallpaperWindow->consumeMotionPointerDown(1 /* pointerIndex */, ADISPLAY_ID_DEFAULT, + wallpaperWindow->consumeMotionPointerDown(/*pointerIndex=*/1, ADISPLAY_ID_DEFAULT, expectedWallpaperFlags); // Now, leftWindow, which received the first finger, disappears. @@ -1895,12 +2206,8 @@ TEST_F(InputDispatcherTest, TwoWindows_SplitWallpaperTouch) { const MotionEvent secondFingerMoveEvent = MotionEventBuilder(AMOTION_EVENT_ACTION_MOVE, AINPUT_SOURCE_TOUCHSCREEN) .eventTime(systemTime(SYSTEM_TIME_MONOTONIC)) - .pointer(PointerBuilder(/* id */ 0, AMOTION_EVENT_TOOL_TYPE_FINGER) - .x(100) - .y(100)) - .pointer(PointerBuilder(/* id */ 1, AMOTION_EVENT_TOOL_TYPE_FINGER) - .x(310) - .y(110)) + .pointer(PointerBuilder(/*id=*/0, ToolType::FINGER).x(100).y(100)) + .pointer(PointerBuilder(/*id=*/1, ToolType::FINGER).x(310).y(110)) .build(); ASSERT_EQ(InputEventInjectionResult::SUCCEEDED, injectMotionEvent(mDispatcher, secondFingerMoveEvent, INJECT_EVENT_TIMEOUT, @@ -1920,17 +2227,17 @@ TEST_F(InputDispatcherTest, TwoWindows_SplitWallpaperTouch) { TEST_F(InputDispatcherTest, WallpaperWindowWhenSlippery) { std::shared_ptr application = std::make_shared(); sp leftWindow = - new FakeWindowHandle(application, mDispatcher, "Left", ADISPLAY_ID_DEFAULT); + sp::make(application, mDispatcher, "Left", ADISPLAY_ID_DEFAULT); leftWindow->setFrame(Rect(0, 0, 200, 200)); leftWindow->setDupTouchToWallpaper(true); leftWindow->setSlippery(true); sp rightWindow = - new FakeWindowHandle(application, mDispatcher, "Right", ADISPLAY_ID_DEFAULT); + sp::make(application, mDispatcher, "Right", ADISPLAY_ID_DEFAULT); rightWindow->setFrame(Rect(200, 0, 400, 200)); sp wallpaperWindow = - new FakeWindowHandle(application, mDispatcher, "Wallpaper", ADISPLAY_ID_DEFAULT); + sp::make(application, mDispatcher, "Wallpaper", ADISPLAY_ID_DEFAULT); wallpaperWindow->setIsWallpaper(true); mDispatcher->setInputWindows( @@ -1958,113 +2265,993 @@ TEST_F(InputDispatcherTest, WallpaperWindowWhenSlippery) { } /** - * On the display, have a single window, and also an area where there's no window. - * First pointer touches the "no window" area of the screen. Second pointer touches the window. - * Make sure that the window receives the second pointer, and first pointer is simply ignored. + * The policy typically sets POLICY_FLAG_PASS_TO_USER to the events. But when the display is not + * interactive, it might stop sending this flag. + * In this test, we check that if the policy stops sending this flag mid-gesture, we still ensure + * to have a consistent input stream. + * + * Test procedure: + * DOWN -> POINTER_DOWN -> (stop sending POLICY_FLAG_PASS_TO_USER) -> CANCEL. + * DOWN (new gesture). + * + * In the bad implementation, we could potentially drop the CANCEL event, and get an inconsistent + * state in the dispatcher. This would cause the final DOWN event to not be delivered to the app. + * + * We technically just need a single window here, but we are using two windows (spy on top and a + * regular window below) to emulate the actual situation where it happens on the device. */ -TEST_F(InputDispatcherTest, SplitWorksWhenEmptyAreaIsTouched) { +TEST_F(InputDispatcherTest, TwoPointerCancelInconsistentPolicy) { std::shared_ptr application = std::make_shared(); - sp window = - new FakeWindowHandle(application, mDispatcher, "Window", DISPLAY_ID); - - mDispatcher->setInputWindows({{DISPLAY_ID, {window}}}); - NotifyMotionArgs args; - - // Touch down on the empty space - mDispatcher->notifyMotion(&(args = generateTouchArgs(AMOTION_EVENT_ACTION_DOWN, {{-1, -1}}))); + sp spyWindow = + sp::make(application, mDispatcher, "Spy", ADISPLAY_ID_DEFAULT); + spyWindow->setFrame(Rect(0, 0, 200, 200)); + spyWindow->setTrustedOverlay(true); + spyWindow->setSpy(true); - mDispatcher->waitForIdle(); + sp window = + sp::make(application, mDispatcher, "Window", ADISPLAY_ID_DEFAULT); + window->setFrame(Rect(0, 0, 200, 200)); + + mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {spyWindow, window}}}); + const int32_t touchDeviceId = 4; + + // Two pointers down + mDispatcher->notifyMotion( + MotionArgsBuilder(AMOTION_EVENT_ACTION_DOWN, AINPUT_SOURCE_TOUCHSCREEN) + .deviceId(touchDeviceId) + .policyFlags(DEFAULT_POLICY_FLAGS) + .pointer(PointerBuilder(0, ToolType::FINGER).x(100).y(100)) + .build()); + + mDispatcher->notifyMotion(MotionArgsBuilder(POINTER_1_DOWN, AINPUT_SOURCE_TOUCHSCREEN) + .deviceId(touchDeviceId) + .policyFlags(DEFAULT_POLICY_FLAGS) + .pointer(PointerBuilder(0, ToolType::FINGER).x(100).y(100)) + .pointer(PointerBuilder(1, ToolType::FINGER).x(120).y(120)) + .build()); + spyWindow->consumeMotionEvent(WithMotionAction(AMOTION_EVENT_ACTION_DOWN)); + spyWindow->consumeMotionEvent(WithMotionAction(POINTER_1_DOWN)); + window->consumeMotionEvent(WithMotionAction(AMOTION_EVENT_ACTION_DOWN)); + window->consumeMotionEvent(WithMotionAction(POINTER_1_DOWN)); + + // Cancel the current gesture. Send the cancel without the default policy flags. + mDispatcher->notifyMotion( + MotionArgsBuilder(AMOTION_EVENT_ACTION_CANCEL, AINPUT_SOURCE_TOUCHSCREEN) + .deviceId(touchDeviceId) + .policyFlags(0) + .pointer(PointerBuilder(0, ToolType::FINGER).x(100).y(100)) + .pointer(PointerBuilder(1, ToolType::FINGER).x(120).y(120)) + .build()); + spyWindow->consumeMotionEvent(WithMotionAction(AMOTION_EVENT_ACTION_CANCEL)); + window->consumeMotionEvent(WithMotionAction(AMOTION_EVENT_ACTION_CANCEL)); + + // We don't need to reset the device to reproduce the issue, but the reset event typically + // follows, so we keep it here to model the actual listener behaviour more closely. + mDispatcher->notifyDeviceReset({/*id=*/1, systemTime(SYSTEM_TIME_MONOTONIC), touchDeviceId}); + + // Start new gesture + mDispatcher->notifyMotion( + MotionArgsBuilder(AMOTION_EVENT_ACTION_DOWN, AINPUT_SOURCE_TOUCHSCREEN) + .deviceId(touchDeviceId) + .policyFlags(DEFAULT_POLICY_FLAGS) + .pointer(PointerBuilder(0, ToolType::FINGER).x(100).y(100)) + .build()); + spyWindow->consumeMotionEvent(WithMotionAction(AMOTION_EVENT_ACTION_DOWN)); + window->consumeMotionEvent(WithMotionAction(AMOTION_EVENT_ACTION_DOWN)); + + // No more events + spyWindow->assertNoEvents(); window->assertNoEvents(); - - // Now touch down on the window with another pointer - mDispatcher->notifyMotion(&(args = generateTouchArgs(POINTER_1_DOWN, {{-1, -1}, {10, 10}}))); - mDispatcher->waitForIdle(); - window->consumeMotionDown(); } /** - * Same test as above, but instead of touching the empty space, the first touch goes to - * non-touchable window. + * Two windows: a window on the left and a window on the right. + * Mouse is hovered from the right window into the left window. + * Next, we tap on the left window, where the cursor was last seen. + * The second tap is done onto the right window. + * The mouse and tap are from two different devices. + * We technically don't need to set the downtime / eventtime for these events, but setting these + * explicitly helps during debugging. + * This test reproduces a crash where there is a mismatch between the downTime and eventTime. + * In the buggy implementation, a tap on the right window would cause a crash. */ -TEST_F(InputDispatcherTest, SplitWorksWhenNonTouchableWindowIsTouched) { - std::shared_ptr application = std::make_shared(); - sp window1 = - new FakeWindowHandle(application, mDispatcher, "Window1", DISPLAY_ID); - window1->setTouchableRegion(Region{{0, 0, 100, 100}}); - window1->setTouchable(false); - sp window2 = - new FakeWindowHandle(application, mDispatcher, "Window2", DISPLAY_ID); - window2->setTouchableRegion(Region{{100, 0, 200, 100}}); - - mDispatcher->setInputWindows({{DISPLAY_ID, {window1, window2}}}); - - NotifyMotionArgs args; - // Touch down on the non-touchable window - mDispatcher->notifyMotion(&(args = generateTouchArgs(AMOTION_EVENT_ACTION_DOWN, {{50, 50}}))); - - mDispatcher->waitForIdle(); - window1->assertNoEvents(); - window2->assertNoEvents(); - - // Now touch down on the window with another pointer - mDispatcher->notifyMotion(&(args = generateTouchArgs(POINTER_1_DOWN, {{50, 50}, {150, 50}}))); - mDispatcher->waitForIdle(); - window2->consumeMotionDown(); -} - -TEST_F(InputDispatcherTest, HoverMoveEnterMouseClickAndHoverMoveExit) { +TEST_F(InputDispatcherTest, HoverFromLeftToRightAndTap) { std::shared_ptr application = std::make_shared(); - sp windowLeft = - new FakeWindowHandle(application, mDispatcher, "Left", ADISPLAY_ID_DEFAULT); - windowLeft->setFrame(Rect(0, 0, 600, 800)); - sp windowRight = - new FakeWindowHandle(application, mDispatcher, "Right", ADISPLAY_ID_DEFAULT); - windowRight->setFrame(Rect(600, 0, 1200, 800)); - - mDispatcher->setFocusedApplication(ADISPLAY_ID_DEFAULT, application); + sp leftWindow = + sp::make(application, mDispatcher, "Left", ADISPLAY_ID_DEFAULT); + leftWindow->setFrame(Rect(0, 0, 200, 200)); - mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {windowLeft, windowRight}}}); + sp rightWindow = + sp::make(application, mDispatcher, "Right", ADISPLAY_ID_DEFAULT); + rightWindow->setFrame(Rect(200, 0, 400, 200)); - // Start cursor position in right window so that we can move the cursor to left window. + mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {leftWindow, rightWindow}}}); + // All times need to start at the current time, otherwise the dispatcher will drop the events as + // stale. + const nsecs_t baseTime = systemTime(SYSTEM_TIME_MONOTONIC); + const int32_t mouseDeviceId = 6; + const int32_t touchDeviceId = 4; + // Move the cursor from right ASSERT_EQ(InputEventInjectionResult::SUCCEEDED, injectMotionEvent(mDispatcher, MotionEventBuilder(AMOTION_EVENT_ACTION_HOVER_MOVE, AINPUT_SOURCE_MOUSE) - .pointer(PointerBuilder(0, AMOTION_EVENT_TOOL_TYPE_MOUSE) - .x(900) - .y(400)) + .deviceId(mouseDeviceId) + .downTime(baseTime + 10) + .eventTime(baseTime + 20) + .pointer(PointerBuilder(0, ToolType::MOUSE) + .x(300) + .y(100)) + .build())); + rightWindow->consumeMotionEvent(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_ENTER)); + + // .. to the left window + ASSERT_EQ(InputEventInjectionResult::SUCCEEDED, + injectMotionEvent(mDispatcher, + MotionEventBuilder(AMOTION_EVENT_ACTION_HOVER_MOVE, + AINPUT_SOURCE_MOUSE) + .deviceId(mouseDeviceId) + .downTime(baseTime + 10) + .eventTime(baseTime + 30) + .pointer(PointerBuilder(0, ToolType::MOUSE) + .x(110) + .y(100)) + .build())); + rightWindow->consumeMotionEvent(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_EXIT)); + leftWindow->consumeMotionEvent(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_ENTER)); + // Now tap the left window + ASSERT_EQ(InputEventInjectionResult::SUCCEEDED, + injectMotionEvent(mDispatcher, + MotionEventBuilder(AMOTION_EVENT_ACTION_DOWN, + AINPUT_SOURCE_TOUCHSCREEN) + .deviceId(touchDeviceId) + .downTime(baseTime + 40) + .eventTime(baseTime + 40) + .pointer(PointerBuilder(0, ToolType::FINGER) + .x(100) + .y(100)) + .build())); + leftWindow->consumeMotionEvent(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_EXIT)); + leftWindow->consumeMotionEvent(WithMotionAction(AMOTION_EVENT_ACTION_DOWN)); + + // release tap + ASSERT_EQ(InputEventInjectionResult::SUCCEEDED, + injectMotionEvent(mDispatcher, + MotionEventBuilder(AMOTION_EVENT_ACTION_UP, + AINPUT_SOURCE_TOUCHSCREEN) + .deviceId(touchDeviceId) + .downTime(baseTime + 40) + .eventTime(baseTime + 50) + .pointer(PointerBuilder(0, ToolType::FINGER) + .x(100) + .y(100)) + .build())); + leftWindow->consumeMotionEvent(WithMotionAction(AMOTION_EVENT_ACTION_UP)); + + // Tap the window on the right + ASSERT_EQ(InputEventInjectionResult::SUCCEEDED, + injectMotionEvent(mDispatcher, + MotionEventBuilder(AMOTION_EVENT_ACTION_DOWN, + AINPUT_SOURCE_TOUCHSCREEN) + .deviceId(touchDeviceId) + .downTime(baseTime + 60) + .eventTime(baseTime + 60) + .pointer(PointerBuilder(0, ToolType::FINGER) + .x(300) + .y(100)) + .build())); + rightWindow->consumeMotionEvent(WithMotionAction(AMOTION_EVENT_ACTION_DOWN)); + + // release tap + ASSERT_EQ(InputEventInjectionResult::SUCCEEDED, + injectMotionEvent(mDispatcher, + MotionEventBuilder(AMOTION_EVENT_ACTION_UP, + AINPUT_SOURCE_TOUCHSCREEN) + .deviceId(touchDeviceId) + .downTime(baseTime + 60) + .eventTime(baseTime + 70) + .pointer(PointerBuilder(0, ToolType::FINGER) + .x(300) + .y(100)) + .build())); + rightWindow->consumeMotionEvent(WithMotionAction(AMOTION_EVENT_ACTION_UP)); + + // No more events + leftWindow->assertNoEvents(); + rightWindow->assertNoEvents(); +} + +/** + * Start hovering in a window. While this hover is still active, make another window appear on top. + * The top, obstructing window has no input channel, so it's not supposed to receive input. + * While the top window is present, the hovering is stopped. + * Later, hovering gets resumed again. + * Ensure that new hover gesture is handled correctly. + * This test reproduces a crash where the HOVER_EXIT event wasn't getting dispatched correctly + * to the window that's currently being hovered over. + */ +TEST_F(InputDispatcherTest, HoverWhileWindowAppears) { + std::shared_ptr application = std::make_shared(); + sp window = + sp::make(application, mDispatcher, "Window", ADISPLAY_ID_DEFAULT); + window->setFrame(Rect(0, 0, 200, 200)); + + // Only a single window is present at first + mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {window}}}); + + // Start hovering in the window + mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_HOVER_ENTER, AINPUT_SOURCE_STYLUS) + .pointer(PointerBuilder(0, ToolType::STYLUS).x(100).y(100)) + .build()); + window->consumeMotionEvent(WithMotionAction(ACTION_HOVER_ENTER)); + + // Now, an obscuring window appears! + sp obscuringWindow = + sp::make(application, mDispatcher, "Obscuring window", + ADISPLAY_ID_DEFAULT, + /*token=*/std::make_optional>(nullptr)); + obscuringWindow->setFrame(Rect(0, 0, 200, 200)); + obscuringWindow->setTouchOcclusionMode(TouchOcclusionMode::BLOCK_UNTRUSTED); + obscuringWindow->setOwnerInfo(SECONDARY_WINDOW_PID, SECONDARY_WINDOW_UID); + obscuringWindow->setNoInputChannel(true); + obscuringWindow->setFocusable(false); + obscuringWindow->setAlpha(1.0); + mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {obscuringWindow, window}}}); + + // While this new obscuring window is present, the hovering is stopped + mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_HOVER_EXIT, AINPUT_SOURCE_STYLUS) + .pointer(PointerBuilder(0, ToolType::STYLUS).x(100).y(100)) + .build()); + window->consumeMotionEvent(WithMotionAction(ACTION_HOVER_EXIT)); + + // Now the obscuring window goes away. + mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {window}}}); + + // And a new hover gesture starts. + mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_HOVER_ENTER, AINPUT_SOURCE_STYLUS) + .pointer(PointerBuilder(0, ToolType::STYLUS).x(100).y(100)) + .build()); + window->consumeMotionEvent(WithMotionAction(ACTION_HOVER_ENTER)); +} + +/** + * Same test as 'HoverWhileWindowAppears' above, but here, we also send some HOVER_MOVE events to + * the obscuring window. + */ +TEST_F(InputDispatcherTest, HoverMoveWhileWindowAppears) { + std::shared_ptr application = std::make_shared(); + sp window = + sp::make(application, mDispatcher, "Window", ADISPLAY_ID_DEFAULT); + window->setFrame(Rect(0, 0, 200, 200)); + + // Only a single window is present at first + mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {window}}}); + + // Start hovering in the window + mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_HOVER_ENTER, AINPUT_SOURCE_STYLUS) + .pointer(PointerBuilder(0, ToolType::STYLUS).x(100).y(100)) + .build()); + window->consumeMotionEvent(WithMotionAction(ACTION_HOVER_ENTER)); + + // Now, an obscuring window appears! + sp obscuringWindow = + sp::make(application, mDispatcher, "Obscuring window", + ADISPLAY_ID_DEFAULT, + /*token=*/std::make_optional>(nullptr)); + obscuringWindow->setFrame(Rect(0, 0, 200, 200)); + obscuringWindow->setTouchOcclusionMode(TouchOcclusionMode::BLOCK_UNTRUSTED); + obscuringWindow->setOwnerInfo(SECONDARY_WINDOW_PID, SECONDARY_WINDOW_UID); + obscuringWindow->setNoInputChannel(true); + obscuringWindow->setFocusable(false); + obscuringWindow->setAlpha(1.0); + mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {obscuringWindow, window}}}); + + // While this new obscuring window is present, the hovering continues. The event can't go to the + // bottom window due to obstructed touches, so it should generate HOVER_EXIT for that window. + mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_HOVER_MOVE, AINPUT_SOURCE_STYLUS) + .pointer(PointerBuilder(0, ToolType::STYLUS).x(100).y(100)) + .build()); + obscuringWindow->assertNoEvents(); + window->consumeMotionEvent(WithMotionAction(ACTION_HOVER_EXIT)); + + // Now the obscuring window goes away. + mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {window}}}); + + // Hovering continues in the same position. The hovering pointer re-enters the bottom window, + // so it should generate a HOVER_ENTER + mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_HOVER_MOVE, AINPUT_SOURCE_STYLUS) + .pointer(PointerBuilder(0, ToolType::STYLUS).x(100).y(100)) + .build()); + window->consumeMotionEvent(WithMotionAction(ACTION_HOVER_ENTER)); + + // Now the MOVE should be getting dispatched normally + mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_HOVER_MOVE, AINPUT_SOURCE_STYLUS) + .pointer(PointerBuilder(0, ToolType::STYLUS).x(110).y(110)) + .build()); + window->consumeMotionEvent(WithMotionAction(ACTION_HOVER_MOVE)); +} + +/** + * Two windows: a window on the left and a window on the right. + * Mouse is clicked on the left window and remains down. Touch is touched on the right and remains + * down. Then, on the left window, also place second touch pointer down. + * This test tries to reproduce a crash. + * In the buggy implementation, second pointer down on the left window would cause a crash. + */ +TEST_F(InputDispatcherTest, MultiDeviceSplitTouch) { + std::shared_ptr application = std::make_shared(); + sp leftWindow = + sp::make(application, mDispatcher, "Left", ADISPLAY_ID_DEFAULT); + leftWindow->setFrame(Rect(0, 0, 200, 200)); + + sp rightWindow = + sp::make(application, mDispatcher, "Right", ADISPLAY_ID_DEFAULT); + rightWindow->setFrame(Rect(200, 0, 400, 200)); + + mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {leftWindow, rightWindow}}}); + + const int32_t touchDeviceId = 4; + const int32_t mouseDeviceId = 6; + NotifyMotionArgs args; + + // Start hovering over the left window + mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_HOVER_ENTER, AINPUT_SOURCE_MOUSE) + .deviceId(mouseDeviceId) + .pointer(PointerBuilder(0, ToolType::MOUSE).x(100).y(100)) + .build()); + leftWindow->consumeMotionEvent( + AllOf(WithMotionAction(ACTION_HOVER_ENTER), WithDeviceId(mouseDeviceId))); + + // Mouse down on left window + mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_DOWN, AINPUT_SOURCE_MOUSE) + .deviceId(mouseDeviceId) + .buttonState(AMOTION_EVENT_BUTTON_PRIMARY) + .pointer(PointerBuilder(0, ToolType::MOUSE).x(100).y(100)) + .build()); + + leftWindow->consumeMotionEvent( + AllOf(WithMotionAction(ACTION_HOVER_EXIT), WithDeviceId(mouseDeviceId))); + leftWindow->consumeMotionEvent( + AllOf(WithMotionAction(ACTION_DOWN), WithDeviceId(mouseDeviceId))); + + mDispatcher->notifyMotion( + MotionArgsBuilder(AMOTION_EVENT_ACTION_BUTTON_PRESS, AINPUT_SOURCE_MOUSE) + .deviceId(mouseDeviceId) + .buttonState(AMOTION_EVENT_BUTTON_PRIMARY) + .actionButton(AMOTION_EVENT_BUTTON_PRIMARY) + .pointer(PointerBuilder(0, ToolType::MOUSE).x(100).y(100)) + .build()); + leftWindow->consumeMotionEvent(WithMotionAction(AMOTION_EVENT_ACTION_BUTTON_PRESS)); + + // First touch pointer down on right window + mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_DOWN, AINPUT_SOURCE_TOUCHSCREEN) + .deviceId(touchDeviceId) + .pointer(PointerBuilder(0, ToolType::FINGER).x(300).y(100)) + .build()); + leftWindow->consumeMotionEvent(WithMotionAction(ACTION_CANCEL)); + + rightWindow->consumeMotionEvent(WithMotionAction(ACTION_DOWN)); + + // Second touch pointer down on left window + mDispatcher->notifyMotion(MotionArgsBuilder(POINTER_1_DOWN, AINPUT_SOURCE_TOUCHSCREEN) + .deviceId(touchDeviceId) + .pointer(PointerBuilder(0, ToolType::FINGER).x(300).y(100)) + .pointer(PointerBuilder(1, ToolType::FINGER).x(100).y(100)) + .build()); + leftWindow->consumeMotionEvent( + AllOf(WithMotionAction(ACTION_DOWN), WithDeviceId(touchDeviceId))); + // This MOVE event is not necessary (doesn't carry any new information), but it's there in the + // current implementation. + const std::map expectedPointers{{0, PointF{100, 100}}}; + rightWindow->consumeMotionEvent( + AllOf(WithMotionAction(ACTION_MOVE), WithPointers(expectedPointers))); + + leftWindow->assertNoEvents(); + rightWindow->assertNoEvents(); +} + +/** + * On a single window, use two different devices: mouse and touch. + * Touch happens first, with two pointers going down, and then the first pointer leaving. + * Mouse is clicked next, which causes the touch stream to be aborted with ACTION_CANCEL. + * Finally, a second touch pointer goes down again. Ensure the second touch pointer is ignored, + * because the mouse is currently down, and a POINTER_DOWN event from the touchscreen does not + * represent a new gesture. + */ +TEST_F(InputDispatcherTest, MixedTouchAndMouseWithPointerDown) { + std::shared_ptr application = std::make_shared(); + sp window = + sp::make(application, mDispatcher, "Window", ADISPLAY_ID_DEFAULT); + window->setFrame(Rect(0, 0, 400, 400)); + + mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {window}}}); + + const int32_t touchDeviceId = 4; + const int32_t mouseDeviceId = 6; + NotifyMotionArgs args; + + // First touch pointer down + mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_DOWN, AINPUT_SOURCE_TOUCHSCREEN) + .deviceId(touchDeviceId) + .pointer(PointerBuilder(0, ToolType::FINGER).x(300).y(100)) + .build()); + // Second touch pointer down + mDispatcher->notifyMotion(MotionArgsBuilder(POINTER_1_DOWN, AINPUT_SOURCE_TOUCHSCREEN) + .deviceId(touchDeviceId) + .pointer(PointerBuilder(0, ToolType::FINGER).x(300).y(100)) + .pointer(PointerBuilder(1, ToolType::FINGER).x(350).y(100)) + .build()); + // First touch pointer lifts. The second one remains down + mDispatcher->notifyMotion(MotionArgsBuilder(POINTER_0_UP, AINPUT_SOURCE_TOUCHSCREEN) + .deviceId(touchDeviceId) + .pointer(PointerBuilder(0, ToolType::FINGER).x(300).y(100)) + .pointer(PointerBuilder(1, ToolType::FINGER).x(350).y(100)) + .build()); + window->consumeMotionEvent(WithMotionAction(ACTION_DOWN)); + window->consumeMotionEvent(WithMotionAction(POINTER_1_DOWN)); + window->consumeMotionEvent(WithMotionAction(POINTER_0_UP)); + + // Mouse down. The touch should be canceled + mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_DOWN, AINPUT_SOURCE_MOUSE) + .deviceId(mouseDeviceId) + .buttonState(AMOTION_EVENT_BUTTON_PRIMARY) + .pointer(PointerBuilder(0, ToolType::MOUSE).x(320).y(100)) + .build()); + + window->consumeMotionEvent(AllOf(WithMotionAction(ACTION_CANCEL), WithDeviceId(touchDeviceId), + WithPointerCount(1u))); + window->consumeMotionEvent(AllOf(WithMotionAction(ACTION_DOWN), WithDeviceId(mouseDeviceId))); + + mDispatcher->notifyMotion( + MotionArgsBuilder(AMOTION_EVENT_ACTION_BUTTON_PRESS, AINPUT_SOURCE_MOUSE) + .deviceId(mouseDeviceId) + .buttonState(AMOTION_EVENT_BUTTON_PRIMARY) + .actionButton(AMOTION_EVENT_BUTTON_PRIMARY) + .pointer(PointerBuilder(0, ToolType::MOUSE).x(320).y(100)) + .build()); + window->consumeMotionEvent(WithMotionAction(AMOTION_EVENT_ACTION_BUTTON_PRESS)); + + // Second touch pointer down. + mDispatcher->notifyMotion(MotionArgsBuilder(POINTER_0_DOWN, AINPUT_SOURCE_TOUCHSCREEN) + .deviceId(touchDeviceId) + .pointer(PointerBuilder(0, ToolType::FINGER).x(300).y(100)) + .pointer(PointerBuilder(1, ToolType::FINGER).x(350).y(100)) + .build()); + // The pointer_down event should be ignored + window->assertNoEvents(); +} + +/** + * Inject a touch down and then send a new event via 'notifyMotion'. Ensure the new event cancels + * the injected event. + */ +TEST_F(InputDispatcherTest, UnfinishedInjectedEvent) { + std::shared_ptr application = std::make_shared(); + sp window = + sp::make(application, mDispatcher, "Window", ADISPLAY_ID_DEFAULT); + window->setFrame(Rect(0, 0, 400, 400)); + + mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {window}}}); + + const int32_t touchDeviceId = 4; + NotifyMotionArgs args; + // Pretend a test injects an ACTION_DOWN mouse event, but forgets to lift up the touch after + // completion. + ASSERT_EQ(InputEventInjectionResult::SUCCEEDED, + injectMotionEvent(mDispatcher, + MotionEventBuilder(ACTION_DOWN, AINPUT_SOURCE_MOUSE) + .deviceId(ReservedInputDeviceId::VIRTUAL_KEYBOARD_ID) + .pointer(PointerBuilder(0, ToolType::MOUSE) + .x(50) + .y(50)) + .build())); + window->consumeMotionEvent( + AllOf(WithMotionAction(ACTION_DOWN), WithDeviceId(VIRTUAL_KEYBOARD_ID))); + + // Now a real touch comes. Rather than crashing or dropping the real event, the injected pointer + // should be canceled and the new gesture should take over. + mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_DOWN, AINPUT_SOURCE_TOUCHSCREEN) + .deviceId(touchDeviceId) + .pointer(PointerBuilder(0, ToolType::FINGER).x(300).y(100)) + .build()); + + window->consumeMotionEvent( + AllOf(WithMotionAction(ACTION_CANCEL), WithDeviceId(VIRTUAL_KEYBOARD_ID))); + window->consumeMotionEvent(AllOf(WithMotionAction(ACTION_DOWN), WithDeviceId(touchDeviceId))); +} + +/** + * This test is similar to the test above, but the sequence of injected events is different. + * + * Two windows: a window on the left and a window on the right. + * Mouse is hovered over the left window. + * Next, we tap on the left window, where the cursor was last seen. + * + * After that, we inject one finger down onto the right window, and then a second finger down onto + * the left window. + * The touch is split, so this last gesture should cause 2 ACTION_DOWN events, one in the right + * window (first), and then another on the left window (second). + * This test reproduces a crash where there is a mismatch between the downTime and eventTime. + * In the buggy implementation, second finger down on the left window would cause a crash. + */ +TEST_F(InputDispatcherTest, HoverTapAndSplitTouch) { + std::shared_ptr application = std::make_shared(); + sp leftWindow = + sp::make(application, mDispatcher, "Left", ADISPLAY_ID_DEFAULT); + leftWindow->setFrame(Rect(0, 0, 200, 200)); + + sp rightWindow = + sp::make(application, mDispatcher, "Right", ADISPLAY_ID_DEFAULT); + rightWindow->setFrame(Rect(200, 0, 400, 200)); + + mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {leftWindow, rightWindow}}}); + + const int32_t mouseDeviceId = 6; + const int32_t touchDeviceId = 4; + // Hover over the left window. Keep the cursor there. + ASSERT_EQ(InputEventInjectionResult::SUCCEEDED, + injectMotionEvent(mDispatcher, + MotionEventBuilder(AMOTION_EVENT_ACTION_HOVER_ENTER, + AINPUT_SOURCE_MOUSE) + .deviceId(mouseDeviceId) + .pointer(PointerBuilder(0, ToolType::MOUSE) + .x(50) + .y(50)) + .build())); + leftWindow->consumeMotionEvent(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_ENTER)); + + // Tap on left window + ASSERT_EQ(InputEventInjectionResult::SUCCEEDED, + injectMotionEvent(mDispatcher, + MotionEventBuilder(AMOTION_EVENT_ACTION_DOWN, + AINPUT_SOURCE_TOUCHSCREEN) + .deviceId(touchDeviceId) + .pointer(PointerBuilder(0, ToolType::FINGER) + .x(100) + .y(100)) + .build())); + + ASSERT_EQ(InputEventInjectionResult::SUCCEEDED, + injectMotionEvent(mDispatcher, + MotionEventBuilder(AMOTION_EVENT_ACTION_UP, + AINPUT_SOURCE_TOUCHSCREEN) + .deviceId(touchDeviceId) + .pointer(PointerBuilder(0, ToolType::FINGER) + .x(100) + .y(100)) + .build())); + leftWindow->consumeMotionEvent(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_EXIT)); + leftWindow->consumeMotionEvent(WithMotionAction(AMOTION_EVENT_ACTION_DOWN)); + leftWindow->consumeMotionEvent(WithMotionAction(AMOTION_EVENT_ACTION_UP)); + + // First finger down on right window + ASSERT_EQ(InputEventInjectionResult::SUCCEEDED, + injectMotionEvent(mDispatcher, + MotionEventBuilder(AMOTION_EVENT_ACTION_DOWN, + AINPUT_SOURCE_TOUCHSCREEN) + .deviceId(touchDeviceId) + .pointer(PointerBuilder(0, ToolType::FINGER) + .x(300) + .y(100)) + .build())); + rightWindow->consumeMotionEvent(WithMotionAction(AMOTION_EVENT_ACTION_DOWN)); + + // Second finger down on the left window + ASSERT_EQ(InputEventInjectionResult::SUCCEEDED, + injectMotionEvent(mDispatcher, + MotionEventBuilder(POINTER_1_DOWN, AINPUT_SOURCE_TOUCHSCREEN) + .deviceId(touchDeviceId) + .pointer(PointerBuilder(0, ToolType::FINGER) + .x(300) + .y(100)) + .pointer(PointerBuilder(1, ToolType::FINGER) + .x(100) + .y(100)) + .build())); + leftWindow->consumeMotionEvent(WithMotionAction(AMOTION_EVENT_ACTION_DOWN)); + rightWindow->consumeMotionEvent(WithMotionAction(AMOTION_EVENT_ACTION_MOVE)); + + // No more events + leftWindow->assertNoEvents(); + rightWindow->assertNoEvents(); +} + +/** + * Start hovering with a stylus device, and then tap with a touch device. Ensure no crash occurs. + * While the touch is down, new hover events from the stylus device should be ignored. After the + * touch is gone, stylus hovering should start working again. + */ +TEST_F(InputDispatcherTest, StylusHoverAndTouchTap) { + std::shared_ptr application = std::make_shared(); + sp window = + sp::make(application, mDispatcher, "Window", ADISPLAY_ID_DEFAULT); + window->setFrame(Rect(0, 0, 200, 200)); + + mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {window}}}); + + const int32_t stylusDeviceId = 5; + const int32_t touchDeviceId = 4; + // Start hovering with stylus + ASSERT_EQ(InputEventInjectionResult::SUCCEEDED, + injectMotionEvent(mDispatcher, + MotionEventBuilder(AMOTION_EVENT_ACTION_HOVER_ENTER, + AINPUT_SOURCE_STYLUS) + .deviceId(stylusDeviceId) + .pointer(PointerBuilder(0, ToolType::STYLUS) + .x(50) + .y(50)) + .build())); + window->consumeMotionEvent(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_ENTER)); + + // Finger down on the window + ASSERT_EQ(InputEventInjectionResult::SUCCEEDED, + injectMotionEvent(mDispatcher, + MotionEventBuilder(AMOTION_EVENT_ACTION_DOWN, + AINPUT_SOURCE_TOUCHSCREEN) + .deviceId(touchDeviceId) + .pointer(PointerBuilder(0, ToolType::FINGER) + .x(100) + .y(100)) + .build())); + window->consumeMotionEvent(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_EXIT)); + window->consumeMotionEvent(WithMotionAction(AMOTION_EVENT_ACTION_DOWN)); + + // Try to continue hovering with stylus. Since we are already down, injection should fail + ASSERT_EQ(InputEventInjectionResult::FAILED, + injectMotionEvent(mDispatcher, + MotionEventBuilder(AMOTION_EVENT_ACTION_HOVER_MOVE, + AINPUT_SOURCE_STYLUS) + .deviceId(stylusDeviceId) + .pointer(PointerBuilder(0, ToolType::STYLUS) + .x(50) + .y(50)) + .build())); + // No event should be sent. This event should be ignored because a pointer from another device + // is already down. + + // Lift up the finger + ASSERT_EQ(InputEventInjectionResult::SUCCEEDED, + injectMotionEvent(mDispatcher, + MotionEventBuilder(AMOTION_EVENT_ACTION_UP, + AINPUT_SOURCE_TOUCHSCREEN) + .deviceId(touchDeviceId) + .pointer(PointerBuilder(0, ToolType::FINGER) + .x(100) + .y(100)) + .build())); + window->consumeMotionEvent(WithMotionAction(AMOTION_EVENT_ACTION_UP)); + + // Now that the touch is gone, stylus hovering should start working again + ASSERT_EQ(InputEventInjectionResult::SUCCEEDED, + injectMotionEvent(mDispatcher, + MotionEventBuilder(AMOTION_EVENT_ACTION_HOVER_MOVE, + AINPUT_SOURCE_STYLUS) + .deviceId(stylusDeviceId) + .pointer(PointerBuilder(0, ToolType::STYLUS) + .x(50) + .y(50)) + .build())); + window->consumeMotionEvent(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_ENTER)); + // No more events + window->assertNoEvents(); +} + +/** + * A spy window above a window with no input channel. + * Start hovering with a stylus device, and then tap with it. + * Ensure spy window receives the entire sequence. + */ +TEST_F(InputDispatcherTest, StylusHoverAndDownNoInputChannel) { + std::shared_ptr application = std::make_shared(); + sp spyWindow = + sp::make(application, mDispatcher, "Spy", ADISPLAY_ID_DEFAULT); + spyWindow->setFrame(Rect(0, 0, 200, 200)); + spyWindow->setTrustedOverlay(true); + spyWindow->setSpy(true); + sp window = + sp::make(application, mDispatcher, "Window", ADISPLAY_ID_DEFAULT); + window->setNoInputChannel(true); + window->setFrame(Rect(0, 0, 200, 200)); + + mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {spyWindow, window}}}); + + // Start hovering with stylus + mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_HOVER_ENTER, AINPUT_SOURCE_STYLUS) + .pointer(PointerBuilder(0, ToolType::STYLUS).x(50).y(50)) + .build()); + spyWindow->consumeMotionEvent(WithMotionAction(ACTION_HOVER_ENTER)); + // Stop hovering + mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_HOVER_EXIT, AINPUT_SOURCE_STYLUS) + .pointer(PointerBuilder(0, ToolType::STYLUS).x(50).y(50)) + .build()); + spyWindow->consumeMotionEvent(WithMotionAction(ACTION_HOVER_EXIT)); + + // Stylus touches down + mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_DOWN, AINPUT_SOURCE_STYLUS) + .pointer(PointerBuilder(0, ToolType::STYLUS).x(50).y(50)) + .build()); + spyWindow->consumeMotionEvent(WithMotionAction(ACTION_DOWN)); + + // Stylus goes up + mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_UP, AINPUT_SOURCE_STYLUS) + .pointer(PointerBuilder(0, ToolType::STYLUS).x(50).y(50)) + .build()); + spyWindow->consumeMotionEvent(WithMotionAction(ACTION_UP)); + + // Again hover + mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_HOVER_ENTER, AINPUT_SOURCE_STYLUS) + .pointer(PointerBuilder(0, ToolType::STYLUS).x(50).y(50)) + .build()); + spyWindow->consumeMotionEvent(WithMotionAction(ACTION_HOVER_ENTER)); + // Stop hovering + mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_HOVER_EXIT, AINPUT_SOURCE_STYLUS) + .pointer(PointerBuilder(0, ToolType::STYLUS).x(50).y(50)) + .build()); + spyWindow->consumeMotionEvent(WithMotionAction(ACTION_HOVER_EXIT)); + + // No more events + spyWindow->assertNoEvents(); + window->assertNoEvents(); +} + +/** + * Start hovering with a mouse, and then tap with a touch device. Pilfer the touch stream. + * Next, click with the mouse device. Both windows (spy and regular) should receive the new mouse + * ACTION_DOWN event because that's a new gesture, and pilfering should no longer be active. + * While the mouse is down, new move events from the touch device should be ignored. + */ +TEST_F(InputDispatcherTest, TouchPilferAndMouseMove) { + std::shared_ptr application = std::make_shared(); + sp spyWindow = + sp::make(application, mDispatcher, "Spy", ADISPLAY_ID_DEFAULT); + spyWindow->setFrame(Rect(0, 0, 200, 200)); + spyWindow->setTrustedOverlay(true); + spyWindow->setSpy(true); + sp window = + sp::make(application, mDispatcher, "Window", ADISPLAY_ID_DEFAULT); + window->setFrame(Rect(0, 0, 200, 200)); + + mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {spyWindow, window}}}); + + const int32_t mouseDeviceId = 7; + const int32_t touchDeviceId = 4; + + // Hover a bit with mouse first + mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_HOVER_ENTER, AINPUT_SOURCE_MOUSE) + .deviceId(mouseDeviceId) + .pointer(PointerBuilder(0, ToolType::MOUSE).x(100).y(100)) + .build()); + spyWindow->consumeMotionEvent( + AllOf(WithMotionAction(ACTION_HOVER_ENTER), WithDeviceId(mouseDeviceId))); + window->consumeMotionEvent( + AllOf(WithMotionAction(ACTION_HOVER_ENTER), WithDeviceId(mouseDeviceId))); + + // Start touching + mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_DOWN, AINPUT_SOURCE_TOUCHSCREEN) + .deviceId(touchDeviceId) + .pointer(PointerBuilder(0, ToolType::FINGER).x(50).y(50)) + .build()); + spyWindow->consumeMotionEvent(WithMotionAction(ACTION_HOVER_EXIT)); + window->consumeMotionEvent(WithMotionAction(ACTION_HOVER_EXIT)); + spyWindow->consumeMotionEvent(WithMotionAction(ACTION_DOWN)); + window->consumeMotionEvent(WithMotionAction(ACTION_DOWN)); + + mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_MOVE, AINPUT_SOURCE_TOUCHSCREEN) + .deviceId(touchDeviceId) + .pointer(PointerBuilder(0, ToolType::FINGER).x(55).y(55)) + .build()); + spyWindow->consumeMotionEvent(WithMotionAction(ACTION_MOVE)); + window->consumeMotionEvent(WithMotionAction(ACTION_MOVE)); + + // Pilfer the stream + EXPECT_EQ(OK, mDispatcher->pilferPointers(spyWindow->getToken())); + window->consumeMotionEvent(WithMotionAction(ACTION_CANCEL)); + + mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_MOVE, AINPUT_SOURCE_TOUCHSCREEN) + .deviceId(touchDeviceId) + .pointer(PointerBuilder(0, ToolType::FINGER).x(60).y(60)) + .build()); + spyWindow->consumeMotionEvent(WithMotionAction(ACTION_MOVE)); + + // Mouse down + mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_DOWN, AINPUT_SOURCE_MOUSE) + .deviceId(mouseDeviceId) + .buttonState(AMOTION_EVENT_BUTTON_PRIMARY) + .pointer(PointerBuilder(0, ToolType::MOUSE).x(100).y(100)) + .build()); + + spyWindow->consumeMotionEvent( + AllOf(WithMotionAction(ACTION_CANCEL), WithDeviceId(touchDeviceId))); + spyWindow->consumeMotionEvent( + AllOf(WithMotionAction(ACTION_DOWN), WithDeviceId(mouseDeviceId))); + window->consumeMotionEvent(AllOf(WithMotionAction(ACTION_DOWN), WithDeviceId(mouseDeviceId))); + + mDispatcher->notifyMotion( + MotionArgsBuilder(AMOTION_EVENT_ACTION_BUTTON_PRESS, AINPUT_SOURCE_MOUSE) + .deviceId(mouseDeviceId) + .buttonState(AMOTION_EVENT_BUTTON_PRIMARY) + .actionButton(AMOTION_EVENT_BUTTON_PRIMARY) + .pointer(PointerBuilder(0, ToolType::MOUSE).x(100).y(100)) + .build()); + spyWindow->consumeMotionEvent(WithMotionAction(AMOTION_EVENT_ACTION_BUTTON_PRESS)); + window->consumeMotionEvent(WithMotionAction(AMOTION_EVENT_ACTION_BUTTON_PRESS)); + + // Mouse move! + mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_MOVE, AINPUT_SOURCE_MOUSE) + .deviceId(mouseDeviceId) + .buttonState(AMOTION_EVENT_BUTTON_PRIMARY) + .pointer(PointerBuilder(0, ToolType::MOUSE).x(110).y(110)) + .build()); + spyWindow->consumeMotionEvent(WithMotionAction(ACTION_MOVE)); + window->consumeMotionEvent(WithMotionAction(ACTION_MOVE)); + + // Touch move! + mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_MOVE, AINPUT_SOURCE_TOUCHSCREEN) + .deviceId(touchDeviceId) + .pointer(PointerBuilder(0, ToolType::FINGER).x(65).y(65)) + .build()); + + // No more events + spyWindow->assertNoEvents(); + window->assertNoEvents(); +} + +/** + * On the display, have a single window, and also an area where there's no window. + * First pointer touches the "no window" area of the screen. Second pointer touches the window. + * Make sure that the window receives the second pointer, and first pointer is simply ignored. + */ +TEST_F(InputDispatcherTest, SplitWorksWhenEmptyAreaIsTouched) { + std::shared_ptr application = std::make_shared(); + sp window = + sp::make(application, mDispatcher, "Window", DISPLAY_ID); + + mDispatcher->setInputWindows({{DISPLAY_ID, {window}}}); + + // Touch down on the empty space + mDispatcher->notifyMotion(generateTouchArgs(AMOTION_EVENT_ACTION_DOWN, {{-1, -1}})); + + mDispatcher->waitForIdle(); + window->assertNoEvents(); + + // Now touch down on the window with another pointer + mDispatcher->notifyMotion(generateTouchArgs(POINTER_1_DOWN, {{-1, -1}, {10, 10}})); + mDispatcher->waitForIdle(); + window->consumeMotionDown(); +} + +/** + * Same test as above, but instead of touching the empty space, the first touch goes to + * non-touchable window. + */ +TEST_F(InputDispatcherTest, SplitWorksWhenNonTouchableWindowIsTouched) { + std::shared_ptr application = std::make_shared(); + sp window1 = + sp::make(application, mDispatcher, "Window1", DISPLAY_ID); + window1->setTouchableRegion(Region{{0, 0, 100, 100}}); + window1->setTouchable(false); + sp window2 = + sp::make(application, mDispatcher, "Window2", DISPLAY_ID); + window2->setTouchableRegion(Region{{100, 0, 200, 100}}); + + mDispatcher->setInputWindows({{DISPLAY_ID, {window1, window2}}}); + + // Touch down on the non-touchable window + mDispatcher->notifyMotion(generateTouchArgs(AMOTION_EVENT_ACTION_DOWN, {{50, 50}})); + + mDispatcher->waitForIdle(); + window1->assertNoEvents(); + window2->assertNoEvents(); + + // Now touch down on the window with another pointer + mDispatcher->notifyMotion(generateTouchArgs(POINTER_1_DOWN, {{50, 50}, {150, 50}})); + mDispatcher->waitForIdle(); + window2->consumeMotionDown(); +} + +/** + * When splitting touch events the downTime should be adjusted such that the downTime corresponds + * to the event time of the first ACTION_DOWN sent to the particular window. + */ +TEST_F(InputDispatcherTest, SplitTouchesSendCorrectActionDownTime) { + std::shared_ptr application = std::make_shared(); + sp window1 = + sp::make(application, mDispatcher, "Window1", DISPLAY_ID); + window1->setTouchableRegion(Region{{0, 0, 100, 100}}); + sp window2 = + sp::make(application, mDispatcher, "Window2", DISPLAY_ID); + window2->setTouchableRegion(Region{{100, 0, 200, 100}}); + + mDispatcher->setInputWindows({{DISPLAY_ID, {window1, window2}}}); + + // Touch down on the first window + mDispatcher->notifyMotion(generateTouchArgs(AMOTION_EVENT_ACTION_DOWN, {{50, 50}})); + + mDispatcher->waitForIdle(); + InputEvent* inputEvent1 = window1->consume(); + ASSERT_NE(inputEvent1, nullptr); + window2->assertNoEvents(); + MotionEvent& motionEvent1 = static_cast(*inputEvent1); + nsecs_t downTimeForWindow1 = motionEvent1.getDownTime(); + ASSERT_EQ(motionEvent1.getDownTime(), motionEvent1.getEventTime()); + + // Now touch down on the window with another pointer + mDispatcher->notifyMotion(generateTouchArgs(POINTER_1_DOWN, {{50, 50}, {150, 50}})); + mDispatcher->waitForIdle(); + InputEvent* inputEvent2 = window2->consume(); + ASSERT_NE(inputEvent2, nullptr); + MotionEvent& motionEvent2 = static_cast(*inputEvent2); + nsecs_t downTimeForWindow2 = motionEvent2.getDownTime(); + ASSERT_NE(downTimeForWindow1, downTimeForWindow2); + ASSERT_EQ(motionEvent2.getDownTime(), motionEvent2.getEventTime()); + + // Now move the pointer on the second window + mDispatcher->notifyMotion(generateTouchArgs(AMOTION_EVENT_ACTION_MOVE, {{50, 50}, {151, 51}})); + mDispatcher->waitForIdle(); + window2->consumeMotionEvent(WithDownTime(downTimeForWindow2)); + + // Now add new touch down on the second window + mDispatcher->notifyMotion(generateTouchArgs(POINTER_2_DOWN, {{50, 50}, {151, 51}, {150, 50}})); + mDispatcher->waitForIdle(); + window2->consumeMotionEvent(WithDownTime(downTimeForWindow2)); + + // TODO(b/232530217): do not send the unnecessary MOVE event and delete the next line + window1->consumeMotionMove(); + window1->assertNoEvents(); + + // Now move the pointer on the first window + mDispatcher->notifyMotion( + generateTouchArgs(AMOTION_EVENT_ACTION_MOVE, {{51, 51}, {151, 51}, {150, 50}})); + mDispatcher->waitForIdle(); + window1->consumeMotionEvent(WithDownTime(downTimeForWindow1)); + + mDispatcher->notifyMotion( + generateTouchArgs(POINTER_3_DOWN, {{51, 51}, {151, 51}, {150, 50}, {50, 50}})); + mDispatcher->waitForIdle(); + window1->consumeMotionEvent(WithDownTime(downTimeForWindow1)); +} + +TEST_F(InputDispatcherTest, HoverMoveEnterMouseClickAndHoverMoveExit) { + std::shared_ptr application = std::make_shared(); + sp windowLeft = + sp::make(application, mDispatcher, "Left", ADISPLAY_ID_DEFAULT); + windowLeft->setFrame(Rect(0, 0, 600, 800)); + sp windowRight = + sp::make(application, mDispatcher, "Right", ADISPLAY_ID_DEFAULT); + windowRight->setFrame(Rect(600, 0, 1200, 800)); + + mDispatcher->setFocusedApplication(ADISPLAY_ID_DEFAULT, application); + + mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {windowLeft, windowRight}}}); + + // Start cursor position in right window so that we can move the cursor to left window. + ASSERT_EQ(InputEventInjectionResult::SUCCEEDED, + injectMotionEvent(mDispatcher, + MotionEventBuilder(AMOTION_EVENT_ACTION_HOVER_MOVE, + AINPUT_SOURCE_MOUSE) + .pointer(PointerBuilder(0, ToolType::MOUSE).x(900).y(400)) .build())); - windowRight->consumeEvent(AINPUT_EVENT_TYPE_MOTION, AMOTION_EVENT_ACTION_HOVER_ENTER, - ADISPLAY_ID_DEFAULT, 0 /* expectedFlag */); - windowRight->consumeEvent(AINPUT_EVENT_TYPE_MOTION, AMOTION_EVENT_ACTION_HOVER_MOVE, - ADISPLAY_ID_DEFAULT, 0 /* expectedFlag */); + windowRight->consumeMotionEvent(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_ENTER)); // Move cursor into left window ASSERT_EQ(InputEventInjectionResult::SUCCEEDED, injectMotionEvent(mDispatcher, MotionEventBuilder(AMOTION_EVENT_ACTION_HOVER_MOVE, AINPUT_SOURCE_MOUSE) - .pointer(PointerBuilder(0, AMOTION_EVENT_TOOL_TYPE_MOUSE) - .x(300) - .y(400)) + .pointer(PointerBuilder(0, ToolType::MOUSE).x(300).y(400)) .build())); - windowRight->consumeEvent(AINPUT_EVENT_TYPE_MOTION, AMOTION_EVENT_ACTION_HOVER_EXIT, - ADISPLAY_ID_DEFAULT, 0 /* expectedFlag */); - windowLeft->consumeEvent(AINPUT_EVENT_TYPE_MOTION, AMOTION_EVENT_ACTION_HOVER_ENTER, - ADISPLAY_ID_DEFAULT, 0 /* expectedFlag */); - windowLeft->consumeEvent(AINPUT_EVENT_TYPE_MOTION, AMOTION_EVENT_ACTION_HOVER_MOVE, - ADISPLAY_ID_DEFAULT, 0 /* expectedFlag */); + windowRight->consumeMotionEvent(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_EXIT)); + windowLeft->consumeMotionEvent(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_ENTER)); // Inject a series of mouse events for a mouse click ASSERT_EQ(InputEventInjectionResult::SUCCEEDED, injectMotionEvent(mDispatcher, MotionEventBuilder(AMOTION_EVENT_ACTION_DOWN, AINPUT_SOURCE_MOUSE) .buttonState(AMOTION_EVENT_BUTTON_PRIMARY) - .pointer(PointerBuilder(0, AMOTION_EVENT_TOOL_TYPE_MOUSE) - .x(300) - .y(400)) + .pointer(PointerBuilder(0, ToolType::MOUSE).x(300).y(400)) .build())); - windowLeft->consumeMotionDown(ADISPLAY_ID_DEFAULT); + windowLeft->consumeMotionEvent(WithMotionAction(ACTION_HOVER_EXIT)); + windowLeft->consumeMotionEvent(WithMotionAction(ACTION_DOWN)); ASSERT_EQ(InputEventInjectionResult::SUCCEEDED, injectMotionEvent(mDispatcher, @@ -2072,12 +3259,9 @@ TEST_F(InputDispatcherTest, HoverMoveEnterMouseClickAndHoverMoveExit) { AINPUT_SOURCE_MOUSE) .buttonState(AMOTION_EVENT_BUTTON_PRIMARY) .actionButton(AMOTION_EVENT_BUTTON_PRIMARY) - .pointer(PointerBuilder(0, AMOTION_EVENT_TOOL_TYPE_MOUSE) - .x(300) - .y(400)) + .pointer(PointerBuilder(0, ToolType::MOUSE).x(300).y(400)) .build())); - windowLeft->consumeEvent(AINPUT_EVENT_TYPE_MOTION, AMOTION_EVENT_ACTION_BUTTON_PRESS, - ADISPLAY_ID_DEFAULT, 0 /* expectedFlag */); + windowLeft->consumeMotionEvent(WithMotionAction(AMOTION_EVENT_ACTION_BUTTON_PRESS)); ASSERT_EQ(InputEventInjectionResult::SUCCEEDED, injectMotionEvent(mDispatcher, @@ -2085,20 +3269,15 @@ TEST_F(InputDispatcherTest, HoverMoveEnterMouseClickAndHoverMoveExit) { AINPUT_SOURCE_MOUSE) .buttonState(0) .actionButton(AMOTION_EVENT_BUTTON_PRIMARY) - .pointer(PointerBuilder(0, AMOTION_EVENT_TOOL_TYPE_MOUSE) - .x(300) - .y(400)) + .pointer(PointerBuilder(0, ToolType::MOUSE).x(300).y(400)) .build())); - windowLeft->consumeEvent(AINPUT_EVENT_TYPE_MOTION, AMOTION_EVENT_ACTION_BUTTON_RELEASE, - ADISPLAY_ID_DEFAULT, 0 /* expectedFlag */); + windowLeft->consumeMotionEvent(WithMotionAction(AMOTION_EVENT_ACTION_BUTTON_RELEASE)); ASSERT_EQ(InputEventInjectionResult::SUCCEEDED, injectMotionEvent(mDispatcher, MotionEventBuilder(AMOTION_EVENT_ACTION_UP, AINPUT_SOURCE_MOUSE) .buttonState(0) - .pointer(PointerBuilder(0, AMOTION_EVENT_TOOL_TYPE_MOUSE) - .x(300) - .y(400)) + .pointer(PointerBuilder(0, ToolType::MOUSE).x(300).y(400)) .build())); windowLeft->consumeMotionUp(ADISPLAY_ID_DEFAULT); @@ -2107,16 +3286,224 @@ TEST_F(InputDispatcherTest, HoverMoveEnterMouseClickAndHoverMoveExit) { injectMotionEvent(mDispatcher, MotionEventBuilder(AMOTION_EVENT_ACTION_HOVER_MOVE, AINPUT_SOURCE_MOUSE) - .pointer(PointerBuilder(0, AMOTION_EVENT_TOOL_TYPE_MOUSE) - .x(900) - .y(400)) + .pointer(PointerBuilder(0, ToolType::MOUSE).x(900).y(400)) + .build())); + windowRight->consumeMotionEvent(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_ENTER)); + + // No more events + windowLeft->assertNoEvents(); + windowRight->assertNoEvents(); +} + +/** + * Put two fingers down (and don't release them) and click the mouse button. + * The clicking of mouse is a new ACTION_DOWN event. Since it's from a different device, the + * currently active gesture should be canceled, and the new one should proceed. + */ +TEST_F(InputDispatcherTest, TwoPointersDownMouseClick) { + std::shared_ptr application = std::make_shared(); + sp window = + sp::make(application, mDispatcher, "Window", ADISPLAY_ID_DEFAULT); + window->setFrame(Rect(0, 0, 600, 800)); + + mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {window}}}); + + const int32_t touchDeviceId = 4; + const int32_t mouseDeviceId = 6; + + // Two pointers down + mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_DOWN, AINPUT_SOURCE_TOUCHSCREEN) + .deviceId(touchDeviceId) + .pointer(PointerBuilder(0, ToolType::FINGER).x(100).y(100)) + .build()); + + mDispatcher->notifyMotion(MotionArgsBuilder(POINTER_1_DOWN, AINPUT_SOURCE_TOUCHSCREEN) + .deviceId(touchDeviceId) + .pointer(PointerBuilder(0, ToolType::FINGER).x(100).y(100)) + .pointer(PointerBuilder(1, ToolType::FINGER).x(120).y(120)) + .build()); + window->consumeMotionEvent(WithMotionAction(ACTION_DOWN)); + window->consumeMotionEvent(WithMotionAction(POINTER_1_DOWN)); + + // Inject a series of mouse events for a mouse click + mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_DOWN, AINPUT_SOURCE_MOUSE) + .deviceId(mouseDeviceId) + .buttonState(AMOTION_EVENT_BUTTON_PRIMARY) + .pointer(PointerBuilder(0, ToolType::MOUSE).x(300).y(400)) + .build()); + window->consumeMotionEvent(AllOf(WithMotionAction(ACTION_CANCEL), WithDeviceId(touchDeviceId), + WithPointerCount(2u))); + window->consumeMotionEvent(AllOf(WithMotionAction(ACTION_DOWN), WithDeviceId(mouseDeviceId))); + + mDispatcher->notifyMotion( + MotionArgsBuilder(AMOTION_EVENT_ACTION_BUTTON_PRESS, AINPUT_SOURCE_MOUSE) + .deviceId(mouseDeviceId) + .buttonState(AMOTION_EVENT_BUTTON_PRIMARY) + .actionButton(AMOTION_EVENT_BUTTON_PRIMARY) + .pointer(PointerBuilder(0, ToolType::MOUSE).x(300).y(400)) + .build()); + window->consumeMotionEvent(WithMotionAction(AMOTION_EVENT_ACTION_BUTTON_PRESS)); + + // Try to send more touch events while the mouse is down. Since it's a continuation of an + // already canceled gesture, it should be ignored. + mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_MOVE, AINPUT_SOURCE_TOUCHSCREEN) + .deviceId(touchDeviceId) + .pointer(PointerBuilder(0, ToolType::FINGER).x(101).y(101)) + .pointer(PointerBuilder(1, ToolType::FINGER).x(121).y(121)) + .build()); + window->assertNoEvents(); +} + +TEST_F(InputDispatcherTest, HoverWithSpyWindows) { + std::shared_ptr application = std::make_shared(); + + sp spyWindow = + sp::make(application, mDispatcher, "Spy", ADISPLAY_ID_DEFAULT); + spyWindow->setFrame(Rect(0, 0, 600, 800)); + spyWindow->setTrustedOverlay(true); + spyWindow->setSpy(true); + sp window = + sp::make(application, mDispatcher, "Window", ADISPLAY_ID_DEFAULT); + window->setFrame(Rect(0, 0, 600, 800)); + + mDispatcher->setFocusedApplication(ADISPLAY_ID_DEFAULT, application); + mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {spyWindow, window}}}); + + // Send mouse cursor to the window + ASSERT_EQ(InputEventInjectionResult::SUCCEEDED, + injectMotionEvent(mDispatcher, + MotionEventBuilder(AMOTION_EVENT_ACTION_HOVER_ENTER, + AINPUT_SOURCE_MOUSE) + .pointer(PointerBuilder(0, ToolType::MOUSE) + .x(100) + .y(100)) + .build())); + + window->consumeMotionEvent(AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_ENTER), + WithSource(AINPUT_SOURCE_MOUSE))); + spyWindow->consumeMotionEvent(AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_ENTER), + WithSource(AINPUT_SOURCE_MOUSE))); + + window->assertNoEvents(); + spyWindow->assertNoEvents(); +} + +TEST_F(InputDispatcherTest, MouseAndTouchWithSpyWindows) { + std::shared_ptr application = std::make_shared(); + + sp spyWindow = + sp::make(application, mDispatcher, "Spy", ADISPLAY_ID_DEFAULT); + spyWindow->setFrame(Rect(0, 0, 600, 800)); + spyWindow->setTrustedOverlay(true); + spyWindow->setSpy(true); + sp window = + sp::make(application, mDispatcher, "Window", ADISPLAY_ID_DEFAULT); + window->setFrame(Rect(0, 0, 600, 800)); + + mDispatcher->setFocusedApplication(ADISPLAY_ID_DEFAULT, application); + mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {spyWindow, window}}}); + + // Send mouse cursor to the window + ASSERT_EQ(InputEventInjectionResult::SUCCEEDED, + injectMotionEvent(mDispatcher, + MotionEventBuilder(AMOTION_EVENT_ACTION_HOVER_ENTER, + AINPUT_SOURCE_MOUSE) + .pointer(PointerBuilder(0, ToolType::MOUSE) + .x(100) + .y(100)) + .build())); + + // Move mouse cursor + ASSERT_EQ(InputEventInjectionResult::SUCCEEDED, + injectMotionEvent(mDispatcher, + MotionEventBuilder(AMOTION_EVENT_ACTION_HOVER_MOVE, + AINPUT_SOURCE_MOUSE) + .pointer(PointerBuilder(0, ToolType::MOUSE) + .x(110) + .y(110)) + .build())); + + window->consumeMotionEvent(AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_ENTER), + WithSource(AINPUT_SOURCE_MOUSE))); + spyWindow->consumeMotionEvent(AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_ENTER), + WithSource(AINPUT_SOURCE_MOUSE))); + window->consumeMotionEvent(AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_MOVE), + WithSource(AINPUT_SOURCE_MOUSE))); + spyWindow->consumeMotionEvent(AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_MOVE), + WithSource(AINPUT_SOURCE_MOUSE))); + // Touch down on the window + ASSERT_EQ(InputEventInjectionResult::SUCCEEDED, + injectMotionEvent(mDispatcher, + MotionEventBuilder(AMOTION_EVENT_ACTION_DOWN, + AINPUT_SOURCE_TOUCHSCREEN) + .deviceId(SECOND_DEVICE_ID) + .pointer(PointerBuilder(0, ToolType::FINGER) + .x(200) + .y(200)) + .build())); + window->consumeMotionEvent(AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_EXIT), + WithSource(AINPUT_SOURCE_MOUSE))); + spyWindow->consumeMotionEvent(AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_EXIT), + WithSource(AINPUT_SOURCE_MOUSE))); + window->consumeMotionEvent(AllOf(WithMotionAction(AMOTION_EVENT_ACTION_DOWN), + WithSource(AINPUT_SOURCE_TOUCHSCREEN))); + spyWindow->consumeMotionEvent(AllOf(WithMotionAction(AMOTION_EVENT_ACTION_DOWN), + WithSource(AINPUT_SOURCE_TOUCHSCREEN))); + + // pilfer the motion, retaining the gesture on the spy window. + EXPECT_EQ(OK, mDispatcher->pilferPointers(spyWindow->getToken())); + window->consumeMotionEvent(AllOf(WithMotionAction(AMOTION_EVENT_ACTION_CANCEL), + WithSource(AINPUT_SOURCE_TOUCHSCREEN))); + + // Touch UP on the window + ASSERT_EQ(InputEventInjectionResult::SUCCEEDED, + injectMotionEvent(mDispatcher, + MotionEventBuilder(AMOTION_EVENT_ACTION_UP, + AINPUT_SOURCE_TOUCHSCREEN) + .deviceId(SECOND_DEVICE_ID) + .pointer(PointerBuilder(0, ToolType::FINGER) + .x(200) + .y(200)) + .build())); + spyWindow->consumeMotionEvent(AllOf(WithMotionAction(AMOTION_EVENT_ACTION_UP), + WithSource(AINPUT_SOURCE_TOUCHSCREEN))); + + // Previously, a touch was pilfered. However, that gesture was just finished. Now, we are going + // to send a new gesture. It should again go to both windows (spy and the window below), just + // like the first gesture did, before pilfering. The window configuration has not changed. + + // One more tap - DOWN + ASSERT_EQ(InputEventInjectionResult::SUCCEEDED, + injectMotionEvent(mDispatcher, + MotionEventBuilder(AMOTION_EVENT_ACTION_DOWN, + AINPUT_SOURCE_TOUCHSCREEN) + .deviceId(SECOND_DEVICE_ID) + .pointer(PointerBuilder(0, ToolType::FINGER) + .x(250) + .y(250)) .build())); - windowLeft->consumeEvent(AINPUT_EVENT_TYPE_MOTION, AMOTION_EVENT_ACTION_HOVER_EXIT, - ADISPLAY_ID_DEFAULT, 0 /* expectedFlag */); - windowRight->consumeEvent(AINPUT_EVENT_TYPE_MOTION, AMOTION_EVENT_ACTION_HOVER_ENTER, - ADISPLAY_ID_DEFAULT, 0 /* expectedFlag */); - windowRight->consumeEvent(AINPUT_EVENT_TYPE_MOTION, AMOTION_EVENT_ACTION_HOVER_MOVE, - ADISPLAY_ID_DEFAULT, 0 /* expectedFlag */); + window->consumeMotionEvent(AllOf(WithMotionAction(AMOTION_EVENT_ACTION_DOWN), + WithSource(AINPUT_SOURCE_TOUCHSCREEN))); + spyWindow->consumeMotionEvent(AllOf(WithMotionAction(AMOTION_EVENT_ACTION_DOWN), + WithSource(AINPUT_SOURCE_TOUCHSCREEN))); + + // Touch UP on the window + ASSERT_EQ(InputEventInjectionResult::SUCCEEDED, + injectMotionEvent(mDispatcher, + MotionEventBuilder(AMOTION_EVENT_ACTION_UP, + AINPUT_SOURCE_TOUCHSCREEN) + .deviceId(SECOND_DEVICE_ID) + .pointer(PointerBuilder(0, ToolType::FINGER) + .x(250) + .y(250)) + .build())); + window->consumeMotionEvent(AllOf(WithMotionAction(AMOTION_EVENT_ACTION_UP), + WithSource(AINPUT_SOURCE_TOUCHSCREEN))); + spyWindow->consumeMotionEvent(AllOf(WithMotionAction(AMOTION_EVENT_ACTION_UP), + WithSource(AINPUT_SOURCE_TOUCHSCREEN))); + + window->assertNoEvents(); + spyWindow->assertNoEvents(); } // This test is different from the test above that HOVER_ENTER and HOVER_EXIT events are injected @@ -2124,7 +3511,7 @@ TEST_F(InputDispatcherTest, HoverMoveEnterMouseClickAndHoverMoveExit) { TEST_F(InputDispatcherTest, HoverEnterMouseClickAndHoverExit) { std::shared_ptr application = std::make_shared(); sp window = - new FakeWindowHandle(application, mDispatcher, "Window", ADISPLAY_ID_DEFAULT); + sp::make(application, mDispatcher, "Window", ADISPLAY_ID_DEFAULT); window->setFrame(Rect(0, 0, 1200, 800)); mDispatcher->setFocusedApplication(ADISPLAY_ID_DEFAULT, application); @@ -2135,23 +3522,18 @@ TEST_F(InputDispatcherTest, HoverEnterMouseClickAndHoverExit) { injectMotionEvent(mDispatcher, MotionEventBuilder(AMOTION_EVENT_ACTION_HOVER_ENTER, AINPUT_SOURCE_MOUSE) - .pointer(PointerBuilder(0, AMOTION_EVENT_TOOL_TYPE_MOUSE) - .x(300) - .y(400)) + .pointer(PointerBuilder(0, ToolType::MOUSE).x(300).y(400)) .build())); - window->consumeEvent(AINPUT_EVENT_TYPE_MOTION, AMOTION_EVENT_ACTION_HOVER_ENTER, - ADISPLAY_ID_DEFAULT, 0 /* expectedFlag */); - + window->consumeMotionEvent(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_ENTER)); // Inject a series of mouse events for a mouse click ASSERT_EQ(InputEventInjectionResult::SUCCEEDED, injectMotionEvent(mDispatcher, MotionEventBuilder(AMOTION_EVENT_ACTION_DOWN, AINPUT_SOURCE_MOUSE) .buttonState(AMOTION_EVENT_BUTTON_PRIMARY) - .pointer(PointerBuilder(0, AMOTION_EVENT_TOOL_TYPE_MOUSE) - .x(300) - .y(400)) + .pointer(PointerBuilder(0, ToolType::MOUSE).x(300).y(400)) .build())); - window->consumeMotionDown(ADISPLAY_ID_DEFAULT); + window->consumeMotionEvent(WithMotionAction(ACTION_HOVER_EXIT)); + window->consumeMotionEvent(WithMotionAction(ACTION_DOWN)); ASSERT_EQ(InputEventInjectionResult::SUCCEEDED, injectMotionEvent(mDispatcher, @@ -2159,12 +3541,9 @@ TEST_F(InputDispatcherTest, HoverEnterMouseClickAndHoverExit) { AINPUT_SOURCE_MOUSE) .buttonState(AMOTION_EVENT_BUTTON_PRIMARY) .actionButton(AMOTION_EVENT_BUTTON_PRIMARY) - .pointer(PointerBuilder(0, AMOTION_EVENT_TOOL_TYPE_MOUSE) - .x(300) - .y(400)) + .pointer(PointerBuilder(0, ToolType::MOUSE).x(300).y(400)) .build())); - window->consumeEvent(AINPUT_EVENT_TYPE_MOTION, AMOTION_EVENT_ACTION_BUTTON_PRESS, - ADISPLAY_ID_DEFAULT, 0 /* expectedFlag */); + window->consumeMotionEvent(WithMotionAction(AMOTION_EVENT_ACTION_BUTTON_PRESS)); ASSERT_EQ(InputEventInjectionResult::SUCCEEDED, injectMotionEvent(mDispatcher, @@ -2172,33 +3551,155 @@ TEST_F(InputDispatcherTest, HoverEnterMouseClickAndHoverExit) { AINPUT_SOURCE_MOUSE) .buttonState(0) .actionButton(AMOTION_EVENT_BUTTON_PRIMARY) - .pointer(PointerBuilder(0, AMOTION_EVENT_TOOL_TYPE_MOUSE) - .x(300) - .y(400)) + .pointer(PointerBuilder(0, ToolType::MOUSE).x(300).y(400)) .build())); - window->consumeEvent(AINPUT_EVENT_TYPE_MOTION, AMOTION_EVENT_ACTION_BUTTON_RELEASE, - ADISPLAY_ID_DEFAULT, 0 /* expectedFlag */); + window->consumeMotionEvent(WithMotionAction(AMOTION_EVENT_ACTION_BUTTON_RELEASE)); ASSERT_EQ(InputEventInjectionResult::SUCCEEDED, injectMotionEvent(mDispatcher, MotionEventBuilder(AMOTION_EVENT_ACTION_UP, AINPUT_SOURCE_MOUSE) .buttonState(0) - .pointer(PointerBuilder(0, AMOTION_EVENT_TOOL_TYPE_MOUSE) - .x(300) - .y(400)) + .pointer(PointerBuilder(0, ToolType::MOUSE).x(300).y(400)) .build())); window->consumeMotionUp(ADISPLAY_ID_DEFAULT); - ASSERT_EQ(InputEventInjectionResult::SUCCEEDED, + // 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. + ASSERT_EQ(InputEventInjectionResult::FAILED, injectMotionEvent(mDispatcher, MotionEventBuilder(AMOTION_EVENT_ACTION_HOVER_EXIT, AINPUT_SOURCE_MOUSE) - .pointer(PointerBuilder(0, AMOTION_EVENT_TOOL_TYPE_MOUSE) + .pointer(PointerBuilder(0, ToolType::MOUSE).x(300).y(400)) + .build())); + window->assertNoEvents(); +} + +/** + * Hover over a window, and then remove that window. Make sure that HOVER_EXIT for that event + * is generated. + */ +TEST_F(InputDispatcherTest, HoverExitIsSentToRemovedWindow) { + std::shared_ptr application = std::make_shared(); + sp window = + sp::make(application, mDispatcher, "Window", ADISPLAY_ID_DEFAULT); + window->setFrame(Rect(0, 0, 1200, 800)); + + mDispatcher->setFocusedApplication(ADISPLAY_ID_DEFAULT, application); + + mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {window}}}); + + ASSERT_EQ(InputEventInjectionResult::SUCCEEDED, + injectMotionEvent(mDispatcher, + MotionEventBuilder(AMOTION_EVENT_ACTION_HOVER_ENTER, + AINPUT_SOURCE_MOUSE) + .pointer(PointerBuilder(0, ToolType::MOUSE) .x(300) .y(400)) .build())); - window->consumeEvent(AINPUT_EVENT_TYPE_MOTION, AMOTION_EVENT_ACTION_HOVER_EXIT, - ADISPLAY_ID_DEFAULT, 0 /* expectedFlag */); + window->consumeMotionEvent(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_ENTER)); + + // Remove the window, but keep the channel. + mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {}}}); + window->consumeMotionEvent(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_EXIT)); +} + +/** + * Test that invalid HOVER events sent by accessibility do not cause a fatal crash. + */ +TEST_F(InputDispatcherTest, InvalidA11yHoverStreamDoesNotCrash) { + std::shared_ptr application = std::make_shared(); + sp window = + sp::make(application, mDispatcher, "Window", ADISPLAY_ID_DEFAULT); + window->setFrame(Rect(0, 0, 1200, 800)); + mDispatcher->setFocusedApplication(ADISPLAY_ID_DEFAULT, application); + mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {window}}}); + + MotionEventBuilder hoverEnterBuilder = + MotionEventBuilder(AMOTION_EVENT_ACTION_HOVER_ENTER, AINPUT_SOURCE_MOUSE) + .pointer(PointerBuilder(0, ToolType::MOUSE).x(300).y(400)) + .addFlag(AMOTION_EVENT_FLAG_IS_ACCESSIBILITY_EVENT); + ASSERT_EQ(InputEventInjectionResult::SUCCEEDED, + injectMotionEvent(mDispatcher, hoverEnterBuilder.build())); + ASSERT_EQ(InputEventInjectionResult::SUCCEEDED, + injectMotionEvent(mDispatcher, hoverEnterBuilder.build())); + window->consumeMotionEvent(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_ENTER)); + window->consumeMotionEvent(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_ENTER)); +} + +/** + * If mouse is hovering when the touch goes down, the hovering should be stopped via HOVER_EXIT. + */ +TEST_F(InputDispatcherTest, TouchDownAfterMouseHover) { + std::shared_ptr application = std::make_shared(); + sp window = + sp::make(application, mDispatcher, "Window", ADISPLAY_ID_DEFAULT); + window->setFrame(Rect(0, 0, 100, 100)); + + mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {window}}}); + + const int32_t mouseDeviceId = 7; + const int32_t touchDeviceId = 4; + + // Start hovering with the mouse + mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_HOVER_ENTER, AINPUT_SOURCE_MOUSE) + .deviceId(mouseDeviceId) + .pointer(PointerBuilder(0, ToolType::MOUSE).x(10).y(10)) + .build()); + window->consumeMotionEvent( + AllOf(WithMotionAction(ACTION_HOVER_ENTER), WithDeviceId(mouseDeviceId))); + + // Touch goes down + mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_DOWN, AINPUT_SOURCE_TOUCHSCREEN) + .deviceId(touchDeviceId) + .pointer(PointerBuilder(0, ToolType::FINGER).x(50).y(50)) + .build()); + + window->consumeMotionEvent( + AllOf(WithMotionAction(ACTION_HOVER_EXIT), WithDeviceId(mouseDeviceId))); + window->consumeMotionEvent(AllOf(WithMotionAction(ACTION_DOWN), WithDeviceId(touchDeviceId))); +} + +/** + * Inject a mouse hover event followed by a tap from touchscreen. + * The tap causes a HOVER_EXIT event to be generated because the current event + * stream's source has been switched. + */ +TEST_F(InputDispatcherTest, MouseHoverAndTouchTap) { + std::shared_ptr application = std::make_shared(); + sp window = + sp::make(application, mDispatcher, "Window", ADISPLAY_ID_DEFAULT); + window->setFrame(Rect(0, 0, 100, 100)); + + mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {window}}}); + + // Inject a hover_move from mouse. + NotifyMotionArgs motionArgs = + generateMotionArgs(AMOTION_EVENT_ACTION_HOVER_MOVE, AINPUT_SOURCE_MOUSE, + ADISPLAY_ID_DEFAULT, {{50, 50}}); + motionArgs.xCursorPosition = 50; + motionArgs.yCursorPosition = 50; + mDispatcher->notifyMotion(motionArgs); + ASSERT_NO_FATAL_FAILURE( + window->consumeMotionEvent(AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_ENTER), + WithSource(AINPUT_SOURCE_MOUSE)))); + + // Tap on the window + mDispatcher->notifyMotion(generateMotionArgs(AMOTION_EVENT_ACTION_DOWN, + AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT, + {{10, 10}})); + ASSERT_NO_FATAL_FAILURE( + window->consumeMotionEvent(AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_EXIT), + WithSource(AINPUT_SOURCE_MOUSE)))); + + ASSERT_NO_FATAL_FAILURE( + window->consumeMotionEvent(AllOf(WithMotionAction(AMOTION_EVENT_ACTION_DOWN), + WithSource(AINPUT_SOURCE_TOUCHSCREEN)))); + + mDispatcher->notifyMotion(generateMotionArgs(AMOTION_EVENT_ACTION_UP, AINPUT_SOURCE_TOUCHSCREEN, + ADISPLAY_ID_DEFAULT, {{10, 10}})); + ASSERT_NO_FATAL_FAILURE( + window->consumeMotionEvent(AllOf(WithMotionAction(AMOTION_EVENT_ACTION_UP), + WithSource(AINPUT_SOURCE_TOUCHSCREEN)))); } TEST_F(InputDispatcherTest, HoverEnterMoveRemoveWindowsInSecondDisplay) { @@ -2222,18 +3723,16 @@ TEST_F(InputDispatcherTest, HoverEnterMoveRemoveWindowsInSecondDisplay) { MotionEventBuilder(AMOTION_EVENT_ACTION_HOVER_MOVE, AINPUT_SOURCE_MOUSE) .displayId(ADISPLAY_ID_DEFAULT) - .pointer(PointerBuilder(0, AMOTION_EVENT_TOOL_TYPE_MOUSE) + .pointer(PointerBuilder(0, ToolType::MOUSE) .x(300) .y(600)) .build())); - windowDefaultDisplay->consumeEvent(AINPUT_EVENT_TYPE_MOTION, AMOTION_EVENT_ACTION_HOVER_ENTER, - ADISPLAY_ID_DEFAULT, 0 /* expectedFlag */); - windowDefaultDisplay->consumeEvent(AINPUT_EVENT_TYPE_MOTION, AMOTION_EVENT_ACTION_HOVER_MOVE, - ADISPLAY_ID_DEFAULT, 0 /* expectedFlag */); + windowDefaultDisplay->consumeMotionEvent(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_ENTER)); // Remove all windows in secondary display and check that no event happens on window in // primary display. - mDispatcher->setInputWindows({{SECOND_DISPLAY_ID, {}}}); + mDispatcher->setInputWindows( + {{ADISPLAY_ID_DEFAULT, {windowDefaultDisplay}}, {SECOND_DISPLAY_ID, {}}}); windowDefaultDisplay->assertNoEvents(); // Move cursor position in window in default display and check that only hover move @@ -2245,12 +3744,13 @@ TEST_F(InputDispatcherTest, HoverEnterMoveRemoveWindowsInSecondDisplay) { MotionEventBuilder(AMOTION_EVENT_ACTION_HOVER_MOVE, AINPUT_SOURCE_MOUSE) .displayId(ADISPLAY_ID_DEFAULT) - .pointer(PointerBuilder(0, AMOTION_EVENT_TOOL_TYPE_MOUSE) + .pointer(PointerBuilder(0, ToolType::MOUSE) .x(400) .y(700)) .build())); - windowDefaultDisplay->consumeEvent(AINPUT_EVENT_TYPE_MOTION, AMOTION_EVENT_ACTION_HOVER_MOVE, - ADISPLAY_ID_DEFAULT, 0 /* expectedFlag */); + windowDefaultDisplay->consumeMotionEvent( + AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_MOVE), + WithSource(AINPUT_SOURCE_MOUSE))); windowDefaultDisplay->assertNoEvents(); } @@ -2258,10 +3758,10 @@ TEST_F(InputDispatcherTest, DispatchMouseEventsUnderCursor) { std::shared_ptr application = std::make_shared(); sp windowLeft = - new FakeWindowHandle(application, mDispatcher, "Left", ADISPLAY_ID_DEFAULT); + sp::make(application, mDispatcher, "Left", ADISPLAY_ID_DEFAULT); windowLeft->setFrame(Rect(0, 0, 600, 800)); sp windowRight = - new FakeWindowHandle(application, mDispatcher, "Right", ADISPLAY_ID_DEFAULT); + sp::make(application, mDispatcher, "Right", ADISPLAY_ID_DEFAULT); windowRight->setFrame(Rect(600, 0, 1200, 800)); mDispatcher->setFocusedApplication(ADISPLAY_ID_DEFAULT, application); @@ -2279,8 +3779,8 @@ TEST_F(InputDispatcherTest, DispatchMouseEventsUnderCursor) { TEST_F(InputDispatcherTest, NotifyDeviceReset_CancelsKeyStream) { std::shared_ptr application = std::make_shared(); - sp window = - new FakeWindowHandle(application, mDispatcher, "Fake Window", ADISPLAY_ID_DEFAULT); + sp window = sp::make(application, mDispatcher, + "Fake Window", ADISPLAY_ID_DEFAULT); window->setFocusable(true); mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {window}}}); @@ -2288,47 +3788,66 @@ TEST_F(InputDispatcherTest, NotifyDeviceReset_CancelsKeyStream) { window->consumeFocusEvent(true); - NotifyKeyArgs keyArgs = generateKeyArgs(AKEY_EVENT_ACTION_DOWN, ADISPLAY_ID_DEFAULT); - mDispatcher->notifyKey(&keyArgs); + mDispatcher->notifyKey(generateKeyArgs(AKEY_EVENT_ACTION_DOWN, ADISPLAY_ID_DEFAULT)); // Window should receive key down event. window->consumeKeyDown(ADISPLAY_ID_DEFAULT); // When device reset happens, that key stream should be terminated with FLAG_CANCELED // on the app side. - NotifyDeviceResetArgs args(10 /*id*/, 20 /*eventTime*/, DEVICE_ID); - mDispatcher->notifyDeviceReset(&args); - window->consumeEvent(AINPUT_EVENT_TYPE_KEY, AKEY_EVENT_ACTION_UP, ADISPLAY_ID_DEFAULT, + mDispatcher->notifyDeviceReset({/*id=*/10, /*eventTime=*/20, DEVICE_ID}); + window->consumeEvent(InputEventType::KEY, AKEY_EVENT_ACTION_UP, ADISPLAY_ID_DEFAULT, AKEY_EVENT_FLAG_CANCELED); } TEST_F(InputDispatcherTest, NotifyDeviceReset_CancelsMotionStream) { std::shared_ptr application = std::make_shared(); - sp window = - new FakeWindowHandle(application, mDispatcher, "Fake Window", ADISPLAY_ID_DEFAULT); + sp window = sp::make(application, mDispatcher, + "Fake Window", ADISPLAY_ID_DEFAULT); mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {window}}}); - NotifyMotionArgs motionArgs = - generateMotionArgs(AMOTION_EVENT_ACTION_DOWN, AINPUT_SOURCE_TOUCHSCREEN, - ADISPLAY_ID_DEFAULT); - mDispatcher->notifyMotion(&motionArgs); + mDispatcher->notifyMotion(generateMotionArgs(AMOTION_EVENT_ACTION_DOWN, + AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT)); // Window should receive motion down event. window->consumeMotionDown(ADISPLAY_ID_DEFAULT); // When device reset happens, that motion stream should be terminated with ACTION_CANCEL // on the app side. - NotifyDeviceResetArgs args(10 /*id*/, 20 /*eventTime*/, DEVICE_ID); - mDispatcher->notifyDeviceReset(&args); - window->consumeEvent(AINPUT_EVENT_TYPE_MOTION, AMOTION_EVENT_ACTION_CANCEL, ADISPLAY_ID_DEFAULT, - 0 /*expectedFlags*/); + mDispatcher->notifyDeviceReset({/*id=*/10, /*eventTime=*/20, DEVICE_ID}); + window->consumeMotionEvent( + AllOf(WithMotionAction(ACTION_CANCEL), WithDisplayId(ADISPLAY_ID_DEFAULT))); +} + +TEST_F(InputDispatcherTest, NotifyDeviceResetCancelsHoveringStream) { + std::shared_ptr application = std::make_shared(); + sp window = sp::make(application, mDispatcher, + "Fake Window", ADISPLAY_ID_DEFAULT); + + mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {window}}}); + + mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_HOVER_ENTER, AINPUT_SOURCE_STYLUS) + .pointer(PointerBuilder(0, ToolType::STYLUS).x(10).y(10)) + .build()); + + window->consumeMotionEvent(WithMotionAction(ACTION_HOVER_ENTER)); + + // When device reset happens, that hover stream should be terminated with ACTION_HOVER_EXIT + mDispatcher->notifyDeviceReset({/*id=*/10, /*eventTime=*/20, DEVICE_ID}); + window->consumeMotionEvent(WithMotionAction(ACTION_HOVER_EXIT)); + + // After the device has been reset, a new hovering stream can be sent to the window + mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_HOVER_ENTER, AINPUT_SOURCE_STYLUS) + .pointer(PointerBuilder(0, ToolType::STYLUS).x(15).y(15)) + .build()); + window->consumeMotionEvent(WithMotionAction(ACTION_HOVER_ENTER)); } TEST_F(InputDispatcherTest, InterceptKeyByPolicy) { std::shared_ptr application = std::make_shared(); - sp window = - new FakeWindowHandle(application, mDispatcher, "Fake Window", ADISPLAY_ID_DEFAULT); + sp window = sp::make(application, mDispatcher, + "Fake Window", ADISPLAY_ID_DEFAULT); window->setFocusable(true); mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {window}}}); @@ -2336,11 +3855,11 @@ TEST_F(InputDispatcherTest, InterceptKeyByPolicy) { window->consumeFocusEvent(true); - NotifyKeyArgs keyArgs = generateKeyArgs(AKEY_EVENT_ACTION_DOWN, ADISPLAY_ID_DEFAULT); + const NotifyKeyArgs keyArgs = generateKeyArgs(AKEY_EVENT_ACTION_DOWN, ADISPLAY_ID_DEFAULT); const std::chrono::milliseconds interceptKeyTimeout = 50ms; const nsecs_t injectTime = keyArgs.eventTime; mFakePolicy->setInterceptKeyTimeout(interceptKeyTimeout); - mDispatcher->notifyKey(&keyArgs); + mDispatcher->notifyKey(keyArgs); // The dispatching time should be always greater than or equal to intercept key timeout. window->consumeKeyDown(ADISPLAY_ID_DEFAULT); ASSERT_TRUE((systemTime(SYSTEM_TIME_MONOTONIC) - injectTime) >= @@ -2349,8 +3868,8 @@ TEST_F(InputDispatcherTest, InterceptKeyByPolicy) { TEST_F(InputDispatcherTest, InterceptKeyIfKeyUp) { std::shared_ptr application = std::make_shared(); - sp window = - new FakeWindowHandle(application, mDispatcher, "Fake Window", ADISPLAY_ID_DEFAULT); + sp window = sp::make(application, mDispatcher, + "Fake Window", ADISPLAY_ID_DEFAULT); window->setFocusable(true); mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {window}}}); @@ -2358,17 +3877,47 @@ TEST_F(InputDispatcherTest, InterceptKeyIfKeyUp) { window->consumeFocusEvent(true); - NotifyKeyArgs keyDown = generateKeyArgs(AKEY_EVENT_ACTION_DOWN, ADISPLAY_ID_DEFAULT); - NotifyKeyArgs keyUp = generateKeyArgs(AKEY_EVENT_ACTION_UP, ADISPLAY_ID_DEFAULT); mFakePolicy->setInterceptKeyTimeout(150ms); - mDispatcher->notifyKey(&keyDown); - mDispatcher->notifyKey(&keyUp); + mDispatcher->notifyKey(generateKeyArgs(AKEY_EVENT_ACTION_DOWN, ADISPLAY_ID_DEFAULT)); + mDispatcher->notifyKey(generateKeyArgs(AKEY_EVENT_ACTION_UP, ADISPLAY_ID_DEFAULT)); // Window should receive key event immediately when same key up. window->consumeKeyDown(ADISPLAY_ID_DEFAULT); window->consumeKeyUp(ADISPLAY_ID_DEFAULT); } +/** + * Two windows. First is a regular window. Second does not overlap with the first, and has + * WATCH_OUTSIDE_TOUCH. + * Both windows are owned by the same UID. + * Tap first window. Make sure that the second window receives ACTION_OUTSIDE with correct, non-zero + * coordinates. The coordinates are not zeroed out because both windows are owned by the same UID. + */ +TEST_F(InputDispatcherTest, ActionOutsideForOwnedWindowHasValidCoordinates) { + std::shared_ptr application = std::make_shared(); + sp window = sp::make(application, mDispatcher, + "First Window", ADISPLAY_ID_DEFAULT); + window->setFrame(Rect{0, 0, 100, 100}); + + sp outsideWindow = + sp::make(application, mDispatcher, "Second Window", + ADISPLAY_ID_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 + mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {outsideWindow, window}}}); + + // Tap on first window. + mDispatcher->notifyMotion(generateMotionArgs(AMOTION_EVENT_ACTION_DOWN, + AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_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. + outsideWindow->consumeMotionEvent( + AllOf(WithMotionAction(ACTION_OUTSIDE), WithCoords(-50, -50))); +} + /** * This test documents the behavior of WATCH_OUTSIDE_TOUCH. The window will get ACTION_OUTSIDE when * a another pointer causes ACTION_DOWN to be sent to another window for the first time. Only one @@ -2377,40 +3926,43 @@ TEST_F(InputDispatcherTest, InterceptKeyIfKeyUp) { 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 = - new FakeWindowHandle(application, mDispatcher, "First Window", ADISPLAY_ID_DEFAULT); + sp window = sp::make(application, mDispatcher, + "First Window", ADISPLAY_ID_DEFAULT); window->setWatchOutsideTouch(true); window->setFrame(Rect{0, 0, 100, 100}); sp secondWindow = - new FakeWindowHandle(application, mDispatcher, "Second Window", ADISPLAY_ID_DEFAULT); + sp::make(application, mDispatcher, "Second Window", + ADISPLAY_ID_DEFAULT); secondWindow->setFrame(Rect{100, 100, 200, 200}); sp thirdWindow = - new FakeWindowHandle(application, mDispatcher, "Third Window", ADISPLAY_ID_DEFAULT); + sp::make(application, mDispatcher, "Third Window", + ADISPLAY_ID_DEFAULT); thirdWindow->setFrame(Rect{200, 200, 300, 300}); mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {window, secondWindow, thirdWindow}}}); // First pointer lands outside all windows. `window` does not get ACTION_OUTSIDE. - NotifyMotionArgs motionArgs = - generateMotionArgs(AMOTION_EVENT_ACTION_DOWN, AINPUT_SOURCE_TOUCHSCREEN, - ADISPLAY_ID_DEFAULT, {PointF{-10, -10}}); - mDispatcher->notifyMotion(&motionArgs); + mDispatcher->notifyMotion(generateMotionArgs(AMOTION_EVENT_ACTION_DOWN, + AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_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. - motionArgs = generateMotionArgs(POINTER_1_DOWN, AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT, - {PointF{-10, -10}, PointF{105, 105}}); - mDispatcher->notifyMotion(&motionArgs); - window->consumeMotionOutside(); + mDispatcher->notifyMotion(generateMotionArgs(POINTER_1_DOWN, AINPUT_SOURCE_TOUCHSCREEN, + ADISPLAY_ID_DEFAULT, + {PointF{-10, -10}, PointF{105, 105}})); + const std::map expectedPointers{{0, PointF{-10, -10}}, {1, PointF{105, 105}}}; + window->consumeMotionEvent( + AllOf(WithMotionAction(ACTION_OUTSIDE), WithPointers(expectedPointers))); secondWindow->consumeMotionDown(); thirdWindow->assertNoEvents(); // 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. - motionArgs = generateMotionArgs(POINTER_2_DOWN, AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT, - {PointF{-10, -10}, PointF{105, 105}, PointF{205, 205}}); - mDispatcher->notifyMotion(&motionArgs); + mDispatcher->notifyMotion( + generateMotionArgs(POINTER_2_DOWN, AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT, + {PointF{-10, -10}, PointF{105, 105}, PointF{205, 205}})); window->assertNoEvents(); secondWindow->consumeMotionMove(); thirdWindow->consumeMotionDown(); @@ -2418,30 +3970,30 @@ TEST_F(InputDispatcherTest, ActionOutsideSentOnlyWhenAWindowIsTouched) { TEST_F(InputDispatcherTest, OnWindowInfosChanged_RemoveAllWindowsOnDisplay) { std::shared_ptr application = std::make_shared(); - sp window = - new FakeWindowHandle(application, mDispatcher, "Fake Window", ADISPLAY_ID_DEFAULT); + sp window = sp::make(application, mDispatcher, + "Fake Window", ADISPLAY_ID_DEFAULT); window->setFocusable(true); - mDispatcher->onWindowInfosChanged({*window->getInfo()}, {}); + mDispatcher->onWindowInfosChanged({{*window->getInfo()}, {}, 0, 0}); setFocusedWindow(window); window->consumeFocusEvent(true); - NotifyKeyArgs keyDown = generateKeyArgs(AKEY_EVENT_ACTION_DOWN, ADISPLAY_ID_DEFAULT); - NotifyKeyArgs keyUp = generateKeyArgs(AKEY_EVENT_ACTION_UP, ADISPLAY_ID_DEFAULT); - mDispatcher->notifyKey(&keyDown); - mDispatcher->notifyKey(&keyUp); + const NotifyKeyArgs keyDown = generateKeyArgs(AKEY_EVENT_ACTION_DOWN, ADISPLAY_ID_DEFAULT); + const NotifyKeyArgs keyUp = generateKeyArgs(AKEY_EVENT_ACTION_UP, ADISPLAY_ID_DEFAULT); + mDispatcher->notifyKey(keyDown); + mDispatcher->notifyKey(keyUp); window->consumeKeyDown(ADISPLAY_ID_DEFAULT); window->consumeKeyUp(ADISPLAY_ID_DEFAULT); // All windows are removed from the display. Ensure that we can no longer dispatch to it. - mDispatcher->onWindowInfosChanged({}, {}); + mDispatcher->onWindowInfosChanged({{}, {}, 0, 0}); window->consumeFocusEvent(false); - mDispatcher->notifyKey(&keyDown); - mDispatcher->notifyKey(&keyUp); + mDispatcher->notifyKey(keyDown); + mDispatcher->notifyKey(keyUp); window->assertNoEvents(); } @@ -2452,7 +4004,7 @@ TEST_F(InputDispatcherTest, NonSplitTouchableWindowReceivesMultiTouch) { // Ensure window is non-split and have some transform. window->setPreventSplitting(true); window->setWindowOffset(20, 40); - mDispatcher->onWindowInfosChanged({*window->getInfo()}, {}); + mDispatcher->onWindowInfosChanged({{*window->getInfo()}, {}, 0, 0}); ASSERT_EQ(InputEventInjectionResult::SUCCEEDED, injectMotionDown(mDispatcher, AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT, @@ -2464,10 +4016,8 @@ TEST_F(InputDispatcherTest, NonSplitTouchableWindowReceivesMultiTouch) { MotionEventBuilder(POINTER_1_DOWN, AINPUT_SOURCE_TOUCHSCREEN) .displayId(ADISPLAY_ID_DEFAULT) .eventTime(systemTime(SYSTEM_TIME_MONOTONIC)) - .pointer(PointerBuilder(/* id */ 0, AMOTION_EVENT_TOOL_TYPE_FINGER).x(50).y(50)) - .pointer(PointerBuilder(/* id */ 1, AMOTION_EVENT_TOOL_TYPE_FINGER) - .x(-30) - .y(-50)) + .pointer(PointerBuilder(/*id=*/0, ToolType::FINGER).x(50).y(50)) + .pointer(PointerBuilder(/*id=*/1, ToolType::FINGER).x(-30).y(-50)) .build(); ASSERT_EQ(InputEventInjectionResult::SUCCEEDED, injectMotionEvent(mDispatcher, secondFingerDownEvent, INJECT_EVENT_TIMEOUT, @@ -2482,6 +4032,126 @@ TEST_F(InputDispatcherTest, NonSplitTouchableWindowReceivesMultiTouch) { EXPECT_EQ(-10, event->getY(1)); // -50 + 40 } +TEST_F(InputDispatcherTest, TouchpadThreeFingerSwipeOnlySentToTrustedOverlays) { + std::shared_ptr application = std::make_shared(); + sp window = + sp::make(application, mDispatcher, "Window", ADISPLAY_ID_DEFAULT); + window->setFrame(Rect(0, 0, 400, 400)); + sp trustedOverlay = + sp::make(application, mDispatcher, "Trusted Overlay", + ADISPLAY_ID_DEFAULT); + trustedOverlay->setSpy(true); + trustedOverlay->setTrustedOverlay(true); + + mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {trustedOverlay, window}}}); + + // Start a three-finger touchpad swipe + mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_DOWN, AINPUT_SOURCE_MOUSE) + .pointer(PointerBuilder(0, ToolType::FINGER).x(200).y(100)) + .classification(MotionClassification::MULTI_FINGER_SWIPE) + .build()); + mDispatcher->notifyMotion(MotionArgsBuilder(POINTER_1_DOWN, AINPUT_SOURCE_MOUSE) + .pointer(PointerBuilder(0, ToolType::FINGER).x(200).y(100)) + .pointer(PointerBuilder(1, ToolType::FINGER).x(250).y(100)) + .classification(MotionClassification::MULTI_FINGER_SWIPE) + .build()); + mDispatcher->notifyMotion(MotionArgsBuilder(POINTER_2_DOWN, AINPUT_SOURCE_MOUSE) + .pointer(PointerBuilder(0, ToolType::FINGER).x(200).y(100)) + .pointer(PointerBuilder(1, ToolType::FINGER).x(250).y(100)) + .pointer(PointerBuilder(2, ToolType::FINGER).x(300).y(100)) + .classification(MotionClassification::MULTI_FINGER_SWIPE) + .build()); + + trustedOverlay->consumeMotionEvent(WithMotionAction(ACTION_DOWN)); + trustedOverlay->consumeMotionEvent(WithMotionAction(POINTER_1_DOWN)); + trustedOverlay->consumeMotionEvent(WithMotionAction(POINTER_2_DOWN)); + + // Move the swipe a bit + mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_MOVE, AINPUT_SOURCE_MOUSE) + .pointer(PointerBuilder(0, ToolType::FINGER).x(200).y(105)) + .pointer(PointerBuilder(1, ToolType::FINGER).x(250).y(105)) + .pointer(PointerBuilder(2, ToolType::FINGER).x(300).y(105)) + .classification(MotionClassification::MULTI_FINGER_SWIPE) + .build()); + + trustedOverlay->consumeMotionEvent(WithMotionAction(ACTION_MOVE)); + + // End the swipe + mDispatcher->notifyMotion(MotionArgsBuilder(POINTER_2_UP, AINPUT_SOURCE_MOUSE) + .pointer(PointerBuilder(0, ToolType::FINGER).x(200).y(105)) + .pointer(PointerBuilder(1, ToolType::FINGER).x(250).y(105)) + .pointer(PointerBuilder(2, ToolType::FINGER).x(300).y(105)) + .classification(MotionClassification::MULTI_FINGER_SWIPE) + .build()); + mDispatcher->notifyMotion(MotionArgsBuilder(POINTER_1_UP, AINPUT_SOURCE_MOUSE) + .pointer(PointerBuilder(0, ToolType::FINGER).x(200).y(105)) + .pointer(PointerBuilder(1, ToolType::FINGER).x(250).y(105)) + .classification(MotionClassification::MULTI_FINGER_SWIPE) + .build()); + mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_UP, AINPUT_SOURCE_MOUSE) + .pointer(PointerBuilder(0, ToolType::FINGER).x(200).y(105)) + .classification(MotionClassification::MULTI_FINGER_SWIPE) + .build()); + + trustedOverlay->consumeMotionEvent(WithMotionAction(POINTER_2_UP)); + trustedOverlay->consumeMotionEvent(WithMotionAction(POINTER_1_UP)); + trustedOverlay->consumeMotionEvent(WithMotionAction(ACTION_UP)); + + window->assertNoEvents(); +} + +TEST_F(InputDispatcherTest, TouchpadThreeFingerSwipeNotSentToSingleWindow) { + std::shared_ptr application = std::make_shared(); + sp window = + sp::make(application, mDispatcher, "Window", ADISPLAY_ID_DEFAULT); + window->setFrame(Rect(0, 0, 400, 400)); + mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {window}}}); + + // Start a three-finger touchpad swipe + mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_DOWN, AINPUT_SOURCE_MOUSE) + .pointer(PointerBuilder(0, ToolType::FINGER).x(200).y(100)) + .classification(MotionClassification::MULTI_FINGER_SWIPE) + .build()); + mDispatcher->notifyMotion(MotionArgsBuilder(POINTER_1_DOWN, AINPUT_SOURCE_MOUSE) + .pointer(PointerBuilder(0, ToolType::FINGER).x(200).y(100)) + .pointer(PointerBuilder(1, ToolType::FINGER).x(250).y(100)) + .classification(MotionClassification::MULTI_FINGER_SWIPE) + .build()); + mDispatcher->notifyMotion(MotionArgsBuilder(POINTER_2_DOWN, AINPUT_SOURCE_MOUSE) + .pointer(PointerBuilder(0, ToolType::FINGER).x(200).y(100)) + .pointer(PointerBuilder(1, ToolType::FINGER).x(250).y(100)) + .pointer(PointerBuilder(2, ToolType::FINGER).x(300).y(100)) + .classification(MotionClassification::MULTI_FINGER_SWIPE) + .build()); + + // Move the swipe a bit + mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_MOVE, AINPUT_SOURCE_MOUSE) + .pointer(PointerBuilder(0, ToolType::FINGER).x(200).y(105)) + .pointer(PointerBuilder(1, ToolType::FINGER).x(250).y(105)) + .pointer(PointerBuilder(2, ToolType::FINGER).x(300).y(105)) + .classification(MotionClassification::MULTI_FINGER_SWIPE) + .build()); + + // End the swipe + mDispatcher->notifyMotion(MotionArgsBuilder(POINTER_2_UP, AINPUT_SOURCE_MOUSE) + .pointer(PointerBuilder(0, ToolType::FINGER).x(200).y(105)) + .pointer(PointerBuilder(1, ToolType::FINGER).x(250).y(105)) + .pointer(PointerBuilder(2, ToolType::FINGER).x(300).y(105)) + .classification(MotionClassification::MULTI_FINGER_SWIPE) + .build()); + mDispatcher->notifyMotion(MotionArgsBuilder(POINTER_1_UP, AINPUT_SOURCE_MOUSE) + .pointer(PointerBuilder(0, ToolType::FINGER).x(200).y(105)) + .pointer(PointerBuilder(1, ToolType::FINGER).x(250).y(105)) + .classification(MotionClassification::MULTI_FINGER_SWIPE) + .build()); + mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_UP, AINPUT_SOURCE_MOUSE) + .pointer(PointerBuilder(0, ToolType::FINGER).x(200).y(105)) + .classification(MotionClassification::MULTI_FINGER_SWIPE) + .build()); + + window->assertNoEvents(); +} + /** * Ensure the correct coordinate spaces are used by InputDispatcher. * @@ -2501,12 +4171,12 @@ public: info.displayId = displayId; info.transform = transform; mDisplayInfos.push_back(std::move(info)); - mDispatcher->onWindowInfosChanged(mWindowInfos, mDisplayInfos); + mDispatcher->onWindowInfosChanged({mWindowInfos, mDisplayInfos, 0, 0}); } void addWindow(const sp& windowHandle) { mWindowInfos.push_back(*windowHandle->getInfo()); - mDispatcher->onWindowInfosChanged(mWindowInfos, mDisplayInfos); + mDispatcher->onWindowInfosChanged({mWindowInfos, mDisplayInfos, 0, 0}); } void removeAllWindowsAndDisplays() { @@ -2528,13 +4198,14 @@ public: // Add two windows to the display. Their frames are represented in the display space. sp firstWindow = - new FakeWindowHandle(application, mDispatcher, "First Window", ADISPLAY_ID_DEFAULT); + sp::make(application, mDispatcher, "First Window", + ADISPLAY_ID_DEFAULT); firstWindow->setFrame(Rect(0, 0, 100, 200), displayTransform); addWindow(firstWindow); sp secondWindow = - new FakeWindowHandle(application, mDispatcher, "Second Window", - ADISPLAY_ID_DEFAULT); + sp::make(application, mDispatcher, "Second Window", + ADISPLAY_ID_DEFAULT); secondWindow->setFrame(Rect(100, 200, 200, 400), displayTransform); addWindow(secondWindow); return {std::move(firstWindow), std::move(secondWindow)}; @@ -2550,10 +4221,9 @@ TEST_F(InputDispatcherDisplayProjectionTest, HitTestCoordinateSpaceConsistency) // Send down to the first window. The point is represented in the display space. The point is // 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. - NotifyMotionArgs downMotionArgs = - generateMotionArgs(AMOTION_EVENT_ACTION_DOWN, AINPUT_SOURCE_TOUCHSCREEN, - ADISPLAY_ID_DEFAULT, {PointF{75, 55}}); - mDispatcher->notifyMotion(&downMotionArgs); + mDispatcher->notifyMotion(generateMotionArgs(AMOTION_EVENT_ACTION_DOWN, + AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT, + {PointF{75, 55}})); firstWindow->consumeMotionDown(); secondWindow->assertNoEvents(); @@ -2587,7 +4257,7 @@ TEST_F(InputDispatcherDisplayProjectionTest, InjectionWithTransformInLogicalDisp MotionEvent event = MotionEventBuilder(AMOTION_EVENT_ACTION_DOWN, AINPUT_SOURCE_TOUCHSCREEN) .displayId(ADISPLAY_ID_DEFAULT) .eventTime(systemTime(SYSTEM_TIME_MONOTONIC)) - .pointer(PointerBuilder(/* id */ 0, AMOTION_EVENT_TOOL_TYPE_FINGER) + .pointer(PointerBuilder(/*id=*/0, ToolType::FINGER) .x(untransformedPoint.x) .y(untransformedPoint.y)) .build(); @@ -2604,13 +4274,13 @@ TEST_F(InputDispatcherDisplayProjectionTest, WindowGetsEventsInCorrectCoordinate auto [firstWindow, secondWindow] = setupScaledDisplayScenario(); // Send down to the second window. - NotifyMotionArgs downMotionArgs = - generateMotionArgs(AMOTION_EVENT_ACTION_DOWN, AINPUT_SOURCE_TOUCHSCREEN, - ADISPLAY_ID_DEFAULT, {PointF{150, 220}}); - mDispatcher->notifyMotion(&downMotionArgs); + mDispatcher->notifyMotion(generateMotionArgs(AMOTION_EVENT_ACTION_DOWN, + AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT, + {PointF{150, 220}})); firstWindow->assertNoEvents(); const MotionEvent* event = secondWindow->consumeMotion(); + ASSERT_NE(nullptr, event); EXPECT_EQ(AMOTION_EVENT_ACTION_DOWN, event->getAction()); // Ensure that the events from the "getRaw" API are in logical display coordinates. @@ -2662,14 +4332,14 @@ TEST_P(InputDispatcherDisplayOrientationFixture, HitTestInDifferentOrientations) for (const auto pointInsideWindow : insidePoints) { const vec2 p = displayTransform.inverse().transform(pointInsideWindow); const PointF pointInDisplaySpace{p.x, p.y}; - const auto down = generateMotionArgs(AMOTION_EVENT_ACTION_DOWN, AINPUT_SOURCE_TOUCHSCREEN, - ADISPLAY_ID_DEFAULT, {pointInDisplaySpace}); - mDispatcher->notifyMotion(&down); + mDispatcher->notifyMotion(generateMotionArgs(AMOTION_EVENT_ACTION_DOWN, + AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT, + {pointInDisplaySpace})); window->consumeMotionDown(); - const auto up = generateMotionArgs(AMOTION_EVENT_ACTION_UP, AINPUT_SOURCE_TOUCHSCREEN, - ADISPLAY_ID_DEFAULT, {pointInDisplaySpace}); - mDispatcher->notifyMotion(&up); + mDispatcher->notifyMotion(generateMotionArgs(AMOTION_EVENT_ACTION_UP, + AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT, + {pointInDisplaySpace})); window->consumeMotionUp(); } @@ -2679,13 +4349,13 @@ TEST_P(InputDispatcherDisplayOrientationFixture, HitTestInDifferentOrientations) for (const auto pointOutsideWindow : outsidePoints) { const vec2 p = displayTransform.inverse().transform(pointOutsideWindow); const PointF pointInDisplaySpace{p.x, p.y}; - const auto down = generateMotionArgs(AMOTION_EVENT_ACTION_DOWN, AINPUT_SOURCE_TOUCHSCREEN, - ADISPLAY_ID_DEFAULT, {pointInDisplaySpace}); - mDispatcher->notifyMotion(&down); + mDispatcher->notifyMotion(generateMotionArgs(AMOTION_EVENT_ACTION_DOWN, + AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT, + {pointInDisplaySpace})); - const auto up = generateMotionArgs(AMOTION_EVENT_ACTION_UP, AINPUT_SOURCE_TOUCHSCREEN, - ADISPLAY_ID_DEFAULT, {pointInDisplaySpace}); - mDispatcher->notifyMotion(&up); + mDispatcher->notifyMotion(generateMotionArgs(AMOTION_EVENT_ACTION_UP, + AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT, + {pointInDisplaySpace})); } window->assertNoEvents(); } @@ -2694,7 +4364,10 @@ TEST_P(InputDispatcherDisplayOrientationFixture, HitTestInDifferentOrientations) INSTANTIATE_TEST_SUITE_P(InputDispatcherDisplayOrientationTests, InputDispatcherDisplayOrientationFixture, ::testing::Values(ui::ROTATION_0, ui::ROTATION_90, ui::ROTATION_180, - ui::ROTATION_270)); + ui::ROTATION_270), + [](const testing::TestParamInfo& testParamInfo) { + return ftl::enum_string(testParamInfo.param); + }); using TransferFunction = std::function& dispatcher, sp, sp)>; @@ -2707,22 +4380,21 @@ TEST_P(TransferTouchFixture, TransferTouch_OnePointer) { // Create a couple of windows sp firstWindow = - new FakeWindowHandle(application, mDispatcher, "First Window", ADISPLAY_ID_DEFAULT); + sp::make(application, mDispatcher, "First Window", + ADISPLAY_ID_DEFAULT); firstWindow->setDupTouchToWallpaper(true); - sp secondWindow = - new FakeWindowHandle(application, mDispatcher, "Second Window", ADISPLAY_ID_DEFAULT); + sp::make(application, mDispatcher, "Second Window", + ADISPLAY_ID_DEFAULT); sp wallpaper = - new FakeWindowHandle(application, mDispatcher, "Wallpaper", ADISPLAY_ID_DEFAULT); + sp::make(application, mDispatcher, "Wallpaper", ADISPLAY_ID_DEFAULT); wallpaper->setIsWallpaper(true); // Add the windows to the dispatcher mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {firstWindow, secondWindow, wallpaper}}}); // Send down to the first window - NotifyMotionArgs downMotionArgs = - generateMotionArgs(AMOTION_EVENT_ACTION_DOWN, AINPUT_SOURCE_TOUCHSCREEN, - ADISPLAY_ID_DEFAULT); - mDispatcher->notifyMotion(&downMotionArgs); + mDispatcher->notifyMotion(generateMotionArgs(AMOTION_EVENT_ACTION_DOWN, + AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT)); // Only the first window should get the down event firstWindow->consumeMotionDown(); @@ -2739,10 +4411,8 @@ TEST_P(TransferTouchFixture, TransferTouch_OnePointer) { wallpaper->consumeMotionCancel(ADISPLAY_ID_DEFAULT, expectedWallpaperFlags); // Send up event to the second window - NotifyMotionArgs upMotionArgs = - generateMotionArgs(AMOTION_EVENT_ACTION_UP, AINPUT_SOURCE_TOUCHSCREEN, - ADISPLAY_ID_DEFAULT); - mDispatcher->notifyMotion(&upMotionArgs); + mDispatcher->notifyMotion(generateMotionArgs(AMOTION_EVENT_ACTION_UP, AINPUT_SOURCE_TOUCHSCREEN, + ADISPLAY_ID_DEFAULT)); // The first window gets no events and the second gets up firstWindow->assertNoEvents(); secondWindow->consumeMotionUp(); @@ -2766,22 +4436,20 @@ TEST_P(TransferTouchFixture, TransferTouch_MultipleWindowsWithSpy) { // Create a couple of windows + a spy window sp spyWindow = - new FakeWindowHandle(application, mDispatcher, "Spy", ADISPLAY_ID_DEFAULT); + sp::make(application, mDispatcher, "Spy", ADISPLAY_ID_DEFAULT); spyWindow->setTrustedOverlay(true); spyWindow->setSpy(true); sp firstWindow = - new FakeWindowHandle(application, mDispatcher, "First", ADISPLAY_ID_DEFAULT); + sp::make(application, mDispatcher, "First", ADISPLAY_ID_DEFAULT); sp secondWindow = - new FakeWindowHandle(application, mDispatcher, "Second", ADISPLAY_ID_DEFAULT); + sp::make(application, mDispatcher, "Second", ADISPLAY_ID_DEFAULT); // Add the windows to the dispatcher mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {spyWindow, firstWindow, secondWindow}}}); // Send down to the first window - NotifyMotionArgs downMotionArgs = - generateMotionArgs(AMOTION_EVENT_ACTION_DOWN, AINPUT_SOURCE_TOUCHSCREEN, - ADISPLAY_ID_DEFAULT); - mDispatcher->notifyMotion(&downMotionArgs); + mDispatcher->notifyMotion(generateMotionArgs(AMOTION_EVENT_ACTION_DOWN, + AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT)); // Only the first window and spy should get the down event spyWindow->consumeMotionDown(); firstWindow->consumeMotionDown(); @@ -2796,10 +4464,8 @@ TEST_P(TransferTouchFixture, TransferTouch_MultipleWindowsWithSpy) { secondWindow->consumeMotionDown(); // Send up event to the second window - NotifyMotionArgs upMotionArgs = - generateMotionArgs(AMOTION_EVENT_ACTION_UP, AINPUT_SOURCE_TOUCHSCREEN, - ADISPLAY_ID_DEFAULT); - mDispatcher->notifyMotion(&upMotionArgs); + mDispatcher->notifyMotion(generateMotionArgs(AMOTION_EVENT_ACTION_UP, AINPUT_SOURCE_TOUCHSCREEN, + ADISPLAY_ID_DEFAULT)); // The first window gets no events and the second+spy get up firstWindow->assertNoEvents(); spyWindow->consumeMotionUp(); @@ -2813,29 +4479,28 @@ TEST_P(TransferTouchFixture, TransferTouch_TwoPointersNonSplitTouch) { // Create a couple of windows sp firstWindow = - new FakeWindowHandle(application, mDispatcher, "First Window", ADISPLAY_ID_DEFAULT); + sp::make(application, mDispatcher, "First Window", + ADISPLAY_ID_DEFAULT); firstWindow->setPreventSplitting(true); sp secondWindow = - new FakeWindowHandle(application, mDispatcher, "Second Window", ADISPLAY_ID_DEFAULT); + sp::make(application, mDispatcher, "Second Window", + ADISPLAY_ID_DEFAULT); secondWindow->setPreventSplitting(true); // Add the windows to the dispatcher mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {firstWindow, secondWindow}}}); // Send down to the first window - NotifyMotionArgs downMotionArgs = - generateMotionArgs(AMOTION_EVENT_ACTION_DOWN, AINPUT_SOURCE_TOUCHSCREEN, - ADISPLAY_ID_DEFAULT, {touchPoint}); - mDispatcher->notifyMotion(&downMotionArgs); + mDispatcher->notifyMotion(generateMotionArgs(AMOTION_EVENT_ACTION_DOWN, + AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT, + {touchPoint})); // Only the first window should get the down event firstWindow->consumeMotionDown(); secondWindow->assertNoEvents(); // Send pointer down to the first window - NotifyMotionArgs pointerDownMotionArgs = - generateMotionArgs(POINTER_1_DOWN, AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT, - {touchPoint, touchPoint}); - mDispatcher->notifyMotion(&pointerDownMotionArgs); + mDispatcher->notifyMotion(generateMotionArgs(POINTER_1_DOWN, AINPUT_SOURCE_TOUCHSCREEN, + ADISPLAY_ID_DEFAULT, {touchPoint, touchPoint})); // Only the first window should get the pointer down event firstWindow->consumeMotionPointerDown(1); secondWindow->assertNoEvents(); @@ -2850,19 +4515,15 @@ TEST_P(TransferTouchFixture, TransferTouch_TwoPointersNonSplitTouch) { secondWindow->consumeMotionPointerDown(1); // Send pointer up to the second window - NotifyMotionArgs pointerUpMotionArgs = - generateMotionArgs(POINTER_1_UP, AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT, - {touchPoint, touchPoint}); - mDispatcher->notifyMotion(&pointerUpMotionArgs); + mDispatcher->notifyMotion(generateMotionArgs(POINTER_1_UP, AINPUT_SOURCE_TOUCHSCREEN, + ADISPLAY_ID_DEFAULT, {touchPoint, touchPoint})); // The first window gets nothing and the second gets pointer up firstWindow->assertNoEvents(); secondWindow->consumeMotionPointerUp(1); // Send up event to the second window - NotifyMotionArgs upMotionArgs = - generateMotionArgs(AMOTION_EVENT_ACTION_UP, AINPUT_SOURCE_TOUCHSCREEN, - ADISPLAY_ID_DEFAULT); - mDispatcher->notifyMotion(&upMotionArgs); + mDispatcher->notifyMotion(generateMotionArgs(AMOTION_EVENT_ACTION_UP, AINPUT_SOURCE_TOUCHSCREEN, + ADISPLAY_ID_DEFAULT)); // The first window gets nothing and the second gets up firstWindow->assertNoEvents(); secondWindow->consumeMotionUp(); @@ -2893,10 +4554,8 @@ TEST_P(TransferTouchFixture, TransferTouch_MultipleWallpapers) { {{ADISPLAY_ID_DEFAULT, {firstWindow, wallpaper1, secondWindow, wallpaper2}}}); // Send down to the first window - NotifyMotionArgs downMotionArgs = - generateMotionArgs(AMOTION_EVENT_ACTION_DOWN, AINPUT_SOURCE_TOUCHSCREEN, - ADISPLAY_ID_DEFAULT); - mDispatcher->notifyMotion(&downMotionArgs); + mDispatcher->notifyMotion(generateMotionArgs(AMOTION_EVENT_ACTION_DOWN, + AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT)); // Only the first window should get the down event firstWindow->consumeMotionDown(); @@ -2916,10 +4575,8 @@ TEST_P(TransferTouchFixture, TransferTouch_MultipleWallpapers) { wallpaper2->consumeMotionDown(ADISPLAY_ID_DEFAULT, expectedWallpaperFlags); // Send up event to the second window - NotifyMotionArgs upMotionArgs = - generateMotionArgs(AMOTION_EVENT_ACTION_UP, AINPUT_SOURCE_TOUCHSCREEN, - ADISPLAY_ID_DEFAULT); - mDispatcher->notifyMotion(&upMotionArgs); + mDispatcher->notifyMotion(generateMotionArgs(AMOTION_EVENT_ACTION_UP, AINPUT_SOURCE_TOUCHSCREEN, + ADISPLAY_ID_DEFAULT)); // The first window gets no events and the second gets up firstWindow->assertNoEvents(); secondWindow->consumeMotionUp(); @@ -2940,18 +4597,20 @@ INSTANTIATE_TEST_SUITE_P(TransferFunctionTests, TransferTouchFixture, [&](const std::unique_ptr& dispatcher, sp from, sp to) { return dispatcher->transferTouchFocus(from, to, - false /*isDragAndDrop*/); + /*isDragAndDrop=*/false); })); TEST_F(InputDispatcherTest, TransferTouchFocus_TwoPointersSplitTouch) { std::shared_ptr application = std::make_shared(); sp firstWindow = - new FakeWindowHandle(application, mDispatcher, "First Window", ADISPLAY_ID_DEFAULT); + sp::make(application, mDispatcher, "First Window", + ADISPLAY_ID_DEFAULT); firstWindow->setFrame(Rect(0, 0, 600, 400)); sp secondWindow = - new FakeWindowHandle(application, mDispatcher, "Second Window", ADISPLAY_ID_DEFAULT); + sp::make(application, mDispatcher, "Second Window", + ADISPLAY_ID_DEFAULT); secondWindow->setFrame(Rect(0, 400, 600, 800)); // Add the windows to the dispatcher @@ -2961,19 +4620,17 @@ TEST_F(InputDispatcherTest, TransferTouchFocus_TwoPointersSplitTouch) { PointF pointInSecond = {300, 600}; // Send down to the first window - NotifyMotionArgs firstDownMotionArgs = - generateMotionArgs(AMOTION_EVENT_ACTION_DOWN, AINPUT_SOURCE_TOUCHSCREEN, - ADISPLAY_ID_DEFAULT, {pointInFirst}); - mDispatcher->notifyMotion(&firstDownMotionArgs); + mDispatcher->notifyMotion(generateMotionArgs(AMOTION_EVENT_ACTION_DOWN, + AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT, + {pointInFirst})); // Only the first window should get the down event firstWindow->consumeMotionDown(); secondWindow->assertNoEvents(); // Send down to the second window - NotifyMotionArgs secondDownMotionArgs = - generateMotionArgs(POINTER_1_DOWN, AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT, - {pointInFirst, pointInSecond}); - mDispatcher->notifyMotion(&secondDownMotionArgs); + mDispatcher->notifyMotion(generateMotionArgs(POINTER_1_DOWN, AINPUT_SOURCE_TOUCHSCREEN, + ADISPLAY_ID_DEFAULT, + {pointInFirst, pointInSecond})); // The first window gets a move and the second a down firstWindow->consumeMotionMove(); secondWindow->consumeMotionDown(); @@ -2985,19 +4642,16 @@ TEST_F(InputDispatcherTest, TransferTouchFocus_TwoPointersSplitTouch) { secondWindow->consumeMotionPointerDown(1); // Send pointer up to the second window - NotifyMotionArgs pointerUpMotionArgs = - generateMotionArgs(POINTER_1_UP, AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT, - {pointInFirst, pointInSecond}); - mDispatcher->notifyMotion(&pointerUpMotionArgs); + mDispatcher->notifyMotion(generateMotionArgs(POINTER_1_UP, AINPUT_SOURCE_TOUCHSCREEN, + ADISPLAY_ID_DEFAULT, + {pointInFirst, pointInSecond})); // The first window gets nothing and the second gets pointer up firstWindow->assertNoEvents(); secondWindow->consumeMotionPointerUp(1); // Send up event to the second window - NotifyMotionArgs upMotionArgs = - generateMotionArgs(AMOTION_EVENT_ACTION_UP, AINPUT_SOURCE_TOUCHSCREEN, - ADISPLAY_ID_DEFAULT); - mDispatcher->notifyMotion(&upMotionArgs); + mDispatcher->notifyMotion(generateMotionArgs(AMOTION_EVENT_ACTION_UP, AINPUT_SOURCE_TOUCHSCREEN, + ADISPLAY_ID_DEFAULT)); // The first window gets nothing and the second gets up firstWindow->assertNoEvents(); secondWindow->consumeMotionUp(); @@ -3011,11 +4665,13 @@ TEST_F(InputDispatcherTest, TransferTouch_TwoPointersSplitTouch) { std::shared_ptr application = std::make_shared(); sp firstWindow = - new FakeWindowHandle(application, mDispatcher, "First Window", ADISPLAY_ID_DEFAULT); + sp::make(application, mDispatcher, "First Window", + ADISPLAY_ID_DEFAULT); firstWindow->setFrame(Rect(0, 0, 600, 400)); sp secondWindow = - new FakeWindowHandle(application, mDispatcher, "Second Window", ADISPLAY_ID_DEFAULT); + sp::make(application, mDispatcher, "Second Window", + ADISPLAY_ID_DEFAULT); secondWindow->setFrame(Rect(0, 400, 600, 800)); // Add the windows to the dispatcher @@ -3025,19 +4681,17 @@ TEST_F(InputDispatcherTest, TransferTouch_TwoPointersSplitTouch) { PointF pointInSecond = {300, 600}; // Send down to the first window - NotifyMotionArgs firstDownMotionArgs = - generateMotionArgs(AMOTION_EVENT_ACTION_DOWN, AINPUT_SOURCE_TOUCHSCREEN, - ADISPLAY_ID_DEFAULT, {pointInFirst}); - mDispatcher->notifyMotion(&firstDownMotionArgs); + mDispatcher->notifyMotion(generateMotionArgs(AMOTION_EVENT_ACTION_DOWN, + AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT, + {pointInFirst})); // Only the first window should get the down event firstWindow->consumeMotionDown(); secondWindow->assertNoEvents(); // Send down to the second window - NotifyMotionArgs secondDownMotionArgs = - generateMotionArgs(POINTER_1_DOWN, AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT, - {pointInFirst, pointInSecond}); - mDispatcher->notifyMotion(&secondDownMotionArgs); + mDispatcher->notifyMotion(generateMotionArgs(POINTER_1_DOWN, AINPUT_SOURCE_TOUCHSCREEN, + ADISPLAY_ID_DEFAULT, + {pointInFirst, pointInSecond})); // The first window gets a move and the second a down firstWindow->consumeMotionMove(); secondWindow->consumeMotionDown(); @@ -3052,19 +4706,16 @@ TEST_F(InputDispatcherTest, TransferTouch_TwoPointersSplitTouch) { // The rest of the dispatch should proceed as normal // Send pointer up to the second window - NotifyMotionArgs pointerUpMotionArgs = - generateMotionArgs(POINTER_1_UP, AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT, - {pointInFirst, pointInSecond}); - mDispatcher->notifyMotion(&pointerUpMotionArgs); + mDispatcher->notifyMotion(generateMotionArgs(POINTER_1_UP, AINPUT_SOURCE_TOUCHSCREEN, + ADISPLAY_ID_DEFAULT, + {pointInFirst, pointInSecond})); // The first window gets MOVE and the second gets pointer up firstWindow->consumeMotionMove(); secondWindow->consumeMotionUp(); // Send up event to the first window - NotifyMotionArgs upMotionArgs = - generateMotionArgs(AMOTION_EVENT_ACTION_UP, AINPUT_SOURCE_TOUCHSCREEN, - ADISPLAY_ID_DEFAULT); - mDispatcher->notifyMotion(&upMotionArgs); + mDispatcher->notifyMotion(generateMotionArgs(AMOTION_EVENT_ACTION_UP, AINPUT_SOURCE_TOUCHSCREEN, + ADISPLAY_ID_DEFAULT)); // The first window gets nothing and the second gets up firstWindow->consumeMotionUp(); secondWindow->assertNoEvents(); @@ -3076,10 +4727,10 @@ TEST_F(InputDispatcherTest, TransferTouch_TwoPointersSplitTouch) { TEST_F(InputDispatcherTest, TransferTouchFocus_CloneSurface) { std::shared_ptr application = std::make_shared(); sp firstWindowInPrimary = - new FakeWindowHandle(application, mDispatcher, "D_1_W1", ADISPLAY_ID_DEFAULT); + sp::make(application, mDispatcher, "D_1_W1", ADISPLAY_ID_DEFAULT); firstWindowInPrimary->setFrame(Rect(0, 0, 100, 100)); sp secondWindowInPrimary = - new FakeWindowHandle(application, mDispatcher, "D_1_W2", ADISPLAY_ID_DEFAULT); + sp::make(application, mDispatcher, "D_1_W2", ADISPLAY_ID_DEFAULT); secondWindowInPrimary->setFrame(Rect(100, 0, 200, 100)); sp mirrorWindowInPrimary = @@ -3135,10 +4786,10 @@ TEST_F(InputDispatcherTest, TransferTouchFocus_CloneSurface) { TEST_F(InputDispatcherTest, TransferTouch_CloneSurface) { std::shared_ptr application = std::make_shared(); sp firstWindowInPrimary = - new FakeWindowHandle(application, mDispatcher, "D_1_W1", ADISPLAY_ID_DEFAULT); + sp::make(application, mDispatcher, "D_1_W1", ADISPLAY_ID_DEFAULT); firstWindowInPrimary->setFrame(Rect(0, 0, 100, 100)); sp secondWindowInPrimary = - new FakeWindowHandle(application, mDispatcher, "D_1_W2", ADISPLAY_ID_DEFAULT); + sp::make(application, mDispatcher, "D_1_W2", ADISPLAY_ID_DEFAULT); secondWindowInPrimary->setFrame(Rect(100, 0, 200, 100)); sp mirrorWindowInPrimary = @@ -3190,31 +4841,117 @@ TEST_F(InputDispatcherTest, TransferTouch_CloneSurface) { TEST_F(InputDispatcherTest, FocusedWindow_ReceivesFocusEventAndKeyEvent) { std::shared_ptr application = std::make_shared(); - sp window = - new FakeWindowHandle(application, mDispatcher, "Fake Window", ADISPLAY_ID_DEFAULT); + sp window = sp::make(application, mDispatcher, + "Fake Window", ADISPLAY_ID_DEFAULT); + + window->setFocusable(true); + mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {window}}}); + setFocusedWindow(window); + + window->consumeFocusEvent(true); + + mDispatcher->notifyKey(generateKeyArgs(AKEY_EVENT_ACTION_DOWN, ADISPLAY_ID_DEFAULT)); + + // Window should receive key down event. + window->consumeKeyDown(ADISPLAY_ID_DEFAULT); + + // Should have poked user activity + mFakePolicy->assertUserActivityPoked(); +} + +TEST_F(InputDispatcherTest, FocusedWindow_DisableUserActivity) { + std::shared_ptr application = std::make_shared(); + sp window = sp::make(application, mDispatcher, + "Fake Window", ADISPLAY_ID_DEFAULT); + window->setDisableUserActivity(true); window->setFocusable(true); mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {window}}}); setFocusedWindow(window); window->consumeFocusEvent(true); - NotifyKeyArgs keyArgs = generateKeyArgs(AKEY_EVENT_ACTION_DOWN, ADISPLAY_ID_DEFAULT); - mDispatcher->notifyKey(&keyArgs); + mDispatcher->notifyKey(generateKeyArgs(AKEY_EVENT_ACTION_DOWN, ADISPLAY_ID_DEFAULT)); // Window should receive key down event. window->consumeKeyDown(ADISPLAY_ID_DEFAULT); + + // Should have poked user activity + mFakePolicy->assertUserActivityNotPoked(); +} + +TEST_F(InputDispatcherTest, FocusedWindow_DoesNotReceiveSystemShortcut) { + std::shared_ptr application = std::make_shared(); + sp window = sp::make(application, mDispatcher, + "Fake Window", ADISPLAY_ID_DEFAULT); + + window->setFocusable(true); + mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {window}}}); + setFocusedWindow(window); + + window->consumeFocusEvent(true); + + mDispatcher->notifyKey(generateSystemShortcutArgs(AKEY_EVENT_ACTION_DOWN, ADISPLAY_ID_DEFAULT)); + mDispatcher->waitForIdle(); + + // System key is not passed down + window->assertNoEvents(); + + // Should have poked user activity + mFakePolicy->assertUserActivityPoked(); +} + +TEST_F(InputDispatcherTest, FocusedWindow_DoesNotReceiveAssistantKey) { + std::shared_ptr application = std::make_shared(); + sp window = sp::make(application, mDispatcher, + "Fake Window", ADISPLAY_ID_DEFAULT); + + window->setFocusable(true); + mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {window}}}); + setFocusedWindow(window); + + window->consumeFocusEvent(true); + + mDispatcher->notifyKey(generateAssistantKeyArgs(AKEY_EVENT_ACTION_DOWN, ADISPLAY_ID_DEFAULT)); + mDispatcher->waitForIdle(); + + // System key is not passed down + window->assertNoEvents(); + + // Should have poked user activity + mFakePolicy->assertUserActivityPoked(); +} + +TEST_F(InputDispatcherTest, FocusedWindow_SystemKeyIgnoresDisableUserActivity) { + std::shared_ptr application = std::make_shared(); + sp window = sp::make(application, mDispatcher, + "Fake Window", ADISPLAY_ID_DEFAULT); + + window->setDisableUserActivity(true); + window->setFocusable(true); + mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {window}}}); + setFocusedWindow(window); + + window->consumeFocusEvent(true); + + mDispatcher->notifyKey(generateSystemShortcutArgs(AKEY_EVENT_ACTION_DOWN, ADISPLAY_ID_DEFAULT)); + mDispatcher->waitForIdle(); + + // System key is not passed down + window->assertNoEvents(); + + // Should have poked user activity + mFakePolicy->assertUserActivityPoked(); } TEST_F(InputDispatcherTest, UnfocusedWindow_DoesNotReceiveFocusEventOrKeyEvent) { std::shared_ptr application = std::make_shared(); - sp window = - new FakeWindowHandle(application, mDispatcher, "Fake Window", ADISPLAY_ID_DEFAULT); + sp window = sp::make(application, mDispatcher, + "Fake Window", ADISPLAY_ID_DEFAULT); mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {window}}}); - NotifyKeyArgs keyArgs = generateKeyArgs(AKEY_EVENT_ACTION_DOWN, ADISPLAY_ID_DEFAULT); - mDispatcher->notifyKey(&keyArgs); + mDispatcher->notifyKey(generateKeyArgs(AKEY_EVENT_ACTION_DOWN, ADISPLAY_ID_DEFAULT)); mDispatcher->waitForIdle(); window->assertNoEvents(); @@ -3223,19 +4960,16 @@ 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 = - new FakeWindowHandle(application, mDispatcher, "Fake Window", ADISPLAY_ID_DEFAULT); + sp window = sp::make(application, mDispatcher, + "Fake Window", ADISPLAY_ID_DEFAULT); mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {window}}}); // Send key - NotifyKeyArgs keyArgs = generateKeyArgs(AKEY_EVENT_ACTION_DOWN, ADISPLAY_ID_DEFAULT); - mDispatcher->notifyKey(&keyArgs); + mDispatcher->notifyKey(generateKeyArgs(AKEY_EVENT_ACTION_DOWN, ADISPLAY_ID_DEFAULT)); // Send motion - NotifyMotionArgs motionArgs = - generateMotionArgs(AMOTION_EVENT_ACTION_DOWN, AINPUT_SOURCE_TOUCHSCREEN, - ADISPLAY_ID_DEFAULT); - mDispatcher->notifyMotion(&motionArgs); + mDispatcher->notifyMotion(generateMotionArgs(AMOTION_EVENT_ACTION_DOWN, + AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT)); // Window should receive only the motion event window->consumeMotionDown(ADISPLAY_ID_DEFAULT); @@ -3246,11 +4980,13 @@ TEST_F(InputDispatcherTest, PointerCancel_SendCancelWhenSplitTouch) { std::shared_ptr application = std::make_shared(); sp firstWindow = - new FakeWindowHandle(application, mDispatcher, "First Window", ADISPLAY_ID_DEFAULT); + sp::make(application, mDispatcher, "First Window", + ADISPLAY_ID_DEFAULT); firstWindow->setFrame(Rect(0, 0, 600, 400)); sp secondWindow = - new FakeWindowHandle(application, mDispatcher, "Second Window", ADISPLAY_ID_DEFAULT); + sp::make(application, mDispatcher, "Second Window", + ADISPLAY_ID_DEFAULT); secondWindow->setFrame(Rect(0, 400, 600, 800)); // Add the windows to the dispatcher @@ -3260,19 +4996,17 @@ TEST_F(InputDispatcherTest, PointerCancel_SendCancelWhenSplitTouch) { PointF pointInSecond = {300, 600}; // Send down to the first window - NotifyMotionArgs firstDownMotionArgs = - generateMotionArgs(AMOTION_EVENT_ACTION_DOWN, AINPUT_SOURCE_TOUCHSCREEN, - ADISPLAY_ID_DEFAULT, {pointInFirst}); - mDispatcher->notifyMotion(&firstDownMotionArgs); + mDispatcher->notifyMotion(generateMotionArgs(AMOTION_EVENT_ACTION_DOWN, + AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT, + {pointInFirst})); // Only the first window should get the down event firstWindow->consumeMotionDown(); secondWindow->assertNoEvents(); // Send down to the second window - NotifyMotionArgs secondDownMotionArgs = - generateMotionArgs(POINTER_1_DOWN, AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT, - {pointInFirst, pointInSecond}); - mDispatcher->notifyMotion(&secondDownMotionArgs); + mDispatcher->notifyMotion(generateMotionArgs(POINTER_1_DOWN, AINPUT_SOURCE_TOUCHSCREEN, + ADISPLAY_ID_DEFAULT, + {pointInFirst, pointInSecond})); // The first window gets a move and the second a down firstWindow->consumeMotionMove(); secondWindow->consumeMotionDown(); @@ -3282,16 +5016,14 @@ TEST_F(InputDispatcherTest, PointerCancel_SendCancelWhenSplitTouch) { generateMotionArgs(POINTER_1_UP, AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT, {pointInFirst, pointInSecond}); pointerUpMotionArgs.flags |= AMOTION_EVENT_FLAG_CANCELED; - mDispatcher->notifyMotion(&pointerUpMotionArgs); + 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); // Send up event. - NotifyMotionArgs upMotionArgs = - generateMotionArgs(AMOTION_EVENT_ACTION_UP, AINPUT_SOURCE_TOUCHSCREEN, - ADISPLAY_ID_DEFAULT); - mDispatcher->notifyMotion(&upMotionArgs); + mDispatcher->notifyMotion(generateMotionArgs(AMOTION_EVENT_ACTION_UP, AINPUT_SOURCE_TOUCHSCREEN, + ADISPLAY_ID_DEFAULT)); // The first window gets up and the second gets nothing. firstWindow->consumeMotionUp(); secondWindow->assertNoEvents(); @@ -3301,13 +5033,13 @@ TEST_F(InputDispatcherTest, SendTimeline_DoesNotCrashDispatcher) { std::shared_ptr application = std::make_shared(); sp window = - new FakeWindowHandle(application, mDispatcher, "Window", ADISPLAY_ID_DEFAULT); + sp::make(application, mDispatcher, "Window", ADISPLAY_ID_DEFAULT); mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {window}}}); std::array graphicsTimeline; graphicsTimeline[GraphicsTimeline::GPU_COMPLETED_TIME] = 2; graphicsTimeline[GraphicsTimeline::PRESENT_TIME] = 3; - window->sendTimeline(1 /*inputEventId*/, graphicsTimeline); + window->sendTimeline(/*inputEventId=*/1, graphicsTimeline); window->assertNoEvents(); mDispatcher->waitForIdle(); } @@ -3324,8 +5056,8 @@ public: sp getToken() { return mInputReceiver->getToken(); } void consumeKeyDown(int32_t expectedDisplayId, int32_t expectedFlags = 0) { - mInputReceiver->consumeEvent(AINPUT_EVENT_TYPE_KEY, AKEY_EVENT_ACTION_DOWN, - expectedDisplayId, expectedFlags); + mInputReceiver->consumeEvent(InputEventType::KEY, AKEY_EVENT_ACTION_DOWN, expectedDisplayId, + expectedFlags); } std::optional receiveEvent() { return mInputReceiver->receiveEvent(); } @@ -3333,30 +5065,32 @@ public: void finishEvent(uint32_t consumeSeq) { return mInputReceiver->finishEvent(consumeSeq); } void consumeMotionDown(int32_t expectedDisplayId, int32_t expectedFlags = 0) { - mInputReceiver->consumeEvent(AINPUT_EVENT_TYPE_MOTION, AMOTION_EVENT_ACTION_DOWN, + mInputReceiver->consumeEvent(InputEventType::MOTION, AMOTION_EVENT_ACTION_DOWN, expectedDisplayId, expectedFlags); } void consumeMotionMove(int32_t expectedDisplayId, int32_t expectedFlags = 0) { - mInputReceiver->consumeEvent(AINPUT_EVENT_TYPE_MOTION, AMOTION_EVENT_ACTION_MOVE, + mInputReceiver->consumeEvent(InputEventType::MOTION, AMOTION_EVENT_ACTION_MOVE, expectedDisplayId, expectedFlags); } void consumeMotionUp(int32_t expectedDisplayId, int32_t expectedFlags = 0) { - mInputReceiver->consumeEvent(AINPUT_EVENT_TYPE_MOTION, AMOTION_EVENT_ACTION_UP, + mInputReceiver->consumeEvent(InputEventType::MOTION, AMOTION_EVENT_ACTION_UP, expectedDisplayId, expectedFlags); } void consumeMotionCancel(int32_t expectedDisplayId, int32_t expectedFlags = 0) { - mInputReceiver->consumeEvent(AINPUT_EVENT_TYPE_MOTION, AMOTION_EVENT_ACTION_CANCEL, - expectedDisplayId, expectedFlags); + mInputReceiver->consumeMotionEvent( + AllOf(WithMotionAction(AMOTION_EVENT_ACTION_CANCEL), + WithDisplayId(expectedDisplayId), + WithFlags(expectedFlags | AMOTION_EVENT_FLAG_CANCELED))); } void consumeMotionPointerDown(int32_t pointerIdx) { int32_t action = AMOTION_EVENT_ACTION_POINTER_DOWN | (pointerIdx << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT); - mInputReceiver->consumeEvent(AINPUT_EVENT_TYPE_MOTION, action, ADISPLAY_ID_DEFAULT, - 0 /*expectedFlags*/); + mInputReceiver->consumeEvent(InputEventType::MOTION, action, ADISPLAY_ID_DEFAULT, + /*expectedFlags=*/0); } MotionEvent* consumeMotion() { @@ -3365,8 +5099,8 @@ public: ADD_FAILURE() << "No event was produced"; return nullptr; } - if (event->getType() != AINPUT_EVENT_TYPE_MOTION) { - ADD_FAILURE() << "Received event of type " << event->getType() << " instead of motion"; + if (event->getType() != InputEventType::MOTION) { + ADD_FAILURE() << "Expected MotionEvent, got " << *event; return nullptr; } return static_cast(event); @@ -3391,7 +5125,7 @@ using InputDispatcherMonitorTest = InputDispatcherTest; TEST_F(InputDispatcherMonitorTest, MonitorTouchIsCanceledWhenForegroundWindowDisappears) { std::shared_ptr application = std::make_shared(); sp window = - new FakeWindowHandle(application, mDispatcher, "Foreground", ADISPLAY_ID_DEFAULT); + sp::make(application, mDispatcher, "Foreground", ADISPLAY_ID_DEFAULT); FakeMonitorReceiver monitor = FakeMonitorReceiver(mDispatcher, "M_1", ADISPLAY_ID_DEFAULT); @@ -3431,8 +5165,8 @@ TEST_F(InputDispatcherMonitorTest, MonitorTouchIsCanceledWhenForegroundWindowDis TEST_F(InputDispatcherMonitorTest, ReceivesMotionEvents) { std::shared_ptr application = std::make_shared(); - sp window = - new FakeWindowHandle(application, mDispatcher, "Fake Window", ADISPLAY_ID_DEFAULT); + sp window = sp::make(application, mDispatcher, + "Fake Window", ADISPLAY_ID_DEFAULT); mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {window}}}); FakeMonitorReceiver monitor = FakeMonitorReceiver(mDispatcher, "M_1", ADISPLAY_ID_DEFAULT); @@ -3448,8 +5182,8 @@ TEST_F(InputDispatcherMonitorTest, MonitorCannotPilferPointers) { FakeMonitorReceiver monitor = FakeMonitorReceiver(mDispatcher, "M_1", ADISPLAY_ID_DEFAULT); std::shared_ptr application = std::make_shared(); - sp window = - new FakeWindowHandle(application, mDispatcher, "Fake Window", ADISPLAY_ID_DEFAULT); + sp window = sp::make(application, mDispatcher, + "Fake Window", ADISPLAY_ID_DEFAULT); mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {window}}}); ASSERT_EQ(InputEventInjectionResult::SUCCEEDED, @@ -3473,8 +5207,8 @@ TEST_F(InputDispatcherMonitorTest, MonitorCannotPilferPointers) { TEST_F(InputDispatcherMonitorTest, NoWindowTransform) { std::shared_ptr application = std::make_shared(); - sp window = - new FakeWindowHandle(application, mDispatcher, "Fake Window", ADISPLAY_ID_DEFAULT); + sp window = sp::make(application, mDispatcher, + "Fake Window", ADISPLAY_ID_DEFAULT); mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {window}}}); window->setWindowOffset(20, 40); window->setWindowTransform(0, 1, -1, 0); @@ -3502,8 +5236,8 @@ TEST_F(InputDispatcherMonitorTest, InjectionFailsWithNoWindow) { TEST_F(InputDispatcherTest, TestMoveEvent) { std::shared_ptr application = std::make_shared(); - sp window = - new FakeWindowHandle(application, mDispatcher, "Fake Window", ADISPLAY_ID_DEFAULT); + sp window = sp::make(application, mDispatcher, + "Fake Window", ADISPLAY_ID_DEFAULT); mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {window}}}); @@ -3511,7 +5245,7 @@ TEST_F(InputDispatcherTest, TestMoveEvent) { generateMotionArgs(AMOTION_EVENT_ACTION_DOWN, AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT); - mDispatcher->notifyMotion(&motionArgs); + mDispatcher->notifyMotion(motionArgs); // Window should receive motion down event. window->consumeMotionDown(ADISPLAY_ID_DEFAULT); @@ -3521,9 +5255,9 @@ TEST_F(InputDispatcherTest, TestMoveEvent) { motionArgs.pointerCoords[0].setAxisValue(AMOTION_EVENT_AXIS_X, motionArgs.pointerCoords[0].getX() - 10); - mDispatcher->notifyMotion(&motionArgs); - window->consumeEvent(AINPUT_EVENT_TYPE_MOTION, AMOTION_EVENT_ACTION_MOVE, ADISPLAY_ID_DEFAULT, - 0 /*expectedFlags*/); + mDispatcher->notifyMotion(motionArgs); + window->consumeEvent(InputEventType::MOTION, AMOTION_EVENT_ACTION_MOVE, ADISPLAY_ID_DEFAULT, + /*expectedFlags=*/0); } /** @@ -3533,8 +5267,8 @@ TEST_F(InputDispatcherTest, TestMoveEvent) { */ TEST_F(InputDispatcherTest, TouchModeState_IsSentToApps) { std::shared_ptr application = std::make_shared(); - sp window = - new FakeWindowHandle(application, mDispatcher, "Test window", ADISPLAY_ID_DEFAULT); + sp window = sp::make(application, mDispatcher, + "Test window", ADISPLAY_ID_DEFAULT); const WindowInfo& windowInfo = *window->getInfo(); // Set focused application. @@ -3544,43 +5278,43 @@ TEST_F(InputDispatcherTest, TouchModeState_IsSentToApps) { SCOPED_TRACE("Check default value of touch mode"); mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {window}}}); setFocusedWindow(window); - window->consumeFocusEvent(true /*hasFocus*/, true /*inTouchMode*/); + window->consumeFocusEvent(/*hasFocus=*/true, /*inTouchMode=*/true); SCOPED_TRACE("Remove the window to trigger focus loss"); window->setFocusable(false); mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {window}}}); - window->consumeFocusEvent(false /*hasFocus*/, true /*inTouchMode*/); + window->consumeFocusEvent(/*hasFocus=*/false, /*inTouchMode=*/true); SCOPED_TRACE("Disable touch mode"); mDispatcher->setInTouchMode(false, windowInfo.ownerPid, windowInfo.ownerUid, - /* hasPermission */ true); + /*hasPermission=*/true, ADISPLAY_ID_DEFAULT); window->consumeTouchModeEvent(false); window->setFocusable(true); mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {window}}}); setFocusedWindow(window); - window->consumeFocusEvent(true /*hasFocus*/, false /*inTouchMode*/); + window->consumeFocusEvent(/*hasFocus=*/true, /*inTouchMode=*/false); SCOPED_TRACE("Remove the window to trigger focus loss"); window->setFocusable(false); mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {window}}}); - window->consumeFocusEvent(false /*hasFocus*/, false /*inTouchMode*/); + window->consumeFocusEvent(/*hasFocus=*/false, /*inTouchMode=*/false); SCOPED_TRACE("Enable touch mode again"); mDispatcher->setInTouchMode(true, windowInfo.ownerPid, windowInfo.ownerUid, - /* hasPermission */ true); + /*hasPermission=*/true, ADISPLAY_ID_DEFAULT); window->consumeTouchModeEvent(true); window->setFocusable(true); mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {window}}}); setFocusedWindow(window); - window->consumeFocusEvent(true /*hasFocus*/, true /*inTouchMode*/); + window->consumeFocusEvent(/*hasFocus=*/true, /*inTouchMode=*/true); window->assertNoEvents(); } TEST_F(InputDispatcherTest, VerifyInputEvent_KeyEvent) { std::shared_ptr application = std::make_shared(); - sp window = - new FakeWindowHandle(application, mDispatcher, "Test window", ADISPLAY_ID_DEFAULT); + sp window = sp::make(application, mDispatcher, + "Test window", ADISPLAY_ID_DEFAULT); mDispatcher->setFocusedApplication(ADISPLAY_ID_DEFAULT, application); window->setFocusable(true); @@ -3588,10 +5322,10 @@ TEST_F(InputDispatcherTest, VerifyInputEvent_KeyEvent) { mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {window}}}); setFocusedWindow(window); - window->consumeFocusEvent(true /*hasFocus*/, true /*inTouchMode*/); + window->consumeFocusEvent(/*hasFocus=*/true, /*inTouchMode=*/true); - NotifyKeyArgs keyArgs = generateKeyArgs(AKEY_EVENT_ACTION_DOWN); - mDispatcher->notifyKey(&keyArgs); + const NotifyKeyArgs keyArgs = generateKeyArgs(AKEY_EVENT_ACTION_DOWN); + mDispatcher->notifyKey(keyArgs); InputEvent* event = window->consume(); ASSERT_NE(event, nullptr); @@ -3618,8 +5352,8 @@ TEST_F(InputDispatcherTest, VerifyInputEvent_KeyEvent) { TEST_F(InputDispatcherTest, VerifyInputEvent_MotionEvent) { std::shared_ptr application = std::make_shared(); - sp window = - new FakeWindowHandle(application, mDispatcher, "Test window", ADISPLAY_ID_DEFAULT); + sp window = sp::make(application, mDispatcher, + "Test window", ADISPLAY_ID_DEFAULT); mDispatcher->setFocusedApplication(ADISPLAY_ID_DEFAULT, application); @@ -3630,12 +5364,12 @@ TEST_F(InputDispatcherTest, VerifyInputEvent_MotionEvent) { displayInfo.displayId = ADISPLAY_ID_DEFAULT; displayInfo.transform = transform; - mDispatcher->onWindowInfosChanged({*window->getInfo()}, {displayInfo}); + mDispatcher->onWindowInfosChanged({{*window->getInfo()}, {displayInfo}, 0, 0}); - NotifyMotionArgs motionArgs = + const NotifyMotionArgs motionArgs = generateMotionArgs(AMOTION_EVENT_ACTION_DOWN, AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT); - mDispatcher->notifyMotion(&motionArgs); + mDispatcher->notifyMotion(motionArgs); InputEvent* event = window->consume(); ASSERT_NE(event, nullptr); @@ -3723,9 +5457,9 @@ TEST_F(InputDispatcherTest, GeneratedHmac_ChangesWhenFieldsChange) { TEST_F(InputDispatcherTest, SetFocusedWindow) { std::shared_ptr application = std::make_shared(); sp windowTop = - new FakeWindowHandle(application, mDispatcher, "Top", ADISPLAY_ID_DEFAULT); + sp::make(application, mDispatcher, "Top", ADISPLAY_ID_DEFAULT); sp windowSecond = - new FakeWindowHandle(application, mDispatcher, "Second", ADISPLAY_ID_DEFAULT); + sp::make(application, mDispatcher, "Second", ADISPLAY_ID_DEFAULT); mDispatcher->setFocusedApplication(ADISPLAY_ID_DEFAULT, application); // Top window is also focusable but is not granted focus. @@ -3746,7 +5480,7 @@ TEST_F(InputDispatcherTest, SetFocusedWindow) { TEST_F(InputDispatcherTest, SetFocusedWindow_DropRequestInvalidChannel) { std::shared_ptr application = std::make_shared(); sp window = - new FakeWindowHandle(application, mDispatcher, "TestWindow", ADISPLAY_ID_DEFAULT); + sp::make(application, mDispatcher, "TestWindow", ADISPLAY_ID_DEFAULT); mDispatcher->setFocusedApplication(ADISPLAY_ID_DEFAULT, application); window->setFocusable(true); @@ -3766,7 +5500,7 @@ TEST_F(InputDispatcherTest, SetFocusedWindow_DropRequestInvalidChannel) { TEST_F(InputDispatcherTest, SetFocusedWindow_DropRequestNoFocusableWindow) { std::shared_ptr application = std::make_shared(); sp window = - new FakeWindowHandle(application, mDispatcher, "TestWindow", ADISPLAY_ID_DEFAULT); + sp::make(application, mDispatcher, "TestWindow", ADISPLAY_ID_DEFAULT); window->setFocusable(false); mDispatcher->setFocusedApplication(ADISPLAY_ID_DEFAULT, application); @@ -3784,9 +5518,9 @@ TEST_F(InputDispatcherTest, SetFocusedWindow_DropRequestNoFocusableWindow) { TEST_F(InputDispatcherTest, SetFocusedWindow_CheckFocusedToken) { std::shared_ptr application = std::make_shared(); sp windowTop = - new FakeWindowHandle(application, mDispatcher, "Top", ADISPLAY_ID_DEFAULT); + sp::make(application, mDispatcher, "Top", ADISPLAY_ID_DEFAULT); sp windowSecond = - new FakeWindowHandle(application, mDispatcher, "Second", ADISPLAY_ID_DEFAULT); + sp::make(application, mDispatcher, "Second", ADISPLAY_ID_DEFAULT); mDispatcher->setFocusedApplication(ADISPLAY_ID_DEFAULT, application); windowTop->setFocusable(true); @@ -3795,7 +5529,8 @@ TEST_F(InputDispatcherTest, SetFocusedWindow_CheckFocusedToken) { setFocusedWindow(windowTop); windowTop->consumeFocusEvent(true); - setFocusedWindow(windowSecond, windowTop); + windowTop->editInfo()->focusTransferTarget = windowSecond->getToken(); + mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {windowTop, windowSecond}}}); windowSecond->consumeFocusEvent(true); windowTop->consumeFocusEvent(false); @@ -3806,34 +5541,36 @@ TEST_F(InputDispatcherTest, SetFocusedWindow_CheckFocusedToken) { windowSecond->consumeKeyDown(ADISPLAY_ID_NONE); } -TEST_F(InputDispatcherTest, SetFocusedWindow_DropRequestFocusTokenNotFocused) { +TEST_F(InputDispatcherTest, SetFocusedWindow_TransferFocusTokenNotFocusable) { std::shared_ptr application = std::make_shared(); sp windowTop = - new FakeWindowHandle(application, mDispatcher, "Top", ADISPLAY_ID_DEFAULT); + sp::make(application, mDispatcher, "Top", ADISPLAY_ID_DEFAULT); sp windowSecond = - new FakeWindowHandle(application, mDispatcher, "Second", ADISPLAY_ID_DEFAULT); + sp::make(application, mDispatcher, "Second", ADISPLAY_ID_DEFAULT); mDispatcher->setFocusedApplication(ADISPLAY_ID_DEFAULT, application); windowTop->setFocusable(true); - windowSecond->setFocusable(true); + windowSecond->setFocusable(false); + windowTop->editInfo()->focusTransferTarget = windowSecond->getToken(); mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {windowTop, windowSecond}}}); - setFocusedWindow(windowSecond, windowTop); + setFocusedWindow(windowTop); + windowTop->consumeFocusEvent(true); - ASSERT_EQ(InputEventInjectionResult::TIMED_OUT, injectKeyDown(mDispatcher)) - << "Inject key event should return InputEventInjectionResult::TIMED_OUT"; + ASSERT_EQ(InputEventInjectionResult::SUCCEEDED, injectKeyDown(mDispatcher)) + << "Inject key event should return InputEventInjectionResult::SUCCEEDED"; // Event should be dropped. - windowTop->assertNoEvents(); + windowTop->consumeKeyDown(ADISPLAY_ID_NONE); windowSecond->assertNoEvents(); } TEST_F(InputDispatcherTest, SetFocusedWindow_DeferInvisibleWindow) { std::shared_ptr application = std::make_shared(); sp window = - new FakeWindowHandle(application, mDispatcher, "TestWindow", ADISPLAY_ID_DEFAULT); + sp::make(application, mDispatcher, "TestWindow", ADISPLAY_ID_DEFAULT); sp previousFocusedWindow = - new FakeWindowHandle(application, mDispatcher, "previousFocusedWindow", - ADISPLAY_ID_DEFAULT); + sp::make(application, mDispatcher, "previousFocusedWindow", + ADISPLAY_ID_DEFAULT); mDispatcher->setFocusedApplication(ADISPLAY_ID_DEFAULT, application); window->setFocusable(true); @@ -3849,8 +5586,8 @@ TEST_F(InputDispatcherTest, SetFocusedWindow_DeferInvisibleWindow) { // Injected key goes to pending queue. ASSERT_EQ(InputEventInjectionResult::SUCCEEDED, - injectKey(mDispatcher, AKEY_EVENT_ACTION_DOWN, 0 /* repeatCount */, - ADISPLAY_ID_DEFAULT, InputEventInjectionSync::NONE)); + injectKey(mDispatcher, AKEY_EVENT_ACTION_DOWN, /*repeatCount=*/0, ADISPLAY_ID_DEFAULT, + InputEventInjectionSync::NONE)); // Window does not get focus event or key down. window->assertNoEvents(); @@ -3868,7 +5605,7 @@ TEST_F(InputDispatcherTest, SetFocusedWindow_DeferInvisibleWindow) { TEST_F(InputDispatcherTest, DisplayRemoved) { std::shared_ptr application = std::make_shared(); sp window = - new FakeWindowHandle(application, mDispatcher, "window", ADISPLAY_ID_DEFAULT); + sp::make(application, mDispatcher, "window", ADISPLAY_ID_DEFAULT); mDispatcher->setFocusedApplication(ADISPLAY_ID_DEFAULT, application); // window is granted focus. @@ -3913,7 +5650,7 @@ TEST_F(InputDispatcherTest, SlipperyWindow_SetsFlagPartiallyObscured) { mDispatcher->setFocusedApplication(ADISPLAY_ID_DEFAULT, application); sp slipperyExitWindow = - new FakeWindowHandle(application, mDispatcher, "Top", ADISPLAY_ID_DEFAULT); + sp::make(application, mDispatcher, "Top", ADISPLAY_ID_DEFAULT); slipperyExitWindow->setSlippery(true); // Make sure this one overlaps the bottom window slipperyExitWindow->setFrame(Rect(25, 25, 75, 75)); @@ -3922,24 +5659,24 @@ TEST_F(InputDispatcherTest, SlipperyWindow_SetsFlagPartiallyObscured) { slipperyExitWindow->setOwnerInfo(SLIPPERY_PID, SLIPPERY_UID); sp slipperyEnterWindow = - new FakeWindowHandle(application, mDispatcher, "Second", ADISPLAY_ID_DEFAULT); + sp::make(application, mDispatcher, "Second", ADISPLAY_ID_DEFAULT); slipperyExitWindow->setFrame(Rect(0, 0, 100, 100)); mDispatcher->setInputWindows( {{ADISPLAY_ID_DEFAULT, {slipperyExitWindow, slipperyEnterWindow}}}); // Use notifyMotion instead of injecting to avoid dealing with injection permissions - NotifyMotionArgs args = generateMotionArgs(AMOTION_EVENT_ACTION_DOWN, AINPUT_SOURCE_TOUCHSCREEN, - ADISPLAY_ID_DEFAULT, {{50, 50}}); - mDispatcher->notifyMotion(&args); + mDispatcher->notifyMotion(generateMotionArgs(AMOTION_EVENT_ACTION_DOWN, + AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT, + {{50, 50}})); slipperyExitWindow->consumeMotionDown(); slipperyExitWindow->setFrame(Rect(70, 70, 100, 100)); mDispatcher->setInputWindows( {{ADISPLAY_ID_DEFAULT, {slipperyExitWindow, slipperyEnterWindow}}}); - args = generateMotionArgs(AMOTION_EVENT_ACTION_MOVE, AINPUT_SOURCE_TOUCHSCREEN, - ADISPLAY_ID_DEFAULT, {{51, 51}}); - mDispatcher->notifyMotion(&args); + mDispatcher->notifyMotion(generateMotionArgs(AMOTION_EVENT_ACTION_MOVE, + AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT, + {{51, 51}})); slipperyExitWindow->consumeMotionCancel(); @@ -3947,6 +5684,46 @@ TEST_F(InputDispatcherTest, SlipperyWindow_SetsFlagPartiallyObscured) { AMOTION_EVENT_FLAG_WINDOW_IS_PARTIALLY_OBSCURED); } +/** + * Two windows, one on the left and another on the right. The left window is slippery. The right + * window isn't eligible to receive touch because it specifies InputConfig::DROP_INPUT. When the + * touch moves from the left window into the right window, the gesture should continue to go to the + * left window. Touch shouldn't slip because the right window can't receive touches. This test + * reproduces a crash. + */ +TEST_F(InputDispatcherTest, TouchSlippingIntoWindowThatDropsTouches) { + std::shared_ptr application = std::make_shared(); + + sp leftSlipperyWindow = + sp::make(application, mDispatcher, "Left", ADISPLAY_ID_DEFAULT); + leftSlipperyWindow->setSlippery(true); + leftSlipperyWindow->setFrame(Rect(0, 0, 100, 100)); + + sp rightDropTouchesWindow = + sp::make(application, mDispatcher, "Right", ADISPLAY_ID_DEFAULT); + rightDropTouchesWindow->setFrame(Rect(100, 0, 200, 100)); + rightDropTouchesWindow->setDropInput(true); + + mDispatcher->setInputWindows( + {{ADISPLAY_ID_DEFAULT, {leftSlipperyWindow, rightDropTouchesWindow}}}); + + // Start touch in the left window + mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_DOWN, AINPUT_SOURCE_TOUCHSCREEN) + .pointer(PointerBuilder(0, ToolType::FINGER).x(50).y(50)) + .build()); + leftSlipperyWindow->consumeMotionDown(); + + // And move it into the right window + mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_MOVE, AINPUT_SOURCE_TOUCHSCREEN) + .pointer(PointerBuilder(0, ToolType::FINGER).x(150).y(50)) + .build()); + + // Since the right window isn't eligible to receive input, touch does not slip. + // The left window continues to receive the gesture. + leftSlipperyWindow->consumeMotionEvent(WithMotionAction(ACTION_MOVE)); + rightDropTouchesWindow->assertNoEvents(); +} + class InputDispatcherKeyRepeatTest : public InputDispatcherTest { protected: static constexpr nsecs_t KEY_REPEAT_TIMEOUT = 40 * 1000000; // 40 ms @@ -3956,9 +5733,10 @@ protected: sp mWindow; virtual void SetUp() override { - mFakePolicy = new FakeInputDispatcherPolicy(); + mFakePolicy = std::make_unique(); mFakePolicy->setKeyRepeatConfiguration(KEY_REPEAT_TIMEOUT, KEY_REPEAT_DELAY); - mDispatcher = std::make_unique(mFakePolicy); + mDispatcher = std::make_unique(*mFakePolicy); + mDispatcher->requestRefreshConfiguration(); mDispatcher->setInputDispatchMode(/*enabled*/ true, /*frozen*/ false); ASSERT_EQ(OK, mDispatcher->start()); @@ -3967,7 +5745,7 @@ protected: void setUpWindow() { mApp = std::make_shared(); - mWindow = new FakeWindowHandle(mApp, mDispatcher, "Fake Window", ADISPLAY_ID_DEFAULT); + mWindow = sp::make(mApp, mDispatcher, "Fake Window", ADISPLAY_ID_DEFAULT); mWindow->setFocusable(true); mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {mWindow}}}); @@ -3979,7 +5757,7 @@ protected: NotifyKeyArgs keyArgs = generateKeyArgs(AKEY_EVENT_ACTION_DOWN, ADISPLAY_ID_DEFAULT); keyArgs.deviceId = deviceId; keyArgs.policyFlags |= POLICY_FLAG_TRUSTED; // Otherwise it won't generate repeat event - mDispatcher->notifyKey(&keyArgs); + mDispatcher->notifyKey(keyArgs); // Window should receive key down event. mWindow->consumeKeyDown(ADISPLAY_ID_DEFAULT); @@ -3990,8 +5768,7 @@ protected: InputEvent* repeatEvent = mWindow->consume(); ASSERT_NE(nullptr, repeatEvent); - uint32_t eventType = repeatEvent->getType(); - ASSERT_EQ(AINPUT_EVENT_TYPE_KEY, eventType); + ASSERT_EQ(InputEventType::KEY, repeatEvent->getType()); KeyEvent* repeatKeyEvent = static_cast(repeatEvent); uint32_t eventAction = repeatKeyEvent->getAction(); @@ -4003,27 +5780,27 @@ protected: NotifyKeyArgs keyArgs = generateKeyArgs(AKEY_EVENT_ACTION_UP, ADISPLAY_ID_DEFAULT); keyArgs.deviceId = deviceId; keyArgs.policyFlags |= POLICY_FLAG_TRUSTED; // Unless it won't generate repeat event - mDispatcher->notifyKey(&keyArgs); + mDispatcher->notifyKey(keyArgs); // Window should receive key down event. - mWindow->consumeEvent(AINPUT_EVENT_TYPE_KEY, AKEY_EVENT_ACTION_UP, ADISPLAY_ID_DEFAULT, - 0 /*expectedFlags*/); + mWindow->consumeEvent(InputEventType::KEY, AKEY_EVENT_ACTION_UP, ADISPLAY_ID_DEFAULT, + /*expectedFlags=*/0); } }; TEST_F(InputDispatcherKeyRepeatTest, FocusedWindow_ReceivesKeyRepeat) { - sendAndConsumeKeyDown(1 /* deviceId */); + sendAndConsumeKeyDown(/*deviceId=*/1); for (int32_t repeatCount = 1; repeatCount <= 10; ++repeatCount) { expectKeyRepeatOnce(repeatCount); } } TEST_F(InputDispatcherKeyRepeatTest, FocusedWindow_ReceivesKeyRepeatFromTwoDevices) { - sendAndConsumeKeyDown(1 /* deviceId */); + sendAndConsumeKeyDown(/*deviceId=*/1); for (int32_t repeatCount = 1; repeatCount <= 10; ++repeatCount) { expectKeyRepeatOnce(repeatCount); } - sendAndConsumeKeyDown(2 /* deviceId */); + sendAndConsumeKeyDown(/*deviceId=*/2); /* repeatCount will start from 1 for deviceId 2 */ for (int32_t repeatCount = 1; repeatCount <= 10; ++repeatCount) { expectKeyRepeatOnce(repeatCount); @@ -4031,50 +5808,50 @@ TEST_F(InputDispatcherKeyRepeatTest, FocusedWindow_ReceivesKeyRepeatFromTwoDevic } TEST_F(InputDispatcherKeyRepeatTest, FocusedWindow_StopsKeyRepeatAfterUp) { - sendAndConsumeKeyDown(1 /* deviceId */); - expectKeyRepeatOnce(1 /*repeatCount*/); - sendAndConsumeKeyUp(1 /* deviceId */); + sendAndConsumeKeyDown(/*deviceId=*/1); + expectKeyRepeatOnce(/*repeatCount=*/1); + sendAndConsumeKeyUp(/*deviceId=*/1); mWindow->assertNoEvents(); } TEST_F(InputDispatcherKeyRepeatTest, FocusedWindow_KeyRepeatAfterStaleDeviceKeyUp) { - sendAndConsumeKeyDown(1 /* deviceId */); - expectKeyRepeatOnce(1 /*repeatCount*/); - sendAndConsumeKeyDown(2 /* deviceId */); - expectKeyRepeatOnce(1 /*repeatCount*/); + sendAndConsumeKeyDown(/*deviceId=*/1); + expectKeyRepeatOnce(/*repeatCount=*/1); + sendAndConsumeKeyDown(/*deviceId=*/2); + expectKeyRepeatOnce(/*repeatCount=*/1); // Stale key up from device 1. - sendAndConsumeKeyUp(1 /* deviceId */); + sendAndConsumeKeyUp(/*deviceId=*/1); // Device 2 is still down, keep repeating - expectKeyRepeatOnce(2 /*repeatCount*/); - expectKeyRepeatOnce(3 /*repeatCount*/); + expectKeyRepeatOnce(/*repeatCount=*/2); + expectKeyRepeatOnce(/*repeatCount=*/3); // Device 2 key up - sendAndConsumeKeyUp(2 /* deviceId */); + sendAndConsumeKeyUp(/*deviceId=*/2); mWindow->assertNoEvents(); } TEST_F(InputDispatcherKeyRepeatTest, FocusedWindow_KeyRepeatStopsAfterRepeatingKeyUp) { - sendAndConsumeKeyDown(1 /* deviceId */); - expectKeyRepeatOnce(1 /*repeatCount*/); - sendAndConsumeKeyDown(2 /* deviceId */); - expectKeyRepeatOnce(1 /*repeatCount*/); + sendAndConsumeKeyDown(/*deviceId=*/1); + expectKeyRepeatOnce(/*repeatCount=*/1); + sendAndConsumeKeyDown(/*deviceId=*/2); + expectKeyRepeatOnce(/*repeatCount=*/1); // Device 2 which holds the key repeating goes up, expect the repeating to stop. - sendAndConsumeKeyUp(2 /* deviceId */); + sendAndConsumeKeyUp(/*deviceId=*/2); // Device 1 still holds key down, but the repeating was already stopped mWindow->assertNoEvents(); } TEST_F(InputDispatcherKeyRepeatTest, FocusedWindow_StopsKeyRepeatAfterDisableInputDevice) { sendAndConsumeKeyDown(DEVICE_ID); - expectKeyRepeatOnce(1 /*repeatCount*/); - NotifyDeviceResetArgs args(10 /*id*/, 20 /*eventTime*/, DEVICE_ID); - mDispatcher->notifyDeviceReset(&args); + expectKeyRepeatOnce(/*repeatCount=*/1); + mDispatcher->notifyDeviceReset({/*id=*/10, /*eventTime=*/20, DEVICE_ID}); mWindow->consumeKeyUp(ADISPLAY_ID_DEFAULT, AKEY_EVENT_FLAG_CANCELED | AKEY_EVENT_FLAG_LONG_PRESS); mWindow->assertNoEvents(); } TEST_F(InputDispatcherKeyRepeatTest, FocusedWindow_RepeatKeyEventsUseEventIdFromInputDispatcher) { - sendAndConsumeKeyDown(1 /* deviceId */); + GTEST_SKIP() << "Flaky test (b/270393106)"; + sendAndConsumeKeyDown(/*deviceId=*/1); for (int32_t repeatCount = 1; repeatCount <= 10; ++repeatCount) { InputEvent* repeatEvent = mWindow->consume(); ASSERT_NE(nullptr, repeatEvent) << "Didn't receive event with repeat count " << repeatCount; @@ -4084,7 +5861,8 @@ TEST_F(InputDispatcherKeyRepeatTest, FocusedWindow_RepeatKeyEventsUseEventIdFrom } TEST_F(InputDispatcherKeyRepeatTest, FocusedWindow_RepeatKeyEventsUseUniqueEventId) { - sendAndConsumeKeyDown(1 /* deviceId */); + GTEST_SKIP() << "Flaky test (b/270393106)"; + sendAndConsumeKeyDown(/*deviceId=*/1); std::unordered_set idSet; for (int32_t repeatCount = 1; repeatCount <= 10; ++repeatCount) { @@ -4104,7 +5882,7 @@ public: application1 = std::make_shared(); windowInPrimary = - new FakeWindowHandle(application1, mDispatcher, "D_1", ADISPLAY_ID_DEFAULT); + sp::make(application1, mDispatcher, "D_1", ADISPLAY_ID_DEFAULT); // Set focus window for primary display, but focused display would be second one. mDispatcher->setFocusedApplication(ADISPLAY_ID_DEFAULT, application1); @@ -4115,7 +5893,7 @@ public: application2 = std::make_shared(); windowInSecondary = - new FakeWindowHandle(application2, mDispatcher, "D_2", SECOND_DISPLAY_ID); + sp::make(application2, mDispatcher, "D_2", SECOND_DISPLAY_ID); // Set focus to second display window. // Set focus display to second one. mDispatcher->setFocusedDisplay(SECOND_DISPLAY_ID); @@ -4177,7 +5955,7 @@ TEST_F(InputDispatcherFocusOnTwoDisplaysTest, SetInputWindow_MultiDisplayFocus) mDispatcher->setInputWindows({{SECOND_DISPLAY_ID, {}}}); // Old focus should receive a cancel event. - windowInSecondary->consumeEvent(AINPUT_EVENT_TYPE_KEY, AKEY_EVENT_ACTION_UP, ADISPLAY_ID_NONE, + windowInSecondary->consumeEvent(InputEventType::KEY, AKEY_EVENT_ACTION_UP, ADISPLAY_ID_NONE, AKEY_EVENT_FLAG_CANCELED); // Test inject a key down, should timeout because of no target window. @@ -4213,6 +5991,13 @@ TEST_F(InputDispatcherFocusOnTwoDisplaysTest, MonitorMotionEvent_MultiDisplay) { windowInSecondary->consumeMotionDown(SECOND_DISPLAY_ID); monitorInSecondary.consumeMotionDown(SECOND_DISPLAY_ID); + // Lift up the touch from the second display + ASSERT_EQ(InputEventInjectionResult::SUCCEEDED, + injectMotionUp(mDispatcher, AINPUT_SOURCE_TOUCHSCREEN, SECOND_DISPLAY_ID)) + << "Inject motion event should return InputEventInjectionResult::SUCCEEDED"; + windowInSecondary->consumeMotionUp(SECOND_DISPLAY_ID); + monitorInSecondary.consumeMotionUp(SECOND_DISPLAY_ID); + // Test inject a non-pointer motion event. // 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. @@ -4244,7 +6029,7 @@ TEST_F(InputDispatcherFocusOnTwoDisplaysTest, MonitorKeyEvent_MultiDisplay) { TEST_F(InputDispatcherFocusOnTwoDisplaysTest, CanFocusWindowOnUnfocusedDisplay) { sp secondWindowInPrimary = - new FakeWindowHandle(application1, mDispatcher, "D_1_W2", ADISPLAY_ID_DEFAULT); + sp::make(application1, mDispatcher, "D_1_W2", ADISPLAY_ID_DEFAULT); secondWindowInPrimary->setFocusable(true); mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {windowInPrimary, secondWindowInPrimary}}}); setFocusedWindow(secondWindowInPrimary); @@ -4310,10 +6095,10 @@ protected: motionArgs = generateMotionArgs(AMOTION_EVENT_ACTION_DOWN, AINPUT_SOURCE_TOUCHSCREEN, displayId); - mDispatcher->notifyMotion(&motionArgs); + mDispatcher->notifyMotion(motionArgs); motionArgs = generateMotionArgs(AMOTION_EVENT_ACTION_UP, AINPUT_SOURCE_TOUCHSCREEN, displayId); - mDispatcher->notifyMotion(&motionArgs); + mDispatcher->notifyMotion(motionArgs); ASSERT_TRUE(mDispatcher->waitForIdle()); if (expectToBeFiltered) { const auto xy = transform.transform(motionArgs.pointerCoords->getXYValue()); @@ -4327,9 +6112,9 @@ protected: NotifyKeyArgs keyArgs; keyArgs = generateKeyArgs(AKEY_EVENT_ACTION_DOWN); - mDispatcher->notifyKey(&keyArgs); + mDispatcher->notifyKey(keyArgs); keyArgs = generateKeyArgs(AKEY_EVENT_ACTION_UP); - mDispatcher->notifyKey(&keyArgs); + mDispatcher->notifyKey(keyArgs); ASSERT_TRUE(mDispatcher->waitForIdle()); if (expectToBeFiltered) { @@ -4389,7 +6174,7 @@ TEST_F(InputFilterTest, MotionEvent_UsesLogicalDisplayCoordinates_notifyMotion) displayInfos[1].displayId = SECOND_DISPLAY_ID; displayInfos[1].transform = secondDisplayTransform; - mDispatcher->onWindowInfosChanged({}, displayInfos); + mDispatcher->onWindowInfosChanged({{}, displayInfos, 0, 0}); // Enable InputFilter mDispatcher->setInputFilterEnabled(true); @@ -4413,8 +6198,8 @@ protected: std::shared_ptr application = std::make_shared(); - mWindow = - new FakeWindowHandle(application, mDispatcher, "Test Window", ADISPLAY_ID_DEFAULT); + mWindow = sp::make(application, mDispatcher, "Test Window", + ADISPLAY_ID_DEFAULT); mDispatcher->setFocusedApplication(ADISPLAY_ID_DEFAULT, application); mWindow->setFocusable(true); @@ -4430,18 +6215,18 @@ 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, - KEY_A, AMETA_NONE, 0 /*repeatCount*/, eventTime, eventTime); + 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, - mDispatcher->injectInputEvent(&event, {} /*targetUid*/, + mDispatcher->injectInputEvent(&event, /*targetUid=*/{}, InputEventInjectionSync::WAIT_FOR_RESULT, 10ms, policyFlags | additionalPolicyFlags)); InputEvent* received = mWindow->consume(); ASSERT_NE(nullptr, received); ASSERT_EQ(resolvedDeviceId, received->getDeviceId()); - ASSERT_EQ(received->getType(), AINPUT_EVENT_TYPE_KEY); + ASSERT_EQ(received->getType(), InputEventType::KEY); KeyEvent& keyEvent = static_cast(*received); ASSERT_EQ(flags, keyEvent.getFlags()); } @@ -4469,14 +6254,14 @@ protected: const int32_t additionalPolicyFlags = POLICY_FLAG_PASS_TO_USER; ASSERT_EQ(InputEventInjectionResult::SUCCEEDED, - mDispatcher->injectInputEvent(&event, {} /*targetUid*/, + mDispatcher->injectInputEvent(&event, /*targetUid=*/{}, InputEventInjectionSync::WAIT_FOR_RESULT, 10ms, policyFlags | additionalPolicyFlags)); InputEvent* received = mWindow->consume(); ASSERT_NE(nullptr, received); ASSERT_EQ(resolvedDeviceId, received->getDeviceId()); - ASSERT_EQ(received->getType(), AINPUT_EVENT_TYPE_MOTION); + ASSERT_EQ(received->getType(), InputEventType::MOTION); MotionEvent& motionEvent = static_cast(*received); ASSERT_EQ(flags, motionEvent.getFlags()); } @@ -4489,26 +6274,26 @@ TEST_F(InputFilterInjectionPolicyTest, TrustedFilteredEvents_KeepOriginalDeviceI // Must have POLICY_FLAG_FILTERED here to indicate that the event has gone through the input // filter. Without it, the event will no different from a regularly injected event, and the // injected device id will be overwritten. - testInjectedKey(POLICY_FLAG_FILTERED, 3 /*injectedDeviceId*/, 3 /*resolvedDeviceId*/, - 0 /*flags*/); + testInjectedKey(POLICY_FLAG_FILTERED, /*injectedDeviceId=*/3, /*resolvedDeviceId=*/3, + /*flags=*/0); } TEST_F(InputFilterInjectionPolicyTest, KeyEventsInjectedFromAccessibility_HaveAccessibilityFlag) { testInjectedKey(POLICY_FLAG_FILTERED | POLICY_FLAG_INJECTED_FROM_ACCESSIBILITY, - 3 /*injectedDeviceId*/, 3 /*resolvedDeviceId*/, + /*injectedDeviceId=*/3, /*resolvedDeviceId=*/3, AKEY_EVENT_FLAG_IS_ACCESSIBILITY_EVENT); } TEST_F(InputFilterInjectionPolicyTest, MotionEventsInjectedFromAccessibility_HaveAccessibilityFlag) { testInjectedMotion(POLICY_FLAG_FILTERED | POLICY_FLAG_INJECTED_FROM_ACCESSIBILITY, - 3 /*injectedDeviceId*/, 3 /*resolvedDeviceId*/, + /*injectedDeviceId=*/3, /*resolvedDeviceId=*/3, AMOTION_EVENT_FLAG_IS_ACCESSIBILITY_EVENT); } TEST_F(InputFilterInjectionPolicyTest, RegularInjectedEvents_ReceiveVirtualDeviceId) { - testInjectedKey(0 /*policyFlags*/, 3 /*injectedDeviceId*/, - VIRTUAL_KEYBOARD_ID /*resolvedDeviceId*/, 0 /*flags*/); + testInjectedKey(/*policyFlags=*/0, /*injectedDeviceId=*/3, + /*resolvedDeviceId=*/VIRTUAL_KEYBOARD_ID, /*flags=*/0); } class InputDispatcherOnPointerDownOutsideFocus : public InputDispatcherTest { @@ -4518,11 +6303,11 @@ class InputDispatcherOnPointerDownOutsideFocus : public InputDispatcherTest { std::shared_ptr application = std::make_shared(); mUnfocusedWindow = - new FakeWindowHandle(application, mDispatcher, "Top", ADISPLAY_ID_DEFAULT); + sp::make(application, mDispatcher, "Top", ADISPLAY_ID_DEFAULT); mUnfocusedWindow->setFrame(Rect(0, 0, 30, 30)); mFocusedWindow = - new FakeWindowHandle(application, mDispatcher, "Second", ADISPLAY_ID_DEFAULT); + sp::make(application, mDispatcher, "Second", ADISPLAY_ID_DEFAULT); mFocusedWindow->setFrame(Rect(50, 50, 100, 100)); // Set focused application. @@ -4607,7 +6392,7 @@ TEST_F(InputDispatcherOnPointerDownOutsideFocus, NoFocusChangeFlag) { const MotionEvent event = MotionEventBuilder(AMOTION_EVENT_ACTION_DOWN, AINPUT_SOURCE_MOUSE) .eventTime(systemTime(SYSTEM_TIME_MONOTONIC)) - .pointer(PointerBuilder(/* id */ 0, AMOTION_EVENT_TOOL_TYPE_FINGER).x(20).y(20)) + .pointer(PointerBuilder(/*id=*/0, ToolType::FINGER).x(20).y(20)) .addFlag(AMOTION_EVENT_FLAG_NO_FOCUS_CHANGE) .build(); ASSERT_EQ(InputEventInjectionResult::SUCCEEDED, injectMotionEvent(mDispatcher, event)) @@ -4628,12 +6413,12 @@ class InputDispatcherMultiWindowSameTokenTests : public InputDispatcherTest { std::shared_ptr application = std::make_shared(); - mWindow1 = new FakeWindowHandle(application, mDispatcher, "Fake Window 1", - ADISPLAY_ID_DEFAULT); + mWindow1 = sp::make(application, mDispatcher, "Fake Window 1", + ADISPLAY_ID_DEFAULT); mWindow1->setFrame(Rect(0, 0, 100, 100)); - mWindow2 = new FakeWindowHandle(application, mDispatcher, "Fake Window 2", - ADISPLAY_ID_DEFAULT, mWindow1->getToken()); + mWindow2 = sp::make(application, mDispatcher, "Fake Window 2", + ADISPLAY_ID_DEFAULT, mWindow1->getToken()); mWindow2->setFrame(Rect(100, 100, 200, 200)); mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {mWindow1, mWindow2}}}); @@ -4657,12 +6442,12 @@ protected: ASSERT_NE(nullptr, event) << name.c_str() << ": consumer should have returned non-NULL event."; - ASSERT_EQ(AINPUT_EVENT_TYPE_MOTION, event->getType()) - << name.c_str() << "expected " << inputEventTypeToString(AINPUT_EVENT_TYPE_MOTION) - << " event, got " << inputEventTypeToString(event->getType()) << " event"; + ASSERT_EQ(InputEventType::MOTION, event->getType()) + << name.c_str() << ": expected MotionEvent, got " << *event; const MotionEvent& motionEvent = static_cast(*event); assertMotionAction(expectedAction, motionEvent.getAction()); + ASSERT_EQ(points.size(), motionEvent.getPointerCount()); for (size_t i = 0; i < points.size(); i++) { float expectedX = points[i].x; @@ -4679,9 +6464,8 @@ protected: void touchAndAssertPositions(int32_t action, const std::vector& touchedPoints, std::vector expectedPoints) { - NotifyMotionArgs motionArgs = generateMotionArgs(action, AINPUT_SOURCE_TOUCHSCREEN, - ADISPLAY_ID_DEFAULT, touchedPoints); - mDispatcher->notifyMotion(&motionArgs); + mDispatcher->notifyMotion(generateMotionArgs(action, AINPUT_SOURCE_TOUCHSCREEN, + ADISPLAY_ID_DEFAULT, touchedPoints)); // Always consume from window1 since it's the window that has the InputReceiver consumeMotionEvent(mWindow1, action, expectedPoints); @@ -4809,14 +6593,59 @@ TEST_F(InputDispatcherMultiWindowSameTokenTests, MultipleWindowsFirstTouchWithSc touchAndAssertPositions(AMOTION_EVENT_ACTION_MOVE, touchedPoints, expectedPoints); } +/** + * When one of the windows is slippery, the touch should not slip into the other window with the + * same input channel. + */ +TEST_F(InputDispatcherMultiWindowSameTokenTests, TouchDoesNotSlipEvenIfSlippery) { + mWindow1->setSlippery(true); + mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {mWindow1, mWindow2}}}); + + // Touch down in window 1 + mDispatcher->notifyMotion(generateMotionArgs(ACTION_DOWN, AINPUT_SOURCE_TOUCHSCREEN, + ADISPLAY_ID_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}})); + + consumeMotionEvent(mWindow1, ACTION_MOVE, {{150, 150}}); +} + +/** + * When hover starts in one window and continues into the other, there should be a HOVER_EXIT and + * a HOVER_ENTER generated, even if the windows have the same token. This is because the new window + * that the pointer is hovering over may have a different transform. + */ +TEST_F(InputDispatcherMultiWindowSameTokenTests, HoverIntoClone) { + mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {mWindow1, mWindow2}}}); + + // Start hover in window 1 + mDispatcher->notifyMotion(generateMotionArgs(ACTION_HOVER_ENTER, AINPUT_SOURCE_TOUCHSCREEN, + ADISPLAY_ID_DEFAULT, {{50, 50}})); + consumeMotionEvent(mWindow1, ACTION_HOVER_ENTER, + {getPointInWindow(mWindow1->getInfo(), PointF{50, 50})}); + + // Move hover to window 2. + mDispatcher->notifyMotion(generateMotionArgs(ACTION_HOVER_MOVE, AINPUT_SOURCE_TOUCHSCREEN, + ADISPLAY_ID_DEFAULT, {{150, 150}})); + + consumeMotionEvent(mWindow1, ACTION_HOVER_EXIT, {{50, 50}}); + consumeMotionEvent(mWindow1, ACTION_HOVER_ENTER, + {getPointInWindow(mWindow2->getInfo(), PointF{150, 150})}); +} + class InputDispatcherSingleWindowAnr : public InputDispatcherTest { virtual void SetUp() override { InputDispatcherTest::SetUp(); mApplication = std::make_shared(); mApplication->setDispatchingTimeout(20ms); - mWindow = - new FakeWindowHandle(mApplication, mDispatcher, "TestWindow", ADISPLAY_ID_DEFAULT); + mWindow = sp::make(mApplication, mDispatcher, "TestWindow", + ADISPLAY_ID_DEFAULT); mWindow->setFrame(Rect(0, 0, 30, 30)); mWindow->setDispatchingTimeout(30ms); mWindow->setFocusable(true); @@ -4850,7 +6679,7 @@ protected: sp addSpyWindow() { sp spy = - new FakeWindowHandle(mApplication, mDispatcher, "Spy", ADISPLAY_ID_DEFAULT); + sp::make(mApplication, mDispatcher, "Spy", ADISPLAY_ID_DEFAULT); spy->setTrustedOverlay(true); spy->setFocusable(false); spy->setSpy(true); @@ -4883,9 +6712,9 @@ TEST_F(InputDispatcherSingleWindowAnr, WhenFocusedApplicationChanges_NoAnr) { mWindow->consumeFocusEvent(false); InputEventInjectionResult result = - injectKey(mDispatcher, AKEY_EVENT_ACTION_DOWN, 0 /*repeatCount*/, ADISPLAY_ID_DEFAULT, - InputEventInjectionSync::NONE, 10ms /*injectionTimeout*/, - false /* allowKeyRepeat */); + injectKey(mDispatcher, AKEY_EVENT_ACTION_DOWN, /*repeatCount=*/0, ADISPLAY_ID_DEFAULT, + InputEventInjectionSync::NONE, /*injectionTimeout=*/10ms, + /*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. @@ -4912,8 +6741,8 @@ TEST_F(InputDispatcherSingleWindowAnr, OnPointerDown_BasicAnr) { mFakePolicy->assertNotifyWindowUnresponsiveWasCalled(timeout, mWindow); mWindow->finishEvent(*sequenceNum); - mWindow->consumeEvent(AINPUT_EVENT_TYPE_MOTION, AMOTION_EVENT_ACTION_CANCEL, - ADISPLAY_ID_DEFAULT, 0 /*flags*/); + mWindow->consumeMotionEvent( + AllOf(WithMotionAction(ACTION_CANCEL), WithDisplayId(ADISPLAY_ID_DEFAULT))); ASSERT_TRUE(mDispatcher->waitForIdle()); mFakePolicy->assertNotifyWindowResponsiveWasCalled(mWindow->getToken(), mWindow->getPid()); } @@ -4947,8 +6776,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, 0 /* repeatCount */, ADISPLAY_ID_DEFAULT, - InputEventInjectionSync::WAIT_FOR_RESULT, 10ms, false /* allowKeyRepeat */); + injectKey(mDispatcher, AKEY_EVENT_ACTION_DOWN, /*repeatCount=*/0, ADISPLAY_ID_DEFAULT, + InputEventInjectionSync::WAIT_FOR_RESULT, 10ms, /*allowKeyRepeat=*/false); ASSERT_EQ(InputEventInjectionResult::TIMED_OUT, result); const std::chrono::duration timeout = mApplication->getDispatchingTimeout(DISPATCHING_TIMEOUT); mFakePolicy->assertNotifyNoFocusedWindowAnrWasCalled(timeout, mApplication); @@ -4971,12 +6800,12 @@ 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, 1 /*repeatCount*/, eventTime, eventTime); + AMETA_NONE, /*repeatCount=*/1, eventTime, eventTime); const int32_t policyFlags = POLICY_FLAG_FILTERED | POLICY_FLAG_PASS_TO_USER; InputEventInjectionResult result = - mDispatcher->injectInputEvent(&event, {} /* targetUid */, + mDispatcher->injectInputEvent(&event, /*targetUid=*/{}, InputEventInjectionSync::WAIT_FOR_RESULT, INJECT_EVENT_TIMEOUT, policyFlags); ASSERT_EQ(InputEventInjectionResult::FAILED, result) @@ -4998,8 +6827,8 @@ TEST_F(InputDispatcherSingleWindowAnr, NoFocusedWindow_DoesNotSendDuplicateAnr) // 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, 0 /* repeatCount */, ADISPLAY_ID_DEFAULT, - InputEventInjectionSync::WAIT_FOR_RESULT, 10ms, false /* allowKeyRepeat */); + injectKey(mDispatcher, AKEY_EVENT_ACTION_DOWN, /*repeatCount=*/0, ADISPLAY_ID_DEFAULT, + InputEventInjectionSync::WAIT_FOR_RESULT, 10ms, /*allowKeyRepeat=*/false); ASSERT_EQ(InputEventInjectionResult::TIMED_OUT, result); const std::chrono::duration appTimeout = mApplication->getDispatchingTimeout(DISPATCHING_TIMEOUT); @@ -5022,7 +6851,7 @@ TEST_F(InputDispatcherSingleWindowAnr, NoFocusedWindow_DropsFocusedEvents) { // Once a focused event arrives, we get an ANR for this application const InputEventInjectionResult result = - injectKey(mDispatcher, AKEY_EVENT_ACTION_DOWN, 0 /* repeatCount */, ADISPLAY_ID_DEFAULT, + injectKey(mDispatcher, AKEY_EVENT_ACTION_DOWN, /*repeatCount=*/0, ADISPLAY_ID_DEFAULT, InputEventInjectionSync::WAIT_FOR_RESULT, 10ms); ASSERT_EQ(InputEventInjectionResult::TIMED_OUT, result); @@ -5080,8 +6909,8 @@ TEST_F(InputDispatcherSingleWindowAnr, SpyWindowAnr) { mFakePolicy->assertNotifyWindowUnresponsiveWasCalled(timeout, spy); spy->finishEvent(*sequenceNum); - spy->consumeEvent(AINPUT_EVENT_TYPE_MOTION, AMOTION_EVENT_ACTION_CANCEL, ADISPLAY_ID_DEFAULT, - 0 /*flags*/); + spy->consumeMotionEvent( + AllOf(WithMotionAction(ACTION_CANCEL), WithDisplayId(ADISPLAY_ID_DEFAULT))); ASSERT_TRUE(mDispatcher->waitForIdle()); mFakePolicy->assertNotifyWindowResponsiveWasCalled(spy->getToken(), mWindow->getPid()); } @@ -5203,8 +7032,8 @@ TEST_F(InputDispatcherSingleWindowAnr, Policy_DoesNotGetDuplicateAnr) { mFakePolicy->assertNotifyAnrWasNotCalled(); // When the ANR happened, dispatcher should abort the current event stream via ACTION_CANCEL mWindow->consumeMotionDown(); - mWindow->consumeEvent(AINPUT_EVENT_TYPE_MOTION, AMOTION_EVENT_ACTION_CANCEL, - ADISPLAY_ID_DEFAULT, 0 /*flags*/); + mWindow->consumeMotionEvent( + AllOf(WithMotionAction(ACTION_CANCEL), WithDisplayId(ADISPLAY_ID_DEFAULT))); mWindow->assertNoEvents(); mDispatcher->waitForIdle(); mFakePolicy->assertNotifyWindowResponsiveWasCalled(mWindow->getToken(), mWindow->getPid()); @@ -5238,7 +7067,7 @@ TEST_F(InputDispatcherSingleWindowAnr, Key_StaysPendingWhileMotionIsProcessed) { // we will receive INJECTION_TIMED_OUT as the result. InputEventInjectionResult result = - injectKey(mDispatcher, AKEY_EVENT_ACTION_DOWN, 0 /* repeatCount */, ADISPLAY_ID_DEFAULT, + injectKey(mDispatcher, AKEY_EVENT_ACTION_DOWN, /*repeatCount=*/0, ADISPLAY_ID_DEFAULT, InputEventInjectionSync::WAIT_FOR_RESULT, 10ms); ASSERT_EQ(InputEventInjectionResult::TIMED_OUT, result); // Key will not be sent to the window, yet, because the window is still processing events @@ -5273,8 +7102,8 @@ TEST_F(InputDispatcherSingleWindowAnr, // Don't finish the events yet, and send a key // Injection is async, so it will succeed ASSERT_EQ(InputEventInjectionResult::SUCCEEDED, - injectKey(mDispatcher, AKEY_EVENT_ACTION_DOWN, 0 /* repeatCount */, - ADISPLAY_ID_DEFAULT, InputEventInjectionSync::NONE)); + injectKey(mDispatcher, AKEY_EVENT_ACTION_DOWN, /*repeatCount=*/0, ADISPLAY_ID_DEFAULT, + InputEventInjectionSync::NONE)); // At this point, key is still pending, and should not be sent to the application yet. std::optional keySequenceNum = mWindow->receiveEvent(); ASSERT_FALSE(keySequenceNum); @@ -5296,14 +7125,14 @@ class InputDispatcherMultiWindowAnr : public InputDispatcherTest { mApplication = std::make_shared(); mApplication->setDispatchingTimeout(10ms); - mUnfocusedWindow = - new FakeWindowHandle(mApplication, mDispatcher, "Unfocused", ADISPLAY_ID_DEFAULT); + mUnfocusedWindow = sp::make(mApplication, mDispatcher, "Unfocused", + ADISPLAY_ID_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 = - new FakeWindowHandle(mApplication, mDispatcher, "Focused", ADISPLAY_ID_DEFAULT); + mFocusedWindow = sp::make(mApplication, mDispatcher, "Focused", + ADISPLAY_ID_DEFAULT); mFocusedWindow->setDispatchingTimeout(30ms); mFocusedWindow->setFrame(Rect(50, 50, 100, 100)); @@ -5355,8 +7184,8 @@ TEST_F(InputDispatcherMultiWindowAnr, TwoWindows_BothUnresponsive) { FOCUSED_WINDOW_LOCATION)) << "Inject motion event should return InputEventInjectionResult::SUCCEEDED"; mFocusedWindow->consumeMotionDown(); - mUnfocusedWindow->consumeEvent(AINPUT_EVENT_TYPE_MOTION, AMOTION_EVENT_ACTION_OUTSIDE, - ADISPLAY_ID_DEFAULT, 0 /*flags*/); + mUnfocusedWindow->consumeEvent(InputEventType::MOTION, AMOTION_EVENT_ACTION_OUTSIDE, + ADISPLAY_ID_DEFAULT, /*flags=*/0); // We consumed all events, so no ANR ASSERT_TRUE(mDispatcher->waitForIdle()); mFakePolicy->assertNotifyAnrWasNotCalled(); @@ -5429,8 +7258,8 @@ 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->consumeEvent(AINPUT_EVENT_TYPE_MOTION, AMOTION_EVENT_ACTION_OUTSIDE, - ADISPLAY_ID_DEFAULT, 0 /*flags*/); + mUnfocusedWindow->consumeEvent(InputEventType::MOTION, AMOTION_EVENT_ACTION_OUTSIDE, + ADISPLAY_ID_DEFAULT, /*flags=*/0); // Receive the events, but don't respond std::optional downEventSequenceNum = mFocusedWindow->receiveEvent(); // ACTION_DOWN ASSERT_TRUE(downEventSequenceNum); @@ -5519,8 +7348,8 @@ TEST_F(InputDispatcherMultiWindowAnr, PendingKey_GoesToNewlyFocusedWindow) { // window even if motions are still being processed. InputEventInjectionResult result = - injectKey(mDispatcher, AKEY_EVENT_ACTION_DOWN, 0 /*repeatCount*/, ADISPLAY_ID_DEFAULT, - InputEventInjectionSync::NONE, 10ms /*injectionTimeout*/); + injectKey(mDispatcher, AKEY_EVENT_ACTION_DOWN, /*repeatCount=*/0, ADISPLAY_ID_DEFAULT, + InputEventInjectionSync::NONE, /*injectionTimeout=*/10ms); 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 @@ -5554,17 +7383,16 @@ TEST_F(InputDispatcherMultiWindowAnr, PendingKey_GoesToNewlyFocusedWindow) { // The other window should not be affected by that. TEST_F(InputDispatcherMultiWindowAnr, SplitTouch_SingleWindowAnr) { // Touch Window 1 - NotifyMotionArgs motionArgs = - generateMotionArgs(AMOTION_EVENT_ACTION_DOWN, AINPUT_SOURCE_TOUCHSCREEN, - ADISPLAY_ID_DEFAULT, {FOCUSED_WINDOW_LOCATION}); - mDispatcher->notifyMotion(&motionArgs); - mUnfocusedWindow->consumeEvent(AINPUT_EVENT_TYPE_MOTION, AMOTION_EVENT_ACTION_OUTSIDE, - ADISPLAY_ID_DEFAULT, 0 /*flags*/); + mDispatcher->notifyMotion(generateMotionArgs(AMOTION_EVENT_ACTION_DOWN, + AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT, + {FOCUSED_WINDOW_LOCATION})); + mUnfocusedWindow->consumeEvent(InputEventType::MOTION, AMOTION_EVENT_ACTION_OUTSIDE, + ADISPLAY_ID_DEFAULT, /*flags=*/0); // Touch Window 2 - motionArgs = generateMotionArgs(POINTER_1_DOWN, AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT, - {FOCUSED_WINDOW_LOCATION, UNFOCUSED_WINDOW_LOCATION}); - mDispatcher->notifyMotion(&motionArgs); + mDispatcher->notifyMotion( + generateMotionArgs(POINTER_1_DOWN, AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT, + {FOCUSED_WINDOW_LOCATION, UNFOCUSED_WINDOW_LOCATION})); const std::chrono::duration timeout = mFocusedWindow->getDispatchingTimeout(DISPATCHING_TIMEOUT); @@ -5579,7 +7407,7 @@ TEST_F(InputDispatcherMultiWindowAnr, SplitTouch_SingleWindowAnr) { ASSERT_TRUE(moveOrCancelSequenceNum); mFocusedWindow->finishEvent(*moveOrCancelSequenceNum); ASSERT_NE(nullptr, event); - ASSERT_EQ(event->getType(), AINPUT_EVENT_TYPE_MOTION); + ASSERT_EQ(event->getType(), InputEventType::MOTION); MotionEvent& motionEvent = static_cast(*event); if (motionEvent.getAction() == AMOTION_EVENT_ACTION_MOVE) { mFocusedWindow->consumeMotionCancel(); @@ -5621,9 +7449,9 @@ 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, 0 /*repeatCount*/, ADISPLAY_ID_DEFAULT, - InputEventInjectionSync::NONE, 10ms /*injectionTimeout*/, - false /* allowKeyRepeat */); + injectKey(mDispatcher, AKEY_EVENT_ACTION_DOWN, /*repeatCount=*/0, ADISPLAY_ID_DEFAULT, + InputEventInjectionSync::NONE, /*injectionTimeout=*/10ms, + /*allowKeyRepeat=*/false); ASSERT_EQ(InputEventInjectionResult::SUCCEEDED, result); // Wait until dispatcher starts the "no focused window" timer. If we don't wait here, @@ -5636,10 +7464,9 @@ TEST_F(InputDispatcherMultiWindowAnr, FocusedWindowWithoutSetFocusedApplication_ std::this_thread::sleep_for(10ms); // Touch unfocused window. This should force the pending key to get dropped. - NotifyMotionArgs motionArgs = - generateMotionArgs(AMOTION_EVENT_ACTION_DOWN, AINPUT_SOURCE_TOUCHSCREEN, - ADISPLAY_ID_DEFAULT, {UNFOCUSED_WINDOW_LOCATION}); - mDispatcher->notifyMotion(&motionArgs); + mDispatcher->notifyMotion(generateMotionArgs(AMOTION_EVENT_ACTION_DOWN, + AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_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. @@ -5670,16 +7497,16 @@ class InputDispatcherMultiWindowOcclusionTests : public InputDispatcherTest { InputDispatcherTest::SetUp(); mApplication = std::make_shared(); - mNoInputWindow = new FakeWindowHandle(mApplication, mDispatcher, - "Window without input channel", ADISPLAY_ID_DEFAULT, - std::make_optional>(nullptr) /*token*/); - + mNoInputWindow = + sp::make(mApplication, mDispatcher, + "Window without input channel", ADISPLAY_ID_DEFAULT, + /*token=*/std::make_optional>(nullptr)); 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 = new FakeWindowHandle(mApplication, mDispatcher, "Bottom window", - ADISPLAY_ID_DEFAULT); + mBottomWindow = sp::make(mApplication, mDispatcher, "Bottom window", + ADISPLAY_ID_DEFAULT); mBottomWindow->setFrame(Rect(0, 0, 100, 100)); mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {mNoInputWindow, mBottomWindow}}}); @@ -5694,10 +7521,9 @@ protected: TEST_F(InputDispatcherMultiWindowOcclusionTests, NoInputChannelFeature_DropsTouches) { PointF touchedPoint = {10, 10}; - NotifyMotionArgs motionArgs = - generateMotionArgs(AMOTION_EVENT_ACTION_DOWN, AINPUT_SOURCE_TOUCHSCREEN, - ADISPLAY_ID_DEFAULT, {touchedPoint}); - mDispatcher->notifyMotion(&motionArgs); + mDispatcher->notifyMotion(generateMotionArgs(AMOTION_EVENT_ACTION_DOWN, + AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT, + {touchedPoint})); mNoInputWindow->assertNoEvents(); // Even though the window 'mNoInputWindow' positioned above 'mBottomWindow' does not have @@ -5712,9 +7538,9 @@ TEST_F(InputDispatcherMultiWindowOcclusionTests, NoInputChannelFeature_DropsTouc */ TEST_F(InputDispatcherMultiWindowOcclusionTests, NoInputChannelFeature_DropsTouchesWithValidChannel) { - mNoInputWindow = new FakeWindowHandle(mApplication, mDispatcher, - "Window with input channel and NO_INPUT_CHANNEL", - ADISPLAY_ID_DEFAULT); + mNoInputWindow = sp::make(mApplication, mDispatcher, + "Window with input channel and NO_INPUT_CHANNEL", + ADISPLAY_ID_DEFAULT); mNoInputWindow->setNoInputChannel(true); mNoInputWindow->setFrame(Rect(0, 0, 100, 100)); @@ -5722,10 +7548,9 @@ TEST_F(InputDispatcherMultiWindowOcclusionTests, PointF touchedPoint = {10, 10}; - NotifyMotionArgs motionArgs = - generateMotionArgs(AMOTION_EVENT_ACTION_DOWN, AINPUT_SOURCE_TOUCHSCREEN, - ADISPLAY_ID_DEFAULT, {touchedPoint}); - mDispatcher->notifyMotion(&motionArgs); + mDispatcher->notifyMotion(generateMotionArgs(AMOTION_EVENT_ACTION_DOWN, + AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT, + {touchedPoint})); mNoInputWindow->assertNoEvents(); mBottomWindow->assertNoEvents(); @@ -5740,9 +7565,9 @@ protected: virtual void SetUp() override { InputDispatcherTest::SetUp(); mApp = std::make_shared(); - mWindow = new FakeWindowHandle(mApp, mDispatcher, "TestWindow", ADISPLAY_ID_DEFAULT); - mMirror = new FakeWindowHandle(mApp, mDispatcher, "TestWindowMirror", ADISPLAY_ID_DEFAULT, - mWindow->getToken()); + mWindow = sp::make(mApp, mDispatcher, "TestWindow", ADISPLAY_ID_DEFAULT); + mMirror = sp::make(mApp, mDispatcher, "TestWindowMirror", + ADISPLAY_ID_DEFAULT, mWindow->getToken()); mDispatcher->setFocusedApplication(ADISPLAY_ID_DEFAULT, mApp); mWindow->setFocusable(true); mMirror->setFocusable(true); @@ -5863,8 +7688,8 @@ TEST_F(InputDispatcherMirrorWindowFocusTests, DeferFocusWhenInvisible) { // Injected key goes to pending queue. ASSERT_EQ(InputEventInjectionResult::SUCCEEDED, - injectKey(mDispatcher, AKEY_EVENT_ACTION_DOWN, 0 /* repeatCount */, - ADISPLAY_ID_DEFAULT, InputEventInjectionSync::NONE)); + injectKey(mDispatcher, AKEY_EVENT_ACTION_DOWN, /*repeatCount=*/0, ADISPLAY_ID_DEFAULT, + InputEventInjectionSync::NONE)); mMirror->setVisible(true); mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {mWindow, mMirror}}}); @@ -5884,9 +7709,10 @@ protected: void SetUp() override { InputDispatcherTest::SetUp(); mApp = std::make_shared(); - mWindow = new FakeWindowHandle(mApp, mDispatcher, "TestWindow", ADISPLAY_ID_DEFAULT); + mWindow = sp::make(mApp, mDispatcher, "TestWindow", ADISPLAY_ID_DEFAULT); mWindow->setFocusable(true); - mSecondWindow = new FakeWindowHandle(mApp, mDispatcher, "TestWindow2", ADISPLAY_ID_DEFAULT); + mSecondWindow = + sp::make(mApp, mDispatcher, "TestWindow2", ADISPLAY_ID_DEFAULT); mSecondWindow->setFocusable(true); mDispatcher->setFocusedApplication(ADISPLAY_ID_DEFAULT, mApp); @@ -5897,8 +7723,7 @@ protected: } void notifyPointerCaptureChanged(const PointerCaptureRequest& request) { - const NotifyPointerCaptureChangedArgs args = generatePointerCaptureChangedArgs(request); - mDispatcher->notifyPointerCaptureChanged(&args); + mDispatcher->notifyPointerCaptureChanged(generatePointerCaptureChangedArgs(request)); } PointerCaptureRequest requestAndVerifyPointerCapture(const sp& window, @@ -6047,7 +7872,6 @@ protected: virtual void SetUp() override { InputDispatcherTest::SetUp(); mTouchWindow = getWindow(TOUCHED_APP_UID, "Touched"); - mDispatcher->setBlockUntrustedTouchesMode(android::os::BlockUntrustedTouchesMode::BLOCK); mDispatcher->setMaximumObscuringOpacityForTouch(MAXIMUM_OBSCURING_OPACITY); } @@ -6068,17 +7892,16 @@ protected: sp getWindow(int32_t uid, std::string name) { std::shared_ptr app = std::make_shared(); sp window = - new FakeWindowHandle(app, mDispatcher, name, ADISPLAY_ID_DEFAULT); + sp::make(app, mDispatcher, name, ADISPLAY_ID_DEFAULT); // Generate an arbitrary PID based on the UID window->setOwnerInfo(1777 + (uid % 10000), uid); return window; } void touch(const std::vector& points = {PointF{100, 200}}) { - NotifyMotionArgs args = - generateMotionArgs(AMOTION_EVENT_ACTION_DOWN, AINPUT_SOURCE_TOUCHSCREEN, - ADISPLAY_ID_DEFAULT, points); - mDispatcher->notifyMotion(&args); + mDispatcher->notifyMotion(generateMotionArgs(AMOTION_EVENT_ACTION_DOWN, + AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT, + points)); } }; @@ -6428,20 +8251,28 @@ protected: sp mWindow; sp mSecondWindow; sp mDragWindow; + sp mSpyWindow; // Mouse would force no-split, set the id as non-zero to verify if drag state could track it. static constexpr int32_t MOUSE_POINTER_ID = 1; void SetUp() override { InputDispatcherTest::SetUp(); mApp = std::make_shared(); - mWindow = new FakeWindowHandle(mApp, mDispatcher, "TestWindow", ADISPLAY_ID_DEFAULT); + mWindow = sp::make(mApp, mDispatcher, "TestWindow", ADISPLAY_ID_DEFAULT); mWindow->setFrame(Rect(0, 0, 100, 100)); - mSecondWindow = new FakeWindowHandle(mApp, mDispatcher, "TestWindow2", ADISPLAY_ID_DEFAULT); + mSecondWindow = + sp::make(mApp, mDispatcher, "TestWindow2", ADISPLAY_ID_DEFAULT); mSecondWindow->setFrame(Rect(100, 0, 200, 100)); + mSpyWindow = + sp::make(mApp, mDispatcher, "SpyWindow", ADISPLAY_ID_DEFAULT); + mSpyWindow->setSpy(true); + mSpyWindow->setTrustedOverlay(true); + mSpyWindow->setFrame(Rect(0, 0, 200, 100)); + mDispatcher->setFocusedApplication(ADISPLAY_ID_DEFAULT, mApp); - mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {mWindow, mSecondWindow}}}); + mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {mSpyWindow, mWindow, mSecondWindow}}}); } void injectDown(int fromSource = AINPUT_SOURCE_TOUCHSCREEN) { @@ -6459,7 +8290,7 @@ protected: MotionEventBuilder(AMOTION_EVENT_ACTION_DOWN, AINPUT_SOURCE_STYLUS) .buttonState(AMOTION_EVENT_BUTTON_STYLUS_PRIMARY) - .pointer(PointerBuilder(0, AMOTION_EVENT_TOOL_TYPE_STYLUS) + .pointer(PointerBuilder(0, ToolType::STYLUS) .x(50) .y(50)) .build())); @@ -6471,7 +8302,7 @@ protected: MotionEventBuilder(AMOTION_EVENT_ACTION_DOWN, AINPUT_SOURCE_MOUSE) .buttonState(AMOTION_EVENT_BUTTON_PRIMARY) .pointer(PointerBuilder(MOUSE_POINTER_ID, - AMOTION_EVENT_TOOL_TYPE_MOUSE) + ToolType::MOUSE) .x(50) .y(50)) .build())); @@ -6482,6 +8313,8 @@ protected: // Window should receive motion event. mWindow->consumeMotionDown(ADISPLAY_ID_DEFAULT); + // Spy window should also receive motion event + mSpyWindow->consumeMotionDown(ADISPLAY_ID_DEFAULT); } // Start performing drag, we will create a drag window and transfer touch to it. @@ -6493,14 +8326,16 @@ protected: } // The drag window covers the entire display - mDragWindow = new FakeWindowHandle(mApp, mDispatcher, "DragWindow", ADISPLAY_ID_DEFAULT); + mDragWindow = + sp::make(mApp, mDispatcher, "DragWindow", ADISPLAY_ID_DEFAULT); + mDragWindow->setTouchableRegion(Region{{0, 0, 0, 0}}); mDispatcher->setInputWindows( - {{ADISPLAY_ID_DEFAULT, {mDragWindow, mWindow, mSecondWindow}}}); + {{ADISPLAY_ID_DEFAULT, {mDragWindow, mSpyWindow, mWindow, mSecondWindow}}}); // Transfer touch focus to the drag window bool transferred = mDispatcher->transferTouchFocus(mWindow->getToken(), mDragWindow->getToken(), - true /* isDragDrop */); + /*isDragDrop=*/true); if (transferred) { mWindow->consumeMotionCancel(); mDragWindow->consumeMotionDown(); @@ -6547,6 +8382,30 @@ TEST_F(InputDispatcherDragTests, DragEnterAndDragExit) { mSecondWindow->assertNoEvents(); } +TEST_F(InputDispatcherDragTests, DragEnterAndPointerDownPilfersPointers) { + startDrag(); + + // No cancel event after drag start + mSpyWindow->assertNoEvents(); + + const MotionEvent secondFingerDownEvent = + MotionEventBuilder(POINTER_1_DOWN, AINPUT_SOURCE_TOUCHSCREEN) + .eventTime(systemTime(SYSTEM_TIME_MONOTONIC)) + .pointer(PointerBuilder(/*id=*/0, ToolType::FINGER).x(50).y(50)) + .pointer(PointerBuilder(/*id=*/1, ToolType::FINGER).x(60).y(60)) + .build(); + ASSERT_EQ(InputEventInjectionResult::SUCCEEDED, + injectMotionEvent(mDispatcher, secondFingerDownEvent, INJECT_EVENT_TIMEOUT, + InputEventInjectionSync::WAIT_FOR_RESULT)) + << "Inject motion event should return InputEventInjectionResult::SUCCEEDED"; + + // Receives cancel for first pointer after next pointer down + mSpyWindow->consumeMotionCancel(); + mSpyWindow->consumeMotionDown(); + + mSpyWindow->assertNoEvents(); +} + TEST_F(InputDispatcherDragTests, DragAndDrop) { startDrag(); @@ -6587,9 +8446,7 @@ TEST_F(InputDispatcherDragTests, StylusDragAndDrop) { injectMotionEvent(mDispatcher, MotionEventBuilder(AMOTION_EVENT_ACTION_MOVE, AINPUT_SOURCE_STYLUS) .buttonState(AMOTION_EVENT_BUTTON_STYLUS_PRIMARY) - .pointer(PointerBuilder(0, AMOTION_EVENT_TOOL_TYPE_STYLUS) - .x(50) - .y(50)) + .pointer(PointerBuilder(0, ToolType::STYLUS).x(50).y(50)) .build())) << "Inject motion event should return InputEventInjectionResult::SUCCEEDED"; mDragWindow->consumeMotionMove(ADISPLAY_ID_DEFAULT); @@ -6601,9 +8458,7 @@ TEST_F(InputDispatcherDragTests, StylusDragAndDrop) { injectMotionEvent(mDispatcher, MotionEventBuilder(AMOTION_EVENT_ACTION_MOVE, AINPUT_SOURCE_STYLUS) .buttonState(0) - .pointer(PointerBuilder(0, AMOTION_EVENT_TOOL_TYPE_STYLUS) - .x(150) - .y(50)) + .pointer(PointerBuilder(0, ToolType::STYLUS).x(150).y(50)) .build())) << "Inject motion event should return InputEventInjectionResult::SUCCEEDED"; mDragWindow->consumeMotionMove(ADISPLAY_ID_DEFAULT); @@ -6616,9 +8471,7 @@ TEST_F(InputDispatcherDragTests, StylusDragAndDrop) { injectMotionEvent(mDispatcher, MotionEventBuilder(AMOTION_EVENT_ACTION_UP, AINPUT_SOURCE_STYLUS) .buttonState(0) - .pointer(PointerBuilder(0, AMOTION_EVENT_TOOL_TYPE_STYLUS) - .x(150) - .y(50)) + .pointer(PointerBuilder(0, ToolType::STYLUS).x(150).y(50)) .build())) << "Inject motion event should return InputEventInjectionResult::SUCCEEDED"; mDragWindow->consumeMotionUp(ADISPLAY_ID_DEFAULT); @@ -6676,14 +8529,14 @@ TEST_F(InputDispatcherDragTests, NoDragAndDropWhenMultiFingers) { MotionEventBuilder(POINTER_1_DOWN, AINPUT_SOURCE_TOUCHSCREEN) .displayId(ADISPLAY_ID_DEFAULT) .eventTime(systemTime(SYSTEM_TIME_MONOTONIC)) - .pointer(PointerBuilder(/* id */ 0, AMOTION_EVENT_TOOL_TYPE_FINGER).x(50).y(50)) - .pointer(PointerBuilder(/* id */ 1, AMOTION_EVENT_TOOL_TYPE_FINGER).x(75).y(50)) + .pointer(PointerBuilder(/*id=*/0, ToolType::FINGER).x(50).y(50)) + .pointer(PointerBuilder(/*id=*/1, ToolType::FINGER).x(75).y(50)) .build(); ASSERT_EQ(InputEventInjectionResult::SUCCEEDED, injectMotionEvent(mDispatcher, secondFingerDownEvent, INJECT_EVENT_TIMEOUT, InputEventInjectionSync::WAIT_FOR_RESULT)) << "Inject motion event should return InputEventInjectionResult::SUCCEEDED"; - mWindow->consumeMotionPointerDown(1 /* pointerIndex */); + mWindow->consumeMotionPointerDown(/*pointerIndex=*/1); // Should not perform drag and drop when window has multi fingers. ASSERT_FALSE(startDrag(false)); @@ -6703,9 +8556,8 @@ TEST_F(InputDispatcherDragTests, DragAndDropWhenSplitTouch) { MotionEventBuilder(POINTER_1_DOWN, AINPUT_SOURCE_TOUCHSCREEN) .displayId(ADISPLAY_ID_DEFAULT) .eventTime(systemTime(SYSTEM_TIME_MONOTONIC)) - .pointer( - PointerBuilder(/* id */ 0, AMOTION_EVENT_TOOL_TYPE_FINGER).x(150).y(50)) - .pointer(PointerBuilder(/* id */ 1, AMOTION_EVENT_TOOL_TYPE_FINGER).x(50).y(50)) + .pointer(PointerBuilder(/*id=*/0, ToolType::FINGER).x(150).y(50)) + .pointer(PointerBuilder(/*id=*/1, ToolType::FINGER).x(50).y(50)) .build(); ASSERT_EQ(InputEventInjectionResult::SUCCEEDED, injectMotionEvent(mDispatcher, secondFingerDownEvent, INJECT_EVENT_TIMEOUT, @@ -6720,9 +8572,8 @@ TEST_F(InputDispatcherDragTests, DragAndDropWhenSplitTouch) { const MotionEvent secondFingerMoveEvent = MotionEventBuilder(AMOTION_EVENT_ACTION_MOVE, AINPUT_SOURCE_TOUCHSCREEN) .eventTime(systemTime(SYSTEM_TIME_MONOTONIC)) - .pointer( - PointerBuilder(/* id */ 0, AMOTION_EVENT_TOOL_TYPE_FINGER).x(150).y(50)) - .pointer(PointerBuilder(/* id */ 1, AMOTION_EVENT_TOOL_TYPE_FINGER).x(50).y(50)) + .pointer(PointerBuilder(/*id=*/0, ToolType::FINGER).x(150).y(50)) + .pointer(PointerBuilder(/*id=*/1, ToolType::FINGER).x(50).y(50)) .build(); ASSERT_EQ(InputEventInjectionResult::SUCCEEDED, injectMotionEvent(mDispatcher, secondFingerMoveEvent, INJECT_EVENT_TIMEOUT, @@ -6735,9 +8586,8 @@ TEST_F(InputDispatcherDragTests, DragAndDropWhenSplitTouch) { const MotionEvent secondFingerUpEvent = MotionEventBuilder(POINTER_1_UP, AINPUT_SOURCE_TOUCHSCREEN) .eventTime(systemTime(SYSTEM_TIME_MONOTONIC)) - .pointer( - PointerBuilder(/* id */ 0, AMOTION_EVENT_TOOL_TYPE_FINGER).x(150).y(50)) - .pointer(PointerBuilder(/* id */ 1, AMOTION_EVENT_TOOL_TYPE_FINGER).x(50).y(50)) + .pointer(PointerBuilder(/*id=*/0, ToolType::FINGER).x(150).y(50)) + .pointer(PointerBuilder(/*id=*/1, ToolType::FINGER).x(50).y(50)) .build(); ASSERT_EQ(InputEventInjectionResult::SUCCEEDED, injectMotionEvent(mDispatcher, secondFingerUpEvent, INJECT_EVENT_TIMEOUT, @@ -6753,7 +8603,7 @@ TEST_F(InputDispatcherDragTests, DragAndDropWhenMultiDisplays) { // Update window of second display. sp windowInSecondary = - new FakeWindowHandle(mApp, mDispatcher, "D_2", SECOND_DISPLAY_ID); + sp::make(mApp, mDispatcher, "D_2", SECOND_DISPLAY_ID); mDispatcher->setInputWindows({{SECOND_DISPLAY_ID, {windowInSecondary}}}); // Let second display has a touch state. @@ -6761,13 +8611,11 @@ TEST_F(InputDispatcherDragTests, DragAndDropWhenMultiDisplays) { injectMotionEvent(mDispatcher, MotionEventBuilder(AMOTION_EVENT_ACTION_DOWN, AINPUT_SOURCE_TOUCHSCREEN) - .displayId(SECOND_DISPLAY_ID) - .pointer(PointerBuilder(0, AMOTION_EVENT_TOOL_TYPE_FINGER) - .x(100) - .y(100)) + .displayId(SECOND_DISPLAY_ID) + .pointer(PointerBuilder(0, ToolType::FINGER).x(100).y(100)) .build())); - windowInSecondary->consumeEvent(AINPUT_EVENT_TYPE_MOTION, AMOTION_EVENT_ACTION_DOWN, - SECOND_DISPLAY_ID, 0 /* expectedFlag */); + windowInSecondary->consumeEvent(InputEventType::MOTION, AMOTION_EVENT_ACTION_DOWN, + SECOND_DISPLAY_ID, /*expectedFlag=*/0); // Update window again. mDispatcher->setInputWindows({{SECOND_DISPLAY_ID, {windowInSecondary}}}); @@ -6808,7 +8656,7 @@ TEST_F(InputDispatcherDragTests, MouseDragAndDrop) { MotionEventBuilder(AMOTION_EVENT_ACTION_MOVE, AINPUT_SOURCE_MOUSE) .buttonState(AMOTION_EVENT_BUTTON_PRIMARY) .pointer(PointerBuilder(MOUSE_POINTER_ID, - AMOTION_EVENT_TOOL_TYPE_MOUSE) + ToolType::MOUSE) .x(50) .y(50)) .build())) @@ -6823,7 +8671,7 @@ TEST_F(InputDispatcherDragTests, MouseDragAndDrop) { MotionEventBuilder(AMOTION_EVENT_ACTION_MOVE, AINPUT_SOURCE_MOUSE) .buttonState(AMOTION_EVENT_BUTTON_PRIMARY) .pointer(PointerBuilder(MOUSE_POINTER_ID, - AMOTION_EVENT_TOOL_TYPE_MOUSE) + ToolType::MOUSE) .x(150) .y(50)) .build())) @@ -6838,7 +8686,7 @@ TEST_F(InputDispatcherDragTests, MouseDragAndDrop) { MotionEventBuilder(AMOTION_EVENT_ACTION_UP, AINPUT_SOURCE_MOUSE) .buttonState(0) .pointer(PointerBuilder(MOUSE_POINTER_ID, - AMOTION_EVENT_TOOL_TYPE_MOUSE) + ToolType::MOUSE) .x(150) .y(50)) .build())) @@ -6853,37 +8701,32 @@ class InputDispatcherDropInputFeatureTest : public InputDispatcherTest {}; TEST_F(InputDispatcherDropInputFeatureTest, WindowDropsInput) { std::shared_ptr application = std::make_shared(); - sp window = - new FakeWindowHandle(application, mDispatcher, "Test window", ADISPLAY_ID_DEFAULT); + sp window = sp::make(application, mDispatcher, + "Test window", ADISPLAY_ID_DEFAULT); window->setDropInput(true); mDispatcher->setFocusedApplication(ADISPLAY_ID_DEFAULT, application); window->setFocusable(true); mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {window}}}); setFocusedWindow(window); - window->consumeFocusEvent(true /*hasFocus*/, true /*inTouchMode*/); + window->consumeFocusEvent(/*hasFocus=*/true, /*inTouchMode=*/true); // With the flag set, window should not get any input - NotifyKeyArgs keyArgs = generateKeyArgs(AKEY_EVENT_ACTION_DOWN, ADISPLAY_ID_DEFAULT); - mDispatcher->notifyKey(&keyArgs); + mDispatcher->notifyKey(generateKeyArgs(AKEY_EVENT_ACTION_DOWN, ADISPLAY_ID_DEFAULT)); window->assertNoEvents(); - NotifyMotionArgs motionArgs = - generateMotionArgs(AMOTION_EVENT_ACTION_DOWN, AINPUT_SOURCE_TOUCHSCREEN, - ADISPLAY_ID_DEFAULT); - mDispatcher->notifyMotion(&motionArgs); + mDispatcher->notifyMotion(generateMotionArgs(AMOTION_EVENT_ACTION_DOWN, + AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT)); window->assertNoEvents(); // With the flag cleared, the window should get input window->setDropInput(false); mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {window}}}); - keyArgs = generateKeyArgs(AKEY_EVENT_ACTION_UP, ADISPLAY_ID_DEFAULT); - mDispatcher->notifyKey(&keyArgs); + mDispatcher->notifyKey(generateKeyArgs(AKEY_EVENT_ACTION_UP, ADISPLAY_ID_DEFAULT)); window->consumeKeyUp(ADISPLAY_ID_DEFAULT); - motionArgs = generateMotionArgs(AMOTION_EVENT_ACTION_DOWN, AINPUT_SOURCE_TOUCHSCREEN, - ADISPLAY_ID_DEFAULT); - mDispatcher->notifyMotion(&motionArgs); + mDispatcher->notifyMotion(generateMotionArgs(AMOTION_EVENT_ACTION_DOWN, + AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT)); window->consumeMotionDown(ADISPLAY_ID_DEFAULT); window->assertNoEvents(); } @@ -6892,44 +8735,39 @@ TEST_F(InputDispatcherDropInputFeatureTest, ObscuredWindowDropsInput) { std::shared_ptr obscuringApplication = std::make_shared(); sp obscuringWindow = - new FakeWindowHandle(obscuringApplication, mDispatcher, "obscuringWindow", - ADISPLAY_ID_DEFAULT); + sp::make(obscuringApplication, mDispatcher, "obscuringWindow", + ADISPLAY_ID_DEFAULT); obscuringWindow->setFrame(Rect(0, 0, 50, 50)); obscuringWindow->setOwnerInfo(111, 111); obscuringWindow->setTouchable(false); std::shared_ptr application = std::make_shared(); - sp window = - new FakeWindowHandle(application, mDispatcher, "Test window", ADISPLAY_ID_DEFAULT); + sp window = sp::make(application, mDispatcher, + "Test window", ADISPLAY_ID_DEFAULT); window->setDropInputIfObscured(true); window->setOwnerInfo(222, 222); mDispatcher->setFocusedApplication(ADISPLAY_ID_DEFAULT, application); window->setFocusable(true); mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {obscuringWindow, window}}}); setFocusedWindow(window); - window->consumeFocusEvent(true /*hasFocus*/, true /*inTouchMode*/); + window->consumeFocusEvent(/*hasFocus=*/true, /*inTouchMode=*/true); // With the flag set, window should not get any input - NotifyKeyArgs keyArgs = generateKeyArgs(AKEY_EVENT_ACTION_DOWN, ADISPLAY_ID_DEFAULT); - mDispatcher->notifyKey(&keyArgs); + mDispatcher->notifyKey(generateKeyArgs(AKEY_EVENT_ACTION_DOWN, ADISPLAY_ID_DEFAULT)); window->assertNoEvents(); - NotifyMotionArgs motionArgs = - generateMotionArgs(AMOTION_EVENT_ACTION_DOWN, AINPUT_SOURCE_TOUCHSCREEN, - ADISPLAY_ID_DEFAULT); - mDispatcher->notifyMotion(&motionArgs); + mDispatcher->notifyMotion(generateMotionArgs(AMOTION_EVENT_ACTION_DOWN, + AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT)); window->assertNoEvents(); // With the flag cleared, the window should get input window->setDropInputIfObscured(false); mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {obscuringWindow, window}}}); - keyArgs = generateKeyArgs(AKEY_EVENT_ACTION_UP, ADISPLAY_ID_DEFAULT); - mDispatcher->notifyKey(&keyArgs); + mDispatcher->notifyKey(generateKeyArgs(AKEY_EVENT_ACTION_UP, ADISPLAY_ID_DEFAULT)); window->consumeKeyUp(ADISPLAY_ID_DEFAULT); - motionArgs = generateMotionArgs(AMOTION_EVENT_ACTION_DOWN, AINPUT_SOURCE_TOUCHSCREEN, - ADISPLAY_ID_DEFAULT); - mDispatcher->notifyMotion(&motionArgs); + 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); window->assertNoEvents(); } @@ -6938,43 +8776,38 @@ TEST_F(InputDispatcherDropInputFeatureTest, UnobscuredWindowGetsInput) { std::shared_ptr obscuringApplication = std::make_shared(); sp obscuringWindow = - new FakeWindowHandle(obscuringApplication, mDispatcher, "obscuringWindow", - ADISPLAY_ID_DEFAULT); + sp::make(obscuringApplication, mDispatcher, "obscuringWindow", + ADISPLAY_ID_DEFAULT); obscuringWindow->setFrame(Rect(0, 0, 50, 50)); obscuringWindow->setOwnerInfo(111, 111); obscuringWindow->setTouchable(false); std::shared_ptr application = std::make_shared(); - sp window = - new FakeWindowHandle(application, mDispatcher, "Test window", ADISPLAY_ID_DEFAULT); + sp window = sp::make(application, mDispatcher, + "Test window", ADISPLAY_ID_DEFAULT); window->setDropInputIfObscured(true); window->setOwnerInfo(222, 222); mDispatcher->setFocusedApplication(ADISPLAY_ID_DEFAULT, application); window->setFocusable(true); mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {obscuringWindow, window}}}); setFocusedWindow(window); - window->consumeFocusEvent(true /*hasFocus*/, true /*inTouchMode*/); + window->consumeFocusEvent(/*hasFocus=*/true, /*inTouchMode=*/true); // With the flag set, window should not get any input - NotifyKeyArgs keyArgs = generateKeyArgs(AKEY_EVENT_ACTION_DOWN, ADISPLAY_ID_DEFAULT); - mDispatcher->notifyKey(&keyArgs); + mDispatcher->notifyKey(generateKeyArgs(AKEY_EVENT_ACTION_DOWN, ADISPLAY_ID_DEFAULT)); window->assertNoEvents(); - NotifyMotionArgs motionArgs = - generateMotionArgs(AMOTION_EVENT_ACTION_DOWN, AINPUT_SOURCE_TOUCHSCREEN, - ADISPLAY_ID_DEFAULT); - mDispatcher->notifyMotion(&motionArgs); + mDispatcher->notifyMotion(generateMotionArgs(AMOTION_EVENT_ACTION_DOWN, + AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT)); window->assertNoEvents(); // When the window is no longer obscured because it went on top, it should get input mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {window, obscuringWindow}}}); - keyArgs = generateKeyArgs(AKEY_EVENT_ACTION_UP, ADISPLAY_ID_DEFAULT); - mDispatcher->notifyKey(&keyArgs); + mDispatcher->notifyKey(generateKeyArgs(AKEY_EVENT_ACTION_UP, ADISPLAY_ID_DEFAULT)); window->consumeKeyUp(ADISPLAY_ID_DEFAULT); - motionArgs = generateMotionArgs(AMOTION_EVENT_ACTION_DOWN, AINPUT_SOURCE_TOUCHSCREEN, - ADISPLAY_ID_DEFAULT); - mDispatcher->notifyMotion(&motionArgs); + mDispatcher->notifyMotion(generateMotionArgs(AMOTION_EVENT_ACTION_DOWN, + AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT)); window->consumeMotionDown(ADISPLAY_ID_DEFAULT); window->assertNoEvents(); } @@ -6982,42 +8815,66 @@ TEST_F(InputDispatcherDropInputFeatureTest, UnobscuredWindowGetsInput) { class InputDispatcherTouchModeChangedTests : public InputDispatcherTest { protected: std::shared_ptr mApp; + std::shared_ptr mSecondaryApp; sp mWindow; sp mSecondWindow; + sp mThirdWindow; void SetUp() override { InputDispatcherTest::SetUp(); mApp = std::make_shared(); - mWindow = new FakeWindowHandle(mApp, mDispatcher, "TestWindow", ADISPLAY_ID_DEFAULT); + mSecondaryApp = std::make_shared(); + mWindow = sp::make(mApp, mDispatcher, "TestWindow", ADISPLAY_ID_DEFAULT); mWindow->setFocusable(true); setFocusedWindow(mWindow); - mSecondWindow = new FakeWindowHandle(mApp, mDispatcher, "TestWindow2", ADISPLAY_ID_DEFAULT); + mSecondWindow = + sp::make(mApp, mDispatcher, "TestWindow2", ADISPLAY_ID_DEFAULT); mSecondWindow->setFocusable(true); + mThirdWindow = + sp::make(mSecondaryApp, mDispatcher, + "TestWindow3_SecondaryDisplay", SECOND_DISPLAY_ID); + mThirdWindow->setFocusable(true); mDispatcher->setFocusedApplication(ADISPLAY_ID_DEFAULT, mApp); - mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {mWindow, mSecondWindow}}}); + mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {mWindow, mSecondWindow}}, + {SECOND_DISPLAY_ID, {mThirdWindow}}}); + mThirdWindow->setOwnerInfo(SECONDARY_WINDOW_PID, SECONDARY_WINDOW_UID); mWindow->consumeFocusEvent(true); - // Set initial touch mode to InputDispatcher::kDefaultInTouchMode. + // Set main display initial touch mode to InputDispatcher::kDefaultInTouchMode. if (mDispatcher->setInTouchMode(InputDispatcher::kDefaultInTouchMode, WINDOW_PID, - WINDOW_UID, /* hasPermission */ true)) { + WINDOW_UID, /*hasPermission=*/true, ADISPLAY_ID_DEFAULT)) { mWindow->consumeTouchModeEvent(InputDispatcher::kDefaultInTouchMode); mSecondWindow->consumeTouchModeEvent(InputDispatcher::kDefaultInTouchMode); + mThirdWindow->assertNoEvents(); + } + + // Set secondary display initial touch mode to InputDispatcher::kDefaultInTouchMode. + if (mDispatcher->setInTouchMode(InputDispatcher::kDefaultInTouchMode, SECONDARY_WINDOW_PID, + SECONDARY_WINDOW_UID, /*hasPermission=*/true, + SECOND_DISPLAY_ID)) { + mWindow->assertNoEvents(); + mSecondWindow->assertNoEvents(); + mThirdWindow->consumeTouchModeEvent(InputDispatcher::kDefaultInTouchMode); } } - void changeAndVerifyTouchMode(bool inTouchMode, int32_t pid, int32_t uid, bool hasPermission) { - ASSERT_TRUE(mDispatcher->setInTouchMode(inTouchMode, pid, uid, hasPermission)); + void changeAndVerifyTouchModeInMainDisplayOnly(bool inTouchMode, int32_t pid, int32_t uid, + bool hasPermission) { + ASSERT_TRUE(mDispatcher->setInTouchMode(inTouchMode, pid, uid, hasPermission, + ADISPLAY_ID_DEFAULT)); mWindow->consumeTouchModeEvent(inTouchMode); mSecondWindow->consumeTouchModeEvent(inTouchMode); + mThirdWindow->assertNoEvents(); } }; TEST_F(InputDispatcherTouchModeChangedTests, FocusedWindowCanChangeTouchMode) { const WindowInfo& windowInfo = *mWindow->getInfo(); - changeAndVerifyTouchMode(!InputDispatcher::kDefaultInTouchMode, windowInfo.ownerPid, - windowInfo.ownerUid, /* hasPermission */ false); + changeAndVerifyTouchModeInMainDisplayOnly(!InputDispatcher::kDefaultInTouchMode, + windowInfo.ownerPid, windowInfo.ownerUid, + /* hasPermission=*/false); } TEST_F(InputDispatcherTouchModeChangedTests, NonFocusedWindowOwnerCannotChangeTouchMode) { @@ -7026,7 +8883,8 @@ TEST_F(InputDispatcherTouchModeChangedTests, NonFocusedWindowOwnerCannotChangeTo int32_t ownerUid = windowInfo.ownerUid; mWindow->setOwnerInfo(/* pid */ -1, /* uid */ -1); ASSERT_FALSE(mDispatcher->setInTouchMode(InputDispatcher::kDefaultInTouchMode, ownerPid, - ownerUid, /* hasPermission */ false)); + ownerUid, /*hasPermission=*/false, + ADISPLAY_ID_DEFAULT)); mWindow->assertNoEvents(); mSecondWindow->assertNoEvents(); } @@ -7036,17 +8894,27 @@ TEST_F(InputDispatcherTouchModeChangedTests, NonWindowOwnerMayChangeTouchModeOnP int32_t ownerPid = windowInfo.ownerPid; int32_t ownerUid = windowInfo.ownerUid; mWindow->setOwnerInfo(/* pid */ -1, /* uid */ -1); - changeAndVerifyTouchMode(!InputDispatcher::kDefaultInTouchMode, ownerPid, ownerUid, - /* hasPermission */ true); + changeAndVerifyTouchModeInMainDisplayOnly(!InputDispatcher::kDefaultInTouchMode, ownerPid, + ownerUid, /*hasPermission=*/true); } TEST_F(InputDispatcherTouchModeChangedTests, EventIsNotGeneratedIfNotChangingTouchMode) { const WindowInfo& windowInfo = *mWindow->getInfo(); ASSERT_FALSE(mDispatcher->setInTouchMode(InputDispatcher::kDefaultInTouchMode, windowInfo.ownerPid, windowInfo.ownerUid, - /* hasPermission */ true)); + /*hasPermission=*/true, ADISPLAY_ID_DEFAULT)); + mWindow->assertNoEvents(); + mSecondWindow->assertNoEvents(); +} + +TEST_F(InputDispatcherTouchModeChangedTests, ChangeTouchOnSecondaryDisplayOnly) { + const WindowInfo& windowInfo = *mThirdWindow->getInfo(); + ASSERT_TRUE(mDispatcher->setInTouchMode(!InputDispatcher::kDefaultInTouchMode, + windowInfo.ownerPid, windowInfo.ownerUid, + /*hasPermission=*/true, SECOND_DISPLAY_ID)); mWindow->assertNoEvents(); mSecondWindow->assertNoEvents(); + mThirdWindow->consumeTouchModeEvent(!InputDispatcher::kDefaultInTouchMode); } TEST_F(InputDispatcherTouchModeChangedTests, CanChangeTouchModeWhenOwningLastInteractedWindow) { @@ -7063,7 +8931,7 @@ TEST_F(InputDispatcherTouchModeChangedTests, CanChangeTouchModeWhenOwningLastInt const WindowInfo& windowInfo = *mWindow->getInfo(); ASSERT_TRUE(mDispatcher->setInTouchMode(!InputDispatcher::kDefaultInTouchMode, windowInfo.ownerPid, windowInfo.ownerUid, - /* hasPermission= */ false)); + /*hasPermission=*/false, ADISPLAY_ID_DEFAULT)); } class InputDispatcherSpyWindowTest : public InputDispatcherTest { @@ -7073,8 +8941,8 @@ public: std::make_shared(); std::string name = "Fake Spy "; name += std::to_string(mSpyCount++); - sp spy = - new FakeWindowHandle(application, mDispatcher, name.c_str(), ADISPLAY_ID_DEFAULT); + sp spy = sp::make(application, mDispatcher, + name.c_str(), ADISPLAY_ID_DEFAULT); spy->setSpy(true); spy->setTrustedOverlay(true); return spy; @@ -7084,7 +8952,8 @@ public: std::shared_ptr application = std::make_shared(); sp window = - new FakeWindowHandle(application, mDispatcher, "Fake Window", ADISPLAY_ID_DEFAULT); + sp::make(application, mDispatcher, "Fake Window", + ADISPLAY_ID_DEFAULT); window->setFocusable(true); return window; } @@ -7168,8 +9037,8 @@ TEST_F(InputDispatcherSpyWindowTest, ReceivesInputInOrder) { break; // epoll_wait timed out } for (int i = 0; i < nFds; i++) { - ASSERT_EQ(EPOLLIN, events[i].events); - eventOrder.push_back(events[i].data.u64); + ASSERT_EQ(static_cast(EPOLLIN), events[i].events); + eventOrder.push_back(static_cast(events[i].data.u64)); channels[i]->consumeMotionDown(); } } @@ -7252,10 +9121,142 @@ TEST_F(InputDispatcherSpyWindowTest, WatchOutsideTouches) { } /** - * A spy window can pilfer pointers. When this happens, touch gestures that are currently sent to - * any other windows - including other spy windows - will also be cancelled. + * Even when a spy window spans over multiple foreground windows, the spy should receive all + * pointers that are down within its bounds. + */ +TEST_F(InputDispatcherSpyWindowTest, ReceivesMultiplePointers) { + auto windowLeft = createForeground(); + windowLeft->setFrame({0, 0, 100, 200}); + auto windowRight = createForeground(); + windowRight->setFrame({100, 0, 200, 200}); + auto spy = createSpy(); + spy->setFrame({0, 0, 200, 200}); + mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {spy, windowLeft, windowRight}}}); + + ASSERT_EQ(InputEventInjectionResult::SUCCEEDED, + injectMotionDown(mDispatcher, AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT, + {50, 50})) + << "Inject motion event should return InputEventInjectionResult::SUCCEEDED"; + windowLeft->consumeMotionDown(); + spy->consumeMotionDown(); + + const MotionEvent secondFingerDownEvent = + MotionEventBuilder(POINTER_1_DOWN, AINPUT_SOURCE_TOUCHSCREEN) + .eventTime(systemTime(SYSTEM_TIME_MONOTONIC)) + .pointer(PointerBuilder(/*id=*/0, ToolType::FINGER).x(50).y(50)) + .pointer(PointerBuilder(/*id=*/1, ToolType::FINGER).x(150).y(50)) + .build(); + ASSERT_EQ(InputEventInjectionResult::SUCCEEDED, + injectMotionEvent(mDispatcher, secondFingerDownEvent, INJECT_EVENT_TIMEOUT, + InputEventInjectionSync::WAIT_FOR_RESULT)) + << "Inject motion event should return InputEventInjectionResult::SUCCEEDED"; + windowRight->consumeMotionDown(); + spy->consumeMotionPointerDown(/*pointerIndex=*/1); +} + +/** + * When the first pointer lands outside the spy window and the second pointer lands inside it, the + * the spy should receive the second pointer with ACTION_DOWN. + */ +TEST_F(InputDispatcherSpyWindowTest, ReceivesSecondPointerAsDown) { + auto window = createForeground(); + window->setFrame({0, 0, 200, 200}); + auto spyRight = createSpy(); + spyRight->setFrame({100, 0, 200, 200}); + mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {spyRight, window}}}); + + ASSERT_EQ(InputEventInjectionResult::SUCCEEDED, + injectMotionDown(mDispatcher, AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT, + {50, 50})) + << "Inject motion event should return InputEventInjectionResult::SUCCEEDED"; + window->consumeMotionDown(); + spyRight->assertNoEvents(); + + const MotionEvent secondFingerDownEvent = + MotionEventBuilder(POINTER_1_DOWN, AINPUT_SOURCE_TOUCHSCREEN) + .eventTime(systemTime(SYSTEM_TIME_MONOTONIC)) + .pointer(PointerBuilder(/*id=*/0, ToolType::FINGER).x(50).y(50)) + .pointer(PointerBuilder(/*id=*/1, ToolType::FINGER).x(150).y(50)) + .build(); + ASSERT_EQ(InputEventInjectionResult::SUCCEEDED, + injectMotionEvent(mDispatcher, secondFingerDownEvent, INJECT_EVENT_TIMEOUT, + InputEventInjectionSync::WAIT_FOR_RESULT)) + << "Inject motion event should return InputEventInjectionResult::SUCCEEDED"; + window->consumeMotionPointerDown(/*pointerIndex=*/1); + spyRight->consumeMotionDown(); +} + +/** + * The spy window should not be able to affect whether or not touches are split. Only the foreground + * windows should be allowed to control split touch. + */ +TEST_F(InputDispatcherSpyWindowTest, SplitIfNoForegroundWindowTouched) { + // This spy window prevents touch splitting. However, we still expect to split touches + // because a foreground window has not disabled splitting. + auto spy = createSpy(); + spy->setPreventSplitting(true); + + auto window = createForeground(); + window->setFrame(Rect(0, 0, 100, 100)); + + mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {spy, window}}}); + + // First finger down, no window touched. + ASSERT_EQ(InputEventInjectionResult::SUCCEEDED, + injectMotionDown(mDispatcher, AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT, + {100, 200})) + << "Inject motion event should return InputEventInjectionResult::SUCCEEDED"; + spy->consumeMotionDown(ADISPLAY_ID_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) + .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)) + .build(); + ASSERT_EQ(InputEventInjectionResult::SUCCEEDED, + injectMotionEvent(mDispatcher, secondFingerDownEvent, INJECT_EVENT_TIMEOUT, + InputEventInjectionSync::WAIT_FOR_RESULT)) + << "Inject motion event should return InputEventInjectionResult::SUCCEEDED"; + + window->consumeMotionDown(ADISPLAY_ID_DEFAULT); + spy->consumeMotionPointerDown(/*pointerIndex=*/1); +} + +/** + * A spy window will usually be implemented as an un-focusable window. Verify that these windows + * do not receive key events. + */ +TEST_F(InputDispatcherSpyWindowTest, UnfocusableSpyDoesNotReceiveKeyEvents) { + auto spy = createSpy(); + spy->setFocusable(false); + + auto window = createForeground(); + mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {spy, window}}}); + setFocusedWindow(window); + window->consumeFocusEvent(true); + + ASSERT_EQ(InputEventInjectionResult::SUCCEEDED, injectKeyDown(mDispatcher)) + << "Inject key event should return InputEventInjectionResult::SUCCEEDED"; + window->consumeKeyDown(ADISPLAY_ID_NONE); + + ASSERT_EQ(InputEventInjectionResult::SUCCEEDED, injectKeyUp(mDispatcher)) + << "Inject key event should return InputEventInjectionResult::SUCCEEDED"; + window->consumeKeyUp(ADISPLAY_ID_NONE); + + spy->assertNoEvents(); +} + +using InputDispatcherPilferPointersTest = InputDispatcherSpyWindowTest; + +/** + * A spy window can pilfer pointers. When this happens, touch gestures used by the spy window that + * are currently sent to any other windows - including other spy windows - will also be cancelled. */ -TEST_F(InputDispatcherSpyWindowTest, PilferPointers) { +TEST_F(InputDispatcherPilferPointersTest, PilferPointers) { auto window = createForeground(); auto spy1 = createSpy(); auto spy2 = createSpy(); @@ -7288,7 +9289,7 @@ TEST_F(InputDispatcherSpyWindowTest, PilferPointers) { * A spy window can pilfer pointers for a gesture even after the foreground window has been removed * in the middle of the gesture. */ -TEST_F(InputDispatcherSpyWindowTest, CanPilferAfterWindowIsRemovedMidStream) { +TEST_F(InputDispatcherPilferPointersTest, CanPilferAfterWindowIsRemovedMidStream) { auto window = createForeground(); auto spy = createSpy(); mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {spy, window}}}); @@ -7313,7 +9314,7 @@ TEST_F(InputDispatcherSpyWindowTest, CanPilferAfterWindowIsRemovedMidStream) { * After a spy window pilfers pointers, new pointers that go down in its bounds should be sent to * the spy, but not to any other windows. */ -TEST_F(InputDispatcherSpyWindowTest, ContinuesToReceiveGestureAfterPilfer) { +TEST_F(InputDispatcherPilferPointersTest, ContinuesToReceiveGestureAfterPilfer) { auto spy = createSpy(); auto window = createForeground(); @@ -7336,169 +9337,177 @@ TEST_F(InputDispatcherSpyWindowTest, ContinuesToReceiveGestureAfterPilfer) { MotionEventBuilder(POINTER_1_DOWN, AINPUT_SOURCE_TOUCHSCREEN) .displayId(ADISPLAY_ID_DEFAULT) .eventTime(systemTime(SYSTEM_TIME_MONOTONIC)) - .pointer(PointerBuilder(/* id */ 0, AMOTION_EVENT_TOOL_TYPE_FINGER) - .x(100) - .y(200)) - .pointer(PointerBuilder(/* id */ 1, AMOTION_EVENT_TOOL_TYPE_FINGER).x(50).y(50)) + .pointer(PointerBuilder(/*id=*/0, ToolType::FINGER).x(100).y(200)) + .pointer(PointerBuilder(/*id=*/1, ToolType::FINGER).x(50).y(50)) .build(); ASSERT_EQ(InputEventInjectionResult::SUCCEEDED, injectMotionEvent(mDispatcher, secondFingerDownEvent, INJECT_EVENT_TIMEOUT, InputEventInjectionSync::WAIT_FOR_RESULT)) << "Inject motion event should return InputEventInjectionResult::SUCCEEDED"; - spy->consumeMotionPointerDown(1 /*pointerIndex*/); + spy->consumeMotionPointerDown(/*pointerIndex=*/1); // 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) .eventTime(systemTime(SYSTEM_TIME_MONOTONIC)) - .pointer(PointerBuilder(/* id */ 0, AMOTION_EVENT_TOOL_TYPE_FINGER) - .x(100) - .y(200)) - .pointer(PointerBuilder(/* id */ 1, AMOTION_EVENT_TOOL_TYPE_FINGER).x(50).y(50)) - .pointer(PointerBuilder(/* id */ 2, AMOTION_EVENT_TOOL_TYPE_FINGER).x(-5).y(-5)) + .pointer(PointerBuilder(/*id=*/0, ToolType::FINGER).x(100).y(200)) + .pointer(PointerBuilder(/*id=*/1, ToolType::FINGER).x(50).y(50)) + .pointer(PointerBuilder(/*id=*/2, ToolType::FINGER).x(-5).y(-5)) .build(); ASSERT_EQ(InputEventInjectionResult::FAILED, injectMotionEvent(mDispatcher, thirdFingerDownEvent, INJECT_EVENT_TIMEOUT, InputEventInjectionSync::WAIT_FOR_RESULT)) - << "Inject motion event should return InputEventInjectionResult::SUCCEEDED"; + << "Inject motion event should return InputEventInjectionResult::FAILED"; spy->assertNoEvents(); window->assertNoEvents(); } /** - * Even when a spy window spans over multiple foreground windows, the spy should receive all - * pointers that are down within its bounds. + * After a spy window pilfers pointers, only the pointers used by the spy should be canceled */ -TEST_F(InputDispatcherSpyWindowTest, ReceivesMultiplePointers) { - auto windowLeft = createForeground(); - windowLeft->setFrame({0, 0, 100, 200}); - auto windowRight = createForeground(); - windowRight->setFrame({100, 0, 200, 200}); +TEST_F(InputDispatcherPilferPointersTest, PartiallyPilferRequiredPointers) { auto spy = createSpy(); - spy->setFrame({0, 0, 200, 200}); - mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {spy, windowLeft, windowRight}}}); + spy->setFrame(Rect(0, 0, 100, 100)); + auto window = createForeground(); + window->setFrame(Rect(0, 0, 200, 200)); + + mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {spy, window}}}); + // First finger down on the window only ASSERT_EQ(InputEventInjectionResult::SUCCEEDED, injectMotionDown(mDispatcher, AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT, - {50, 50})) + {150, 150})) << "Inject motion event should return InputEventInjectionResult::SUCCEEDED"; - windowLeft->consumeMotionDown(); - spy->consumeMotionDown(); + window->consumeMotionDown(); + // Second finger down on the spy and window const MotionEvent secondFingerDownEvent = MotionEventBuilder(POINTER_1_DOWN, AINPUT_SOURCE_TOUCHSCREEN) + .displayId(ADISPLAY_ID_DEFAULT) .eventTime(systemTime(SYSTEM_TIME_MONOTONIC)) - .pointer(PointerBuilder(/* id */ 0, AMOTION_EVENT_TOOL_TYPE_FINGER).x(50).y(50)) - .pointer( - PointerBuilder(/* id */ 1, AMOTION_EVENT_TOOL_TYPE_FINGER).x(150).y(50)) + .pointer(PointerBuilder(/*id=*/0, ToolType::FINGER).x(150).y(150)) + .pointer(PointerBuilder(/*id=*/1, ToolType::FINGER).x(10).y(10)) .build(); ASSERT_EQ(InputEventInjectionResult::SUCCEEDED, injectMotionEvent(mDispatcher, secondFingerDownEvent, INJECT_EVENT_TIMEOUT, InputEventInjectionSync::WAIT_FOR_RESULT)) << "Inject motion event should return InputEventInjectionResult::SUCCEEDED"; - windowRight->consumeMotionDown(); - spy->consumeMotionPointerDown(1 /*pointerIndex*/); + spy->consumeMotionDown(); + window->consumeMotionPointerDown(1); + + // Third finger down on the spy and window + const MotionEvent thirdFingerDownEvent = + MotionEventBuilder(POINTER_2_DOWN, AINPUT_SOURCE_TOUCHSCREEN) + .displayId(ADISPLAY_ID_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)) + .pointer(PointerBuilder(/*id=*/2, ToolType::FINGER).x(50).y(50)) + .build(); + ASSERT_EQ(InputEventInjectionResult::SUCCEEDED, + injectMotionEvent(mDispatcher, thirdFingerDownEvent, INJECT_EVENT_TIMEOUT, + InputEventInjectionSync::WAIT_FOR_RESULT)) + << "Inject motion event should return InputEventInjectionResult::SUCCEEDED"; + spy->consumeMotionPointerDown(1); + window->consumeMotionPointerDown(2); + + // 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); + + spy->assertNoEvents(); + window->assertNoEvents(); } /** - * When the first pointer lands outside the spy window and the second pointer lands inside it, the - * the spy should receive the second pointer with ACTION_DOWN. + * After a spy window pilfers pointers, all pilfered pointers that have already been dispatched to + * other windows should be canceled. If this results in the cancellation of all pointers for some + * window, then that window should receive ACTION_CANCEL. */ -TEST_F(InputDispatcherSpyWindowTest, ReceivesSecondPointerAsDown) { +TEST_F(InputDispatcherPilferPointersTest, PilferAllRequiredPointers) { + auto spy = createSpy(); + spy->setFrame(Rect(0, 0, 100, 100)); auto window = createForeground(); - window->setFrame({0, 0, 200, 200}); - auto spyRight = createSpy(); - spyRight->setFrame({100, 0, 200, 200}); - mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {spyRight, window}}}); + window->setFrame(Rect(0, 0, 200, 200)); + + mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {spy, window}}}); + // First finger down on both spy and window ASSERT_EQ(InputEventInjectionResult::SUCCEEDED, injectMotionDown(mDispatcher, AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT, - {50, 50})) + {10, 10})) << "Inject motion event should return InputEventInjectionResult::SUCCEEDED"; window->consumeMotionDown(); - spyRight->assertNoEvents(); + spy->consumeMotionDown(); + // Second finger down on the spy and window const MotionEvent secondFingerDownEvent = MotionEventBuilder(POINTER_1_DOWN, AINPUT_SOURCE_TOUCHSCREEN) + .displayId(ADISPLAY_ID_DEFAULT) .eventTime(systemTime(SYSTEM_TIME_MONOTONIC)) - .pointer(PointerBuilder(/* id */ 0, AMOTION_EVENT_TOOL_TYPE_FINGER).x(50).y(50)) - .pointer( - PointerBuilder(/* id */ 1, AMOTION_EVENT_TOOL_TYPE_FINGER).x(150).y(50)) + .pointer(PointerBuilder(/*id=*/0, ToolType::FINGER).x(10).y(10)) + .pointer(PointerBuilder(/*id=*/1, ToolType::FINGER).x(50).y(50)) .build(); ASSERT_EQ(InputEventInjectionResult::SUCCEEDED, injectMotionEvent(mDispatcher, secondFingerDownEvent, INJECT_EVENT_TIMEOUT, InputEventInjectionSync::WAIT_FOR_RESULT)) << "Inject motion event should return InputEventInjectionResult::SUCCEEDED"; - window->consumeMotionPointerDown(1 /*pointerIndex*/); - spyRight->consumeMotionDown(); + spy->consumeMotionPointerDown(1); + window->consumeMotionPointerDown(1); + + // Spy window pilfers the pointers. + EXPECT_EQ(OK, mDispatcher->pilferPointers(spy->getToken())); + window->consumeMotionCancel(); + + spy->assertNoEvents(); + window->assertNoEvents(); } /** - * The spy window should not be able to affect whether or not touches are split. Only the foreground - * windows should be allowed to control split touch. + * After a spy window pilfers pointers, new pointers that are not touching the spy window can still + * be sent to other windows */ -TEST_F(InputDispatcherSpyWindowTest, SplitIfNoForegroundWindowTouched) { - // This spy window prevents touch splitting. However, we still expect to split touches - // because a foreground window has not disabled splitting. +TEST_F(InputDispatcherPilferPointersTest, CanReceivePointersAfterPilfer) { auto spy = createSpy(); - spy->setPreventSplitting(true); - + spy->setFrame(Rect(0, 0, 100, 100)); auto window = createForeground(); - window->setFrame(Rect(0, 0, 100, 100)); + window->setFrame(Rect(0, 0, 200, 200)); mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {spy, window}}}); - // First finger down, no window touched. + // First finger down on both window and spy ASSERT_EQ(InputEventInjectionResult::SUCCEEDED, injectMotionDown(mDispatcher, AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT, - {100, 200})) + {10, 10})) << "Inject motion event should return InputEventInjectionResult::SUCCEEDED"; - spy->consumeMotionDown(ADISPLAY_ID_DEFAULT); - window->assertNoEvents(); + window->consumeMotionDown(); + spy->consumeMotionDown(); - // Second finger down on window, the window should receive touch down. + // Spy window pilfers the pointers. + EXPECT_EQ(OK, mDispatcher->pilferPointers(spy->getToken())); + window->consumeMotionCancel(); + + // Second finger down on the window only const MotionEvent secondFingerDownEvent = MotionEventBuilder(POINTER_1_DOWN, AINPUT_SOURCE_TOUCHSCREEN) .displayId(ADISPLAY_ID_DEFAULT) .eventTime(systemTime(SYSTEM_TIME_MONOTONIC)) - .pointer(PointerBuilder(/* id */ 0, AMOTION_EVENT_TOOL_TYPE_FINGER) - .x(100) - .y(200)) - .pointer(PointerBuilder(/* id */ 1, AMOTION_EVENT_TOOL_TYPE_FINGER).x(50).y(50)) + .pointer(PointerBuilder(/*id=*/0, ToolType::FINGER).x(10).y(10)) + .pointer(PointerBuilder(/*id=*/1, ToolType::FINGER).x(150).y(150)) .build(); ASSERT_EQ(InputEventInjectionResult::SUCCEEDED, injectMotionEvent(mDispatcher, secondFingerDownEvent, INJECT_EVENT_TIMEOUT, InputEventInjectionSync::WAIT_FOR_RESULT)) << "Inject motion event should return InputEventInjectionResult::SUCCEEDED"; + window->consumeMotionDown(); + window->assertNoEvents(); - window->consumeMotionDown(ADISPLAY_ID_DEFAULT); - spy->consumeMotionPointerDown(1 /* pointerIndex */); -} - -/** - * A spy window will usually be implemented as an un-focusable window. Verify that these windows - * do not receive key events. - */ -TEST_F(InputDispatcherSpyWindowTest, UnfocusableSpyDoesNotReceiveKeyEvents) { - auto spy = createSpy(); - spy->setFocusable(false); - - auto window = createForeground(); - mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {spy, window}}}); - setFocusedWindow(window); - window->consumeFocusEvent(true); - - ASSERT_EQ(InputEventInjectionResult::SUCCEEDED, injectKeyDown(mDispatcher)) - << "Inject key event should return InputEventInjectionResult::SUCCEEDED"; - window->consumeKeyDown(ADISPLAY_ID_NONE); - - ASSERT_EQ(InputEventInjectionResult::SUCCEEDED, injectKeyUp(mDispatcher)) - << "Inject key event should return InputEventInjectionResult::SUCCEEDED"; - window->consumeKeyUp(ADISPLAY_ID_NONE); - + // TODO(b/232530217): do not send the unnecessary MOVE event and delete the next line + spy->consumeMotionMove(); spy->assertNoEvents(); } @@ -7508,8 +9517,8 @@ public: std::shared_ptr overlayApplication = std::make_shared(); sp overlay = - new FakeWindowHandle(overlayApplication, mDispatcher, "Stylus interceptor window", - ADISPLAY_ID_DEFAULT); + sp::make(overlayApplication, mDispatcher, + "Stylus interceptor window", ADISPLAY_ID_DEFAULT); overlay->setFocusable(false); overlay->setOwnerInfo(111, 111); overlay->setTouchable(false); @@ -7519,31 +9528,30 @@ public: std::shared_ptr application = std::make_shared(); sp window = - new FakeWindowHandle(application, mDispatcher, "Application window", - ADISPLAY_ID_DEFAULT); + sp::make(application, mDispatcher, "Application window", + ADISPLAY_ID_DEFAULT); window->setFocusable(true); window->setOwnerInfo(222, 222); mDispatcher->setFocusedApplication(ADISPLAY_ID_DEFAULT, application); mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {overlay, window}}}); setFocusedWindow(window); - window->consumeFocusEvent(true /*hasFocus*/, true /*inTouchMode*/); + window->consumeFocusEvent(/*hasFocus=*/true, /*inTouchMode=*/true); return {std::move(overlay), std::move(window)}; } void sendFingerEvent(int32_t action) { - NotifyMotionArgs motionArgs = + mDispatcher->notifyMotion( generateMotionArgs(action, AINPUT_SOURCE_TOUCHSCREEN | AINPUT_SOURCE_STYLUS, - ADISPLAY_ID_DEFAULT, {PointF{20, 20}}); - mDispatcher->notifyMotion(&motionArgs); + ADISPLAY_ID_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}}); - motionArgs.pointerProperties[0].toolType = AMOTION_EVENT_TOOL_TYPE_STYLUS; - mDispatcher->notifyMotion(&motionArgs); + motionArgs.pointerProperties[0].toolType = ToolType::STYLUS; + mDispatcher->notifyMotion(motionArgs); } }; @@ -7651,17 +9659,18 @@ struct User { } InputEventInjectionResult injectTargetedKey(int32_t action) const { - return inputdispatcher::injectKey(mDispatcher, action, 0 /* repeatCount*/, ADISPLAY_ID_NONE, + return inputdispatcher::injectKey(mDispatcher, action, /*repeatCount=*/0, ADISPLAY_ID_NONE, InputEventInjectionSync::WAIT_FOR_RESULT, - INJECT_EVENT_TIMEOUT, false /*allowKeyRepeat*/, {mUid}, + INJECT_EVENT_TIMEOUT, /*allowKeyRepeat=*/false, {mUid}, mPolicyFlags); } sp createWindow() const { std::shared_ptr overlayApplication = std::make_shared(); - sp window = new FakeWindowHandle(overlayApplication, mDispatcher, - "Owned Window", ADISPLAY_ID_DEFAULT); + sp window = + sp::make(overlayApplication, mDispatcher, "Owned Window", + ADISPLAY_ID_DEFAULT); window->setOwnerInfo(mPid, mUid); return window; } diff --git a/services/inputflinger/tests/InputFlingerService_test.cpp b/services/inputflinger/tests/InputFlingerService_test.cpp deleted file mode 100644 index 454e531af6b495caff7bf8b3f93ef8095f0d375a..0000000000000000000000000000000000000000 --- a/services/inputflinger/tests/InputFlingerService_test.cpp +++ /dev/null @@ -1,243 +0,0 @@ -/* - * Copyright (C) 2020 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#include -#include - -#include -#include - -#include -#include -#include -#include -#include - -#include -#include - -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#define TAG "InputFlingerServiceTest" - -using android::gui::FocusRequest; -using android::os::BnInputFlinger; -using android::os::IInputFlinger; - -using std::chrono_literals::operator""ms; -using std::chrono_literals::operator""s; - -namespace android { - -static const String16 kTestServiceName = String16("InputFlingerService"); -static const String16 kQueryServiceName = String16("InputFlingerQueryService"); - -// --- InputFlingerServiceTest --- -class InputFlingerServiceTest : public testing::Test { -public: - void SetUp() override; - void TearDown() override; - -protected: - void InitializeInputFlinger(); - - sp mService; - sp mQuery; - -private: - std::unique_ptr mServerChannel, mClientChannel; - std::mutex mLock; -}; - - -class TestInputManager : public BnInputFlinger { -protected: - virtual ~TestInputManager(){}; - -public: - TestInputManager(){}; - - binder::Status getInputChannels(std::vector<::android::InputChannel>* channels); - - status_t dump(int fd, const Vector& args) override; - - binder::Status createInputChannel(const std::string& name, InputChannel* outChannel) override; - binder::Status removeInputChannel(const sp& connectionToken) override; - binder::Status setFocusedWindow(const FocusRequest&) override; - - void reset(); - -private: - mutable Mutex mLock; - std::vector> mInputChannels; -}; - -class TestInputQuery : public BnInputFlingerQuery { -public: - TestInputQuery(sp manager) : mManager(manager){}; - binder::Status getInputChannels(std::vector<::android::InputChannel>* channels) override; - binder::Status resetInputManager() override; - -private: - sp mManager; -}; - -binder::Status TestInputQuery::getInputChannels(std::vector<::android::InputChannel>* channels) { - return mManager->getInputChannels(channels); -} - -binder::Status TestInputQuery::resetInputManager() { - mManager->reset(); - return binder::Status::ok(); -} - -binder::Status TestInputManager::createInputChannel(const std::string& name, - InputChannel* outChannel) { - AutoMutex _l(mLock); - std::unique_ptr serverChannel; - std::unique_ptr clientChannel; - InputChannel::openInputChannelPair(name, serverChannel, clientChannel); - - clientChannel->copyTo(*outChannel); - - mInputChannels.emplace_back(std::move(serverChannel)); - - return binder::Status::ok(); -} - -binder::Status TestInputManager::removeInputChannel(const sp& connectionToken) { - AutoMutex _l(mLock); - - auto it = std::find_if(mInputChannels.begin(), mInputChannels.end(), - [&](std::shared_ptr& c) { - return c->getConnectionToken() == connectionToken; - }); - if (it != mInputChannels.end()) { - mInputChannels.erase(it); - } - - return binder::Status::ok(); -} - -status_t TestInputManager::dump(int fd, const Vector& args) { - std::string dump; - - dump += " InputFlinger dump\n"; - - ::write(fd, dump.c_str(), dump.size()); - return NO_ERROR; -} - -binder::Status TestInputManager::getInputChannels(std::vector<::android::InputChannel>* channels) { - channels->clear(); - for (std::shared_ptr& channel : mInputChannels) { - channels->push_back(*channel); - } - return binder::Status::ok(); -} - -binder::Status TestInputManager::setFocusedWindow(const FocusRequest& request) { - return binder::Status::ok(); -} - -void TestInputManager::reset() { - mInputChannels.clear(); -} - -void InputFlingerServiceTest::SetUp() { - InputChannel::openInputChannelPair("testchannels", mServerChannel, mClientChannel); - InitializeInputFlinger(); -} - -void InputFlingerServiceTest::TearDown() { - mQuery->resetInputManager(); -} - -void InputFlingerServiceTest::InitializeInputFlinger() { - sp input(defaultServiceManager()->waitForService(kTestServiceName)); - ASSERT_TRUE(input != nullptr); - mService = interface_cast(input); - - input = defaultServiceManager()->waitForService(kQueryServiceName); - ASSERT_TRUE(input != nullptr); - mQuery = interface_cast(input); -} - -/** - * Test InputFlinger service interface createInputChannel - */ -TEST_F(InputFlingerServiceTest, CreateInputChannelReturnsUnblockedFd) { - // Test that the unblocked file descriptor flag is kept across processes over binder - // transactions. - - InputChannel channel; - ASSERT_TRUE(mService->createInputChannel("testchannels", &channel).isOk()); - - const base::unique_fd& fd = channel.getFd(); - ASSERT_TRUE(fd.ok()); - - const int result = fcntl(fd, F_GETFL); - EXPECT_NE(result, -1); - EXPECT_EQ(result & O_NONBLOCK, O_NONBLOCK); -} - -TEST_F(InputFlingerServiceTest, CreateInputChannel) { - InputChannel channel; - ASSERT_TRUE(mService->createInputChannel("testchannels", &channel).isOk()); - - std::vector<::android::InputChannel> channels; - mQuery->getInputChannels(&channels); - ASSERT_EQ(channels.size(), 1UL); - EXPECT_EQ(channels[0].getConnectionToken(), channel.getConnectionToken()); - - mService->removeInputChannel(channel.getConnectionToken()); - mQuery->getInputChannels(&channels); - EXPECT_EQ(channels.size(), 0UL); -} - -} // namespace android - -int main(int argc, char** argv) { - pid_t forkPid = fork(); - - if (forkPid == 0) { - // Server process - android::sp manager = new android::TestInputManager(); - android::sp query = new android::TestInputQuery(manager); - - android::defaultServiceManager()->addService(android::kTestServiceName, manager, - false /*allowIsolated*/); - android::defaultServiceManager()->addService(android::kQueryServiceName, query, - false /*allowIsolated*/); - android::ProcessState::self()->startThreadPool(); - android::IPCThreadState::self()->joinThreadPool(); - } else { - android::ProcessState::self()->startThreadPool(); - ::testing::InitGoogleTest(&argc, argv); - int result = RUN_ALL_TESTS(); - kill(forkPid, SIGKILL); - return result; - } - return 0; -} diff --git a/services/inputflinger/tests/InputMapperTest.cpp b/services/inputflinger/tests/InputMapperTest.cpp new file mode 100644 index 0000000000000000000000000000000000000000..0eee2b9be47fa01c25ff6d19695e1b3b3962999b --- /dev/null +++ b/services/inputflinger/tests/InputMapperTest.cpp @@ -0,0 +1,238 @@ +/* + * 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 "InputMapperTest.h" + +#include +#include +#include + +namespace android { + +using testing::Return; + +void InputMapperUnitTest::SetUp() { + mFakePointerController = std::make_shared(); + mFakePointerController->setBounds(0, 0, 800 - 1, 480 - 1); + mFakePointerController->setPosition(400, 240); + + EXPECT_CALL(mMockInputReaderContext, getPointerController(DEVICE_ID)) + .WillRepeatedly(Return(mFakePointerController)); + + EXPECT_CALL(mMockInputReaderContext, getEventHub()).WillRepeatedly(Return(&mMockEventHub)); + InputDeviceIdentifier identifier; + identifier.name = "device"; + identifier.location = "USB1"; + identifier.bus = 0; + + EXPECT_CALL(mMockEventHub, getDeviceIdentifier(EVENTHUB_ID)).WillRepeatedly(Return(identifier)); + mDevice = std::make_unique(&mMockInputReaderContext, DEVICE_ID, + /*generation=*/2, identifier); + mDeviceContext = std::make_unique(*mDevice, EVENTHUB_ID); +} + +void InputMapperUnitTest::setupAxis(int axis, bool valid, int32_t min, int32_t max, + int32_t resolution) { + EXPECT_CALL(mMockEventHub, getAbsoluteAxisInfo(EVENTHUB_ID, axis, testing::_)) + .WillRepeatedly([=](int32_t, int32_t, RawAbsoluteAxisInfo* outAxisInfo) { + outAxisInfo->valid = valid; + outAxisInfo->minValue = min; + outAxisInfo->maxValue = max; + outAxisInfo->flat = 0; + outAxisInfo->fuzz = 0; + outAxisInfo->resolution = resolution; + return valid ? OK : -1; + }); +} + +void InputMapperUnitTest::expectScanCodes(bool present, std::set scanCodes) { + for (const auto& scanCode : scanCodes) { + EXPECT_CALL(mMockEventHub, hasScanCode(EVENTHUB_ID, scanCode)) + .WillRepeatedly(testing::Return(present)); + } +} + +void InputMapperUnitTest::setScanCodeState(KeyState state, std::set scanCodes) { + for (const auto& scanCode : scanCodes) { + EXPECT_CALL(mMockEventHub, getScanCodeState(EVENTHUB_ID, scanCode)) + .WillRepeatedly(testing::Return(static_cast(state))); + } +} + +void InputMapperUnitTest::setKeyCodeState(KeyState state, std::set keyCodes) { + for (const auto& keyCode : keyCodes) { + EXPECT_CALL(mMockEventHub, getKeyCodeState(EVENTHUB_ID, keyCode)) + .WillRepeatedly(testing::Return(static_cast(state))); + } +} + +std::list InputMapperUnitTest::process(int32_t type, int32_t code, int32_t value) { + RawEvent event; + event.when = systemTime(SYSTEM_TIME_MONOTONIC); + event.readTime = event.when; + event.deviceId = mMapper->getDeviceContext().getEventHubId(); + event.type = type; + event.code = code; + event.value = value; + return mMapper->process(&event); +} + +const char* InputMapperTest::DEVICE_NAME = "device"; +const char* InputMapperTest::DEVICE_LOCATION = "USB1"; +const ftl::Flags InputMapperTest::DEVICE_CLASSES = + ftl::Flags(0); // not needed for current tests + +void InputMapperTest::SetUp(ftl::Flags classes, int bus) { + mFakeEventHub = std::make_unique(); + mFakePolicy = sp::make(); + mFakeListener = std::make_unique(); + mReader = std::make_unique(mFakeEventHub, mFakePolicy, *mFakeListener); + mDevice = newDevice(DEVICE_ID, DEVICE_NAME, DEVICE_LOCATION, EVENTHUB_ID, classes, bus); + // Consume the device reset notification generated when adding a new device. + mFakeListener->assertNotifyDeviceResetWasCalled(); +} + +void InputMapperTest::SetUp() { + SetUp(DEVICE_CLASSES); +} + +void InputMapperTest::TearDown() { + mFakeListener.reset(); + mFakePolicy.clear(); +} + +void InputMapperTest::addConfigurationProperty(const char* key, const char* value) { + mFakeEventHub->addConfigurationProperty(EVENTHUB_ID, key, value); +} + +std::list InputMapperTest::configureDevice(ConfigurationChanges changes) { + using namespace ftl::flag_operators; + if (!changes.any() || + (changes.any(InputReaderConfiguration::Change::DISPLAY_INFO | + InputReaderConfiguration::Change::POINTER_CAPTURE | + InputReaderConfiguration::Change::DEVICE_TYPE))) { + mReader->requestRefreshConfiguration(changes); + mReader->loopOnce(); + } + std::list out = + mDevice->configure(ARBITRARY_TIME, mFakePolicy->getReaderConfiguration(), changes); + // Loop the reader to flush the input listener queue. + for (const NotifyArgs& args : out) { + mFakeListener->notify(args); + } + mReader->loopOnce(); + return out; +} + +std::shared_ptr InputMapperTest::newDevice(int32_t deviceId, const std::string& name, + const std::string& location, + int32_t eventHubId, + ftl::Flags classes, + int bus) { + InputDeviceIdentifier identifier; + identifier.name = name; + identifier.location = location; + identifier.bus = bus; + std::shared_ptr device = + std::make_shared(mReader->getContext(), deviceId, DEVICE_GENERATION, + identifier); + mReader->pushNextDevice(device); + mFakeEventHub->addDevice(eventHubId, name, classes, bus); + mReader->loopOnce(); + return device; +} + +void InputMapperTest::setDisplayInfoAndReconfigure(int32_t displayId, int32_t width, int32_t height, + ui::Rotation orientation, + const std::string& uniqueId, + std::optional physicalPort, + ViewportType viewportType) { + mFakePolicy->addDisplayViewport(displayId, width, height, orientation, /* isActive= */ true, + uniqueId, physicalPort, viewportType); + configureDevice(InputReaderConfiguration::Change::DISPLAY_INFO); +} + +void InputMapperTest::clearViewports() { + mFakePolicy->clearViewports(); +} + +std::list InputMapperTest::process(InputMapper& mapper, nsecs_t when, nsecs_t readTime, + int32_t type, int32_t code, int32_t value) { + RawEvent event; + event.when = when; + event.readTime = readTime; + event.deviceId = mapper.getDeviceContext().getEventHubId(); + event.type = type; + event.code = code; + event.value = value; + std::list processArgList = mapper.process(&event); + for (const NotifyArgs& args : processArgList) { + mFakeListener->notify(args); + } + // Loop the reader to flush the input listener queue. + mReader->loopOnce(); + return processArgList; +} + +void InputMapperTest::resetMapper(InputMapper& mapper, nsecs_t when) { + const auto resetArgs = mapper.reset(when); + for (const auto args : resetArgs) { + mFakeListener->notify(args); + } + // Loop the reader to flush the input listener queue. + mReader->loopOnce(); +} + +std::list InputMapperTest::handleTimeout(InputMapper& mapper, nsecs_t when) { + std::list generatedArgs = mapper.timeoutExpired(when); + for (const NotifyArgs& args : generatedArgs) { + mFakeListener->notify(args); + } + // Loop the reader to flush the input listener queue. + mReader->loopOnce(); + return generatedArgs; +} + +void InputMapperTest::assertMotionRange(const InputDeviceInfo& info, int32_t axis, uint32_t source, + float min, float max, float flat, float fuzz) { + const InputDeviceInfo::MotionRange* range = info.getMotionRange(axis, source); + ASSERT_TRUE(range != nullptr) << "Axis: " << axis << " Source: " << source; + ASSERT_EQ(axis, range->axis) << "Axis: " << axis << " Source: " << source; + ASSERT_EQ(source, range->source) << "Axis: " << axis << " Source: " << source; + ASSERT_NEAR(min, range->min, EPSILON) << "Axis: " << axis << " Source: " << source; + ASSERT_NEAR(max, range->max, EPSILON) << "Axis: " << axis << " Source: " << source; + ASSERT_NEAR(flat, range->flat, EPSILON) << "Axis: " << axis << " Source: " << source; + ASSERT_NEAR(fuzz, range->fuzz, EPSILON) << "Axis: " << axis << " Source: " << source; +} + +void InputMapperTest::assertPointerCoords(const PointerCoords& coords, float x, float y, + float pressure, float size, float touchMajor, + float touchMinor, float toolMajor, float toolMinor, + float orientation, float distance, + float scaledAxisEpsilon) { + ASSERT_NEAR(x, coords.getAxisValue(AMOTION_EVENT_AXIS_X), scaledAxisEpsilon); + ASSERT_NEAR(y, coords.getAxisValue(AMOTION_EVENT_AXIS_Y), scaledAxisEpsilon); + ASSERT_NEAR(pressure, coords.getAxisValue(AMOTION_EVENT_AXIS_PRESSURE), EPSILON); + ASSERT_NEAR(size, coords.getAxisValue(AMOTION_EVENT_AXIS_SIZE), EPSILON); + ASSERT_NEAR(touchMajor, coords.getAxisValue(AMOTION_EVENT_AXIS_TOUCH_MAJOR), scaledAxisEpsilon); + ASSERT_NEAR(touchMinor, coords.getAxisValue(AMOTION_EVENT_AXIS_TOUCH_MINOR), scaledAxisEpsilon); + ASSERT_NEAR(toolMajor, coords.getAxisValue(AMOTION_EVENT_AXIS_TOOL_MAJOR), scaledAxisEpsilon); + ASSERT_NEAR(toolMinor, coords.getAxisValue(AMOTION_EVENT_AXIS_TOOL_MINOR), scaledAxisEpsilon); + ASSERT_NEAR(orientation, coords.getAxisValue(AMOTION_EVENT_AXIS_ORIENTATION), EPSILON); + ASSERT_NEAR(distance, coords.getAxisValue(AMOTION_EVENT_AXIS_DISTANCE), EPSILON); +} + +} // namespace android diff --git a/services/inputflinger/tests/InputMapperTest.h b/services/inputflinger/tests/InputMapperTest.h new file mode 100644 index 0000000000000000000000000000000000000000..909bd9c0564b960758d81d90116b37eafd172605 --- /dev/null +++ b/services/inputflinger/tests/InputMapperTest.h @@ -0,0 +1,138 @@ +/* + * 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 + +#include +#include +#include +#include +#include +#include + +#include "FakeEventHub.h" +#include "FakeInputReaderPolicy.h" +#include "InstrumentedInputReader.h" +#include "InterfaceMocks.h" +#include "TestConstants.h" +#include "TestInputListener.h" + +namespace android { + +class InputMapperUnitTest : public testing::Test { +protected: + static constexpr int32_t EVENTHUB_ID = 1; + static constexpr int32_t DEVICE_ID = END_RESERVED_ID + 1000; + virtual void SetUp() override; + + void setupAxis(int axis, bool valid, int32_t min, int32_t max, int32_t resolution); + + void expectScanCodes(bool present, std::set scanCodes); + + void setScanCodeState(KeyState state, std::set scanCodes); + + void setKeyCodeState(KeyState state, std::set keyCodes); + + std::list process(int32_t type, int32_t code, int32_t value); + + MockEventHubInterface mMockEventHub; + std::shared_ptr mFakePointerController; + MockInputReaderContext mMockInputReaderContext; + std::unique_ptr mDevice; + + std::unique_ptr mDeviceContext; + InputReaderConfiguration mReaderConfiguration; + // The mapper should be created by the subclasses. + std::unique_ptr mMapper; +}; + +/** + * Deprecated - use InputMapperUnitTest instead. + */ +class InputMapperTest : public testing::Test { +protected: + static const char* DEVICE_NAME; + static const char* DEVICE_LOCATION; + static constexpr int32_t DEVICE_ID = END_RESERVED_ID + 1000; + static constexpr int32_t DEVICE_GENERATION = 2; + static constexpr int32_t DEVICE_CONTROLLER_NUMBER = 0; + static const ftl::Flags DEVICE_CLASSES; + static constexpr int32_t EVENTHUB_ID = 1; + + std::shared_ptr mFakeEventHub; + sp mFakePolicy; + std::unique_ptr mFakeListener; + std::unique_ptr mReader; + std::shared_ptr mDevice; + + virtual void SetUp(ftl::Flags classes, int bus = 0); + void SetUp() override; + void TearDown() override; + + void addConfigurationProperty(const char* key, const char* value); + std::list configureDevice(ConfigurationChanges changes); + std::shared_ptr newDevice(int32_t deviceId, const std::string& name, + const std::string& location, int32_t eventHubId, + ftl::Flags classes, int bus = 0); + template + T& addMapperAndConfigure(Args... args) { + T& mapper = + mDevice->addMapper(EVENTHUB_ID, mFakePolicy->getReaderConfiguration(), args...); + configureDevice(/*changes=*/{}); + std::list resetArgList = mDevice->reset(ARBITRARY_TIME); + resetArgList += mapper.reset(ARBITRARY_TIME); + // Loop the reader to flush the input listener queue. + for (const NotifyArgs& loopArgs : resetArgList) { + mFakeListener->notify(loopArgs); + } + mReader->loopOnce(); + return mapper; + } + + template + 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...); + } + + void setDisplayInfoAndReconfigure(int32_t displayId, int32_t width, int32_t height, + ui::Rotation orientation, const std::string& uniqueId, + std::optional physicalPort, + ViewportType viewportType); + void clearViewports(); + std::list process(InputMapper& mapper, nsecs_t when, nsecs_t readTime, int32_t type, + int32_t code, int32_t value); + void resetMapper(InputMapper& mapper, nsecs_t when); + + std::list handleTimeout(InputMapper& mapper, nsecs_t when); + + static void assertMotionRange(const InputDeviceInfo& info, int32_t axis, uint32_t source, + float min, float max, float flat, float fuzz); + static void assertPointerCoords(const PointerCoords& coords, float x, float y, float pressure, + float size, float touchMajor, float touchMinor, float toolMajor, + float toolMinor, float orientation, float distance, + float scaledAxisEpsilon = 1.f); +}; + +} // namespace android diff --git a/services/inputflinger/tests/InputClassifierConverter_test.cpp b/services/inputflinger/tests/InputProcessorConverter_test.cpp similarity index 76% rename from services/inputflinger/tests/InputClassifierConverter_test.cpp rename to services/inputflinger/tests/InputProcessorConverter_test.cpp index 81ef9b95f02b922b8dbb8ce88f3ecd3fe926b931..4b42f4b141185b5dcc924d51f5f8fbe29418d726 100644 --- a/services/inputflinger/tests/InputClassifierConverter_test.cpp +++ b/services/inputflinger/tests/InputProcessorConverter_test.cpp @@ -24,13 +24,13 @@ using namespace aidl::android::hardware::input; namespace android { -// --- InputClassifierConverterTest --- +// --- InputProcessorConverterTest --- static NotifyMotionArgs generateBasicMotionArgs() { // Create a basic motion event for testing PointerProperties properties; properties.id = 0; - properties.toolType = AMOTION_EVENT_TOOL_TYPE_FINGER; + properties.toolType = ToolType::FINGER; PointerCoords coords; coords.clear(); @@ -38,21 +38,21 @@ static NotifyMotionArgs generateBasicMotionArgs() { coords.setAxisValue(AMOTION_EVENT_AXIS_Y, 2); coords.setAxisValue(AMOTION_EVENT_AXIS_SIZE, 0.5); static constexpr nsecs_t downTime = 2; - NotifyMotionArgs motionArgs(1 /*sequenceNum*/, downTime /*eventTime*/, 2 /*readTime*/, - 3 /*deviceId*/, AINPUT_SOURCE_ANY, ADISPLAY_ID_DEFAULT, - 4 /*policyFlags*/, AMOTION_EVENT_ACTION_DOWN, 0 /*actionButton*/, - 0 /*flags*/, AMETA_NONE, 0 /*buttonState*/, + NotifyMotionArgs motionArgs(/*sequenceNum=*/1, /*eventTime=*/downTime, /*readTime=*/2, + /*deviceId=*/3, AINPUT_SOURCE_ANY, ADISPLAY_ID_DEFAULT, + /*policyFlags=*/4, AMOTION_EVENT_ACTION_DOWN, /*actionButton=*/0, + /*flags=*/0, AMETA_NONE, /*buttonState=*/0, MotionClassification::NONE, AMOTION_EVENT_EDGE_FLAG_NONE, - 1 /*pointerCount*/, &properties, &coords, 0 /*xPrecision*/, - 0 /*yPrecision*/, AMOTION_EVENT_INVALID_CURSOR_POSITION, + /*pointerCount=*/1, &properties, &coords, /*xPrecision=*/0, + /*yPrecision=*/0, AMOTION_EVENT_INVALID_CURSOR_POSITION, AMOTION_EVENT_INVALID_CURSOR_POSITION, downTime, - {} /*videoFrames*/); + /*videoFrames=*/{}); return motionArgs; } static float getMotionEventAxis(common::PointerCoords coords, common::Axis axis) { uint32_t index = BitSet64::getIndexOfBit(static_cast(coords.bits), - static_cast(axis)); + static_cast(axis)); return coords.values[index]; } @@ -60,7 +60,7 @@ static float getMotionEventAxis(common::PointerCoords coords, common::Axis axis) * Check that coordinates get converted properly from the framework's PointerCoords * to the hidl PointerCoords in input::common. */ -TEST(InputClassifierConverterTest, PointerCoordsAxes) { +TEST(InputProcessorConverterTest, PointerCoordsAxes) { const NotifyMotionArgs motionArgs = generateBasicMotionArgs(); ASSERT_EQ(1, motionArgs.pointerCoords[0].getX()); ASSERT_EQ(2, motionArgs.pointerCoords[0].getY()); @@ -76,7 +76,7 @@ TEST(InputClassifierConverterTest, PointerCoordsAxes) { ASSERT_EQ(getMotionEventAxis(motionEvent.pointerCoords[0], common::Axis::SIZE), motionArgs.pointerCoords[0].getAxisValue(AMOTION_EVENT_AXIS_SIZE)); ASSERT_EQ(BitSet64::count(motionArgs.pointerCoords[0].bits), - BitSet64::count(motionEvent.pointerCoords[0].bits)); + BitSet64::count(motionEvent.pointerCoords[0].bits)); } } // namespace android diff --git a/services/inputflinger/tests/InputClassifier_test.cpp b/services/inputflinger/tests/InputProcessor_test.cpp similarity index 65% rename from services/inputflinger/tests/InputClassifier_test.cpp rename to services/inputflinger/tests/InputProcessor_test.cpp index 3a7712727a26340d7856efdb7c48ea85e84db734..3b7cbfac5c4b73ab7a4a167a114ce1e9908b6815 100644 --- a/services/inputflinger/tests/InputClassifier_test.cpp +++ b/services/inputflinger/tests/InputProcessor_test.cpp @@ -14,7 +14,7 @@ * limitations under the License. */ -#include "../InputClassifier.h" +#include "../InputProcessor.h" #include #include @@ -31,130 +31,125 @@ using aidl::android::hardware::input::processor::IInputProcessor; namespace android { -// --- InputClassifierTest --- +// --- InputProcessorTest --- static NotifyMotionArgs generateBasicMotionArgs() { // Create a basic motion event for testing PointerProperties properties; properties.id = 0; - properties.toolType = AMOTION_EVENT_TOOL_TYPE_FINGER; + properties.toolType = ToolType::FINGER; PointerCoords coords; coords.clear(); coords.setAxisValue(AMOTION_EVENT_AXIS_X, 1); coords.setAxisValue(AMOTION_EVENT_AXIS_Y, 1); static constexpr nsecs_t downTime = 2; - NotifyMotionArgs motionArgs(1 /*sequenceNum*/, downTime /*eventTime*/, 2 /*readTime*/, - 3 /*deviceId*/, AINPUT_SOURCE_ANY, ADISPLAY_ID_DEFAULT, - 4 /*policyFlags*/, AMOTION_EVENT_ACTION_DOWN, 0 /*actionButton*/, - 0 /*flags*/, AMETA_NONE, 0 /*buttonState*/, + NotifyMotionArgs motionArgs(/*sequenceNum=*/1, /*eventTime=*/downTime, /*readTime=*/2, + /*deviceId=*/3, AINPUT_SOURCE_ANY, ADISPLAY_ID_DEFAULT, + /*policyFlags=*/4, AMOTION_EVENT_ACTION_DOWN, /*actionButton=*/0, + /*flags=*/0, AMETA_NONE, /*buttonState=*/0, MotionClassification::NONE, AMOTION_EVENT_EDGE_FLAG_NONE, - 1 /*pointerCount*/, &properties, &coords, 0 /*xPrecision*/, - 0 /*yPrecision*/, AMOTION_EVENT_INVALID_CURSOR_POSITION, + /*pointerCount=*/1, &properties, &coords, /*xPrecision=*/0, + /*yPrecision=*/0, AMOTION_EVENT_INVALID_CURSOR_POSITION, AMOTION_EVENT_INVALID_CURSOR_POSITION, downTime, - {} /*videoFrames*/); + /*videoFrames=*/{}); return motionArgs; } -class InputClassifierTest : public testing::Test { +class InputProcessorTest : public testing::Test { protected: TestInputListener mTestListener; - std::unique_ptr mClassifier; + std::unique_ptr mProcessor; - void SetUp() override { mClassifier = std::make_unique(mTestListener); } + void SetUp() override { mProcessor = std::make_unique(mTestListener); } }; /** - * Create a basic configuration change and send it to input classifier. + * Create a basic configuration change and send it to input processor. * Expect that the event is received by the next input stage, unmodified. */ -TEST_F(InputClassifierTest, SendToNextStage_NotifyConfigurationChangedArgs) { - // Create a basic configuration change and send to classifier - NotifyConfigurationChangedArgs args(1/*sequenceNum*/, 2/*eventTime*/); +TEST_F(InputProcessorTest, SendToNextStage_NotifyConfigurationChangedArgs) { + // Create a basic configuration change and send to processor + NotifyConfigurationChangedArgs args(/*sequenceNum=*/1, /*eventTime=*/2); - mClassifier->notifyConfigurationChanged(&args); + mProcessor->notifyConfigurationChanged(args); NotifyConfigurationChangedArgs outArgs; ASSERT_NO_FATAL_FAILURE(mTestListener.assertNotifyConfigurationChangedWasCalled(&outArgs)); ASSERT_EQ(args, outArgs); } -TEST_F(InputClassifierTest, SendToNextStage_NotifyKeyArgs) { - // Create a basic key event and send to classifier - NotifyKeyArgs args(1 /*sequenceNum*/, 2 /*eventTime*/, 21 /*readTime*/, 3 /*deviceId*/, - AINPUT_SOURCE_KEYBOARD, ADISPLAY_ID_DEFAULT, 0 /*policyFlags*/, - AKEY_EVENT_ACTION_DOWN, 4 /*flags*/, AKEYCODE_HOME, 5 /*scanCode*/, - AMETA_NONE, 6 /*downTime*/); +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, + AKEY_EVENT_ACTION_DOWN, /*flags=*/4, AKEYCODE_HOME, /*scanCode=*/5, + AMETA_NONE, /*downTime=*/6); - mClassifier->notifyKey(&args); - NotifyKeyArgs outArgs; - ASSERT_NO_FATAL_FAILURE(mTestListener.assertNotifyKeyWasCalled(&outArgs)); - ASSERT_EQ(args, outArgs); + mProcessor->notifyKey(args); + ASSERT_NO_FATAL_FAILURE(mTestListener.assertNotifyKeyWasCalled(testing::Eq(args))); } - /** - * Create a basic motion event and send it to input classifier. + * Create a basic motion event and send it to input processor. * Expect that the event is received by the next input stage, unmodified. */ -TEST_F(InputClassifierTest, SendToNextStage_NotifyMotionArgs) { +TEST_F(InputProcessorTest, SendToNextStage_NotifyMotionArgs) { NotifyMotionArgs motionArgs = generateBasicMotionArgs(); - mClassifier->notifyMotion(&motionArgs); - NotifyMotionArgs args; - ASSERT_NO_FATAL_FAILURE(mTestListener.assertNotifyMotionWasCalled(&args)); - ASSERT_EQ(motionArgs, args); + mProcessor->notifyMotion(motionArgs); + ASSERT_NO_FATAL_FAILURE(mTestListener.assertNotifyMotionWasCalled(testing::Eq(motionArgs))); } /** - * Create a basic switch event and send it to input classifier. + * Create a basic switch event and send it to input processor. * Expect that the event is received by the next input stage, unmodified. */ -TEST_F(InputClassifierTest, SendToNextStage_NotifySwitchArgs) { - NotifySwitchArgs args(1/*sequenceNum*/, 2/*eventTime*/, 3/*policyFlags*/, 4/*switchValues*/, - 5/*switchMask*/); +TEST_F(InputProcessorTest, SendToNextStage_NotifySwitchArgs) { + NotifySwitchArgs args(/*sequenceNum=*/1, /*eventTime=*/2, /*policyFlags=*/3, + /*switchValues=*/4, /*switchMask=*/5); - mClassifier->notifySwitch(&args); + mProcessor->notifySwitch(args); NotifySwitchArgs outArgs; ASSERT_NO_FATAL_FAILURE(mTestListener.assertNotifySwitchWasCalled(&outArgs)); ASSERT_EQ(args, outArgs); } /** - * Create a basic device reset event and send it to input classifier. + * Create a basic device reset event and send it to input processor. * Expect that the event is received by the next input stage, unmodified. */ -TEST_F(InputClassifierTest, SendToNextStage_NotifyDeviceResetArgs) { - NotifyDeviceResetArgs args(1/*sequenceNum*/, 2/*eventTime*/, 3/*deviceId*/); +TEST_F(InputProcessorTest, SendToNextStage_NotifyDeviceResetArgs) { + NotifyDeviceResetArgs args(/*sequenceNum=*/1, /*eventTime=*/2, /*deviceId=*/3); - mClassifier->notifyDeviceReset(&args); + mProcessor->notifyDeviceReset(args); NotifyDeviceResetArgs outArgs; ASSERT_NO_FATAL_FAILURE(mTestListener.assertNotifyDeviceResetWasCalled(&outArgs)); ASSERT_EQ(args, outArgs); } -TEST_F(InputClassifierTest, SetMotionClassifier_Enabled) { - mClassifier->setMotionClassifierEnabled(true); +TEST_F(InputProcessorTest, SetMotionClassifier_Enabled) { + mProcessor->setMotionClassifierEnabled(true); } -TEST_F(InputClassifierTest, SetMotionClassifier_Disabled) { - mClassifier->setMotionClassifierEnabled(false); +TEST_F(InputProcessorTest, SetMotionClassifier_Disabled) { + mProcessor->setMotionClassifierEnabled(false); } /** * Try to break it by calling setMotionClassifierEnabled multiple times. */ -TEST_F(InputClassifierTest, SetMotionClassifier_Multiple) { - mClassifier->setMotionClassifierEnabled(true); - mClassifier->setMotionClassifierEnabled(true); - mClassifier->setMotionClassifierEnabled(true); - mClassifier->setMotionClassifierEnabled(false); - mClassifier->setMotionClassifierEnabled(false); - mClassifier->setMotionClassifierEnabled(true); - mClassifier->setMotionClassifierEnabled(true); - mClassifier->setMotionClassifierEnabled(true); +TEST_F(InputProcessorTest, SetMotionClassifier_Multiple) { + mProcessor->setMotionClassifierEnabled(true); + mProcessor->setMotionClassifierEnabled(true); + mProcessor->setMotionClassifierEnabled(true); + mProcessor->setMotionClassifierEnabled(false); + mProcessor->setMotionClassifierEnabled(false); + mProcessor->setMotionClassifierEnabled(true); + mProcessor->setMotionClassifierEnabled(true); + mProcessor->setMotionClassifierEnabled(true); } /** - * A minimal implementation of IInputClassifier. + * A minimal implementation of IInputProcessor. */ class TestHal : public aidl::android::hardware::input::processor::BnInputProcessor { ::ndk::ScopedAStatus classify( @@ -212,7 +207,7 @@ TEST_F(MotionClassifierTest, Classify_OneVideoFrame) { NotifyMotionArgs motionArgs = generateBasicMotionArgs(); std::vector videoData = {1, 2, 3, 4}; - timeval timestamp = { 1, 1}; + timeval timestamp = {1, 1}; TouchVideoFrame frame(2, 2, std::move(videoData), timestamp); motionArgs.videoFrames = {frame}; @@ -228,11 +223,11 @@ TEST_F(MotionClassifierTest, Classify_TwoVideoFrames) { NotifyMotionArgs motionArgs = generateBasicMotionArgs(); std::vector videoData1 = {1, 2, 3, 4}; - timeval timestamp1 = { 1, 1}; + timeval timestamp1 = {1, 1}; TouchVideoFrame frame1(2, 2, std::move(videoData1), timestamp1); std::vector videoData2 = {6, 6, 6, 6}; - timeval timestamp2 = { 1, 2}; + timeval timestamp2 = {1, 2}; TouchVideoFrame frame2(2, 2, std::move(videoData2), timestamp2); motionArgs.videoFrames = {frame1, frame2}; @@ -253,7 +248,7 @@ TEST_F(MotionClassifierTest, Reset_DoesNotCrash) { * Make sure MotionClassifier does not crash when a device is reset. */ TEST_F(MotionClassifierTest, DeviceReset_DoesNotCrash) { - NotifyDeviceResetArgs args(1/*sequenceNum*/, 2/*eventTime*/, 3/*deviceId*/); + NotifyDeviceResetArgs args(/*sequenceNum=*/1, /*eventTime=*/2, /*deviceId=*/3); ASSERT_NO_FATAL_FAILURE(mMotionClassifier->reset(args)); } diff --git a/services/inputflinger/tests/InputReader_test.cpp b/services/inputflinger/tests/InputReader_test.cpp index ad6cf0134005e18912565230efe0dac0e725441f..5141acb5b954c55635d111f3eb51ff261f0fb8c5 100644 --- a/services/inputflinger/tests/InputReader_test.cpp +++ b/services/inputflinger/tests/InputReader_test.cpp @@ -16,6 +16,7 @@ #include #include +#include #include #include @@ -31,29 +32,32 @@ #include #include #include +#include #include #include #include #include +#include #include #include - +#include + +#include +#include "FakeEventHub.h" +#include "FakeInputReaderPolicy.h" +#include "FakePointerController.h" +#include "InputMapperTest.h" +#include "InstrumentedInputReader.h" +#include "TestConstants.h" #include "input/DisplayViewport.h" #include "input/Input.h" namespace android { using namespace ftl::flag_operators; - +using testing::AllOf; using std::chrono_literals::operator""ms; -// Timeout for waiting for an expected event -static constexpr std::chrono::duration WAIT_TIMEOUT = 100ms; - -// An arbitrary time value. -static constexpr nsecs_t ARBITRARY_TIME = 1234; -static constexpr nsecs_t READ_TIME = 4321; - // Arbitrary display properties. static constexpr int32_t DISPLAY_ID = 0; static const std::string DISPLAY_UNIQUE_ID = "local:1"; @@ -74,9 +78,6 @@ static constexpr int32_t INVALID_TRACKING_ID = -1; static constexpr int32_t FIRST_TRACKING_ID = 0; static constexpr int32_t SECOND_TRACKING_ID = 1; static constexpr int32_t THIRD_TRACKING_ID = 2; -static constexpr int32_t DEFAULT_BATTERY = 1; -static constexpr int32_t BATTERY_STATUS = 4; -static constexpr int32_t BATTERY_CAPACITY = 66; static constexpr int32_t LIGHT_BRIGHTNESS = 0x55000000; static constexpr int32_t LIGHT_COLOR = 0x7F448866; static constexpr int32_t LIGHT_PLAYER_ID = 2; @@ -90,33 +91,10 @@ static constexpr int32_t ACTION_POINTER_1_DOWN = static constexpr int32_t ACTION_POINTER_1_UP = AMOTION_EVENT_ACTION_POINTER_UP | (1 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT); -// Error tolerance for floating point assertions. -static const float EPSILON = 0.001f; - -using ::testing::AllOf; - -MATCHER_P(WithAction, action, "InputEvent with specified action") { - return arg.action == action; -} - -MATCHER_P(WithSource, source, "InputEvent with specified source") { - return arg.source == source; -} - -MATCHER_P(WithDisplayId, displayId, "InputEvent with specified displayId") { - return arg.displayId == displayId; -} - -MATCHER_P2(WithCoords, x, y, "MotionEvent with specified action") { - return arg.pointerCoords[0].getX() == x && arg.pointerCoords[0].getY(); -} - -MATCHER_P(WithToolType, toolType, "InputEvent with specified tool type") { - const auto argToolType = arg.pointerProperties[0].toolType; - *result_listener << "expected tool type " << motionToolTypeToString(toolType) << ", but got " - << motionToolTypeToString(argToolType); - return argToolType == toolType; -} +// Minimum timestamp separation between subsequent input events from a Bluetooth device. +static constexpr nsecs_t MIN_BLUETOOTH_TIMESTAMP_DELTA = ms2ns(4); +// Maximum smoothing time delta so that we don't generate events too far into the future. +constexpr static nsecs_t MAX_BLUETOOTH_SMOOTHING_DELTA = ms2ns(32); template static inline T min(T a, T b) { @@ -132,12 +110,12 @@ const std::unordered_map LIGHT_COLORS = {{"red", LightC {"green", LightColor::GREEN}, {"blue", LightColor::BLUE}}; -static int32_t getInverseRotation(int32_t orientation) { +static ui::Rotation getInverseRotation(ui::Rotation orientation) { switch (orientation) { - case DISPLAY_ORIENTATION_90: - return DISPLAY_ORIENTATION_270; - case DISPLAY_ORIENTATION_270: - return DISPLAY_ORIENTATION_90; + case ui::ROTATION_90: + return ui::ROTATION_270; + case ui::ROTATION_270: + return ui::ROTATION_90; default: return orientation; } @@ -145,7 +123,7 @@ static int32_t getInverseRotation(int32_t orientation) { static void assertAxisResolution(MultiTouchInputMapper& mapper, int axis, float resolution) { InputDeviceInfo info; - mapper.populateDeviceInfo(&info); + mapper.populateDeviceInfo(info); const InputDeviceInfo::MotionRange* motionRange = info.getMotionRange(axis, AINPUT_SOURCE_TOUCHSCREEN); @@ -154,3279 +132,3536 @@ static void assertAxisResolution(MultiTouchInputMapper& mapper, int axis, float static void assertAxisNotPresent(MultiTouchInputMapper& mapper, int axis) { InputDeviceInfo info; - mapper.populateDeviceInfo(&info); + mapper.populateDeviceInfo(info); const InputDeviceInfo::MotionRange* motionRange = info.getMotionRange(axis, AINPUT_SOURCE_TOUCHSCREEN); ASSERT_EQ(nullptr, motionRange); } -// --- FakePointerController --- - -class FakePointerController : public PointerControllerInterface { - bool mHaveBounds; - float mMinX, mMinY, mMaxX, mMaxY; - float mX, mY; - int32_t mButtonState; - int32_t mDisplayId; - -public: - FakePointerController() : - mHaveBounds(false), mMinX(0), mMinY(0), mMaxX(0), mMaxY(0), mX(0), mY(0), - mButtonState(0), mDisplayId(ADISPLAY_ID_DEFAULT) { - } - - virtual ~FakePointerController() {} - - void setBounds(float minX, float minY, float maxX, float maxY) { - mHaveBounds = true; - mMinX = minX; - mMinY = minY; - mMaxX = maxX; - mMaxY = maxY; - } - - void setPosition(float x, float y) override { - mX = x; - mY = y; +[[maybe_unused]] static void dumpReader(InputReader& reader) { + std::string dump; + reader.dump(dump); + std::istringstream iss(dump); + for (std::string line; std::getline(iss, line);) { + ALOGE("%s", line.c_str()); + std::this_thread::sleep_for(std::chrono::milliseconds(1)); } +} - void setButtonState(int32_t buttonState) override { mButtonState = buttonState; } +// --- FakeInputMapper --- - int32_t getButtonState() const override { return mButtonState; } +class FakeInputMapper : public InputMapper { + uint32_t mSources; + int32_t mKeyboardType; + int32_t mMetaState; + KeyedVector mKeyCodeStates; + KeyedVector mScanCodeStates; + KeyedVector mSwitchStates; + // fake mapping which would normally come from keyCharacterMap + std::unordered_map mKeyCodeMapping; + std::vector mSupportedKeyCodes; - void getPosition(float* outX, float* outY) const override { - *outX = mX; - *outY = mY; - } + std::mutex mLock; + std::condition_variable mStateChangedCondition; + bool mConfigureWasCalled GUARDED_BY(mLock); + bool mResetWasCalled GUARDED_BY(mLock); + bool mProcessWasCalled GUARDED_BY(mLock); + RawEvent mLastEvent GUARDED_BY(mLock); - int32_t getDisplayId() const override { return mDisplayId; } + std::optional mViewport; +public: + FakeInputMapper(InputDeviceContext& deviceContext, const InputReaderConfiguration& readerConfig, + uint32_t sources) + : InputMapper(deviceContext, readerConfig), + mSources(sources), + mKeyboardType(AINPUT_KEYBOARD_TYPE_NONE), + mMetaState(0), + mConfigureWasCalled(false), + mResetWasCalled(false), + mProcessWasCalled(false) {} - void setDisplayViewport(const DisplayViewport& viewport) override { - mDisplayId = viewport.displayId; - } + virtual ~FakeInputMapper() {} - const std::map>& getSpots() { - return mSpotsByDisplay; + void setKeyboardType(int32_t keyboardType) { + mKeyboardType = keyboardType; } -private: - bool getBounds(float* outMinX, float* outMinY, float* outMaxX, float* outMaxY) const override { - *outMinX = mMinX; - *outMinY = mMinY; - *outMaxX = mMaxX; - *outMaxY = mMaxY; - return mHaveBounds; + void setMetaState(int32_t metaState) { + mMetaState = metaState; } - void move(float deltaX, float deltaY) override { - mX += deltaX; - if (mX < mMinX) mX = mMinX; - if (mX > mMaxX) mX = mMaxX; - mY += deltaY; - if (mY < mMinY) mY = mMinY; - if (mY > mMaxY) mY = mMaxY; + void assertConfigureWasCalled() { + std::unique_lock lock(mLock); + base::ScopedLockAssertion assumeLocked(mLock); + const bool configureCalled = + mStateChangedCondition.wait_for(lock, WAIT_TIMEOUT, [this]() REQUIRES(mLock) { + return mConfigureWasCalled; + }); + if (!configureCalled) { + FAIL() << "Expected configure() to have been called."; + } + mConfigureWasCalled = false; } - void fade(Transition) override {} - - void unfade(Transition) override {} - - void setPresentation(Presentation) override {} - - void setSpots(const PointerCoords*, const uint32_t*, BitSet32 spotIdBits, - int32_t displayId) override { - std::vector newSpots; - // Add spots for fingers that are down. - for (BitSet32 idBits(spotIdBits); !idBits.isEmpty(); ) { - uint32_t id = idBits.clearFirstMarkedBit(); - newSpots.push_back(id); + void assertResetWasCalled() { + std::unique_lock lock(mLock); + base::ScopedLockAssertion assumeLocked(mLock); + const bool resetCalled = + mStateChangedCondition.wait_for(lock, WAIT_TIMEOUT, [this]() REQUIRES(mLock) { + return mResetWasCalled; + }); + if (!resetCalled) { + FAIL() << "Expected reset() to have been called."; } - - mSpotsByDisplay[displayId] = newSpots; + mResetWasCalled = false; } - void clearSpots() override { mSpotsByDisplay.clear(); } - - std::map> mSpotsByDisplay; -}; - - -// --- FakeInputReaderPolicy --- - -class FakeInputReaderPolicy : public InputReaderPolicyInterface { - std::mutex mLock; - 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; - TouchAffineTransformation transform; - -protected: - virtual ~FakeInputReaderPolicy() {} - -public: - FakeInputReaderPolicy() { + void assertProcessWasCalled(RawEvent* outLastEvent = nullptr) { + std::unique_lock lock(mLock); + base::ScopedLockAssertion assumeLocked(mLock); + const bool processCalled = + mStateChangedCondition.wait_for(lock, WAIT_TIMEOUT, [this]() REQUIRES(mLock) { + return mProcessWasCalled; + }); + if (!processCalled) { + FAIL() << "Expected process() to have been called."; + } + if (outLastEvent) { + *outLastEvent = mLastEvent; + } + mProcessWasCalled = false; } - void assertInputDevicesChanged() { - waitForInputDevices([](bool devicesChanged) { - if (!devicesChanged) { - FAIL() << "Timed out waiting for notifyInputDevicesChanged() to be called."; - } - }); + void setKeyCodeState(int32_t keyCode, int32_t state) { + mKeyCodeStates.replaceValueFor(keyCode, state); } - void assertInputDevicesNotChanged() { - waitForInputDevices([](bool devicesChanged) { - if (devicesChanged) { - FAIL() << "Expected notifyInputDevicesChanged() to not be called."; - } - }); + void setScanCodeState(int32_t scanCode, int32_t state) { + mScanCodeStates.replaceValueFor(scanCode, state); } - virtual void clearViewports() { - mViewports.clear(); - mConfig.setDisplayViewports(mViewports); + void setSwitchState(int32_t switchCode, int32_t state) { + mSwitchStates.replaceValueFor(switchCode, state); } - std::optional getDisplayViewportByUniqueId(const std::string& uniqueId) const { - return mConfig.getDisplayViewportByUniqueId(uniqueId); - } - std::optional getDisplayViewportByType(ViewportType type) const { - return mConfig.getDisplayViewportByType(type); + void addSupportedKeyCode(int32_t keyCode) { + mSupportedKeyCodes.push_back(keyCode); } - std::optional getDisplayViewportByPort(uint8_t displayPort) const { - return mConfig.getDisplayViewportByPort(displayPort); + void addKeyCodeMapping(int32_t fromKeyCode, int32_t toKeyCode) { + mKeyCodeMapping.insert_or_assign(fromKeyCode, toKeyCode); } - void addDisplayViewport(DisplayViewport viewport) { - mViewports.push_back(std::move(viewport)); - mConfig.setDisplayViewports(mViewports); - } +private: + uint32_t getSources() const override { return mSources; } - void addDisplayViewport(int32_t displayId, int32_t width, int32_t height, int32_t orientation, - bool isActive, const std::string& uniqueId, - std::optional physicalPort, ViewportType type) { - const bool isRotated = - (orientation == DISPLAY_ORIENTATION_90 || orientation == DISPLAY_ORIENTATION_270); - DisplayViewport v; - v.displayId = displayId; - v.orientation = orientation; - v.logicalLeft = 0; - v.logicalTop = 0; - v.logicalRight = isRotated ? height : width; - v.logicalBottom = isRotated ? width : height; - v.physicalLeft = 0; - v.physicalTop = 0; - v.physicalRight = isRotated ? height : width; - v.physicalBottom = isRotated ? width : height; - v.deviceWidth = isRotated ? height : width; - v.deviceHeight = isRotated ? width : height; - v.isActive = isActive; - v.uniqueId = uniqueId; - v.physicalPort = physicalPort; - v.type = type; - - addDisplayViewport(v); - } + void populateDeviceInfo(InputDeviceInfo& deviceInfo) override { + InputMapper::populateDeviceInfo(deviceInfo); - bool updateViewport(const DisplayViewport& viewport) { - size_t count = mViewports.size(); - for (size_t i = 0; i < count; i++) { - const DisplayViewport& currentViewport = mViewports[i]; - if (currentViewport.displayId == viewport.displayId) { - mViewports[i] = viewport; - mConfig.setDisplayViewports(mViewports); - return true; - } + if (mKeyboardType != AINPUT_KEYBOARD_TYPE_NONE) { + deviceInfo.setKeyboardType(mKeyboardType); } - // no viewport found. - return false; - } - - void addExcludedDeviceName(const std::string& deviceName) { - mConfig.excludedDeviceNames.push_back(deviceName); - } - - void addInputPortAssociation(const std::string& inputPort, uint8_t displayPort) { - mConfig.portAssociations.insert({inputPort, displayPort}); - } - - void addInputUniqueIdAssociation(const std::string& inputUniqueId, - const std::string& displayUniqueId) { - mConfig.uniqueIdAssociations.insert({inputUniqueId, displayUniqueId}); } - void addDisabledDevice(int32_t deviceId) { mConfig.disabledDevices.insert(deviceId); } - - void removeDisabledDevice(int32_t deviceId) { mConfig.disabledDevices.erase(deviceId); } - - void setPointerController(std::shared_ptr controller) { - mPointerController = std::move(controller); - } + std::list reconfigure(nsecs_t, const InputReaderConfiguration& config, + ConfigurationChanges changes) override { + std::scoped_lock lock(mLock); + mConfigureWasCalled = true; - const InputReaderConfiguration* getReaderConfiguration() const { - return &mConfig; - } + // Find the associated viewport if exist. + const std::optional displayPort = getDeviceContext().getAssociatedDisplayPort(); + if (displayPort && changes.test(InputReaderConfiguration::Change::DISPLAY_INFO)) { + mViewport = config.getDisplayViewportByPort(*displayPort); + } - const std::vector& getInputDevices() const { - return mInputDevices; + mStateChangedCondition.notify_all(); + return {}; } - TouchAffineTransformation getTouchAffineTransformation(const std::string& inputDeviceDescriptor, - int32_t surfaceRotation) { - return transform; + std::list reset(nsecs_t) override { + std::scoped_lock lock(mLock); + mResetWasCalled = true; + mStateChangedCondition.notify_all(); + return {}; } - void setTouchAffineTransformation(const TouchAffineTransformation t) { - transform = t; + std::list process(const RawEvent* rawEvent) override { + std::scoped_lock lock(mLock); + mLastEvent = *rawEvent; + mProcessWasCalled = true; + mStateChangedCondition.notify_all(); + return {}; } - PointerCaptureRequest setPointerCapture(bool enabled) { - mConfig.pointerCaptureRequest = {enabled, mNextPointerCaptureSequenceNumber++}; - return mConfig.pointerCaptureRequest; + int32_t getKeyCodeState(uint32_t, int32_t keyCode) override { + ssize_t index = mKeyCodeStates.indexOfKey(keyCode); + return index >= 0 ? mKeyCodeStates.valueAt(index) : AKEY_STATE_UNKNOWN; } - void setShowTouches(bool enabled) { - mConfig.showTouches = enabled; + int32_t getKeyCodeForKeyLocation(int32_t locationKeyCode) const override { + auto it = mKeyCodeMapping.find(locationKeyCode); + return it != mKeyCodeMapping.end() ? it->second : locationKeyCode; } - void setDefaultPointerDisplayId(int32_t pointerDisplayId) { - mConfig.defaultPointerDisplayId = pointerDisplayId; + int32_t getScanCodeState(uint32_t, int32_t scanCode) override { + ssize_t index = mScanCodeStates.indexOfKey(scanCode); + return index >= 0 ? mScanCodeStates.valueAt(index) : AKEY_STATE_UNKNOWN; } - float getPointerGestureMovementSpeedRatio() { return mConfig.pointerGestureMovementSpeedRatio; } - - void setVelocityControlParams(const VelocityControlParameters& params) { - mConfig.pointerVelocityControlParameters = params; - mConfig.wheelVelocityControlParameters = params; + int32_t getSwitchState(uint32_t, int32_t switchCode) override { + ssize_t index = mSwitchStates.indexOfKey(switchCode); + return index >= 0 ? mSwitchStates.valueAt(index) : AKEY_STATE_UNKNOWN; } -private: - uint32_t mNextPointerCaptureSequenceNumber = 0; - - void getReaderConfiguration(InputReaderConfiguration* outConfig) override { - *outConfig = mConfig; + // Return true if the device has non-empty key layout. + bool markSupportedKeyCodes(uint32_t, const std::vector& keyCodes, + uint8_t* outFlags) override { + for (size_t i = 0; i < keyCodes.size(); i++) { + for (size_t j = 0; j < mSupportedKeyCodes.size(); j++) { + if (keyCodes[i] == mSupportedKeyCodes[j]) { + outFlags[i] = 1; + } + } + } + bool result = mSupportedKeyCodes.size() > 0; + return result; } - std::shared_ptr obtainPointerController( - int32_t /*deviceId*/) override { - return mPointerController; + virtual int32_t getMetaState() { + return mMetaState; } - void notifyInputDevicesChanged(const std::vector& inputDevices) override { - std::scoped_lock lock(mLock); - mInputDevices = inputDevices; - mInputDevicesChanged = true; - mDevicesChangedCondition.notify_all(); + virtual void fadePointer() { } - std::shared_ptr getKeyboardLayoutOverlay( - const InputDeviceIdentifier&) override { - return nullptr; + virtual std::optional getAssociatedDisplay() { + if (mViewport) { + return std::make_optional(mViewport->displayId); + } + return std::nullopt; } +}; - std::string getDeviceAlias(const InputDeviceIdentifier&) override { return ""; } - - void waitForInputDevices(std::function processDevicesChanged) { - std::unique_lock lock(mLock); - base::ScopedLockAssertion assumeLocked(mLock); +// --- InputReaderPolicyTest --- +class InputReaderPolicyTest : public testing::Test { +protected: + sp mFakePolicy; - const bool devicesChanged = - mDevicesChangedCondition.wait_for(lock, WAIT_TIMEOUT, [this]() REQUIRES(mLock) { - return mInputDevicesChanged; - }); - ASSERT_NO_FATAL_FAILURE(processDevicesChanged(devicesChanged)); - mInputDevicesChanged = false; - } + void SetUp() override { mFakePolicy = sp::make(); } + void TearDown() override { mFakePolicy.clear(); } }; -// --- FakeEventHub --- - -class FakeEventHub : public EventHubInterface { - struct KeyInfo { - int32_t keyCode; - uint32_t flags; - }; +/** + * Check that empty set of viewports is an acceptable configuration. + * Also try to get internal viewport two different ways - by type and by uniqueId. + * + * There will be confusion if two viewports with empty uniqueId and identical type are present. + * Such configuration is not currently allowed. + */ +TEST_F(InputReaderPolicyTest, Viewports_GetCleared) { + static const std::string uniqueId = "local:0"; - struct SensorInfo { - InputDeviceSensorType sensorType; - int32_t sensorDataIndex; - }; + // We didn't add any viewports yet, so there shouldn't be any. + std::optional internalViewport = + mFakePolicy->getDisplayViewportByType(ViewportType::INTERNAL); + ASSERT_FALSE(internalViewport); - struct Device { - InputDeviceIdentifier identifier; - ftl::Flags classes; - PropertyMap configuration; - KeyedVector absoluteAxes; - KeyedVector relativeAxes; - KeyedVector keyCodeStates; - KeyedVector scanCodeStates; - KeyedVector switchStates; - KeyedVector absoluteAxisValue; - KeyedVector keysByScanCode; - KeyedVector keysByUsageCode; - KeyedVector leds; - // fake mapping which would normally come from keyCharacterMap - std::unordered_map keyCodeMapping; - std::unordered_map sensorsByAbsCode; - BitArray mscBitmask; - std::vector virtualKeys; - bool enabled; - - status_t enable() { - enabled = true; - return OK; - } + // Add an internal viewport, then clear it + mFakePolicy->addDisplayViewport(DISPLAY_ID, DISPLAY_WIDTH, DISPLAY_HEIGHT, ui::ROTATION_0, + /*isActive=*/true, uniqueId, NO_PORT, ViewportType::INTERNAL); - status_t disable() { - enabled = false; - return OK; - } + // Check matching by uniqueId + internalViewport = mFakePolicy->getDisplayViewportByUniqueId(uniqueId); + ASSERT_TRUE(internalViewport); + ASSERT_EQ(ViewportType::INTERNAL, internalViewport->type); - explicit Device(ftl::Flags classes) : classes(classes), enabled(true) {} - }; + // Check matching by viewport type + internalViewport = mFakePolicy->getDisplayViewportByType(ViewportType::INTERNAL); + ASSERT_TRUE(internalViewport); + ASSERT_EQ(uniqueId, internalViewport->uniqueId); - std::mutex mLock; - std::condition_variable mEventsCondition; - - KeyedVector mDevices; - std::vector mExcludedDevices; - std::vector mEvents GUARDED_BY(mLock); - std::unordered_map> mVideoFrames; - std::vector mVibrators = {0, 1}; - std::unordered_map mRawLightInfos; - // Simulates a device light brightness, from light id to light brightness. - std::unordered_map mLightBrightness; - // Simulates a device light intensities, from light id to light intensities map. - std::unordered_map> - mLightIntensities; + mFakePolicy->clearViewports(); + // Make sure nothing is found after clear + internalViewport = mFakePolicy->getDisplayViewportByUniqueId(uniqueId); + ASSERT_FALSE(internalViewport); + internalViewport = mFakePolicy->getDisplayViewportByType(ViewportType::INTERNAL); + ASSERT_FALSE(internalViewport); +} -public: - virtual ~FakeEventHub() { - for (size_t i = 0; i < mDevices.size(); i++) { - delete mDevices.valueAt(i); - } - } +TEST_F(InputReaderPolicyTest, Viewports_GetByType) { + const std::string internalUniqueId = "local:0"; + 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; - FakeEventHub() { } + // Add an internal viewport + mFakePolicy->addDisplayViewport(DISPLAY_ID, DISPLAY_WIDTH, DISPLAY_HEIGHT, ui::ROTATION_0, + /*isActive=*/true, internalUniqueId, NO_PORT, + ViewportType::INTERNAL); + // Add an external viewport + mFakePolicy->addDisplayViewport(DISPLAY_ID, DISPLAY_WIDTH, DISPLAY_HEIGHT, ui::ROTATION_0, + /*isActive=*/true, externalUniqueId, NO_PORT, + ViewportType::EXTERNAL); + // Add an virtual viewport + mFakePolicy->addDisplayViewport(virtualDisplayId1, DISPLAY_WIDTH, DISPLAY_HEIGHT, + ui::ROTATION_0, /*isActive=*/true, virtualUniqueId1, NO_PORT, + ViewportType::VIRTUAL); + // Add another virtual viewport + mFakePolicy->addDisplayViewport(virtualDisplayId2, DISPLAY_WIDTH, DISPLAY_HEIGHT, + ui::ROTATION_0, /*isActive=*/true, virtualUniqueId2, NO_PORT, + ViewportType::VIRTUAL); - void addDevice(int32_t deviceId, const std::string& name, - ftl::Flags classes) { - Device* device = new Device(classes); - device->identifier.name = name; - mDevices.add(deviceId, device); - - enqueueEvent(ARBITRARY_TIME, READ_TIME, deviceId, EventHubInterface::DEVICE_ADDED, 0, 0); - } + // Check matching by type for internal + std::optional internalViewport = + mFakePolicy->getDisplayViewportByType(ViewportType::INTERNAL); + ASSERT_TRUE(internalViewport); + ASSERT_EQ(internalUniqueId, internalViewport->uniqueId); - void removeDevice(int32_t deviceId) { - delete mDevices.valueFor(deviceId); - mDevices.removeItem(deviceId); + // Check matching by type for external + std::optional externalViewport = + mFakePolicy->getDisplayViewportByType(ViewportType::EXTERNAL); + ASSERT_TRUE(externalViewport); + ASSERT_EQ(externalUniqueId, externalViewport->uniqueId); - enqueueEvent(ARBITRARY_TIME, READ_TIME, deviceId, EventHubInterface::DEVICE_REMOVED, 0, 0); - } + // Check matching by uniqueId for virtual viewport #1 + std::optional virtualViewport1 = + mFakePolicy->getDisplayViewportByUniqueId(virtualUniqueId1); + ASSERT_TRUE(virtualViewport1); + ASSERT_EQ(ViewportType::VIRTUAL, virtualViewport1->type); + ASSERT_EQ(virtualUniqueId1, virtualViewport1->uniqueId); + ASSERT_EQ(virtualDisplayId1, virtualViewport1->displayId); - bool isDeviceEnabled(int32_t deviceId) { - Device* device = getDevice(deviceId); - if (device == nullptr) { - ALOGE("Incorrect device id=%" PRId32 " provided to %s", deviceId, __func__); - return false; - } - return device->enabled; - } + // Check matching by uniqueId for virtual viewport #2 + std::optional virtualViewport2 = + mFakePolicy->getDisplayViewportByUniqueId(virtualUniqueId2); + ASSERT_TRUE(virtualViewport2); + ASSERT_EQ(ViewportType::VIRTUAL, virtualViewport2->type); + ASSERT_EQ(virtualUniqueId2, virtualViewport2->uniqueId); + ASSERT_EQ(virtualDisplayId2, virtualViewport2->displayId); +} - status_t enableDevice(int32_t deviceId) { - status_t result; - Device* device = getDevice(deviceId); - if (device == nullptr) { - ALOGE("Incorrect device id=%" PRId32 " provided to %s", deviceId, __func__); - return BAD_VALUE; - } - if (device->enabled) { - ALOGW("Duplicate call to %s, device %" PRId32 " already enabled", __func__, deviceId); - return OK; - } - result = device->enable(); - return result; - } - status_t disableDevice(int32_t deviceId) { - Device* device = getDevice(deviceId); - if (device == nullptr) { - ALOGE("Incorrect device id=%" PRId32 " provided to %s", deviceId, __func__); - return BAD_VALUE; - } - if (!device->enabled) { - ALOGW("Duplicate call to %s, device %" PRId32 " already disabled", __func__, deviceId); - return OK; - } - return device->disable(); - } +/** + * We can have 2 viewports of the same kind. We can distinguish them by uniqueId, and confirm + * that lookup works by checking display id. + * Check that 2 viewports of each kind is possible, for all existing viewport types. + */ +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; - void finishDeviceScan() { - enqueueEvent(ARBITRARY_TIME, READ_TIME, 0, EventHubInterface::FINISHED_DEVICE_SCAN, 0, 0); - } + std::vector types = {ViewportType::INTERNAL, ViewportType::EXTERNAL, + ViewportType::VIRTUAL}; + for (const ViewportType& type : types) { + mFakePolicy->clearViewports(); + // Add a viewport + mFakePolicy->addDisplayViewport(displayId1, DISPLAY_WIDTH, DISPLAY_HEIGHT, ui::ROTATION_0, + /*isActive=*/true, uniqueId1, NO_PORT, type); + // Add another viewport + mFakePolicy->addDisplayViewport(displayId2, DISPLAY_WIDTH, DISPLAY_HEIGHT, ui::ROTATION_0, + /*isActive=*/true, uniqueId2, NO_PORT, type); - void addConfigurationProperty(int32_t deviceId, const String8& key, const String8& value) { - Device* device = getDevice(deviceId); - device->configuration.addProperty(key, value); - } + // Check that correct display viewport was returned by comparing the display IDs. + std::optional viewport1 = + mFakePolicy->getDisplayViewportByUniqueId(uniqueId1); + ASSERT_TRUE(viewport1); + ASSERT_EQ(displayId1, viewport1->displayId); + ASSERT_EQ(type, viewport1->type); - void addConfigurationMap(int32_t deviceId, const PropertyMap* configuration) { - Device* device = getDevice(deviceId); - device->configuration.addAll(configuration); - } + std::optional viewport2 = + mFakePolicy->getDisplayViewportByUniqueId(uniqueId2); + ASSERT_TRUE(viewport2); + ASSERT_EQ(displayId2, viewport2->displayId); + ASSERT_EQ(type, viewport2->type); - void addAbsoluteAxis(int32_t deviceId, int axis, - int32_t minValue, int32_t maxValue, int flat, int fuzz, int resolution = 0) { - Device* device = getDevice(deviceId); - - RawAbsoluteAxisInfo info; - info.valid = true; - info.minValue = minValue; - info.maxValue = maxValue; - info.flat = flat; - info.fuzz = fuzz; - info.resolution = resolution; - device->absoluteAxes.add(axis, info); + // When there are multiple viewports of the same kind, and uniqueId is not specified + // in the call to getDisplayViewport, then that situation is not supported. + // The viewports can be stored in any order, so we cannot rely on the order, since that + // is just implementation detail. + // However, we can check that it still returns *a* viewport, we just cannot assert + // which one specifically is returned. + std::optional someViewport = mFakePolicy->getDisplayViewportByType(type); + ASSERT_TRUE(someViewport); } +} - void addRelativeAxis(int32_t deviceId, int32_t axis) { - Device* device = getDevice(deviceId); - device->relativeAxes.add(axis, true); - } +/** + * When we have multiple internal displays make sure we always return the default display when + * querying by type. + */ +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"); - void setKeyCodeState(int32_t deviceId, int32_t keyCode, int32_t state) { - Device* device = getDevice(deviceId); - device->keyCodeStates.replaceValueFor(keyCode, state); - } + // Add the default display first and ensure it gets returned. + mFakePolicy->clearViewports(); + mFakePolicy->addDisplayViewport(ADISPLAY_ID_DEFAULT, DISPLAY_WIDTH, DISPLAY_HEIGHT, + ui::ROTATION_0, /*isActive=*/true, uniqueId1, NO_PORT, + ViewportType::INTERNAL); + mFakePolicy->addDisplayViewport(nonDefaultDisplayId, DISPLAY_WIDTH, DISPLAY_HEIGHT, + ui::ROTATION_0, /*isActive=*/true, uniqueId2, NO_PORT, + ViewportType::INTERNAL); - void setScanCodeState(int32_t deviceId, int32_t scanCode, int32_t state) { - Device* device = getDevice(deviceId); - device->scanCodeStates.replaceValueFor(scanCode, state); - } + std::optional viewport = + mFakePolicy->getDisplayViewportByType(ViewportType::INTERNAL); + ASSERT_TRUE(viewport); + ASSERT_EQ(ADISPLAY_ID_DEFAULT, viewport->displayId); + ASSERT_EQ(ViewportType::INTERNAL, viewport->type); - void setSwitchState(int32_t deviceId, int32_t switchCode, int32_t state) { - Device* device = getDevice(deviceId); - device->switchStates.replaceValueFor(switchCode, state); - } + // Add the default display second to make sure order doesn't matter. + mFakePolicy->clearViewports(); + 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, + ui::ROTATION_0, /*isActive=*/true, uniqueId1, NO_PORT, + ViewportType::INTERNAL); - void setAbsoluteAxisValue(int32_t deviceId, int32_t axis, int32_t value) { - Device* device = getDevice(deviceId); - device->absoluteAxisValue.replaceValueFor(axis, value); - } + viewport = mFakePolicy->getDisplayViewportByType(ViewportType::INTERNAL); + ASSERT_TRUE(viewport); + ASSERT_EQ(ADISPLAY_ID_DEFAULT, viewport->displayId); + ASSERT_EQ(ViewportType::INTERNAL, viewport->type); +} - void addKey(int32_t deviceId, int32_t scanCode, int32_t usageCode, - int32_t keyCode, uint32_t flags) { - Device* device = getDevice(deviceId); - KeyInfo info; - info.keyCode = keyCode; - info.flags = flags; - if (scanCode) { - device->keysByScanCode.add(scanCode, info); - } - if (usageCode) { - device->keysByUsageCode.add(usageCode, info); - } - } +/** + * Check getDisplayViewportByPort + */ +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; + const uint8_t hdmi1 = 0; + const uint8_t hdmi2 = 1; + const uint8_t hdmi3 = 2; - void addKeyCodeMapping(int32_t deviceId, int32_t fromKeyCode, int32_t toKeyCode) { - Device* device = getDevice(deviceId); - device->keyCodeMapping.insert_or_assign(fromKeyCode, toKeyCode); - } + mFakePolicy->clearViewports(); + // Add a viewport that's associated with some display port that's not of interest. + mFakePolicy->addDisplayViewport(displayId1, DISPLAY_WIDTH, DISPLAY_HEIGHT, ui::ROTATION_0, + /*isActive=*/true, uniqueId1, hdmi3, type); + // Add another viewport, connected to HDMI1 port + mFakePolicy->addDisplayViewport(displayId2, DISPLAY_WIDTH, DISPLAY_HEIGHT, ui::ROTATION_0, + /*isActive=*/true, uniqueId2, hdmi1, type); - void addLed(int32_t deviceId, int32_t led, bool initialState) { - Device* device = getDevice(deviceId); - device->leds.add(led, initialState); - } + // Check that correct display viewport was returned by comparing the display ports. + std::optional hdmi1Viewport = mFakePolicy->getDisplayViewportByPort(hdmi1); + ASSERT_TRUE(hdmi1Viewport); + ASSERT_EQ(displayId2, hdmi1Viewport->displayId); + ASSERT_EQ(uniqueId2, hdmi1Viewport->uniqueId); - void addSensorAxis(int32_t deviceId, int32_t absCode, InputDeviceSensorType sensorType, - int32_t sensorDataIndex) { - Device* device = getDevice(deviceId); - SensorInfo info; - info.sensorType = sensorType; - info.sensorDataIndex = sensorDataIndex; - device->sensorsByAbsCode.emplace(absCode, info); - } + // Check that we can still get the same viewport using the uniqueId + hdmi1Viewport = mFakePolicy->getDisplayViewportByUniqueId(uniqueId2); + ASSERT_TRUE(hdmi1Viewport); + ASSERT_EQ(displayId2, hdmi1Viewport->displayId); + ASSERT_EQ(uniqueId2, hdmi1Viewport->uniqueId); + ASSERT_EQ(type, hdmi1Viewport->type); - void setMscEvent(int32_t deviceId, int32_t mscEvent) { - Device* device = getDevice(deviceId); - typename BitArray::Buffer buffer; - buffer[mscEvent / 32] = 1 << mscEvent % 32; - device->mscBitmask.loadFromBuffer(buffer); - } + // Check that we cannot find a port with "HDMI2", because we never added one + std::optional hdmi2Viewport = mFakePolicy->getDisplayViewportByPort(hdmi2); + ASSERT_FALSE(hdmi2Viewport); +} - void addRawLightInfo(int32_t rawId, RawLightInfo&& info) { - mRawLightInfos.emplace(rawId, std::move(info)); - } +// --- InputReaderTest --- - void fakeLightBrightness(int32_t rawId, int32_t brightness) { - mLightBrightness.emplace(rawId, brightness); - } +class InputReaderTest : public testing::Test { +protected: + std::unique_ptr mFakeListener; + sp mFakePolicy; + std::shared_ptr mFakeEventHub; + std::unique_ptr mReader; - void fakeLightIntensities(int32_t rawId, - const std::unordered_map intensities) { - mLightIntensities.emplace(rawId, std::move(intensities)); - } + void SetUp() override { + mFakeEventHub = std::make_unique(); + mFakePolicy = sp::make(); + mFakeListener = std::make_unique(); - bool getLedState(int32_t deviceId, int32_t led) { - Device* device = getDevice(deviceId); - return device->leds.valueFor(led); + mReader = std::make_unique(mFakeEventHub, mFakePolicy, + *mFakeListener); } - std::vector& getExcludedDevices() { - return mExcludedDevices; + void TearDown() override { + mFakeListener.reset(); + mFakePolicy.clear(); } - void addVirtualKeyDefinition(int32_t deviceId, const VirtualKeyDefinition& definition) { - Device* device = getDevice(deviceId); - device->virtualKeys.push_back(definition); - } + void addDevice(int32_t eventHubId, const std::string& name, + ftl::Flags classes, const PropertyMap* configuration) { + mFakeEventHub->addDevice(eventHubId, name, classes); - void enqueueEvent(nsecs_t when, nsecs_t readTime, int32_t deviceId, int32_t type, int32_t code, - int32_t value) { - std::scoped_lock lock(mLock); - RawEvent event; - event.when = when; - event.readTime = readTime; - event.deviceId = deviceId; - event.type = type; - event.code = code; - event.value = value; - mEvents.push_back(event); - - if (type == EV_ABS) { - setAbsoluteAxisValue(deviceId, code, value); + if (configuration) { + mFakeEventHub->addConfigurationMap(eventHubId, configuration); } + mFakeEventHub->finishDeviceScan(); + mReader->loopOnce(); + mReader->loopOnce(); + ASSERT_NO_FATAL_FAILURE(mFakePolicy->assertInputDevicesChanged()); + ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyInputDevicesChangedWasCalled()); + ASSERT_NO_FATAL_FAILURE(mFakeEventHub->assertQueueIsEmpty()); } - void setVideoFrames(std::unordered_map> videoFrames) { - mVideoFrames = std::move(videoFrames); + void disableDevice(int32_t deviceId) { + mFakePolicy->addDisabledDevice(deviceId); + mReader->requestRefreshConfiguration(InputReaderConfiguration::Change::ENABLED_STATE); } - void assertQueueIsEmpty() { - std::unique_lock lock(mLock); - base::ScopedLockAssertion assumeLocked(mLock); - const bool queueIsEmpty = - mEventsCondition.wait_for(lock, WAIT_TIMEOUT, - [this]() REQUIRES(mLock) { return mEvents.size() == 0; }); - if (!queueIsEmpty) { - FAIL() << "Timed out waiting for EventHub queue to be emptied."; - } + void enableDevice(int32_t deviceId) { + mFakePolicy->removeDisabledDevice(deviceId); + mReader->requestRefreshConfiguration(InputReaderConfiguration::Change::ENABLED_STATE); } -private: - Device* getDevice(int32_t deviceId) const { - ssize_t index = mDevices.indexOfKey(deviceId); - return index >= 0 ? mDevices.valueAt(index) : nullptr; + FakeInputMapper& addDeviceWithFakeInputMapper(int32_t deviceId, int32_t eventHubId, + const std::string& name, + ftl::Flags classes, + uint32_t sources, + const PropertyMap* configuration) { + std::shared_ptr device = mReader->newDevice(deviceId, name); + FakeInputMapper& mapper = + device->addMapper(eventHubId, + mFakePolicy->getReaderConfiguration(), sources); + mReader->pushNextDevice(device); + addDevice(eventHubId, name, classes, configuration); + return mapper; } +}; - ftl::Flags getDeviceClasses(int32_t deviceId) const override { - Device* device = getDevice(deviceId); - return device ? device->classes : ftl::Flags(0); - } +TEST_F(InputReaderTest, PolicyGetInputDevices) { + ASSERT_NO_FATAL_FAILURE(addDevice(1, "keyboard", InputDeviceClass::KEYBOARD, nullptr)); + ASSERT_NO_FATAL_FAILURE(addDevice(2, "ignored", ftl::Flags(0), + nullptr)); // no classes so device will be ignored - InputDeviceIdentifier getDeviceIdentifier(int32_t deviceId) const override { - Device* device = getDevice(deviceId); - return device ? device->identifier : InputDeviceIdentifier(); - } + // Should also have received a notification describing the new input devices. + const std::vector& inputDevices = mFakePolicy->getInputDevices(); + ASSERT_EQ(1U, inputDevices.size()); + ASSERT_EQ(END_RESERVED_ID + 1, inputDevices[0].getId()); + ASSERT_STREQ("keyboard", inputDevices[0].getIdentifier().name.c_str()); + ASSERT_EQ(AINPUT_KEYBOARD_TYPE_NON_ALPHABETIC, inputDevices[0].getKeyboardType()); + ASSERT_EQ(AINPUT_SOURCE_KEYBOARD, inputDevices[0].getSources()); + ASSERT_EQ(0U, inputDevices[0].getMotionRanges().size()); +} - int32_t getDeviceControllerNumber(int32_t) const override { return 0; } +TEST_F(InputReaderTest, InputDeviceRecreatedOnSysfsNodeChanged) { + ASSERT_NO_FATAL_FAILURE(addDevice(1, "keyboard", InputDeviceClass::KEYBOARD, nullptr)); + mFakeEventHub->setSysfsRootPath(1, "xyz"); - void getConfiguration(int32_t deviceId, PropertyMap* outConfiguration) const override { - Device* device = getDevice(deviceId); - if (device) { - *outConfiguration = device->configuration; - } - } + // Should also have received a notification describing the new input device. + ASSERT_EQ(1U, mFakePolicy->getInputDevices().size()); + InputDeviceInfo inputDevice = mFakePolicy->getInputDevices()[0]; + ASSERT_EQ(0U, inputDevice.getLights().size()); + + RawLightInfo infoMonolight = {.id = 123, + .name = "mono_keyboard_backlight", + .maxBrightness = 255, + .flags = InputLightClass::BRIGHTNESS, + .path = ""}; + mFakeEventHub->addRawLightInfo(/*rawId=*/123, std::move(infoMonolight)); + mReader->sysfsNodeChanged("xyz"); + mReader->loopOnce(); - status_t getAbsoluteAxisInfo(int32_t deviceId, int axis, - RawAbsoluteAxisInfo* outAxisInfo) const override { - Device* device = getDevice(deviceId); - if (device && device->enabled) { - ssize_t index = device->absoluteAxes.indexOfKey(axis); - if (index >= 0) { - *outAxisInfo = device->absoluteAxes.valueAt(index); - return OK; - } - } - outAxisInfo->clear(); - return -1; - } + // Should also have received a notification describing the new recreated input device. + ASSERT_NO_FATAL_FAILURE(mFakePolicy->assertInputDevicesChanged()); + inputDevice = mFakePolicy->getInputDevices()[0]; + ASSERT_EQ(1U, inputDevice.getLights().size()); +} - bool hasRelativeAxis(int32_t deviceId, int axis) const override { - Device* device = getDevice(deviceId); - if (device) { - return device->relativeAxes.indexOfKey(axis) >= 0; - } - return false; - } +TEST_F(InputReaderTest, GetMergedInputDevices) { + constexpr int32_t deviceId = END_RESERVED_ID + 1000; + constexpr int32_t eventHubIds[2] = {END_RESERVED_ID, END_RESERVED_ID + 1}; + // Add two subdevices to device + std::shared_ptr device = mReader->newDevice(deviceId, "fake"); + // Must add at least one mapper or the device will be ignored! + device->addMapper(eventHubIds[0], mFakePolicy->getReaderConfiguration(), + AINPUT_SOURCE_KEYBOARD); + device->addMapper(eventHubIds[1], mFakePolicy->getReaderConfiguration(), + AINPUT_SOURCE_KEYBOARD); - bool hasInputProperty(int32_t, int) const override { return false; } + // Push same device instance for next device to be added, so they'll have same identifier. + mReader->pushNextDevice(device); + mReader->pushNextDevice(device); + ASSERT_NO_FATAL_FAILURE( + addDevice(eventHubIds[0], "fake1", InputDeviceClass::KEYBOARD, nullptr)); + ASSERT_NO_FATAL_FAILURE( + addDevice(eventHubIds[1], "fake2", InputDeviceClass::KEYBOARD, nullptr)); - bool hasMscEvent(int32_t deviceId, int mscEvent) const override final { - Device* device = getDevice(deviceId); - if (device) { - return mscEvent >= 0 && mscEvent <= MSC_MAX ? device->mscBitmask.test(mscEvent) : false; - } - return false; - } + // Two devices will be merged to one input device as they have same identifier + ASSERT_EQ(1U, mFakePolicy->getInputDevices().size()); +} - status_t mapKey(int32_t deviceId, int32_t scanCode, int32_t usageCode, int32_t metaState, - int32_t* outKeycode, int32_t* outMetaState, uint32_t* outFlags) const override { - Device* device = getDevice(deviceId); - if (device) { - const KeyInfo* key = getKey(device, scanCode, usageCode); - if (key) { - if (outKeycode) { - *outKeycode = key->keyCode; - } - if (outFlags) { - *outFlags = key->flags; - } - if (outMetaState) { - *outMetaState = metaState; - } - return OK; - } - } - return NAME_NOT_FOUND; - } +TEST_F(InputReaderTest, GetMergedInputDevicesEnabled) { + constexpr int32_t deviceId = END_RESERVED_ID + 1000; + constexpr int32_t eventHubIds[2] = {END_RESERVED_ID, END_RESERVED_ID + 1}; + // Add two subdevices to device + std::shared_ptr device = mReader->newDevice(deviceId, "fake"); + // Must add at least one mapper or the device will be ignored! + device->addMapper(eventHubIds[0], mFakePolicy->getReaderConfiguration(), + AINPUT_SOURCE_KEYBOARD); + device->addMapper(eventHubIds[1], mFakePolicy->getReaderConfiguration(), + AINPUT_SOURCE_KEYBOARD); - const KeyInfo* getKey(Device* device, int32_t scanCode, int32_t usageCode) const { - if (usageCode) { - ssize_t index = device->keysByUsageCode.indexOfKey(usageCode); - if (index >= 0) { - return &device->keysByUsageCode.valueAt(index); - } - } - if (scanCode) { - ssize_t index = device->keysByScanCode.indexOfKey(scanCode); - if (index >= 0) { - return &device->keysByScanCode.valueAt(index); - } - } - return nullptr; - } + // Push same device instance for next device to be added, so they'll have same identifier. + mReader->pushNextDevice(device); + mReader->pushNextDevice(device); + // Sensor device is initially disabled + ASSERT_NO_FATAL_FAILURE(addDevice(eventHubIds[0], "fake1", + InputDeviceClass::KEYBOARD | InputDeviceClass::SENSOR, + nullptr)); + // Device is disabled because the only sub device is a sensor device and disabled initially. + ASSERT_FALSE(mFakeEventHub->isDeviceEnabled(eventHubIds[0])); + ASSERT_FALSE(device->isEnabled()); + ASSERT_NO_FATAL_FAILURE( + addDevice(eventHubIds[1], "fake2", InputDeviceClass::KEYBOARD, nullptr)); + // The merged device is enabled if any sub device is enabled + ASSERT_TRUE(mFakeEventHub->isDeviceEnabled(eventHubIds[1])); + ASSERT_TRUE(device->isEnabled()); +} - status_t mapAxis(int32_t, int32_t, AxisInfo*) const override { return NAME_NOT_FOUND; } +TEST_F(InputReaderTest, WhenEnabledChanges_SendsDeviceResetNotification) { + constexpr int32_t deviceId = END_RESERVED_ID + 1000; + constexpr ftl::Flags deviceClass(InputDeviceClass::KEYBOARD); + constexpr int32_t eventHubId = 1; + std::shared_ptr device = mReader->newDevice(deviceId, "fake"); + // Must add at least one mapper or the device will be ignored! + device->addMapper(eventHubId, mFakePolicy->getReaderConfiguration(), + AINPUT_SOURCE_KEYBOARD); + mReader->pushNextDevice(device); + ASSERT_NO_FATAL_FAILURE(addDevice(eventHubId, "fake", deviceClass, nullptr)); - base::Result> mapSensor(int32_t deviceId, - int32_t absCode) { - Device* device = getDevice(deviceId); - if (!device) { - return Errorf("Sensor device not found."); - } - auto it = device->sensorsByAbsCode.find(absCode); - if (it == device->sensorsByAbsCode.end()) { - return Errorf("Sensor map not found."); - } - const SensorInfo& info = it->second; - return std::make_pair(info.sensorType, info.sensorDataIndex); - } + ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyConfigurationChangedWasCalled(nullptr)); - void setExcludedDevices(const std::vector& devices) override { - mExcludedDevices = devices; - } + NotifyDeviceResetArgs resetArgs; + ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyDeviceResetWasCalled(&resetArgs)); + ASSERT_EQ(deviceId, resetArgs.deviceId); - size_t getEvents(int, RawEvent* buffer, size_t bufferSize) override { - std::scoped_lock lock(mLock); + ASSERT_EQ(device->isEnabled(), true); + disableDevice(deviceId); + mReader->loopOnce(); - const size_t filledSize = std::min(mEvents.size(), bufferSize); - std::copy(mEvents.begin(), mEvents.begin() + filledSize, buffer); + ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyDeviceResetWasCalled(&resetArgs)); + ASSERT_EQ(deviceId, resetArgs.deviceId); + ASSERT_EQ(device->isEnabled(), false); - mEvents.erase(mEvents.begin(), mEvents.begin() + filledSize); - mEventsCondition.notify_all(); - return filledSize; - } + disableDevice(deviceId); + mReader->loopOnce(); + ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyDeviceResetWasNotCalled()); + ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyConfigurationChangedWasNotCalled()); + ASSERT_EQ(device->isEnabled(), false); - std::vector getVideoFrames(int32_t deviceId) override { - auto it = mVideoFrames.find(deviceId); - if (it != mVideoFrames.end()) { - std::vector frames = std::move(it->second); - mVideoFrames.erase(deviceId); - return frames; - } - return {}; - } + enableDevice(deviceId); + mReader->loopOnce(); + ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyDeviceResetWasCalled(&resetArgs)); + ASSERT_EQ(deviceId, resetArgs.deviceId); + ASSERT_EQ(device->isEnabled(), true); +} - int32_t getScanCodeState(int32_t deviceId, int32_t scanCode) const override { - Device* device = getDevice(deviceId); - if (device) { - ssize_t index = device->scanCodeStates.indexOfKey(scanCode); - if (index >= 0) { - return device->scanCodeStates.valueAt(index); - } - } - return AKEY_STATE_UNKNOWN; - } +TEST_F(InputReaderTest, GetKeyCodeState_ForwardsRequestsToMappers) { + constexpr int32_t deviceId = END_RESERVED_ID + 1000; + constexpr ftl::Flags deviceClass = InputDeviceClass::KEYBOARD; + constexpr int32_t eventHubId = 1; + FakeInputMapper& mapper = + addDeviceWithFakeInputMapper(deviceId, eventHubId, "fake", deviceClass, + AINPUT_SOURCE_KEYBOARD, nullptr); + mapper.setKeyCodeState(AKEYCODE_A, AKEY_STATE_DOWN); - int32_t getKeyCodeState(int32_t deviceId, int32_t keyCode) const override { - Device* device = getDevice(deviceId); - if (device) { - ssize_t index = device->keyCodeStates.indexOfKey(keyCode); - if (index >= 0) { - return device->keyCodeStates.valueAt(index); - } - } - return AKEY_STATE_UNKNOWN; - } + ASSERT_EQ(AKEY_STATE_UNKNOWN, mReader->getKeyCodeState(0, + AINPUT_SOURCE_ANY, AKEYCODE_A)) + << "Should return unknown when the device id is >= 0 but unknown."; - int32_t getSwitchState(int32_t deviceId, int32_t sw) const override { - Device* device = getDevice(deviceId); - if (device) { - ssize_t index = device->switchStates.indexOfKey(sw); - if (index >= 0) { - return device->switchStates.valueAt(index); - } - } - return AKEY_STATE_UNKNOWN; - } + ASSERT_EQ(AKEY_STATE_UNKNOWN, + mReader->getKeyCodeState(deviceId, AINPUT_SOURCE_TRACKBALL, AKEYCODE_A)) + << "Should return unknown when the device id is valid but the sources are not " + "supported by the device."; - status_t getAbsoluteAxisValue(int32_t deviceId, int32_t axis, - int32_t* outValue) const override { - Device* device = getDevice(deviceId); - if (device) { - ssize_t index = device->absoluteAxisValue.indexOfKey(axis); - if (index >= 0) { - *outValue = device->absoluteAxisValue.valueAt(index); - return OK; - } - } - *outValue = 0; - return -1; - } + ASSERT_EQ(AKEY_STATE_DOWN, + mReader->getKeyCodeState(deviceId, AINPUT_SOURCE_KEYBOARD | AINPUT_SOURCE_TRACKBALL, + AKEYCODE_A)) + << "Should return value provided by mapper when device id is valid and the device " + "supports some of the sources."; - int32_t getKeyCodeForKeyLocation(int32_t deviceId, int32_t locationKeyCode) const override { - Device* device = getDevice(deviceId); - if (!device) { - return AKEYCODE_UNKNOWN; - } - auto it = device->keyCodeMapping.find(locationKeyCode); - return it != device->keyCodeMapping.end() ? it->second : locationKeyCode; - } + ASSERT_EQ(AKEY_STATE_UNKNOWN, mReader->getKeyCodeState(-1, + AINPUT_SOURCE_TRACKBALL, AKEYCODE_A)) + << "Should return unknown when the device id is < 0 but the sources are not supported by any device."; - // Return true if the device has non-empty key layout. - bool markSupportedKeyCodes(int32_t deviceId, size_t numCodes, const int32_t* keyCodes, - uint8_t* outFlags) const override { - bool result = false; - Device* device = getDevice(deviceId); - if (device) { - result = device->keysByScanCode.size() > 0 || device->keysByUsageCode.size() > 0; - for (size_t i = 0; i < numCodes; i++) { - for (size_t j = 0; j < device->keysByScanCode.size(); j++) { - if (keyCodes[i] == device->keysByScanCode.valueAt(j).keyCode) { - outFlags[i] = 1; - } - } - for (size_t j = 0; j < device->keysByUsageCode.size(); j++) { - if (keyCodes[i] == device->keysByUsageCode.valueAt(j).keyCode) { - outFlags[i] = 1; - } - } - } - } - return result; - } + ASSERT_EQ(AKEY_STATE_DOWN, mReader->getKeyCodeState(-1, + AINPUT_SOURCE_KEYBOARD | AINPUT_SOURCE_TRACKBALL, AKEYCODE_A)) + << "Should return value provided by mapper when device id is < 0 and one of the devices supports some of the sources."; +} - bool hasScanCode(int32_t deviceId, int32_t scanCode) const override { - Device* device = getDevice(deviceId); - if (device) { - ssize_t index = device->keysByScanCode.indexOfKey(scanCode); - return index >= 0; - } - return false; - } +TEST_F(InputReaderTest, GetKeyCodeForKeyLocation_ForwardsRequestsToMappers) { + constexpr int32_t deviceId = END_RESERVED_ID + 1000; + constexpr int32_t eventHubId = 1; + FakeInputMapper& mapper = addDeviceWithFakeInputMapper(deviceId, eventHubId, "keyboard", + InputDeviceClass::KEYBOARD, + AINPUT_SOURCE_KEYBOARD, nullptr); + mapper.addKeyCodeMapping(AKEYCODE_Y, AKEYCODE_Z); - bool hasKeyCode(int32_t deviceId, int32_t keyCode) const override { - Device* device = getDevice(deviceId); - if (!device) { - return false; - } - for (size_t i = 0; i < device->keysByScanCode.size(); i++) { - if (keyCode == device->keysByScanCode.valueAt(i).keyCode) { - return true; - } - } - for (size_t j = 0; j < device->keysByUsageCode.size(); j++) { - if (keyCode == device->keysByUsageCode.valueAt(j).keyCode) { - return true; - } - } - return false; - } + ASSERT_EQ(AKEYCODE_UNKNOWN, mReader->getKeyCodeForKeyLocation(0, AKEYCODE_Y)) + << "Should return unknown when the device with the specified id is not found."; - bool hasLed(int32_t deviceId, int32_t led) const override { - Device* device = getDevice(deviceId); - return device && device->leds.indexOfKey(led) >= 0; - } + ASSERT_EQ(AKEYCODE_Z, mReader->getKeyCodeForKeyLocation(deviceId, AKEYCODE_Y)) + << "Should return correct mapping when device id is valid and mapping exists."; - void setLedState(int32_t deviceId, int32_t led, bool on) override { - Device* device = getDevice(deviceId); - if (device) { - ssize_t index = device->leds.indexOfKey(led); - if (index >= 0) { - device->leds.replaceValueAt(led, on); - } else { - ADD_FAILURE() - << "Attempted to set the state of an LED that the EventHub declared " - "was not present. led=" << led; - } - } - } + ASSERT_EQ(AKEYCODE_A, mReader->getKeyCodeForKeyLocation(deviceId, AKEYCODE_A)) + << "Should return the location key code when device id is valid and there's no " + "mapping."; +} - void getVirtualKeyDefinitions( - int32_t deviceId, std::vector& outVirtualKeys) const override { - outVirtualKeys.clear(); +TEST_F(InputReaderTest, GetKeyCodeForKeyLocation_NoKeyboardMapper) { + constexpr int32_t deviceId = END_RESERVED_ID + 1000; + constexpr int32_t eventHubId = 1; + FakeInputMapper& mapper = addDeviceWithFakeInputMapper(deviceId, eventHubId, "joystick", + InputDeviceClass::JOYSTICK, + AINPUT_SOURCE_GAMEPAD, nullptr); + mapper.addKeyCodeMapping(AKEYCODE_Y, AKEYCODE_Z); - Device* device = getDevice(deviceId); - if (device) { - outVirtualKeys = device->virtualKeys; - } - } + ASSERT_EQ(AKEYCODE_UNKNOWN, mReader->getKeyCodeForKeyLocation(deviceId, AKEYCODE_Y)) + << "Should return unknown when the device id is valid but there is no keyboard mapper"; +} - const std::shared_ptr getKeyCharacterMap(int32_t) const override { - return nullptr; - } +TEST_F(InputReaderTest, GetScanCodeState_ForwardsRequestsToMappers) { + constexpr int32_t deviceId = END_RESERVED_ID + 1000; + constexpr ftl::Flags deviceClass = InputDeviceClass::KEYBOARD; + constexpr int32_t eventHubId = 1; + FakeInputMapper& mapper = + addDeviceWithFakeInputMapper(deviceId, eventHubId, "fake", deviceClass, + AINPUT_SOURCE_KEYBOARD, nullptr); + mapper.setScanCodeState(KEY_A, AKEY_STATE_DOWN); - bool setKeyboardLayoutOverlay(int32_t, std::shared_ptr) override { - return false; - } + ASSERT_EQ(AKEY_STATE_UNKNOWN, mReader->getScanCodeState(0, + AINPUT_SOURCE_ANY, KEY_A)) + << "Should return unknown when the device id is >= 0 but unknown."; - void vibrate(int32_t, const VibrationElement&) override {} + ASSERT_EQ(AKEY_STATE_UNKNOWN, + mReader->getScanCodeState(deviceId, AINPUT_SOURCE_TRACKBALL, KEY_A)) + << "Should return unknown when the device id is valid but the sources are not " + "supported by the device."; - void cancelVibrate(int32_t) override {} + ASSERT_EQ(AKEY_STATE_DOWN, + mReader->getScanCodeState(deviceId, AINPUT_SOURCE_KEYBOARD | AINPUT_SOURCE_TRACKBALL, + KEY_A)) + << "Should return value provided by mapper when device id is valid and the device " + "supports some of the sources."; - std::vector getVibratorIds(int32_t deviceId) override { return mVibrators; }; + ASSERT_EQ(AKEY_STATE_UNKNOWN, mReader->getScanCodeState(-1, + AINPUT_SOURCE_TRACKBALL, KEY_A)) + << "Should return unknown when the device id is < 0 but the sources are not supported by any device."; - std::optional getBatteryCapacity(int32_t, int32_t) const override { - return BATTERY_CAPACITY; - } + ASSERT_EQ(AKEY_STATE_DOWN, mReader->getScanCodeState(-1, + AINPUT_SOURCE_KEYBOARD | AINPUT_SOURCE_TRACKBALL, KEY_A)) + << "Should return value provided by mapper when device id is < 0 and one of the devices supports some of the sources."; +} - std::optional getBatteryStatus(int32_t, int32_t) const override { - return BATTERY_STATUS; - } +TEST_F(InputReaderTest, GetSwitchState_ForwardsRequestsToMappers) { + constexpr int32_t deviceId = END_RESERVED_ID + 1000; + constexpr ftl::Flags deviceClass = InputDeviceClass::KEYBOARD; + constexpr int32_t eventHubId = 1; + FakeInputMapper& mapper = + addDeviceWithFakeInputMapper(deviceId, eventHubId, "fake", deviceClass, + AINPUT_SOURCE_KEYBOARD, nullptr); + mapper.setSwitchState(SW_LID, AKEY_STATE_DOWN); - const std::vector getRawBatteryIds(int32_t deviceId) override { - return {DEFAULT_BATTERY}; - } + ASSERT_EQ(AKEY_STATE_UNKNOWN, mReader->getSwitchState(0, + AINPUT_SOURCE_ANY, SW_LID)) + << "Should return unknown when the device id is >= 0 but unknown."; - std::optional getRawBatteryInfo(int32_t deviceId, int32_t batteryId) { - return std::nullopt; - } + ASSERT_EQ(AKEY_STATE_UNKNOWN, + mReader->getSwitchState(deviceId, AINPUT_SOURCE_TRACKBALL, SW_LID)) + << "Should return unknown when the device id is valid but the sources are not " + "supported by the device."; - const std::vector getRawLightIds(int32_t deviceId) override { - std::vector ids; - for (const auto& [rawId, info] : mRawLightInfos) { - ids.push_back(rawId); - } - return ids; - } + ASSERT_EQ(AKEY_STATE_DOWN, + mReader->getSwitchState(deviceId, AINPUT_SOURCE_KEYBOARD | AINPUT_SOURCE_TRACKBALL, + SW_LID)) + << "Should return value provided by mapper when device id is valid and the device " + "supports some of the sources."; - std::optional getRawLightInfo(int32_t deviceId, int32_t lightId) override { - auto it = mRawLightInfos.find(lightId); - if (it == mRawLightInfos.end()) { - return std::nullopt; - } - return it->second; - } + ASSERT_EQ(AKEY_STATE_UNKNOWN, mReader->getSwitchState(-1, + AINPUT_SOURCE_TRACKBALL, SW_LID)) + << "Should return unknown when the device id is < 0 but the sources are not supported by any device."; - void setLightBrightness(int32_t deviceId, int32_t lightId, int32_t brightness) override { - mLightBrightness.emplace(lightId, brightness); - } + ASSERT_EQ(AKEY_STATE_DOWN, mReader->getSwitchState(-1, + AINPUT_SOURCE_KEYBOARD | AINPUT_SOURCE_TRACKBALL, SW_LID)) + << "Should return value provided by mapper when device id is < 0 and one of the devices supports some of the sources."; +} - void setLightIntensities(int32_t deviceId, int32_t lightId, - std::unordered_map intensities) override { - mLightIntensities.emplace(lightId, intensities); - }; +TEST_F(InputReaderTest, MarkSupportedKeyCodes_ForwardsRequestsToMappers) { + constexpr int32_t deviceId = END_RESERVED_ID + 1000; + constexpr ftl::Flags deviceClass = InputDeviceClass::KEYBOARD; + constexpr int32_t eventHubId = 1; + FakeInputMapper& mapper = + addDeviceWithFakeInputMapper(deviceId, eventHubId, "fake", deviceClass, + AINPUT_SOURCE_KEYBOARD, nullptr); - std::optional getLightBrightness(int32_t deviceId, int32_t lightId) override { - auto lightIt = mLightBrightness.find(lightId); - if (lightIt == mLightBrightness.end()) { - return std::nullopt; - } - return lightIt->second; - } + mapper.addSupportedKeyCode(AKEYCODE_A); + mapper.addSupportedKeyCode(AKEYCODE_B); - std::optional> getLightIntensities( - int32_t deviceId, int32_t lightId) override { - auto lightIt = mLightIntensities.find(lightId); - if (lightIt == mLightIntensities.end()) { - return std::nullopt; - } - return lightIt->second; - }; + const std::vector keyCodes{AKEYCODE_A, AKEYCODE_B, AKEYCODE_1, AKEYCODE_2}; + uint8_t flags[4] = { 0, 0, 0, 1 }; - virtual bool isExternal(int32_t) const { - return false; - } + ASSERT_FALSE(mReader->hasKeys(0, AINPUT_SOURCE_ANY, keyCodes, flags)) + << "Should return false when device id is >= 0 but unknown."; + ASSERT_TRUE(!flags[0] && !flags[1] && !flags[2] && !flags[3]); - void dump(std::string&) override {} + flags[3] = 1; + ASSERT_FALSE(mReader->hasKeys(deviceId, AINPUT_SOURCE_TRACKBALL, keyCodes, flags)) + << "Should return false when device id is valid but the sources are not supported by " + "the device."; + ASSERT_TRUE(!flags[0] && !flags[1] && !flags[2] && !flags[3]); - void monitor() override {} + flags[3] = 1; + ASSERT_TRUE(mReader->hasKeys(deviceId, AINPUT_SOURCE_KEYBOARD | AINPUT_SOURCE_TRACKBALL, + keyCodes, flags)) + << "Should return value provided by mapper when device id is valid and the device " + "supports some of the sources."; + ASSERT_TRUE(flags[0] && flags[1] && !flags[2] && !flags[3]); - void requestReopenDevices() override {} + flags[3] = 1; + ASSERT_FALSE(mReader->hasKeys(-1, AINPUT_SOURCE_TRACKBALL, keyCodes, flags)) + << "Should return false when the device id is < 0 but the sources are not supported by " + "any device."; + ASSERT_TRUE(!flags[0] && !flags[1] && !flags[2] && !flags[3]); - void wake() override {} -}; + flags[3] = 1; + ASSERT_TRUE( + mReader->hasKeys(-1, AINPUT_SOURCE_KEYBOARD | AINPUT_SOURCE_TRACKBALL, keyCodes, flags)) + << "Should return value provided by mapper when device id is < 0 and one of the " + "devices supports some of the sources."; + ASSERT_TRUE(flags[0] && flags[1] && !flags[2] && !flags[3]); +} -// --- FakeInputMapper --- +TEST_F(InputReaderTest, LoopOnce_WhenDeviceScanFinished_SendsConfigurationChanged) { + constexpr int32_t eventHubId = 1; + addDevice(eventHubId, "ignored", InputDeviceClass::KEYBOARD, nullptr); -class FakeInputMapper : public InputMapper { - uint32_t mSources; - int32_t mKeyboardType; - int32_t mMetaState; - KeyedVector mKeyCodeStates; - KeyedVector mScanCodeStates; - KeyedVector mSwitchStates; - // fake mapping which would normally come from keyCharacterMap - std::unordered_map mKeyCodeMapping; - std::vector mSupportedKeyCodes; + NotifyConfigurationChangedArgs args; - std::mutex mLock; - std::condition_variable mStateChangedCondition; - bool mConfigureWasCalled GUARDED_BY(mLock); - bool mResetWasCalled GUARDED_BY(mLock); - bool mProcessWasCalled GUARDED_BY(mLock); - RawEvent mLastEvent GUARDED_BY(mLock); + ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyConfigurationChangedWasCalled(&args)); + ASSERT_EQ(ARBITRARY_TIME, args.eventTime); +} - std::optional mViewport; -public: - FakeInputMapper(InputDeviceContext& deviceContext, uint32_t sources) - : InputMapper(deviceContext), - mSources(sources), - mKeyboardType(AINPUT_KEYBOARD_TYPE_NONE), - mMetaState(0), - mConfigureWasCalled(false), - mResetWasCalled(false), - mProcessWasCalled(false) {} +TEST_F(InputReaderTest, LoopOnce_ForwardsRawEventsToMappers) { + constexpr int32_t deviceId = END_RESERVED_ID + 1000; + constexpr ftl::Flags deviceClass = InputDeviceClass::KEYBOARD; + constexpr nsecs_t when = 0; + constexpr int32_t eventHubId = 1; + constexpr nsecs_t readTime = 2; + FakeInputMapper& mapper = + addDeviceWithFakeInputMapper(deviceId, eventHubId, "fake", deviceClass, + AINPUT_SOURCE_KEYBOARD, nullptr); - virtual ~FakeInputMapper() {} + mFakeEventHub->enqueueEvent(when, readTime, eventHubId, EV_KEY, KEY_A, 1); + mReader->loopOnce(); + ASSERT_NO_FATAL_FAILURE(mFakeEventHub->assertQueueIsEmpty()); - void setKeyboardType(int32_t keyboardType) { - mKeyboardType = keyboardType; - } + RawEvent event; + ASSERT_NO_FATAL_FAILURE(mapper.assertProcessWasCalled(&event)); + ASSERT_EQ(when, event.when); + ASSERT_EQ(readTime, event.readTime); + ASSERT_EQ(eventHubId, event.deviceId); + ASSERT_EQ(EV_KEY, event.type); + ASSERT_EQ(KEY_A, event.code); + ASSERT_EQ(1, event.value); +} - void setMetaState(int32_t metaState) { - mMetaState = metaState; - } +TEST_F(InputReaderTest, DeviceReset_RandomId) { + constexpr int32_t deviceId = END_RESERVED_ID + 1000; + constexpr ftl::Flags deviceClass = InputDeviceClass::KEYBOARD; + constexpr int32_t eventHubId = 1; + std::shared_ptr device = mReader->newDevice(deviceId, "fake"); + // Must add at least one mapper or the device will be ignored! + device->addMapper(eventHubId, mFakePolicy->getReaderConfiguration(), + AINPUT_SOURCE_KEYBOARD); + mReader->pushNextDevice(device); + ASSERT_NO_FATAL_FAILURE(addDevice(eventHubId, "fake", deviceClass, nullptr)); - void assertConfigureWasCalled() { - std::unique_lock lock(mLock); - base::ScopedLockAssertion assumeLocked(mLock); - const bool configureCalled = - mStateChangedCondition.wait_for(lock, WAIT_TIMEOUT, [this]() REQUIRES(mLock) { - return mConfigureWasCalled; - }); - if (!configureCalled) { - FAIL() << "Expected configure() to have been called."; - } - mConfigureWasCalled = false; - } + NotifyDeviceResetArgs resetArgs; + ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyDeviceResetWasCalled(&resetArgs)); + int32_t prevId = resetArgs.id; - void assertResetWasCalled() { - std::unique_lock lock(mLock); - base::ScopedLockAssertion assumeLocked(mLock); - const bool resetCalled = - mStateChangedCondition.wait_for(lock, WAIT_TIMEOUT, [this]() REQUIRES(mLock) { - return mResetWasCalled; - }); - if (!resetCalled) { - FAIL() << "Expected reset() to have been called."; - } - mResetWasCalled = false; - } + disableDevice(deviceId); + mReader->loopOnce(); + ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyDeviceResetWasCalled(&resetArgs)); + ASSERT_NE(prevId, resetArgs.id); + prevId = resetArgs.id; - void assertProcessWasCalled(RawEvent* outLastEvent = nullptr) { - std::unique_lock lock(mLock); - base::ScopedLockAssertion assumeLocked(mLock); - const bool processCalled = - mStateChangedCondition.wait_for(lock, WAIT_TIMEOUT, [this]() REQUIRES(mLock) { - return mProcessWasCalled; - }); - if (!processCalled) { - FAIL() << "Expected process() to have been called."; - } - if (outLastEvent) { - *outLastEvent = mLastEvent; - } - mProcessWasCalled = false; - } + enableDevice(deviceId); + mReader->loopOnce(); + ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyDeviceResetWasCalled(&resetArgs)); + ASSERT_NE(prevId, resetArgs.id); + prevId = resetArgs.id; - void setKeyCodeState(int32_t keyCode, int32_t state) { - mKeyCodeStates.replaceValueFor(keyCode, state); - } + disableDevice(deviceId); + mReader->loopOnce(); + ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyDeviceResetWasCalled(&resetArgs)); + ASSERT_NE(prevId, resetArgs.id); + prevId = resetArgs.id; +} - void setScanCodeState(int32_t scanCode, int32_t state) { - mScanCodeStates.replaceValueFor(scanCode, state); - } +TEST_F(InputReaderTest, DeviceReset_GenerateIdWithInputReaderSource) { + constexpr int32_t deviceId = 1; + constexpr ftl::Flags deviceClass = InputDeviceClass::KEYBOARD; + constexpr int32_t eventHubId = 1; + std::shared_ptr device = mReader->newDevice(deviceId, "fake"); + // Must add at least one mapper or the device will be ignored! + device->addMapper(eventHubId, mFakePolicy->getReaderConfiguration(), + AINPUT_SOURCE_KEYBOARD); + mReader->pushNextDevice(device); + ASSERT_NO_FATAL_FAILURE(addDevice(deviceId, "fake", deviceClass, nullptr)); - void setSwitchState(int32_t switchCode, int32_t state) { - mSwitchStates.replaceValueFor(switchCode, state); - } + NotifyDeviceResetArgs resetArgs; + ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyDeviceResetWasCalled(&resetArgs)); + ASSERT_EQ(IdGenerator::Source::INPUT_READER, IdGenerator::getSource(resetArgs.id)); +} - void addSupportedKeyCode(int32_t keyCode) { - mSupportedKeyCodes.push_back(keyCode); - } +TEST_F(InputReaderTest, Device_CanDispatchToDisplay) { + constexpr int32_t deviceId = END_RESERVED_ID + 1000; + constexpr ftl::Flags deviceClass = InputDeviceClass::KEYBOARD; + constexpr int32_t eventHubId = 1; + const char* DEVICE_LOCATION = "USB1"; + std::shared_ptr device = mReader->newDevice(deviceId, "fake", DEVICE_LOCATION); + FakeInputMapper& mapper = + device->addMapper(eventHubId, mFakePolicy->getReaderConfiguration(), + AINPUT_SOURCE_TOUCHSCREEN); + mReader->pushNextDevice(device); - void addKeyCodeMapping(int32_t fromKeyCode, int32_t toKeyCode) { - mKeyCodeMapping.insert_or_assign(fromKeyCode, toKeyCode); - } + const uint8_t hdmi1 = 1; -private: - uint32_t getSources() const override { return mSources; } + // Associated touch screen with second display. + mFakePolicy->addInputPortAssociation(DEVICE_LOCATION, hdmi1); - void populateDeviceInfo(InputDeviceInfo* deviceInfo) override { - InputMapper::populateDeviceInfo(deviceInfo); + // Add default and second display. + mFakePolicy->clearViewports(); + mFakePolicy->addDisplayViewport(DISPLAY_ID, DISPLAY_WIDTH, DISPLAY_HEIGHT, ui::ROTATION_0, + /*isActive=*/true, "local:0", NO_PORT, ViewportType::INTERNAL); + mFakePolicy->addDisplayViewport(SECONDARY_DISPLAY_ID, DISPLAY_WIDTH, DISPLAY_HEIGHT, + ui::ROTATION_0, /*isActive=*/true, "local:1", hdmi1, + ViewportType::EXTERNAL); + mReader->requestRefreshConfiguration(InputReaderConfiguration::Change::DISPLAY_INFO); + mReader->loopOnce(); - if (mKeyboardType != AINPUT_KEYBOARD_TYPE_NONE) { - deviceInfo->setKeyboardType(mKeyboardType); - } - } + // Add the device, and make sure all of the callbacks are triggered. + // The device is added after the input port associations are processed since + // we do not yet support dynamic device-to-display associations. + ASSERT_NO_FATAL_FAILURE(addDevice(eventHubId, "fake", deviceClass, nullptr)); + ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyConfigurationChangedWasCalled()); + ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyDeviceResetWasCalled()); + ASSERT_NO_FATAL_FAILURE(mapper.assertConfigureWasCalled()); - void configure(nsecs_t, const InputReaderConfiguration* config, uint32_t changes) override { - std::scoped_lock lock(mLock); - mConfigureWasCalled = true; + // Device should only dispatch to the specified display. + ASSERT_EQ(deviceId, device->getId()); + ASSERT_FALSE(mReader->canDispatchToDisplay(deviceId, DISPLAY_ID)); + ASSERT_TRUE(mReader->canDispatchToDisplay(deviceId, SECONDARY_DISPLAY_ID)); - // Find the associated viewport if exist. - const std::optional displayPort = getDeviceContext().getAssociatedDisplayPort(); - if (displayPort && (changes & InputReaderConfiguration::CHANGE_DISPLAY_INFO)) { - mViewport = config->getDisplayViewportByPort(*displayPort); - } + // Can't dispatch event from a disabled device. + disableDevice(deviceId); + mReader->loopOnce(); + ASSERT_FALSE(mReader->canDispatchToDisplay(deviceId, SECONDARY_DISPLAY_ID)); +} - mStateChangedCondition.notify_all(); - } +TEST_F(InputReaderTest, WhenEnabledChanges_AllSubdevicesAreUpdated) { + constexpr int32_t deviceId = END_RESERVED_ID + 1000; + constexpr ftl::Flags deviceClass = InputDeviceClass::KEYBOARD; + constexpr int32_t eventHubIds[2] = {END_RESERVED_ID, END_RESERVED_ID + 1}; + std::shared_ptr device = mReader->newDevice(deviceId, "fake"); + // Must add at least one mapper or the device will be ignored! + device->addMapper(eventHubIds[0], mFakePolicy->getReaderConfiguration(), + AINPUT_SOURCE_KEYBOARD); + device->addMapper(eventHubIds[1], mFakePolicy->getReaderConfiguration(), + AINPUT_SOURCE_KEYBOARD); + mReader->pushNextDevice(device); + mReader->pushNextDevice(device); + ASSERT_NO_FATAL_FAILURE(addDevice(eventHubIds[0], "fake1", deviceClass, nullptr)); + ASSERT_NO_FATAL_FAILURE(addDevice(eventHubIds[1], "fake2", deviceClass, nullptr)); - void reset(nsecs_t) override { - std::scoped_lock lock(mLock); - mResetWasCalled = true; - mStateChangedCondition.notify_all(); - } + ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyConfigurationChangedWasCalled(nullptr)); - void process(const RawEvent* rawEvent) override { - std::scoped_lock lock(mLock); - mLastEvent = *rawEvent; - mProcessWasCalled = true; - mStateChangedCondition.notify_all(); - } + NotifyDeviceResetArgs resetArgs; + ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyDeviceResetWasCalled(&resetArgs)); + ASSERT_EQ(deviceId, resetArgs.deviceId); + ASSERT_TRUE(device->isEnabled()); + ASSERT_TRUE(mFakeEventHub->isDeviceEnabled(eventHubIds[0])); + ASSERT_TRUE(mFakeEventHub->isDeviceEnabled(eventHubIds[1])); - int32_t getKeyCodeState(uint32_t, int32_t keyCode) override { - ssize_t index = mKeyCodeStates.indexOfKey(keyCode); - return index >= 0 ? mKeyCodeStates.valueAt(index) : AKEY_STATE_UNKNOWN; - } + disableDevice(deviceId); + mReader->loopOnce(); - int32_t getKeyCodeForKeyLocation(int32_t locationKeyCode) const override { - auto it = mKeyCodeMapping.find(locationKeyCode); - return it != mKeyCodeMapping.end() ? it->second : locationKeyCode; - } + ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyDeviceResetWasCalled(&resetArgs)); + ASSERT_EQ(deviceId, resetArgs.deviceId); + ASSERT_FALSE(device->isEnabled()); + ASSERT_FALSE(mFakeEventHub->isDeviceEnabled(eventHubIds[0])); + ASSERT_FALSE(mFakeEventHub->isDeviceEnabled(eventHubIds[1])); - int32_t getScanCodeState(uint32_t, int32_t scanCode) override { - ssize_t index = mScanCodeStates.indexOfKey(scanCode); - return index >= 0 ? mScanCodeStates.valueAt(index) : AKEY_STATE_UNKNOWN; - } + enableDevice(deviceId); + mReader->loopOnce(); - int32_t getSwitchState(uint32_t, int32_t switchCode) override { - ssize_t index = mSwitchStates.indexOfKey(switchCode); - return index >= 0 ? mSwitchStates.valueAt(index) : AKEY_STATE_UNKNOWN; - } + ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyDeviceResetWasCalled(&resetArgs)); + ASSERT_EQ(deviceId, resetArgs.deviceId); + ASSERT_TRUE(device->isEnabled()); + ASSERT_TRUE(mFakeEventHub->isDeviceEnabled(eventHubIds[0])); + ASSERT_TRUE(mFakeEventHub->isDeviceEnabled(eventHubIds[1])); +} - // Return true if the device has non-empty key layout. - bool markSupportedKeyCodes(uint32_t, size_t numCodes, const int32_t* keyCodes, - uint8_t* outFlags) override { - for (size_t i = 0; i < numCodes; i++) { - for (size_t j = 0; j < mSupportedKeyCodes.size(); j++) { - if (keyCodes[i] == mSupportedKeyCodes[j]) { - outFlags[i] = 1; - } - } - } - bool result = mSupportedKeyCodes.size() > 0; - return result; - } +TEST_F(InputReaderTest, GetKeyCodeState_ForwardsRequestsToSubdeviceMappers) { + constexpr int32_t deviceId = END_RESERVED_ID + 1000; + constexpr ftl::Flags deviceClass = InputDeviceClass::KEYBOARD; + constexpr int32_t eventHubIds[2] = {END_RESERVED_ID, END_RESERVED_ID + 1}; + // Add two subdevices to device + std::shared_ptr device = mReader->newDevice(deviceId, "fake"); + FakeInputMapper& mapperDevice1 = + device->addMapper(eventHubIds[0], + mFakePolicy->getReaderConfiguration(), + AINPUT_SOURCE_KEYBOARD); + FakeInputMapper& mapperDevice2 = + device->addMapper(eventHubIds[1], + mFakePolicy->getReaderConfiguration(), + AINPUT_SOURCE_KEYBOARD); + mReader->pushNextDevice(device); + mReader->pushNextDevice(device); + ASSERT_NO_FATAL_FAILURE(addDevice(eventHubIds[0], "fake1", deviceClass, nullptr)); + ASSERT_NO_FATAL_FAILURE(addDevice(eventHubIds[1], "fake2", deviceClass, nullptr)); - virtual int32_t getMetaState() { - return mMetaState; - } + mapperDevice1.setKeyCodeState(AKEYCODE_A, AKEY_STATE_DOWN); + mapperDevice2.setKeyCodeState(AKEYCODE_B, AKEY_STATE_DOWN); - virtual void fadePointer() { - } + ASSERT_EQ(AKEY_STATE_DOWN, + mReader->getKeyCodeState(deviceId, AINPUT_SOURCE_KEYBOARD, AKEYCODE_A)); + ASSERT_EQ(AKEY_STATE_DOWN, + mReader->getKeyCodeState(deviceId, AINPUT_SOURCE_KEYBOARD, AKEYCODE_B)); + ASSERT_EQ(AKEY_STATE_UNKNOWN, + mReader->getKeyCodeState(deviceId, AINPUT_SOURCE_KEYBOARD, AKEYCODE_C)); +} - virtual std::optional getAssociatedDisplay() { - if (mViewport) { - return std::make_optional(mViewport->displayId); - } - return std::nullopt; - } -}; +TEST_F(InputReaderTest, ChangingPointerCaptureNotifiesInputListener) { + NotifyPointerCaptureChangedArgs args; + auto request = mFakePolicy->setPointerCapture(true); + mReader->requestRefreshConfiguration(InputReaderConfiguration::Change::POINTER_CAPTURE); + mReader->loopOnce(); + mFakeListener->assertNotifyCaptureWasCalled(&args); + ASSERT_TRUE(args.request.enable) << "Pointer Capture should be enabled."; + ASSERT_EQ(args.request, request) << "Pointer Capture sequence number should match."; -// --- InstrumentedInputReader --- + mFakePolicy->setPointerCapture(false); + mReader->requestRefreshConfiguration(InputReaderConfiguration::Change::POINTER_CAPTURE); + mReader->loopOnce(); + mFakeListener->assertNotifyCaptureWasCalled(&args); + ASSERT_FALSE(args.request.enable) << "Pointer Capture should be disabled."; -class InstrumentedInputReader : public InputReader { - std::queue> mNextDevices; + // Verify that the Pointer Capture state is not updated when the configuration value + // does not change. + mReader->requestRefreshConfiguration(InputReaderConfiguration::Change::POINTER_CAPTURE); + mReader->loopOnce(); + mFakeListener->assertNotifyCaptureWasNotCalled(); +} +class FakeVibratorInputMapper : public FakeInputMapper { public: - InstrumentedInputReader(std::shared_ptr eventHub, - const sp& policy, - InputListenerInterface& listener) - : InputReader(eventHub, policy, listener), mFakeContext(this) {} + FakeVibratorInputMapper(InputDeviceContext& deviceContext, + const InputReaderConfiguration& readerConfig, uint32_t sources) + : FakeInputMapper(deviceContext, readerConfig, sources) {} - virtual ~InstrumentedInputReader() {} + std::vector getVibratorIds() override { return getDeviceContext().getVibratorIds(); } +}; - void pushNextDevice(std::shared_ptr device) { mNextDevices.push(device); } +TEST_F(InputReaderTest, VibratorGetVibratorIds) { + constexpr int32_t deviceId = END_RESERVED_ID + 1000; + ftl::Flags deviceClass = + InputDeviceClass::KEYBOARD | InputDeviceClass::VIBRATOR; + constexpr int32_t eventHubId = 1; + const char* DEVICE_LOCATION = "BLUETOOTH"; + std::shared_ptr device = mReader->newDevice(deviceId, "fake", DEVICE_LOCATION); + FakeVibratorInputMapper& mapper = + device->addMapper(eventHubId, + mFakePolicy->getReaderConfiguration(), + AINPUT_SOURCE_KEYBOARD); + mReader->pushNextDevice(device); - std::shared_ptr newDevice(int32_t deviceId, const std::string& name, - const std::string& location = "") { - InputDeviceIdentifier identifier; - identifier.name = name; - identifier.location = location; - int32_t generation = deviceId + 1; - return std::make_shared(&mFakeContext, deviceId, generation, identifier); - } + ASSERT_NO_FATAL_FAILURE(addDevice(eventHubId, "fake", deviceClass, nullptr)); + ASSERT_NO_FATAL_FAILURE(mapper.assertConfigureWasCalled()); - // Make the protected loopOnce method accessible to tests. - using InputReader::loopOnce; + ASSERT_EQ(mapper.getVibratorIds().size(), 2U); + ASSERT_EQ(mReader->getVibratorIds(deviceId).size(), 2U); +} -protected: - virtual std::shared_ptr createDeviceLocked(int32_t eventHubId, - const InputDeviceIdentifier& identifier) - REQUIRES(mLock) { - if (!mNextDevices.empty()) { - std::shared_ptr device(std::move(mNextDevices.front())); - mNextDevices.pop(); - return device; - } - return InputReader::createDeviceLocked(eventHubId, identifier); - } +// --- FakePeripheralController --- - // --- FakeInputReaderContext --- - class FakeInputReaderContext : public ContextImpl { - int32_t mGlobalMetaState; - bool mUpdateGlobalMetaStateWasCalled; - int32_t mGeneration; +class FakePeripheralController : public PeripheralControllerInterface { +public: + FakePeripheralController(InputDeviceContext& deviceContext) : mDeviceContext(deviceContext) {} - public: - FakeInputReaderContext(InputReader* reader) - : ContextImpl(reader), - mGlobalMetaState(0), - mUpdateGlobalMetaStateWasCalled(false), - mGeneration(1) {} - - virtual ~FakeInputReaderContext() {} - - void assertUpdateGlobalMetaStateWasCalled() { - ASSERT_TRUE(mUpdateGlobalMetaStateWasCalled) - << "Expected updateGlobalMetaState() to have been called."; - mUpdateGlobalMetaStateWasCalled = false; - } + ~FakePeripheralController() override {} - void setGlobalMetaState(int32_t state) { mGlobalMetaState = state; } + int32_t getEventHubId() const { return getDeviceContext().getEventHubId(); } - uint32_t getGeneration() { return mGeneration; } + void populateDeviceInfo(InputDeviceInfo* deviceInfo) override {} - void updateGlobalMetaState() override { - mUpdateGlobalMetaStateWasCalled = true; - ContextImpl::updateGlobalMetaState(); - } + void dump(std::string& dump) override {} - int32_t getGlobalMetaState() override { - return mGlobalMetaState | ContextImpl::getGlobalMetaState(); - } + std::optional getBatteryCapacity(int32_t batteryId) override { + return getDeviceContext().getBatteryCapacity(batteryId); + } - int32_t bumpGeneration() override { - mGeneration = ContextImpl::bumpGeneration(); - return mGeneration; - } - } mFakeContext; + std::optional getBatteryStatus(int32_t batteryId) override { + return getDeviceContext().getBatteryStatus(batteryId); + } + + bool setLightColor(int32_t lightId, int32_t color) override { + getDeviceContext().setLightBrightness(lightId, color >> 24); + return true; + } - friend class InputReaderTest; + std::optional getLightColor(int32_t lightId) override { + std::optional result = getDeviceContext().getLightBrightness(lightId); + if (!result.has_value()) { + return std::nullopt; + } + return result.value() << 24; + } -public: - FakeInputReaderContext* getContext() { return &mFakeContext; } -}; + bool setLightPlayerId(int32_t lightId, int32_t playerId) override { return true; } -// --- InputReaderPolicyTest --- -class InputReaderPolicyTest : public testing::Test { -protected: - sp mFakePolicy; + std::optional getLightPlayerId(int32_t lightId) override { return std::nullopt; } - void SetUp() override { mFakePolicy = new FakeInputReaderPolicy(); } - void TearDown() override { mFakePolicy.clear(); } +private: + InputDeviceContext& mDeviceContext; + inline int32_t getDeviceId() { return mDeviceContext.getId(); } + inline InputDeviceContext& getDeviceContext() { return mDeviceContext; } + inline InputDeviceContext& getDeviceContext() const { return mDeviceContext; } }; -/** - * Check that empty set of viewports is an acceptable configuration. - * Also try to get internal viewport two different ways - by type and by uniqueId. - * - * There will be confusion if two viewports with empty uniqueId and identical type are present. - * Such configuration is not currently allowed. - */ -TEST_F(InputReaderPolicyTest, Viewports_GetCleared) { - static const std::string uniqueId = "local:0"; +TEST_F(InputReaderTest, BatteryGetCapacity) { + constexpr int32_t deviceId = END_RESERVED_ID + 1000; + ftl::Flags deviceClass = + InputDeviceClass::KEYBOARD | InputDeviceClass::BATTERY; + constexpr int32_t eventHubId = 1; + const char* DEVICE_LOCATION = "BLUETOOTH"; + std::shared_ptr device = mReader->newDevice(deviceId, "fake", DEVICE_LOCATION); + FakePeripheralController& controller = + device->addController(eventHubId); + mReader->pushNextDevice(device); - // We didn't add any viewports yet, so there shouldn't be any. - std::optional internalViewport = - mFakePolicy->getDisplayViewportByType(ViewportType::INTERNAL); - ASSERT_FALSE(internalViewport); + ASSERT_NO_FATAL_FAILURE(addDevice(eventHubId, "fake", deviceClass, nullptr)); - // Add an internal viewport, then clear it - mFakePolicy->addDisplayViewport(DISPLAY_ID, DISPLAY_WIDTH, DISPLAY_HEIGHT, - DISPLAY_ORIENTATION_0, true /*isActive*/, uniqueId, NO_PORT, - ViewportType::INTERNAL); + ASSERT_EQ(controller.getBatteryCapacity(FakeEventHub::DEFAULT_BATTERY), + FakeEventHub::BATTERY_CAPACITY); + ASSERT_EQ(mReader->getBatteryCapacity(deviceId), FakeEventHub::BATTERY_CAPACITY); +} - // Check matching by uniqueId - internalViewport = mFakePolicy->getDisplayViewportByUniqueId(uniqueId); - ASSERT_TRUE(internalViewport); - ASSERT_EQ(ViewportType::INTERNAL, internalViewport->type); +TEST_F(InputReaderTest, BatteryGetStatus) { + constexpr int32_t deviceId = END_RESERVED_ID + 1000; + ftl::Flags deviceClass = + InputDeviceClass::KEYBOARD | InputDeviceClass::BATTERY; + constexpr int32_t eventHubId = 1; + const char* DEVICE_LOCATION = "BLUETOOTH"; + std::shared_ptr device = mReader->newDevice(deviceId, "fake", DEVICE_LOCATION); + FakePeripheralController& controller = + device->addController(eventHubId); + mReader->pushNextDevice(device); - // Check matching by viewport type - internalViewport = mFakePolicy->getDisplayViewportByType(ViewportType::INTERNAL); - ASSERT_TRUE(internalViewport); - ASSERT_EQ(uniqueId, internalViewport->uniqueId); + ASSERT_NO_FATAL_FAILURE(addDevice(eventHubId, "fake", deviceClass, nullptr)); - mFakePolicy->clearViewports(); - // Make sure nothing is found after clear - internalViewport = mFakePolicy->getDisplayViewportByUniqueId(uniqueId); - ASSERT_FALSE(internalViewport); - internalViewport = mFakePolicy->getDisplayViewportByType(ViewportType::INTERNAL); - ASSERT_FALSE(internalViewport); + ASSERT_EQ(controller.getBatteryStatus(FakeEventHub::DEFAULT_BATTERY), + FakeEventHub::BATTERY_STATUS); + ASSERT_EQ(mReader->getBatteryStatus(deviceId), FakeEventHub::BATTERY_STATUS); } -TEST_F(InputReaderPolicyTest, Viewports_GetByType) { - const std::string internalUniqueId = "local:0"; - 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; +TEST_F(InputReaderTest, BatteryGetDevicePath) { + constexpr int32_t deviceId = END_RESERVED_ID + 1000; + ftl::Flags deviceClass = + InputDeviceClass::KEYBOARD | InputDeviceClass::BATTERY; + constexpr int32_t eventHubId = 1; + const char* DEVICE_LOCATION = "BLUETOOTH"; + std::shared_ptr device = mReader->newDevice(deviceId, "fake", DEVICE_LOCATION); + device->addController(eventHubId); + mReader->pushNextDevice(device); - // Add an internal viewport - mFakePolicy->addDisplayViewport(DISPLAY_ID, DISPLAY_WIDTH, DISPLAY_HEIGHT, - DISPLAY_ORIENTATION_0, true /*isActive*/, internalUniqueId, - NO_PORT, ViewportType::INTERNAL); - // Add an external viewport - mFakePolicy->addDisplayViewport(DISPLAY_ID, DISPLAY_WIDTH, DISPLAY_HEIGHT, - DISPLAY_ORIENTATION_0, true /*isActive*/, externalUniqueId, - NO_PORT, ViewportType::EXTERNAL); - // Add an virtual viewport - mFakePolicy->addDisplayViewport(virtualDisplayId1, DISPLAY_WIDTH, DISPLAY_HEIGHT, - DISPLAY_ORIENTATION_0, true /*isActive*/, virtualUniqueId1, - NO_PORT, ViewportType::VIRTUAL); - // Add another virtual viewport - mFakePolicy->addDisplayViewport(virtualDisplayId2, DISPLAY_WIDTH, DISPLAY_HEIGHT, - DISPLAY_ORIENTATION_0, true /*isActive*/, virtualUniqueId2, - NO_PORT, ViewportType::VIRTUAL); + ASSERT_NO_FATAL_FAILURE(addDevice(eventHubId, "fake", deviceClass, nullptr)); - // Check matching by type for internal - std::optional internalViewport = - mFakePolicy->getDisplayViewportByType(ViewportType::INTERNAL); - ASSERT_TRUE(internalViewport); - ASSERT_EQ(internalUniqueId, internalViewport->uniqueId); + ASSERT_EQ(mReader->getBatteryDevicePath(deviceId), FakeEventHub::BATTERY_DEVPATH); +} - // Check matching by type for external - std::optional externalViewport = - mFakePolicy->getDisplayViewportByType(ViewportType::EXTERNAL); - ASSERT_TRUE(externalViewport); - ASSERT_EQ(externalUniqueId, externalViewport->uniqueId); +TEST_F(InputReaderTest, LightGetColor) { + constexpr int32_t deviceId = END_RESERVED_ID + 1000; + ftl::Flags deviceClass = InputDeviceClass::KEYBOARD | InputDeviceClass::LIGHT; + constexpr int32_t eventHubId = 1; + const char* DEVICE_LOCATION = "BLUETOOTH"; + std::shared_ptr device = mReader->newDevice(deviceId, "fake", DEVICE_LOCATION); + FakePeripheralController& controller = + device->addController(eventHubId); + mReader->pushNextDevice(device); + RawLightInfo info = {.id = 1, + .name = "Mono", + .maxBrightness = 255, + .flags = InputLightClass::BRIGHTNESS, + .path = ""}; + mFakeEventHub->addRawLightInfo(/*rawId=*/1, std::move(info)); + mFakeEventHub->fakeLightBrightness(/*rawId=*/1, 0x55); - // Check matching by uniqueId for virtual viewport #1 - std::optional virtualViewport1 = - mFakePolicy->getDisplayViewportByUniqueId(virtualUniqueId1); - ASSERT_TRUE(virtualViewport1); - ASSERT_EQ(ViewportType::VIRTUAL, virtualViewport1->type); - ASSERT_EQ(virtualUniqueId1, virtualViewport1->uniqueId); - ASSERT_EQ(virtualDisplayId1, virtualViewport1->displayId); + ASSERT_NO_FATAL_FAILURE(addDevice(eventHubId, "fake", deviceClass, nullptr)); - // Check matching by uniqueId for virtual viewport #2 - std::optional virtualViewport2 = - mFakePolicy->getDisplayViewportByUniqueId(virtualUniqueId2); - ASSERT_TRUE(virtualViewport2); - ASSERT_EQ(ViewportType::VIRTUAL, virtualViewport2->type); - ASSERT_EQ(virtualUniqueId2, virtualViewport2->uniqueId); - ASSERT_EQ(virtualDisplayId2, virtualViewport2->displayId); + ASSERT_TRUE(controller.setLightColor(/*lightId=*/1, LIGHT_BRIGHTNESS)); + ASSERT_EQ(controller.getLightColor(/*lightId=*/1), LIGHT_BRIGHTNESS); + ASSERT_TRUE(mReader->setLightColor(deviceId, /*lightId=*/1, LIGHT_BRIGHTNESS)); + ASSERT_EQ(mReader->getLightColor(deviceId, /*lightId=*/1), LIGHT_BRIGHTNESS); } +// --- InputReaderIntegrationTest --- -/** - * We can have 2 viewports of the same kind. We can distinguish them by uniqueId, and confirm - * that lookup works by checking display id. - * Check that 2 viewports of each kind is possible, for all existing viewport types. - */ -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; +// These tests create and interact with the InputReader only through its interface. +// The InputReader is started during SetUp(), which starts its processing in its own +// thread. The tests use linux uinput to emulate input devices. +// NOTE: Interacting with the physical device while these tests are running may cause +// the tests to fail. +class InputReaderIntegrationTest : public testing::Test { +protected: + std::unique_ptr mTestListener; + sp mFakePolicy; + std::unique_ptr mReader; - std::vector types = {ViewportType::INTERNAL, ViewportType::EXTERNAL, - ViewportType::VIRTUAL}; - for (const ViewportType& type : types) { - mFakePolicy->clearViewports(); - // Add a viewport - mFakePolicy->addDisplayViewport(displayId1, DISPLAY_WIDTH, DISPLAY_HEIGHT, - DISPLAY_ORIENTATION_0, true /*isActive*/, uniqueId1, - NO_PORT, type); - // Add another viewport - mFakePolicy->addDisplayViewport(displayId2, DISPLAY_WIDTH, DISPLAY_HEIGHT, - DISPLAY_ORIENTATION_0, true /*isActive*/, uniqueId2, - NO_PORT, type); + std::shared_ptr mFakePointerController; - // Check that correct display viewport was returned by comparing the display IDs. - std::optional viewport1 = - mFakePolicy->getDisplayViewportByUniqueId(uniqueId1); - ASSERT_TRUE(viewport1); - ASSERT_EQ(displayId1, viewport1->displayId); - ASSERT_EQ(type, viewport1->type); + void SetUp() override { +#if !defined(__ANDROID__) + GTEST_SKIP(); +#endif + mFakePolicy = sp::make(); + mFakePointerController = std::make_shared(); + mFakePolicy->setPointerController(mFakePointerController); + mTestListener = std::make_unique(/*eventHappenedTimeout=*/2000ms, + /*eventDidNotHappenTimeout=*/30ms); - std::optional viewport2 = - mFakePolicy->getDisplayViewportByUniqueId(uniqueId2); - ASSERT_TRUE(viewport2); - ASSERT_EQ(displayId2, viewport2->displayId); - ASSERT_EQ(type, viewport2->type); + mReader = std::make_unique(std::make_shared(), mFakePolicy, + *mTestListener); + ASSERT_EQ(mReader->start(), OK); - // When there are multiple viewports of the same kind, and uniqueId is not specified - // in the call to getDisplayViewport, then that situation is not supported. - // The viewports can be stored in any order, so we cannot rely on the order, since that - // is just implementation detail. - // However, we can check that it still returns *a* viewport, we just cannot assert - // which one specifically is returned. - std::optional someViewport = mFakePolicy->getDisplayViewportByType(type); - ASSERT_TRUE(someViewport); + // Since this test is run on a real device, all the input devices connected + // to the test device will show up in mReader. We wait for those input devices to + // show up before beginning the tests. + ASSERT_NO_FATAL_FAILURE(mFakePolicy->assertInputDevicesChanged()); + ASSERT_NO_FATAL_FAILURE(mTestListener->assertNotifyInputDevicesChangedWasCalled()); + ASSERT_NO_FATAL_FAILURE(mTestListener->assertNotifyConfigurationChangedWasCalled()); + } + + void TearDown() override { +#if !defined(__ANDROID__) + return; +#endif + ASSERT_EQ(mReader->stop(), OK); + mReader.reset(); + mTestListener.reset(); + mFakePolicy.clear(); + } + + std::optional findDeviceByName(const std::string& name) { + const std::vector inputDevices = mFakePolicy->getInputDevices(); + const auto& it = std::find_if(inputDevices.begin(), inputDevices.end(), + [&name](const InputDeviceInfo& info) { + return info.getIdentifier().name == name; + }); + return it != inputDevices.end() ? std::make_optional(*it) : std::nullopt; } +}; + +TEST_F(InputReaderIntegrationTest, TestInvalidDevice) { + // An invalid input device that is only used for this test. + class InvalidUinputDevice : public UinputDevice { + public: + InvalidUinputDevice() : UinputDevice("Invalid Device", /*productId=*/99) {} + + private: + void configureDevice(int fd, uinput_user_dev* device) override {} + }; + + const size_t numDevices = mFakePolicy->getInputDevices().size(); + + // UinputDevice does not set any event or key bits, so InputReader should not + // consider it as a valid device. + std::unique_ptr invalidDevice = createUinputDevice(); + ASSERT_NO_FATAL_FAILURE(mFakePolicy->assertInputDevicesNotChanged()); + ASSERT_NO_FATAL_FAILURE(mTestListener->assertNotifyConfigurationChangedWasNotCalled()); + ASSERT_EQ(numDevices, mFakePolicy->getInputDevices().size()); + + invalidDevice.reset(); + ASSERT_NO_FATAL_FAILURE(mFakePolicy->assertInputDevicesNotChanged()); + ASSERT_NO_FATAL_FAILURE(mTestListener->assertNotifyConfigurationChangedWasNotCalled()); + ASSERT_EQ(numDevices, mFakePolicy->getInputDevices().size()); } -/** - * When we have multiple internal displays make sure we always return the default display when - * querying by type. - */ -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"); +TEST_F(InputReaderIntegrationTest, AddNewDevice) { + const size_t initialNumDevices = mFakePolicy->getInputDevices().size(); - // Add the default display first and ensure it gets returned. - mFakePolicy->clearViewports(); - mFakePolicy->addDisplayViewport(ADISPLAY_ID_DEFAULT, DISPLAY_WIDTH, DISPLAY_HEIGHT, - DISPLAY_ORIENTATION_0, true /*isActive*/, uniqueId1, NO_PORT, - ViewportType::INTERNAL); - mFakePolicy->addDisplayViewport(nonDefaultDisplayId, DISPLAY_WIDTH, DISPLAY_HEIGHT, - DISPLAY_ORIENTATION_0, true /*isActive*/, uniqueId2, NO_PORT, - ViewportType::INTERNAL); + std::unique_ptr keyboard = createUinputDevice(); + ASSERT_NO_FATAL_FAILURE(mFakePolicy->assertInputDevicesChanged()); + ASSERT_NO_FATAL_FAILURE(mTestListener->assertNotifyConfigurationChangedWasCalled()); + ASSERT_EQ(initialNumDevices + 1, mFakePolicy->getInputDevices().size()); - std::optional viewport = - mFakePolicy->getDisplayViewportByType(ViewportType::INTERNAL); - ASSERT_TRUE(viewport); - ASSERT_EQ(ADISPLAY_ID_DEFAULT, viewport->displayId); - ASSERT_EQ(ViewportType::INTERNAL, viewport->type); + const auto device = findDeviceByName(keyboard->getName()); + ASSERT_TRUE(device.has_value()); + ASSERT_EQ(AINPUT_KEYBOARD_TYPE_NON_ALPHABETIC, device->getKeyboardType()); + ASSERT_EQ(AINPUT_SOURCE_KEYBOARD, device->getSources()); + ASSERT_EQ(0U, device->getMotionRanges().size()); - // Add the default display second to make sure order doesn't matter. - mFakePolicy->clearViewports(); - mFakePolicy->addDisplayViewport(nonDefaultDisplayId, DISPLAY_WIDTH, DISPLAY_HEIGHT, - DISPLAY_ORIENTATION_0, true /*isActive*/, uniqueId2, NO_PORT, - ViewportType::INTERNAL); - mFakePolicy->addDisplayViewport(ADISPLAY_ID_DEFAULT, DISPLAY_WIDTH, DISPLAY_HEIGHT, - DISPLAY_ORIENTATION_0, true /*isActive*/, uniqueId1, NO_PORT, - ViewportType::INTERNAL); + keyboard.reset(); + ASSERT_NO_FATAL_FAILURE(mFakePolicy->assertInputDevicesChanged()); + ASSERT_NO_FATAL_FAILURE(mTestListener->assertNotifyConfigurationChangedWasCalled()); + ASSERT_EQ(initialNumDevices, mFakePolicy->getInputDevices().size()); +} - viewport = mFakePolicy->getDisplayViewportByType(ViewportType::INTERNAL); - ASSERT_TRUE(viewport); - ASSERT_EQ(ADISPLAY_ID_DEFAULT, viewport->displayId); - ASSERT_EQ(ViewportType::INTERNAL, viewport->type); +TEST_F(InputReaderIntegrationTest, SendsEventsToInputListener) { + std::unique_ptr keyboard = createUinputDevice(); + ASSERT_NO_FATAL_FAILURE(mFakePolicy->assertInputDevicesChanged()); + + NotifyConfigurationChangedArgs configChangedArgs; + ASSERT_NO_FATAL_FAILURE( + mTestListener->assertNotifyConfigurationChangedWasCalled(&configChangedArgs)); + int32_t prevId = configChangedArgs.id; + nsecs_t prevTimestamp = configChangedArgs.eventTime; + + NotifyKeyArgs keyArgs; + keyboard->pressAndReleaseHomeKey(); + ASSERT_NO_FATAL_FAILURE(mTestListener->assertNotifyKeyWasCalled(&keyArgs)); + ASSERT_EQ(AKEY_EVENT_ACTION_DOWN, keyArgs.action); + ASSERT_NE(prevId, keyArgs.id); + prevId = keyArgs.id; + ASSERT_LE(prevTimestamp, keyArgs.eventTime); + ASSERT_LE(keyArgs.eventTime, keyArgs.readTime); + prevTimestamp = keyArgs.eventTime; + + ASSERT_NO_FATAL_FAILURE(mTestListener->assertNotifyKeyWasCalled(&keyArgs)); + ASSERT_EQ(AKEY_EVENT_ACTION_UP, keyArgs.action); + ASSERT_NE(prevId, keyArgs.id); + ASSERT_LE(prevTimestamp, keyArgs.eventTime); + ASSERT_LE(keyArgs.eventTime, keyArgs.readTime); } -/** - * Check getDisplayViewportByPort - */ -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; - const uint8_t hdmi1 = 0; - const uint8_t hdmi2 = 1; - const uint8_t hdmi3 = 2; +TEST_F(InputReaderIntegrationTest, ExternalStylusesButtons) { + std::unique_ptr stylus = createUinputDevice(); + ASSERT_NO_FATAL_FAILURE(mFakePolicy->assertInputDevicesChanged()); - mFakePolicy->clearViewports(); - // Add a viewport that's associated with some display port that's not of interest. - mFakePolicy->addDisplayViewport(displayId1, DISPLAY_WIDTH, DISPLAY_HEIGHT, - DISPLAY_ORIENTATION_0, true /*isActive*/, uniqueId1, hdmi3, - type); - // Add another viewport, connected to HDMI1 port - mFakePolicy->addDisplayViewport(displayId2, DISPLAY_WIDTH, DISPLAY_HEIGHT, - DISPLAY_ORIENTATION_0, true /*isActive*/, uniqueId2, hdmi1, - type); + const auto device = findDeviceByName(stylus->getName()); + ASSERT_TRUE(device.has_value()); - // Check that correct display viewport was returned by comparing the display ports. - std::optional hdmi1Viewport = mFakePolicy->getDisplayViewportByPort(hdmi1); - ASSERT_TRUE(hdmi1Viewport); - ASSERT_EQ(displayId2, hdmi1Viewport->displayId); - ASSERT_EQ(uniqueId2, hdmi1Viewport->uniqueId); + // An external stylus with buttons should also be recognized as a keyboard. + ASSERT_EQ(AINPUT_SOURCE_KEYBOARD | AINPUT_SOURCE_STYLUS, device->getSources()) + << "Unexpected source " << inputEventSourceToString(device->getSources()).c_str(); + ASSERT_EQ(AINPUT_KEYBOARD_TYPE_NON_ALPHABETIC, device->getKeyboardType()); - // Check that we can still get the same viewport using the uniqueId - hdmi1Viewport = mFakePolicy->getDisplayViewportByUniqueId(uniqueId2); - ASSERT_TRUE(hdmi1Viewport); - ASSERT_EQ(displayId2, hdmi1Viewport->displayId); - ASSERT_EQ(uniqueId2, hdmi1Viewport->uniqueId); - ASSERT_EQ(type, hdmi1Viewport->type); + const auto DOWN = + AllOf(WithKeyAction(AKEY_EVENT_ACTION_DOWN), WithSource(AINPUT_SOURCE_KEYBOARD)); + const auto UP = AllOf(WithKeyAction(AKEY_EVENT_ACTION_UP), WithSource(AINPUT_SOURCE_KEYBOARD)); - // Check that we cannot find a port with "HDMI2", because we never added one - std::optional hdmi2Viewport = mFakePolicy->getDisplayViewportByPort(hdmi2); - ASSERT_FALSE(hdmi2Viewport); + stylus->pressAndReleaseKey(BTN_STYLUS); + ASSERT_NO_FATAL_FAILURE(mTestListener->assertNotifyKeyWasCalled( + AllOf(DOWN, WithKeyCode(AKEYCODE_STYLUS_BUTTON_PRIMARY)))); + ASSERT_NO_FATAL_FAILURE(mTestListener->assertNotifyKeyWasCalled( + AllOf(UP, WithKeyCode(AKEYCODE_STYLUS_BUTTON_PRIMARY)))); + + stylus->pressAndReleaseKey(BTN_STYLUS2); + ASSERT_NO_FATAL_FAILURE(mTestListener->assertNotifyKeyWasCalled( + AllOf(DOWN, WithKeyCode(AKEYCODE_STYLUS_BUTTON_SECONDARY)))); + ASSERT_NO_FATAL_FAILURE(mTestListener->assertNotifyKeyWasCalled( + AllOf(UP, WithKeyCode(AKEYCODE_STYLUS_BUTTON_SECONDARY)))); + + stylus->pressAndReleaseKey(BTN_STYLUS3); + ASSERT_NO_FATAL_FAILURE(mTestListener->assertNotifyKeyWasCalled( + AllOf(DOWN, WithKeyCode(AKEYCODE_STYLUS_BUTTON_TERTIARY)))); + ASSERT_NO_FATAL_FAILURE(mTestListener->assertNotifyKeyWasCalled( + AllOf(UP, WithKeyCode(AKEYCODE_STYLUS_BUTTON_TERTIARY)))); } -// --- InputReaderTest --- +/** + * The Steam controller sends BTN_GEAR_DOWN and BTN_GEAR_UP for the two "paddle" buttons + * on the back. In this test, we make sure that BTN_GEAR_DOWN / BTN_WHEEL and BTN_GEAR_UP + * are passed to the listener. + */ +static_assert(BTN_GEAR_DOWN == BTN_WHEEL); +TEST_F(InputReaderIntegrationTest, SendsGearDownAndUpToInputListener) { + std::unique_ptr controller = createUinputDevice(); + ASSERT_NO_FATAL_FAILURE(mFakePolicy->assertInputDevicesChanged()); + NotifyKeyArgs keyArgs; -class InputReaderTest : public testing::Test { -protected: - std::unique_ptr mFakeListener; - sp mFakePolicy; - std::shared_ptr mFakeEventHub; - std::unique_ptr mReader; + controller->pressAndReleaseKey(BTN_GEAR_DOWN); + ASSERT_NO_FATAL_FAILURE(mTestListener->assertNotifyKeyWasCalled(&keyArgs)); // ACTION_DOWN + ASSERT_NO_FATAL_FAILURE(mTestListener->assertNotifyKeyWasCalled(&keyArgs)); // ACTION_UP + ASSERT_EQ(BTN_GEAR_DOWN, keyArgs.scanCode); - void SetUp() override { - mFakeEventHub = std::make_unique(); - mFakePolicy = new FakeInputReaderPolicy(); - mFakeListener = std::make_unique(); + controller->pressAndReleaseKey(BTN_GEAR_UP); + ASSERT_NO_FATAL_FAILURE(mTestListener->assertNotifyKeyWasCalled(&keyArgs)); // ACTION_DOWN + ASSERT_NO_FATAL_FAILURE(mTestListener->assertNotifyKeyWasCalled(&keyArgs)); // ACTION_UP + ASSERT_EQ(BTN_GEAR_UP, keyArgs.scanCode); +} - mReader = std::make_unique(mFakeEventHub, mFakePolicy, - *mFakeListener); - } +// --- TouchIntegrationTest --- - void TearDown() override { - mFakeListener.reset(); - mFakePolicy.clear(); - } +class TouchIntegrationTest : public InputReaderIntegrationTest { +protected: + const std::string UNIQUE_ID = "local:0"; - void addDevice(int32_t eventHubId, const std::string& name, - ftl::Flags classes, const PropertyMap* configuration) { - mFakeEventHub->addDevice(eventHubId, name, classes); + void SetUp() override { +#if !defined(__ANDROID__) + GTEST_SKIP(); +#endif + InputReaderIntegrationTest::SetUp(); + // At least add an internal display. + setDisplayInfoAndReconfigure(DISPLAY_ID, DISPLAY_WIDTH, DISPLAY_HEIGHT, ui::ROTATION_0, + UNIQUE_ID, NO_PORT, ViewportType::INTERNAL); - if (configuration) { - mFakeEventHub->addConfigurationMap(eventHubId, configuration); - } - mFakeEventHub->finishDeviceScan(); - mReader->loopOnce(); - mReader->loopOnce(); + mDevice = createUinputDevice(Rect(0, 0, DISPLAY_WIDTH, DISPLAY_HEIGHT)); ASSERT_NO_FATAL_FAILURE(mFakePolicy->assertInputDevicesChanged()); - ASSERT_NO_FATAL_FAILURE(mFakeEventHub->assertQueueIsEmpty()); + ASSERT_NO_FATAL_FAILURE(mTestListener->assertNotifyConfigurationChangedWasCalled()); + const auto info = findDeviceByName(mDevice->getName()); + ASSERT_TRUE(info); + mDeviceInfo = *info; } - void disableDevice(int32_t deviceId) { - mFakePolicy->addDisabledDevice(deviceId); - mReader->requestRefreshConfiguration(InputReaderConfiguration::CHANGE_ENABLED_STATE); + void setDisplayInfoAndReconfigure(int32_t displayId, int32_t width, int32_t height, + ui::Rotation orientation, const std::string& uniqueId, + std::optional physicalPort, + ViewportType viewportType) { + mFakePolicy->addDisplayViewport(displayId, width, height, orientation, /*isActive=*/true, + uniqueId, physicalPort, viewportType); + mReader->requestRefreshConfiguration(InputReaderConfiguration::Change::DISPLAY_INFO); } - void enableDevice(int32_t deviceId) { - mFakePolicy->removeDisabledDevice(deviceId); - mReader->requestRefreshConfiguration(InputReaderConfiguration::CHANGE_ENABLED_STATE); + void assertReceivedMotion(int32_t action, const std::vector& points) { + NotifyMotionArgs args; + ASSERT_NO_FATAL_FAILURE(mTestListener->assertNotifyMotionWasCalled(&args)); + EXPECT_EQ(action, args.action); + ASSERT_EQ(points.size(), args.pointerCount); + for (size_t i = 0; i < args.pointerCount; i++) { + EXPECT_EQ(points[i].x, args.pointerCoords[i].getX()); + EXPECT_EQ(points[i].y, args.pointerCoords[i].getY()); + } } - FakeInputMapper& addDeviceWithFakeInputMapper(int32_t deviceId, int32_t eventHubId, - const std::string& name, - ftl::Flags classes, - uint32_t sources, - const PropertyMap* configuration) { - std::shared_ptr device = mReader->newDevice(deviceId, name); - FakeInputMapper& mapper = device->addMapper(eventHubId, sources); - mReader->pushNextDevice(device); - addDevice(eventHubId, name, classes, configuration); - return mapper; - } + std::unique_ptr mDevice; + InputDeviceInfo mDeviceInfo; }; -TEST_F(InputReaderTest, PolicyGetInputDevices) { - ASSERT_NO_FATAL_FAILURE(addDevice(1, "keyboard", InputDeviceClass::KEYBOARD, nullptr)); - ASSERT_NO_FATAL_FAILURE(addDevice(2, "ignored", ftl::Flags(0), - nullptr)); // no classes so device will be ignored +TEST_F(TouchIntegrationTest, MultiTouchDeviceSource) { + // The UinputTouchScreen is an MT device that supports MT_TOOL_TYPE and also supports stylus + // buttons. It should show up as a touchscreen, stylus, and keyboard (for reporting button + // presses). + ASSERT_EQ(AINPUT_SOURCE_TOUCHSCREEN | AINPUT_SOURCE_STYLUS | AINPUT_SOURCE_KEYBOARD, + mDeviceInfo.getSources()); +} - // Should also have received a notification describing the new input devices. - const std::vector& inputDevices = mFakePolicy->getInputDevices(); - ASSERT_EQ(1U, inputDevices.size()); - ASSERT_EQ(END_RESERVED_ID + 1, inputDevices[0].getId()); - ASSERT_STREQ("keyboard", inputDevices[0].getIdentifier().name.c_str()); - ASSERT_EQ(AINPUT_KEYBOARD_TYPE_NON_ALPHABETIC, inputDevices[0].getKeyboardType()); - ASSERT_EQ(AINPUT_SOURCE_KEYBOARD, inputDevices[0].getSources()); - ASSERT_EQ(0U, inputDevices[0].getMotionRanges().size()); -} - -TEST_F(InputReaderTest, GetMergedInputDevices) { - constexpr int32_t deviceId = END_RESERVED_ID + 1000; - constexpr int32_t eventHubIds[2] = {END_RESERVED_ID, END_RESERVED_ID + 1}; - // Add two subdevices to device - std::shared_ptr device = mReader->newDevice(deviceId, "fake"); - // Must add at least one mapper or the device will be ignored! - device->addMapper(eventHubIds[0], AINPUT_SOURCE_KEYBOARD); - device->addMapper(eventHubIds[1], AINPUT_SOURCE_KEYBOARD); - - // Push same device instance for next device to be added, so they'll have same identifier. - mReader->pushNextDevice(device); - mReader->pushNextDevice(device); - ASSERT_NO_FATAL_FAILURE( - addDevice(eventHubIds[0], "fake1", InputDeviceClass::KEYBOARD, nullptr)); - ASSERT_NO_FATAL_FAILURE( - addDevice(eventHubIds[1], "fake2", InputDeviceClass::KEYBOARD, nullptr)); +TEST_F(TouchIntegrationTest, InputEvent_ProcessSingleTouch) { + NotifyMotionArgs args; + const Point centerPoint = mDevice->getCenterPoint(); - // Two devices will be merged to one input device as they have same identifier - ASSERT_EQ(1U, mFakePolicy->getInputDevices().size()); -} + // ACTION_DOWN + mDevice->sendTrackingId(FIRST_TRACKING_ID); + mDevice->sendDown(centerPoint); + mDevice->sendSync(); + ASSERT_NO_FATAL_FAILURE(mTestListener->assertNotifyMotionWasCalled(&args)); + ASSERT_EQ(AMOTION_EVENT_ACTION_DOWN, args.action); -TEST_F(InputReaderTest, GetMergedInputDevicesEnabled) { - constexpr int32_t deviceId = END_RESERVED_ID + 1000; - constexpr int32_t eventHubIds[2] = {END_RESERVED_ID, END_RESERVED_ID + 1}; - // Add two subdevices to device - std::shared_ptr device = mReader->newDevice(deviceId, "fake"); - // Must add at least one mapper or the device will be ignored! - device->addMapper(eventHubIds[0], AINPUT_SOURCE_KEYBOARD); - device->addMapper(eventHubIds[1], AINPUT_SOURCE_KEYBOARD); + // ACTION_MOVE + mDevice->sendMove(centerPoint + Point(1, 1)); + mDevice->sendSync(); + ASSERT_NO_FATAL_FAILURE(mTestListener->assertNotifyMotionWasCalled(&args)); + ASSERT_EQ(AMOTION_EVENT_ACTION_MOVE, args.action); - // Push same device instance for next device to be added, so they'll have same identifier. - mReader->pushNextDevice(device); - mReader->pushNextDevice(device); - // Sensor device is initially disabled - ASSERT_NO_FATAL_FAILURE(addDevice(eventHubIds[0], "fake1", - InputDeviceClass::KEYBOARD | InputDeviceClass::SENSOR, - nullptr)); - // Device is disabled because the only sub device is a sensor device and disabled initially. - ASSERT_FALSE(mFakeEventHub->isDeviceEnabled(eventHubIds[0])); - ASSERT_FALSE(device->isEnabled()); - ASSERT_NO_FATAL_FAILURE( - addDevice(eventHubIds[1], "fake2", InputDeviceClass::KEYBOARD, nullptr)); - // The merged device is enabled if any sub device is enabled - ASSERT_TRUE(mFakeEventHub->isDeviceEnabled(eventHubIds[1])); - ASSERT_TRUE(device->isEnabled()); + // ACTION_UP + mDevice->sendUp(); + mDevice->sendSync(); + ASSERT_NO_FATAL_FAILURE(mTestListener->assertNotifyMotionWasCalled(&args)); + ASSERT_EQ(AMOTION_EVENT_ACTION_UP, args.action); } -TEST_F(InputReaderTest, WhenEnabledChanges_SendsDeviceResetNotification) { - constexpr int32_t deviceId = END_RESERVED_ID + 1000; - constexpr ftl::Flags deviceClass(InputDeviceClass::KEYBOARD); - constexpr int32_t eventHubId = 1; - std::shared_ptr device = mReader->newDevice(deviceId, "fake"); - // Must add at least one mapper or the device will be ignored! - device->addMapper(eventHubId, AINPUT_SOURCE_KEYBOARD); - mReader->pushNextDevice(device); - ASSERT_NO_FATAL_FAILURE(addDevice(eventHubId, "fake", deviceClass, nullptr)); - - ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyConfigurationChangedWasCalled(nullptr)); +TEST_F(TouchIntegrationTest, InputEvent_ProcessMultiTouch) { + NotifyMotionArgs args; + const Point centerPoint = mDevice->getCenterPoint(); - NotifyDeviceResetArgs resetArgs; - ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyDeviceResetWasCalled(&resetArgs)); - ASSERT_EQ(deviceId, resetArgs.deviceId); + // ACTION_DOWN + mDevice->sendSlot(FIRST_SLOT); + mDevice->sendTrackingId(FIRST_TRACKING_ID); + mDevice->sendDown(centerPoint); + mDevice->sendSync(); + ASSERT_NO_FATAL_FAILURE(mTestListener->assertNotifyMotionWasCalled(&args)); + ASSERT_EQ(AMOTION_EVENT_ACTION_DOWN, args.action); - ASSERT_EQ(device->isEnabled(), true); - disableDevice(deviceId); - mReader->loopOnce(); + // ACTION_POINTER_DOWN (Second slot) + const Point secondPoint = centerPoint + Point(100, 100); + mDevice->sendSlot(SECOND_SLOT); + mDevice->sendTrackingId(SECOND_TRACKING_ID); + mDevice->sendDown(secondPoint); + mDevice->sendSync(); + ASSERT_NO_FATAL_FAILURE(mTestListener->assertNotifyMotionWasCalled(&args)); + ASSERT_EQ(ACTION_POINTER_1_DOWN, args.action); - ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyDeviceResetWasCalled(&resetArgs)); - ASSERT_EQ(deviceId, resetArgs.deviceId); - ASSERT_EQ(device->isEnabled(), false); + // ACTION_MOVE (Second slot) + mDevice->sendMove(secondPoint + Point(1, 1)); + mDevice->sendSync(); + ASSERT_NO_FATAL_FAILURE(mTestListener->assertNotifyMotionWasCalled(&args)); + ASSERT_EQ(AMOTION_EVENT_ACTION_MOVE, args.action); - disableDevice(deviceId); - mReader->loopOnce(); - ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyDeviceResetWasNotCalled()); - ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyConfigurationChangedWasNotCalled()); - ASSERT_EQ(device->isEnabled(), false); + // ACTION_POINTER_UP (Second slot) + mDevice->sendPointerUp(); + mDevice->sendSync(); + ASSERT_NO_FATAL_FAILURE(mTestListener->assertNotifyMotionWasCalled(&args)); + ASSERT_EQ(ACTION_POINTER_1_UP, args.action); - enableDevice(deviceId); - mReader->loopOnce(); - ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyDeviceResetWasCalled(&resetArgs)); - ASSERT_EQ(deviceId, resetArgs.deviceId); - ASSERT_EQ(device->isEnabled(), true); + // ACTION_UP + mDevice->sendSlot(FIRST_SLOT); + mDevice->sendUp(); + mDevice->sendSync(); + ASSERT_NO_FATAL_FAILURE(mTestListener->assertNotifyMotionWasCalled(&args)); + ASSERT_EQ(AMOTION_EVENT_ACTION_UP, args.action); } -TEST_F(InputReaderTest, GetKeyCodeState_ForwardsRequestsToMappers) { - constexpr int32_t deviceId = END_RESERVED_ID + 1000; - constexpr ftl::Flags deviceClass = InputDeviceClass::KEYBOARD; - constexpr int32_t eventHubId = 1; - FakeInputMapper& mapper = - addDeviceWithFakeInputMapper(deviceId, eventHubId, "fake", deviceClass, - AINPUT_SOURCE_KEYBOARD, nullptr); - mapper.setKeyCodeState(AKEYCODE_A, AKEY_STATE_DOWN); +/** + * What happens when a pointer goes up while another pointer moves in the same frame? Are POINTER_UP + * events guaranteed to contain the same data as a preceding MOVE, or can they contain different + * data? + * In this test, we try to send a change in coordinates in Pointer 0 in the same frame as the + * liftoff of Pointer 1. We check that POINTER_UP event is generated first, and the MOVE event + * for Pointer 0 only is generated after. + * Suppose we are only interested in learning the movement of Pointer 0. If we only observe MOVE + * events, we will not miss any information. + * Even though the Pointer 1 up event contains updated Pointer 0 coordinates, there is another MOVE + * event generated afterwards that contains the newest movement of pointer 0. + * This is important for palm rejection. If there is a subsequent InputListener stage that detects + * palms, and wants to cancel Pointer 1, then it is safe to simply drop POINTER_1_UP event without + * losing information about non-palm pointers. + */ +TEST_F(TouchIntegrationTest, MultiTouch_PointerMoveAndSecondPointerUp) { + NotifyMotionArgs args; + const Point centerPoint = mDevice->getCenterPoint(); - ASSERT_EQ(AKEY_STATE_UNKNOWN, mReader->getKeyCodeState(0, - AINPUT_SOURCE_ANY, AKEYCODE_A)) - << "Should return unknown when the device id is >= 0 but unknown."; + // ACTION_DOWN + mDevice->sendSlot(FIRST_SLOT); + mDevice->sendTrackingId(FIRST_TRACKING_ID); + mDevice->sendDown(centerPoint); + mDevice->sendSync(); + assertReceivedMotion(AMOTION_EVENT_ACTION_DOWN, {centerPoint}); - ASSERT_EQ(AKEY_STATE_UNKNOWN, - mReader->getKeyCodeState(deviceId, AINPUT_SOURCE_TRACKBALL, AKEYCODE_A)) - << "Should return unknown when the device id is valid but the sources are not " - "supported by the device."; + // ACTION_POINTER_DOWN (Second slot) + const Point secondPoint = centerPoint + Point(100, 100); + mDevice->sendSlot(SECOND_SLOT); + mDevice->sendTrackingId(SECOND_TRACKING_ID); + mDevice->sendDown(secondPoint); + mDevice->sendSync(); + assertReceivedMotion(ACTION_POINTER_1_DOWN, {centerPoint, secondPoint}); - ASSERT_EQ(AKEY_STATE_DOWN, - mReader->getKeyCodeState(deviceId, AINPUT_SOURCE_KEYBOARD | AINPUT_SOURCE_TRACKBALL, - AKEYCODE_A)) - << "Should return value provided by mapper when device id is valid and the device " - "supports some of the sources."; + // ACTION_MOVE (First slot) + mDevice->sendSlot(FIRST_SLOT); + mDevice->sendMove(centerPoint + Point(5, 5)); + // ACTION_POINTER_UP (Second slot) + mDevice->sendSlot(SECOND_SLOT); + mDevice->sendPointerUp(); + // Send a single sync for the above 2 pointer updates + mDevice->sendSync(); - ASSERT_EQ(AKEY_STATE_UNKNOWN, mReader->getKeyCodeState(-1, - AINPUT_SOURCE_TRACKBALL, AKEYCODE_A)) - << "Should return unknown when the device id is < 0 but the sources are not supported by any device."; + // First, we should get POINTER_UP for the second pointer + assertReceivedMotion(ACTION_POINTER_1_UP, + {/*first pointer */ centerPoint + Point(5, 5), + /*second pointer*/ secondPoint}); - ASSERT_EQ(AKEY_STATE_DOWN, mReader->getKeyCodeState(-1, - AINPUT_SOURCE_KEYBOARD | AINPUT_SOURCE_TRACKBALL, AKEYCODE_A)) - << "Should return value provided by mapper when device id is < 0 and one of the devices supports some of the sources."; + // Next, the MOVE event for the first pointer + assertReceivedMotion(AMOTION_EVENT_ACTION_MOVE, {centerPoint + Point(5, 5)}); } -TEST_F(InputReaderTest, GetKeyCodeForKeyLocation_ForwardsRequestsToMappers) { - constexpr int32_t deviceId = END_RESERVED_ID + 1000; - constexpr int32_t eventHubId = 1; - FakeInputMapper& mapper = addDeviceWithFakeInputMapper(deviceId, eventHubId, "keyboard", - InputDeviceClass::KEYBOARD, - AINPUT_SOURCE_KEYBOARD, nullptr); - mapper.addKeyCodeMapping(AKEYCODE_Y, AKEYCODE_Z); +/** + * Similar scenario as above. The difference is that when the second pointer goes up, it will first + * move, and then it will go up, all in the same frame. + * In this scenario, the movement of the second pointer just prior to liftoff is ignored, and never + * gets sent to the listener. + */ +TEST_F(TouchIntegrationTest, MultiTouch_PointerMoveAndSecondPointerMoveAndUp) { + NotifyMotionArgs args; + const Point centerPoint = mDevice->getCenterPoint(); - ASSERT_EQ(AKEYCODE_UNKNOWN, mReader->getKeyCodeForKeyLocation(0, AKEYCODE_Y)) - << "Should return unknown when the device with the specified id is not found."; + // ACTION_DOWN + mDevice->sendSlot(FIRST_SLOT); + mDevice->sendTrackingId(FIRST_TRACKING_ID); + mDevice->sendDown(centerPoint); + mDevice->sendSync(); + assertReceivedMotion(AMOTION_EVENT_ACTION_DOWN, {centerPoint}); - ASSERT_EQ(AKEYCODE_Z, mReader->getKeyCodeForKeyLocation(deviceId, AKEYCODE_Y)) - << "Should return correct mapping when device id is valid and mapping exists."; + // ACTION_POINTER_DOWN (Second slot) + const Point secondPoint = centerPoint + Point(100, 100); + mDevice->sendSlot(SECOND_SLOT); + mDevice->sendTrackingId(SECOND_TRACKING_ID); + mDevice->sendDown(secondPoint); + mDevice->sendSync(); + assertReceivedMotion(ACTION_POINTER_1_DOWN, {centerPoint, secondPoint}); - ASSERT_EQ(AKEYCODE_A, mReader->getKeyCodeForKeyLocation(deviceId, AKEYCODE_A)) - << "Should return the location key code when device id is valid and there's no " - "mapping."; + // ACTION_MOVE (First slot) + mDevice->sendSlot(FIRST_SLOT); + mDevice->sendMove(centerPoint + Point(5, 5)); + // ACTION_POINTER_UP (Second slot) + mDevice->sendSlot(SECOND_SLOT); + mDevice->sendMove(secondPoint + Point(6, 6)); + mDevice->sendPointerUp(); + // Send a single sync for the above 2 pointer updates + mDevice->sendSync(); + + // First, we should get POINTER_UP for the second pointer + // The movement of the second pointer during the liftoff frame is ignored. + // The coordinates 'secondPoint + Point(6, 6)' are never sent to the listener. + assertReceivedMotion(ACTION_POINTER_1_UP, + {/*first pointer */ centerPoint + Point(5, 5), + /*second pointer*/ secondPoint}); + + // Next, the MOVE event for the first pointer + assertReceivedMotion(AMOTION_EVENT_ACTION_MOVE, {centerPoint + Point(5, 5)}); } -TEST_F(InputReaderTest, GetKeyCodeForKeyLocation_NoKeyboardMapper) { - constexpr int32_t deviceId = END_RESERVED_ID + 1000; - constexpr int32_t eventHubId = 1; - FakeInputMapper& mapper = addDeviceWithFakeInputMapper(deviceId, eventHubId, "joystick", - InputDeviceClass::JOYSTICK, - AINPUT_SOURCE_GAMEPAD, nullptr); - mapper.addKeyCodeMapping(AKEYCODE_Y, AKEYCODE_Z); +TEST_F(TouchIntegrationTest, InputEvent_ProcessPalm) { + NotifyMotionArgs args; + const Point centerPoint = mDevice->getCenterPoint(); - ASSERT_EQ(AKEYCODE_UNKNOWN, mReader->getKeyCodeForKeyLocation(deviceId, AKEYCODE_Y)) - << "Should return unknown when the device id is valid but there is no keyboard mapper"; -} + // ACTION_DOWN + mDevice->sendSlot(FIRST_SLOT); + mDevice->sendTrackingId(FIRST_TRACKING_ID); + mDevice->sendDown(centerPoint); + mDevice->sendSync(); + ASSERT_NO_FATAL_FAILURE(mTestListener->assertNotifyMotionWasCalled(&args)); + ASSERT_EQ(AMOTION_EVENT_ACTION_DOWN, args.action); -TEST_F(InputReaderTest, GetScanCodeState_ForwardsRequestsToMappers) { - constexpr int32_t deviceId = END_RESERVED_ID + 1000; - constexpr ftl::Flags deviceClass = InputDeviceClass::KEYBOARD; - constexpr int32_t eventHubId = 1; - FakeInputMapper& mapper = - addDeviceWithFakeInputMapper(deviceId, eventHubId, "fake", deviceClass, - AINPUT_SOURCE_KEYBOARD, nullptr); - mapper.setScanCodeState(KEY_A, AKEY_STATE_DOWN); + // ACTION_POINTER_DOWN (second slot) + const Point secondPoint = centerPoint + Point(100, 100); + mDevice->sendSlot(SECOND_SLOT); + mDevice->sendTrackingId(SECOND_TRACKING_ID); + mDevice->sendDown(secondPoint); + mDevice->sendSync(); + ASSERT_NO_FATAL_FAILURE(mTestListener->assertNotifyMotionWasCalled(&args)); + ASSERT_EQ(ACTION_POINTER_1_DOWN, args.action); - ASSERT_EQ(AKEY_STATE_UNKNOWN, mReader->getScanCodeState(0, - AINPUT_SOURCE_ANY, KEY_A)) - << "Should return unknown when the device id is >= 0 but unknown."; + // ACTION_MOVE (second slot) + mDevice->sendMove(secondPoint + Point(1, 1)); + mDevice->sendSync(); + ASSERT_NO_FATAL_FAILURE(mTestListener->assertNotifyMotionWasCalled(&args)); + ASSERT_EQ(AMOTION_EVENT_ACTION_MOVE, args.action); - ASSERT_EQ(AKEY_STATE_UNKNOWN, - mReader->getScanCodeState(deviceId, AINPUT_SOURCE_TRACKBALL, KEY_A)) - << "Should return unknown when the device id is valid but the sources are not " - "supported by the device."; + // Send MT_TOOL_PALM (second slot), which indicates that the touch IC has determined this to be + // a palm event. + // Expect to receive the ACTION_POINTER_UP with cancel flag. + mDevice->sendToolType(MT_TOOL_PALM); + mDevice->sendSync(); + ASSERT_NO_FATAL_FAILURE(mTestListener->assertNotifyMotionWasCalled(&args)); + ASSERT_EQ(ACTION_POINTER_1_UP, args.action); + ASSERT_EQ(AMOTION_EVENT_FLAG_CANCELED, args.flags); - ASSERT_EQ(AKEY_STATE_DOWN, - mReader->getScanCodeState(deviceId, AINPUT_SOURCE_KEYBOARD | AINPUT_SOURCE_TRACKBALL, - KEY_A)) - << "Should return value provided by mapper when device id is valid and the device " - "supports some of the sources."; + // Send up to second slot, expect first slot send moving. + mDevice->sendPointerUp(); + mDevice->sendSync(); + ASSERT_NO_FATAL_FAILURE(mTestListener->assertNotifyMotionWasCalled(&args)); + ASSERT_EQ(AMOTION_EVENT_ACTION_MOVE, args.action); - ASSERT_EQ(AKEY_STATE_UNKNOWN, mReader->getScanCodeState(-1, - AINPUT_SOURCE_TRACKBALL, KEY_A)) - << "Should return unknown when the device id is < 0 but the sources are not supported by any device."; + // Send ACTION_UP (first slot) + mDevice->sendSlot(FIRST_SLOT); + mDevice->sendUp(); + mDevice->sendSync(); - ASSERT_EQ(AKEY_STATE_DOWN, mReader->getScanCodeState(-1, - AINPUT_SOURCE_KEYBOARD | AINPUT_SOURCE_TRACKBALL, KEY_A)) - << "Should return value provided by mapper when device id is < 0 and one of the devices supports some of the sources."; + ASSERT_NO_FATAL_FAILURE(mTestListener->assertNotifyMotionWasCalled(&args)); + ASSERT_EQ(AMOTION_EVENT_ACTION_UP, args.action); } -TEST_F(InputReaderTest, GetSwitchState_ForwardsRequestsToMappers) { - constexpr int32_t deviceId = END_RESERVED_ID + 1000; - constexpr ftl::Flags deviceClass = InputDeviceClass::KEYBOARD; - constexpr int32_t eventHubId = 1; - FakeInputMapper& mapper = - addDeviceWithFakeInputMapper(deviceId, eventHubId, "fake", deviceClass, - AINPUT_SOURCE_KEYBOARD, nullptr); - mapper.setSwitchState(SW_LID, AKEY_STATE_DOWN); +TEST_F(TouchIntegrationTest, NotifiesPolicyWhenStylusGestureStarted) { + const Point centerPoint = mDevice->getCenterPoint(); - ASSERT_EQ(AKEY_STATE_UNKNOWN, mReader->getSwitchState(0, - AINPUT_SOURCE_ANY, SW_LID)) - << "Should return unknown when the device id is >= 0 but unknown."; + // Send down with the pen tool selected. The policy should be notified of the stylus presence. + mDevice->sendSlot(FIRST_SLOT); + mDevice->sendTrackingId(FIRST_TRACKING_ID); + mDevice->sendToolType(MT_TOOL_PEN); + mDevice->sendDown(centerPoint); + mDevice->sendSync(); + ASSERT_NO_FATAL_FAILURE(mTestListener->assertNotifyMotionWasCalled( + AllOf(WithMotionAction(AMOTION_EVENT_ACTION_DOWN), + WithToolType(ToolType::STYLUS)))); - ASSERT_EQ(AKEY_STATE_UNKNOWN, - mReader->getSwitchState(deviceId, AINPUT_SOURCE_TRACKBALL, SW_LID)) - << "Should return unknown when the device id is valid but the sources are not " - "supported by the device."; + ASSERT_NO_FATAL_FAILURE(mFakePolicy->assertStylusGestureNotified(mDeviceInfo.getId())); - ASSERT_EQ(AKEY_STATE_DOWN, - mReader->getSwitchState(deviceId, AINPUT_SOURCE_KEYBOARD | AINPUT_SOURCE_TRACKBALL, - SW_LID)) - << "Should return value provided by mapper when device id is valid and the device " - "supports some of the sources."; + // Release the stylus touch. + mDevice->sendUp(); + mDevice->sendSync(); + ASSERT_NO_FATAL_FAILURE( + mTestListener->assertNotifyMotionWasCalled(WithMotionAction(AMOTION_EVENT_ACTION_UP))); - ASSERT_EQ(AKEY_STATE_UNKNOWN, mReader->getSwitchState(-1, - AINPUT_SOURCE_TRACKBALL, SW_LID)) - << "Should return unknown when the device id is < 0 but the sources are not supported by any device."; + ASSERT_NO_FATAL_FAILURE(mFakePolicy->assertStylusGestureNotNotified()); - ASSERT_EQ(AKEY_STATE_DOWN, mReader->getSwitchState(-1, - AINPUT_SOURCE_KEYBOARD | AINPUT_SOURCE_TRACKBALL, SW_LID)) - << "Should return value provided by mapper when device id is < 0 and one of the devices supports some of the sources."; -} + // Touch down with the finger, without the pen tool selected. The policy is not notified. + mDevice->sendTrackingId(FIRST_TRACKING_ID); + mDevice->sendToolType(MT_TOOL_FINGER); + mDevice->sendDown(centerPoint); + mDevice->sendSync(); + ASSERT_NO_FATAL_FAILURE(mTestListener->assertNotifyMotionWasCalled( + AllOf(WithMotionAction(AMOTION_EVENT_ACTION_DOWN), + WithToolType(ToolType::FINGER)))); -TEST_F(InputReaderTest, MarkSupportedKeyCodes_ForwardsRequestsToMappers) { - constexpr int32_t deviceId = END_RESERVED_ID + 1000; - constexpr ftl::Flags deviceClass = InputDeviceClass::KEYBOARD; - constexpr int32_t eventHubId = 1; - FakeInputMapper& mapper = - addDeviceWithFakeInputMapper(deviceId, eventHubId, "fake", deviceClass, - AINPUT_SOURCE_KEYBOARD, nullptr); + ASSERT_NO_FATAL_FAILURE(mFakePolicy->assertStylusGestureNotNotified()); - mapper.addSupportedKeyCode(AKEYCODE_A); - mapper.addSupportedKeyCode(AKEYCODE_B); + mDevice->sendUp(); + mDevice->sendSync(); + ASSERT_NO_FATAL_FAILURE( + mTestListener->assertNotifyMotionWasCalled(WithMotionAction(AMOTION_EVENT_ACTION_UP))); - const int32_t keyCodes[4] = { AKEYCODE_A, AKEYCODE_B, AKEYCODE_1, AKEYCODE_2 }; - uint8_t flags[4] = { 0, 0, 0, 1 }; + // Send a move event with the stylus tool without BTN_TOUCH to generate a hover enter. + // The policy should be notified of the stylus presence. + mDevice->sendTrackingId(FIRST_TRACKING_ID); + mDevice->sendToolType(MT_TOOL_PEN); + mDevice->sendMove(centerPoint); + mDevice->sendSync(); + ASSERT_NO_FATAL_FAILURE(mTestListener->assertNotifyMotionWasCalled( + AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_ENTER), + WithToolType(ToolType::STYLUS)))); - ASSERT_FALSE(mReader->hasKeys(0, AINPUT_SOURCE_ANY, 4, keyCodes, flags)) - << "Should return false when device id is >= 0 but unknown."; - ASSERT_TRUE(!flags[0] && !flags[1] && !flags[2] && !flags[3]); + ASSERT_NO_FATAL_FAILURE(mFakePolicy->assertStylusGestureNotified(mDeviceInfo.getId())); +} - flags[3] = 1; - ASSERT_FALSE(mReader->hasKeys(deviceId, AINPUT_SOURCE_TRACKBALL, 4, keyCodes, flags)) - << "Should return false when device id is valid but the sources are not supported by " - "the device."; - ASSERT_TRUE(!flags[0] && !flags[1] && !flags[2] && !flags[3]); +// --- StylusButtonIntegrationTest --- - flags[3] = 1; - ASSERT_TRUE(mReader->hasKeys(deviceId, AINPUT_SOURCE_KEYBOARD | AINPUT_SOURCE_TRACKBALL, 4, - keyCodes, flags)) - << "Should return value provided by mapper when device id is valid and the device " - "supports some of the sources."; - ASSERT_TRUE(flags[0] && flags[1] && !flags[2] && !flags[3]); +// Verify the behavior of button presses reported by various kinds of styluses, including buttons +// reported by the touchscreen's device, by a fused external stylus, and by an un-fused external +// stylus. +template +class StylusButtonIntegrationTest : public TouchIntegrationTest { +protected: + void SetUp() override { +#if !defined(__ANDROID__) + GTEST_SKIP(); +#endif + TouchIntegrationTest::SetUp(); + mTouchscreen = mDevice.get(); + mTouchscreenInfo = mDeviceInfo; - flags[3] = 1; - ASSERT_FALSE(mReader->hasKeys(-1, AINPUT_SOURCE_TRACKBALL, 4, keyCodes, flags)) - << "Should return false when the device id is < 0 but the sources are not supported by any device."; - ASSERT_TRUE(!flags[0] && !flags[1] && !flags[2] && !flags[3]); + setUpStylusDevice(); + } - flags[3] = 1; - ASSERT_TRUE(mReader->hasKeys(-1, AINPUT_SOURCE_KEYBOARD | AINPUT_SOURCE_TRACKBALL, 4, keyCodes, flags)) - << "Should return value provided by mapper when device id is < 0 and one of the devices supports some of the sources."; - ASSERT_TRUE(flags[0] && flags[1] && !flags[2] && !flags[3]); -} + UinputStylusDevice* mStylus{nullptr}; + InputDeviceInfo mStylusInfo{}; -TEST_F(InputReaderTest, LoopOnce_WhenDeviceScanFinished_SendsConfigurationChanged) { - constexpr int32_t eventHubId = 1; - addDevice(eventHubId, "ignored", InputDeviceClass::KEYBOARD, nullptr); + UinputTouchScreen* mTouchscreen{nullptr}; + InputDeviceInfo mTouchscreenInfo{}; - NotifyConfigurationChangedArgs args; +private: + // When we are attempting to test stylus button events that are sent from the touchscreen, + // use the same Uinput device for the touchscreen and the stylus. + template + std::enable_if_t, void> setUpStylusDevice() { + mStylus = mDevice.get(); + mStylusInfo = mDeviceInfo; + } + + // When we are attempting to stylus buttons from an external stylus being merged with touches + // from a touchscreen, create a new Uinput device through which stylus buttons can be injected. + template + std::enable_if_t, void> setUpStylusDevice() { + mStylusDeviceLifecycleTracker = createUinputDevice(); + mStylus = mStylusDeviceLifecycleTracker.get(); + ASSERT_NO_FATAL_FAILURE(mFakePolicy->assertInputDevicesChanged()); + ASSERT_NO_FATAL_FAILURE(mTestListener->assertNotifyConfigurationChangedWasCalled()); + const auto info = findDeviceByName(mStylus->getName()); + ASSERT_TRUE(info); + mStylusInfo = *info; + } - ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyConfigurationChangedWasCalled(&args)); - ASSERT_EQ(ARBITRARY_TIME, args.eventTime); -} + std::unique_ptr mStylusDeviceLifecycleTracker{}; -TEST_F(InputReaderTest, LoopOnce_ForwardsRawEventsToMappers) { - constexpr int32_t deviceId = END_RESERVED_ID + 1000; - constexpr ftl::Flags deviceClass = InputDeviceClass::KEYBOARD; - constexpr nsecs_t when = 0; - constexpr int32_t eventHubId = 1; - constexpr nsecs_t readTime = 2; - FakeInputMapper& mapper = - addDeviceWithFakeInputMapper(deviceId, eventHubId, "fake", deviceClass, - AINPUT_SOURCE_KEYBOARD, nullptr); + // Hide the base class's device to expose it with a different name for readability. + using TouchIntegrationTest::mDevice; + using TouchIntegrationTest::mDeviceInfo; +}; - mFakeEventHub->enqueueEvent(when, readTime, eventHubId, EV_KEY, KEY_A, 1); - mReader->loopOnce(); - ASSERT_NO_FATAL_FAILURE(mFakeEventHub->assertQueueIsEmpty()); +using StylusButtonIntegrationTestTypes = + ::testing::Types; +TYPED_TEST_SUITE(StylusButtonIntegrationTest, StylusButtonIntegrationTestTypes); + +TYPED_TEST(StylusButtonIntegrationTest, DISABLED_StylusButtonsGenerateKeyEvents) { + const auto stylusId = TestFixture::mStylusInfo.getId(); + + TestFixture::mStylus->pressKey(BTN_STYLUS); + ASSERT_NO_FATAL_FAILURE(TestFixture::mTestListener->assertNotifyKeyWasCalled( + AllOf(WithKeyAction(AKEY_EVENT_ACTION_DOWN), WithSource(AINPUT_SOURCE_KEYBOARD), + WithKeyCode(AKEYCODE_STYLUS_BUTTON_PRIMARY), WithDeviceId(stylusId)))); + + TestFixture::mStylus->releaseKey(BTN_STYLUS); + ASSERT_NO_FATAL_FAILURE(TestFixture::mTestListener->assertNotifyKeyWasCalled( + AllOf(WithKeyAction(AKEY_EVENT_ACTION_UP), WithSource(AINPUT_SOURCE_KEYBOARD), + WithKeyCode(AKEYCODE_STYLUS_BUTTON_PRIMARY), WithDeviceId(stylusId)))); +} + +TYPED_TEST(StylusButtonIntegrationTest, DISABLED_StylusButtonsSurroundingTouchGesture) { + const Point centerPoint = TestFixture::mTouchscreen->getCenterPoint(); + const auto touchscreenId = TestFixture::mTouchscreenInfo.getId(); + const auto stylusId = TestFixture::mStylusInfo.getId(); + + // Press the stylus button. + TestFixture::mStylus->pressKey(BTN_STYLUS); + ASSERT_NO_FATAL_FAILURE(TestFixture::mTestListener->assertNotifyKeyWasCalled( + AllOf(WithKeyAction(AKEY_EVENT_ACTION_DOWN), WithSource(AINPUT_SOURCE_KEYBOARD), + WithKeyCode(AKEYCODE_STYLUS_BUTTON_PRIMARY), WithDeviceId(stylusId)))); + + // Start and finish a stylus gesture. + TestFixture::mTouchscreen->sendSlot(FIRST_SLOT); + TestFixture::mTouchscreen->sendTrackingId(FIRST_TRACKING_ID); + TestFixture::mTouchscreen->sendToolType(MT_TOOL_PEN); + TestFixture::mTouchscreen->sendDown(centerPoint); + TestFixture::mTouchscreen->sendSync(); + ASSERT_NO_FATAL_FAILURE(TestFixture::mTestListener->assertNotifyMotionWasCalled( + AllOf(WithMotionAction(AMOTION_EVENT_ACTION_DOWN), + WithToolType(ToolType::STYLUS), + WithButtonState(AMOTION_EVENT_BUTTON_STYLUS_PRIMARY), + WithDeviceId(touchscreenId)))); + ASSERT_NO_FATAL_FAILURE(TestFixture::mTestListener->assertNotifyMotionWasCalled( + AllOf(WithMotionAction(AMOTION_EVENT_ACTION_BUTTON_PRESS), + WithToolType(ToolType::STYLUS), + WithButtonState(AMOTION_EVENT_BUTTON_STYLUS_PRIMARY), + WithDeviceId(touchscreenId)))); + + TestFixture::mTouchscreen->sendTrackingId(INVALID_TRACKING_ID); + TestFixture::mTouchscreen->sendSync(); + ASSERT_NO_FATAL_FAILURE(TestFixture::mTestListener->assertNotifyMotionWasCalled( + AllOf(WithMotionAction(AMOTION_EVENT_ACTION_BUTTON_RELEASE), + WithToolType(ToolType::STYLUS), WithButtonState(0), + WithDeviceId(touchscreenId)))); + ASSERT_NO_FATAL_FAILURE(TestFixture::mTestListener->assertNotifyMotionWasCalled( + AllOf(WithMotionAction(AMOTION_EVENT_ACTION_UP), + WithToolType(ToolType::STYLUS), WithButtonState(0), + WithDeviceId(touchscreenId)))); + + // Release the stylus button. + TestFixture::mStylus->releaseKey(BTN_STYLUS); + ASSERT_NO_FATAL_FAILURE(TestFixture::mTestListener->assertNotifyKeyWasCalled( + AllOf(WithKeyAction(AKEY_EVENT_ACTION_UP), WithSource(AINPUT_SOURCE_KEYBOARD), + WithKeyCode(AKEYCODE_STYLUS_BUTTON_PRIMARY), WithDeviceId(stylusId)))); +} + +TYPED_TEST(StylusButtonIntegrationTest, DISABLED_StylusButtonsSurroundingHoveringTouchGesture) { + const Point centerPoint = TestFixture::mTouchscreen->getCenterPoint(); + const auto touchscreenId = TestFixture::mTouchscreenInfo.getId(); + const auto stylusId = TestFixture::mStylusInfo.getId(); + auto toolTypeDevice = + AllOf(WithToolType(ToolType::STYLUS), WithDeviceId(touchscreenId)); + + // Press the stylus button. + TestFixture::mStylus->pressKey(BTN_STYLUS); + ASSERT_NO_FATAL_FAILURE(TestFixture::mTestListener->assertNotifyKeyWasCalled( + AllOf(WithKeyAction(AKEY_EVENT_ACTION_DOWN), WithSource(AINPUT_SOURCE_KEYBOARD), + WithKeyCode(AKEYCODE_STYLUS_BUTTON_PRIMARY), WithDeviceId(stylusId)))); + + // Start hovering with the stylus. + TestFixture::mTouchscreen->sendSlot(FIRST_SLOT); + TestFixture::mTouchscreen->sendTrackingId(FIRST_TRACKING_ID); + TestFixture::mTouchscreen->sendToolType(MT_TOOL_PEN); + TestFixture::mTouchscreen->sendMove(centerPoint); + TestFixture::mTouchscreen->sendSync(); + ASSERT_NO_FATAL_FAILURE(TestFixture::mTestListener->assertNotifyMotionWasCalled( + AllOf(toolTypeDevice, WithMotionAction(AMOTION_EVENT_ACTION_HOVER_ENTER), + WithButtonState(AMOTION_EVENT_BUTTON_STYLUS_PRIMARY)))); + ASSERT_NO_FATAL_FAILURE(TestFixture::mTestListener->assertNotifyMotionWasCalled( + AllOf(toolTypeDevice, WithMotionAction(AMOTION_EVENT_ACTION_HOVER_MOVE), + WithButtonState(AMOTION_EVENT_BUTTON_STYLUS_PRIMARY)))); + ASSERT_NO_FATAL_FAILURE(TestFixture::mTestListener->assertNotifyMotionWasCalled( + AllOf(toolTypeDevice, WithMotionAction(AMOTION_EVENT_ACTION_BUTTON_PRESS), + WithButtonState(AMOTION_EVENT_BUTTON_STYLUS_PRIMARY)))); + + // Touch down with the stylus. + TestFixture::mTouchscreen->sendTrackingId(FIRST_TRACKING_ID); + TestFixture::mTouchscreen->sendToolType(MT_TOOL_PEN); + TestFixture::mTouchscreen->sendDown(centerPoint); + TestFixture::mTouchscreen->sendSync(); + ASSERT_NO_FATAL_FAILURE(TestFixture::mTestListener->assertNotifyMotionWasCalled( + AllOf(toolTypeDevice, WithMotionAction(AMOTION_EVENT_ACTION_HOVER_EXIT), + WithButtonState(AMOTION_EVENT_BUTTON_STYLUS_PRIMARY)))); + + ASSERT_NO_FATAL_FAILURE(TestFixture::mTestListener->assertNotifyMotionWasCalled( + AllOf(toolTypeDevice, WithMotionAction(AMOTION_EVENT_ACTION_DOWN), + WithButtonState(AMOTION_EVENT_BUTTON_STYLUS_PRIMARY)))); + + // Stop touching with the stylus, and start hovering. + TestFixture::mTouchscreen->sendUp(); + TestFixture::mTouchscreen->sendTrackingId(FIRST_TRACKING_ID); + TestFixture::mTouchscreen->sendToolType(MT_TOOL_PEN); + TestFixture::mTouchscreen->sendMove(centerPoint); + TestFixture::mTouchscreen->sendSync(); + ASSERT_NO_FATAL_FAILURE(TestFixture::mTestListener->assertNotifyMotionWasCalled( + AllOf(toolTypeDevice, WithMotionAction(AMOTION_EVENT_ACTION_UP), + WithButtonState(AMOTION_EVENT_BUTTON_STYLUS_PRIMARY)))); + ASSERT_NO_FATAL_FAILURE(TestFixture::mTestListener->assertNotifyMotionWasCalled( + AllOf(toolTypeDevice, WithMotionAction(AMOTION_EVENT_ACTION_HOVER_ENTER), + WithButtonState(AMOTION_EVENT_BUTTON_STYLUS_PRIMARY)))); + ASSERT_NO_FATAL_FAILURE(TestFixture::mTestListener->assertNotifyMotionWasCalled( + AllOf(toolTypeDevice, WithMotionAction(AMOTION_EVENT_ACTION_HOVER_MOVE), + WithButtonState(AMOTION_EVENT_BUTTON_STYLUS_PRIMARY)))); + + // Stop hovering. + TestFixture::mTouchscreen->sendTrackingId(INVALID_TRACKING_ID); + TestFixture::mTouchscreen->sendSync(); + ASSERT_NO_FATAL_FAILURE(TestFixture::mTestListener->assertNotifyMotionWasCalled( + AllOf(toolTypeDevice, WithMotionAction(AMOTION_EVENT_ACTION_BUTTON_RELEASE), + WithButtonState(0)))); + // TODO(b/257971675): Fix inconsistent button state when exiting hover. + ASSERT_NO_FATAL_FAILURE(TestFixture::mTestListener->assertNotifyMotionWasCalled( + AllOf(toolTypeDevice, WithMotionAction(AMOTION_EVENT_ACTION_HOVER_EXIT), + WithButtonState(AMOTION_EVENT_BUTTON_STYLUS_PRIMARY)))); + + // Release the stylus button. + TestFixture::mStylus->releaseKey(BTN_STYLUS); + ASSERT_NO_FATAL_FAILURE(TestFixture::mTestListener->assertNotifyKeyWasCalled( + AllOf(WithKeyAction(AKEY_EVENT_ACTION_UP), WithSource(AINPUT_SOURCE_KEYBOARD), + WithKeyCode(AKEYCODE_STYLUS_BUTTON_PRIMARY), WithDeviceId(stylusId)))); +} + +TYPED_TEST(StylusButtonIntegrationTest, DISABLED_StylusButtonsWithinTouchGesture) { + const Point centerPoint = TestFixture::mTouchscreen->getCenterPoint(); + const auto touchscreenId = TestFixture::mTouchscreenInfo.getId(); + const auto stylusId = TestFixture::mStylusInfo.getId(); - RawEvent event; - ASSERT_NO_FATAL_FAILURE(mapper.assertProcessWasCalled(&event)); - ASSERT_EQ(when, event.when); - ASSERT_EQ(readTime, event.readTime); - ASSERT_EQ(eventHubId, event.deviceId); - ASSERT_EQ(EV_KEY, event.type); - ASSERT_EQ(KEY_A, event.code); - ASSERT_EQ(1, event.value); -} + // Start a stylus gesture. + TestFixture::mTouchscreen->sendSlot(FIRST_SLOT); + TestFixture::mTouchscreen->sendTrackingId(FIRST_TRACKING_ID); + TestFixture::mTouchscreen->sendToolType(MT_TOOL_PEN); + TestFixture::mTouchscreen->sendDown(centerPoint); + TestFixture::mTouchscreen->sendSync(); + ASSERT_NO_FATAL_FAILURE(TestFixture::mTestListener->assertNotifyMotionWasCalled( + AllOf(WithMotionAction(AMOTION_EVENT_ACTION_DOWN), + WithToolType(ToolType::STYLUS), WithButtonState(0), + WithDeviceId(touchscreenId)))); + + // Press and release a stylus button. Each change in button state also generates a MOVE event. + TestFixture::mStylus->pressKey(BTN_STYLUS); + ASSERT_NO_FATAL_FAILURE(TestFixture::mTestListener->assertNotifyKeyWasCalled( + AllOf(WithKeyAction(AKEY_EVENT_ACTION_DOWN), WithSource(AINPUT_SOURCE_KEYBOARD), + WithKeyCode(AKEYCODE_STYLUS_BUTTON_PRIMARY), WithDeviceId(stylusId)))); + ASSERT_NO_FATAL_FAILURE(TestFixture::mTestListener->assertNotifyMotionWasCalled( + AllOf(WithMotionAction(AMOTION_EVENT_ACTION_MOVE), + WithToolType(ToolType::STYLUS), + WithButtonState(AMOTION_EVENT_BUTTON_STYLUS_PRIMARY), + WithDeviceId(touchscreenId)))); + ASSERT_NO_FATAL_FAILURE(TestFixture::mTestListener->assertNotifyMotionWasCalled( + AllOf(WithMotionAction(AMOTION_EVENT_ACTION_BUTTON_PRESS), + WithToolType(ToolType::STYLUS), + WithButtonState(AMOTION_EVENT_BUTTON_STYLUS_PRIMARY), + WithDeviceId(touchscreenId)))); + + TestFixture::mStylus->releaseKey(BTN_STYLUS); + ASSERT_NO_FATAL_FAILURE(TestFixture::mTestListener->assertNotifyKeyWasCalled( + AllOf(WithKeyAction(AKEY_EVENT_ACTION_UP), WithSource(AINPUT_SOURCE_KEYBOARD), + WithKeyCode(AKEYCODE_STYLUS_BUTTON_PRIMARY), WithDeviceId(stylusId)))); + ASSERT_NO_FATAL_FAILURE(TestFixture::mTestListener->assertNotifyMotionWasCalled( + AllOf(WithMotionAction(AMOTION_EVENT_ACTION_BUTTON_RELEASE), + WithToolType(ToolType::STYLUS), WithButtonState(0), + WithDeviceId(touchscreenId)))); + ASSERT_NO_FATAL_FAILURE(TestFixture::mTestListener->assertNotifyMotionWasCalled( + AllOf(WithMotionAction(AMOTION_EVENT_ACTION_MOVE), + WithToolType(ToolType::STYLUS), WithButtonState(0), + WithDeviceId(touchscreenId)))); + + // Finish the stylus gesture. + TestFixture::mTouchscreen->sendTrackingId(INVALID_TRACKING_ID); + TestFixture::mTouchscreen->sendSync(); + ASSERT_NO_FATAL_FAILURE(TestFixture::mTestListener->assertNotifyMotionWasCalled( + AllOf(WithMotionAction(AMOTION_EVENT_ACTION_UP), + WithToolType(ToolType::STYLUS), WithButtonState(0), + WithDeviceId(touchscreenId)))); +} + +TYPED_TEST(StylusButtonIntegrationTest, DISABLED_StylusButtonMotionEventsDisabled) { + TestFixture::mFakePolicy->setStylusButtonMotionEventsEnabled(false); + TestFixture::mReader->requestRefreshConfiguration( + InputReaderConfiguration::Change::STYLUS_BUTTON_REPORTING); + + const Point centerPoint = TestFixture::mTouchscreen->getCenterPoint(); + const auto touchscreenId = TestFixture::mTouchscreenInfo.getId(); + const auto stylusId = TestFixture::mStylusInfo.getId(); + + // Start a stylus gesture. By the time this event is processed, the configuration change that + // was requested is guaranteed to be completed. + TestFixture::mTouchscreen->sendSlot(FIRST_SLOT); + TestFixture::mTouchscreen->sendTrackingId(FIRST_TRACKING_ID); + TestFixture::mTouchscreen->sendToolType(MT_TOOL_PEN); + TestFixture::mTouchscreen->sendDown(centerPoint); + TestFixture::mTouchscreen->sendSync(); + ASSERT_NO_FATAL_FAILURE(TestFixture::mTestListener->assertNotifyMotionWasCalled( + AllOf(WithMotionAction(AMOTION_EVENT_ACTION_DOWN), + WithToolType(ToolType::STYLUS), WithButtonState(0), + WithDeviceId(touchscreenId)))); + + // Press and release a stylus button. Each change only generates a MOVE motion event. + // Key events are unaffected. + TestFixture::mStylus->pressKey(BTN_STYLUS); + ASSERT_NO_FATAL_FAILURE(TestFixture::mTestListener->assertNotifyKeyWasCalled( + AllOf(WithKeyAction(AKEY_EVENT_ACTION_DOWN), WithSource(AINPUT_SOURCE_KEYBOARD), + WithKeyCode(AKEYCODE_STYLUS_BUTTON_PRIMARY), WithDeviceId(stylusId)))); + ASSERT_NO_FATAL_FAILURE(TestFixture::mTestListener->assertNotifyMotionWasCalled( + AllOf(WithMotionAction(AMOTION_EVENT_ACTION_MOVE), + WithToolType(ToolType::STYLUS), WithButtonState(0), + WithDeviceId(touchscreenId)))); + + TestFixture::mStylus->releaseKey(BTN_STYLUS); + ASSERT_NO_FATAL_FAILURE(TestFixture::mTestListener->assertNotifyKeyWasCalled( + AllOf(WithKeyAction(AKEY_EVENT_ACTION_UP), WithSource(AINPUT_SOURCE_KEYBOARD), + WithKeyCode(AKEYCODE_STYLUS_BUTTON_PRIMARY), WithDeviceId(stylusId)))); + ASSERT_NO_FATAL_FAILURE(TestFixture::mTestListener->assertNotifyMotionWasCalled( + AllOf(WithMotionAction(AMOTION_EVENT_ACTION_MOVE), + WithToolType(ToolType::STYLUS), WithButtonState(0), + WithDeviceId(touchscreenId)))); + + // Finish the stylus gesture. + TestFixture::mTouchscreen->sendTrackingId(INVALID_TRACKING_ID); + TestFixture::mTouchscreen->sendSync(); + ASSERT_NO_FATAL_FAILURE(TestFixture::mTestListener->assertNotifyMotionWasCalled( + AllOf(WithMotionAction(AMOTION_EVENT_ACTION_UP), + WithToolType(ToolType::STYLUS), WithButtonState(0), + WithDeviceId(touchscreenId)))); +} + +// --- ExternalStylusIntegrationTest --- + +// Verify the behavior of an external stylus. An external stylus can report pressure or button +// data independently of the touchscreen, which is then sent as a MotionEvent as part of an +// ongoing stylus gesture that is being emitted by the touchscreen. +using ExternalStylusIntegrationTest = TouchIntegrationTest; + +TEST_F(ExternalStylusIntegrationTest, DISABLED_FusedExternalStylusPressureReported) { + const Point centerPoint = mDevice->getCenterPoint(); -TEST_F(InputReaderTest, DeviceReset_RandomId) { - constexpr int32_t deviceId = END_RESERVED_ID + 1000; - constexpr ftl::Flags deviceClass = InputDeviceClass::KEYBOARD; - constexpr int32_t eventHubId = 1; - std::shared_ptr device = mReader->newDevice(deviceId, "fake"); - // Must add at least one mapper or the device will be ignored! - device->addMapper(eventHubId, AINPUT_SOURCE_KEYBOARD); - mReader->pushNextDevice(device); - ASSERT_NO_FATAL_FAILURE(addDevice(eventHubId, "fake", deviceClass, nullptr)); + // Create an external stylus capable of reporting pressure data that + // should be fused with a touch pointer. + std::unique_ptr stylus = + createUinputDevice(); + ASSERT_NO_FATAL_FAILURE(mFakePolicy->assertInputDevicesChanged()); + ASSERT_NO_FATAL_FAILURE(mTestListener->assertNotifyConfigurationChangedWasCalled()); + const auto stylusInfo = findDeviceByName(stylus->getName()); + ASSERT_TRUE(stylusInfo); - NotifyDeviceResetArgs resetArgs; - ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyDeviceResetWasCalled(&resetArgs)); - int32_t prevId = resetArgs.id; + ASSERT_EQ(AINPUT_SOURCE_STYLUS | AINPUT_SOURCE_KEYBOARD, stylusInfo->getSources()); - disableDevice(deviceId); - mReader->loopOnce(); - ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyDeviceResetWasCalled(&resetArgs)); - ASSERT_NE(prevId, resetArgs.id); - prevId = resetArgs.id; + const auto touchscreenId = mDeviceInfo.getId(); - enableDevice(deviceId); - mReader->loopOnce(); - ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyDeviceResetWasCalled(&resetArgs)); - ASSERT_NE(prevId, resetArgs.id); - prevId = resetArgs.id; + // Set a pressure value on the stylus. It doesn't generate any events. + const auto& RAW_PRESSURE_MAX = UinputExternalStylusWithPressure::RAW_PRESSURE_MAX; + stylus->setPressure(100); + ASSERT_NO_FATAL_FAILURE(mTestListener->assertNotifyMotionWasNotCalled()); - disableDevice(deviceId); - mReader->loopOnce(); - ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyDeviceResetWasCalled(&resetArgs)); - ASSERT_NE(prevId, resetArgs.id); - prevId = resetArgs.id; -} + // Start a finger gesture, and ensure it shows up as stylus gesture + // with the pressure set by the external stylus. + mDevice->sendSlot(FIRST_SLOT); + mDevice->sendTrackingId(FIRST_TRACKING_ID); + mDevice->sendToolType(MT_TOOL_FINGER); + mDevice->sendDown(centerPoint); + mDevice->sendSync(); + ASSERT_NO_FATAL_FAILURE(mTestListener->assertNotifyMotionWasCalled( + AllOf(WithMotionAction(AMOTION_EVENT_ACTION_DOWN), + WithToolType(ToolType::STYLUS), WithButtonState(0), + WithDeviceId(touchscreenId), WithPressure(100.f / RAW_PRESSURE_MAX)))); -TEST_F(InputReaderTest, DeviceReset_GenerateIdWithInputReaderSource) { - constexpr int32_t deviceId = 1; - constexpr ftl::Flags deviceClass = InputDeviceClass::KEYBOARD; - constexpr int32_t eventHubId = 1; - std::shared_ptr device = mReader->newDevice(deviceId, "fake"); - // Must add at least one mapper or the device will be ignored! - device->addMapper(eventHubId, AINPUT_SOURCE_KEYBOARD); - mReader->pushNextDevice(device); - ASSERT_NO_FATAL_FAILURE(addDevice(deviceId, "fake", deviceClass, nullptr)); + // Change the pressure on the external stylus, and ensure the touchscreen generates a MOVE + // event with the updated pressure. + stylus->setPressure(200); + ASSERT_NO_FATAL_FAILURE(mTestListener->assertNotifyMotionWasCalled( + AllOf(WithMotionAction(AMOTION_EVENT_ACTION_MOVE), + WithToolType(ToolType::STYLUS), WithButtonState(0), + WithDeviceId(touchscreenId), WithPressure(200.f / RAW_PRESSURE_MAX)))); - NotifyDeviceResetArgs resetArgs; - ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyDeviceResetWasCalled(&resetArgs)); - ASSERT_EQ(IdGenerator::Source::INPUT_READER, IdGenerator::getSource(resetArgs.id)); + // The external stylus did not generate any events. + ASSERT_NO_FATAL_FAILURE(mTestListener->assertNotifyMotionWasNotCalled()); + ASSERT_NO_FATAL_FAILURE(mTestListener->assertNotifyKeyWasNotCalled()); } -TEST_F(InputReaderTest, Device_CanDispatchToDisplay) { - constexpr int32_t deviceId = END_RESERVED_ID + 1000; - constexpr ftl::Flags deviceClass = InputDeviceClass::KEYBOARD; - constexpr int32_t eventHubId = 1; - const char* DEVICE_LOCATION = "USB1"; - std::shared_ptr device = mReader->newDevice(deviceId, "fake", DEVICE_LOCATION); - FakeInputMapper& mapper = - device->addMapper(eventHubId, AINPUT_SOURCE_TOUCHSCREEN); - mReader->pushNextDevice(device); +TEST_F(ExternalStylusIntegrationTest, DISABLED_FusedExternalStylusPressureNotReported) { + const Point centerPoint = mDevice->getCenterPoint(); - const uint8_t hdmi1 = 1; + // Create an external stylus capable of reporting pressure data that + // should be fused with a touch pointer. + std::unique_ptr stylus = + createUinputDevice(); + ASSERT_NO_FATAL_FAILURE(mFakePolicy->assertInputDevicesChanged()); + ASSERT_NO_FATAL_FAILURE(mTestListener->assertNotifyConfigurationChangedWasCalled()); + const auto stylusInfo = findDeviceByName(stylus->getName()); + ASSERT_TRUE(stylusInfo); - // Associated touch screen with second display. - mFakePolicy->addInputPortAssociation(DEVICE_LOCATION, hdmi1); + ASSERT_EQ(AINPUT_SOURCE_STYLUS | AINPUT_SOURCE_KEYBOARD, stylusInfo->getSources()); - // Add default and second display. - mFakePolicy->clearViewports(); - mFakePolicy->addDisplayViewport(DISPLAY_ID, DISPLAY_WIDTH, DISPLAY_HEIGHT, - DISPLAY_ORIENTATION_0, true /*isActive*/, "local:0", NO_PORT, - ViewportType::INTERNAL); - mFakePolicy->addDisplayViewport(SECONDARY_DISPLAY_ID, DISPLAY_WIDTH, DISPLAY_HEIGHT, - DISPLAY_ORIENTATION_0, true /*isActive*/, "local:1", hdmi1, - ViewportType::EXTERNAL); - mReader->requestRefreshConfiguration(InputReaderConfiguration::CHANGE_DISPLAY_INFO); - mReader->loopOnce(); + const auto touchscreenId = mDeviceInfo.getId(); - // Add the device, and make sure all of the callbacks are triggered. - // The device is added after the input port associations are processed since - // we do not yet support dynamic device-to-display associations. - ASSERT_NO_FATAL_FAILURE(addDevice(eventHubId, "fake", deviceClass, nullptr)); - ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyConfigurationChangedWasCalled()); - ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyDeviceResetWasCalled()); - ASSERT_NO_FATAL_FAILURE(mapper.assertConfigureWasCalled()); + // Set a pressure value of 0 on the stylus. It doesn't generate any events. + const auto& RAW_PRESSURE_MAX = UinputExternalStylusWithPressure::RAW_PRESSURE_MAX; + // Send a non-zero value first to prevent the kernel from consuming the zero event. + stylus->setPressure(100); + stylus->setPressure(0); + ASSERT_NO_FATAL_FAILURE(mTestListener->assertNotifyMotionWasNotCalled()); - // Device should only dispatch to the specified display. - ASSERT_EQ(deviceId, device->getId()); - ASSERT_FALSE(mReader->canDispatchToDisplay(deviceId, DISPLAY_ID)); - ASSERT_TRUE(mReader->canDispatchToDisplay(deviceId, SECONDARY_DISPLAY_ID)); + // Start a finger gesture. The touch device will withhold generating any touches for + // up to 72 milliseconds while waiting for pressure data from the external stylus. + mDevice->sendSlot(FIRST_SLOT); + mDevice->sendTrackingId(FIRST_TRACKING_ID); + mDevice->sendToolType(MT_TOOL_FINGER); + mDevice->sendDown(centerPoint); + auto waitUntil = std::chrono::system_clock::now() + + std::chrono::milliseconds(ns2ms(EXTERNAL_STYLUS_DATA_TIMEOUT)); + mDevice->sendSync(); + ASSERT_NO_FATAL_FAILURE(mTestListener->assertNotifyMotionWasNotCalled(waitUntil)); + + // Since the external stylus did not report a pressure value within the timeout, + // it shows up as a finger pointer. + ASSERT_NO_FATAL_FAILURE(mTestListener->assertNotifyMotionWasCalled( + AllOf(WithMotionAction(AMOTION_EVENT_ACTION_DOWN), + WithToolType(ToolType::FINGER), WithDeviceId(touchscreenId), + WithPressure(1.f)))); + + // Change the pressure on the external stylus. Since the pressure was not present at the start + // of the gesture, it is ignored for now. + stylus->setPressure(200); + ASSERT_NO_FATAL_FAILURE(mTestListener->assertNotifyMotionWasNotCalled()); + + // Finish the finger gesture. + mDevice->sendTrackingId(INVALID_TRACKING_ID); + mDevice->sendSync(); + ASSERT_NO_FATAL_FAILURE(mTestListener->assertNotifyMotionWasCalled( + AllOf(WithMotionAction(AMOTION_EVENT_ACTION_UP), + WithToolType(ToolType::FINGER)))); - // Can't dispatch event from a disabled device. - disableDevice(deviceId); - mReader->loopOnce(); - ASSERT_FALSE(mReader->canDispatchToDisplay(deviceId, SECONDARY_DISPLAY_ID)); -} + // Start a new gesture. Since we have a valid pressure value, it shows up as a stylus. + mDevice->sendTrackingId(FIRST_TRACKING_ID); + mDevice->sendToolType(MT_TOOL_FINGER); + mDevice->sendDown(centerPoint); + mDevice->sendSync(); + ASSERT_NO_FATAL_FAILURE(mTestListener->assertNotifyMotionWasCalled( + AllOf(WithMotionAction(AMOTION_EVENT_ACTION_DOWN), + WithToolType(ToolType::STYLUS), WithButtonState(0), + WithDeviceId(touchscreenId), WithPressure(200.f / RAW_PRESSURE_MAX)))); -TEST_F(InputReaderTest, WhenEnabledChanges_AllSubdevicesAreUpdated) { - constexpr int32_t deviceId = END_RESERVED_ID + 1000; - constexpr ftl::Flags deviceClass = InputDeviceClass::KEYBOARD; - constexpr int32_t eventHubIds[2] = {END_RESERVED_ID, END_RESERVED_ID + 1}; - std::shared_ptr device = mReader->newDevice(deviceId, "fake"); - // Must add at least one mapper or the device will be ignored! - device->addMapper(eventHubIds[0], AINPUT_SOURCE_KEYBOARD); - device->addMapper(eventHubIds[1], AINPUT_SOURCE_KEYBOARD); - mReader->pushNextDevice(device); - mReader->pushNextDevice(device); - ASSERT_NO_FATAL_FAILURE(addDevice(eventHubIds[0], "fake1", deviceClass, nullptr)); - ASSERT_NO_FATAL_FAILURE(addDevice(eventHubIds[1], "fake2", deviceClass, nullptr)); + // The external stylus did not generate any events. + ASSERT_NO_FATAL_FAILURE(mTestListener->assertNotifyMotionWasNotCalled()); + ASSERT_NO_FATAL_FAILURE(mTestListener->assertNotifyKeyWasNotCalled()); +} - ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyConfigurationChangedWasCalled(nullptr)); +TEST_F(ExternalStylusIntegrationTest, DISABLED_UnfusedExternalStylus) { + const Point centerPoint = mDevice->getCenterPoint(); - NotifyDeviceResetArgs resetArgs; - ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyDeviceResetWasCalled(&resetArgs)); - ASSERT_EQ(deviceId, resetArgs.deviceId); - ASSERT_TRUE(device->isEnabled()); - ASSERT_TRUE(mFakeEventHub->isDeviceEnabled(eventHubIds[0])); - ASSERT_TRUE(mFakeEventHub->isDeviceEnabled(eventHubIds[1])); + // Create an external stylus device that does not support pressure. It should not affect any + // touch pointers. + std::unique_ptr stylus = createUinputDevice(); + ASSERT_NO_FATAL_FAILURE(mFakePolicy->assertInputDevicesChanged()); + ASSERT_NO_FATAL_FAILURE(mTestListener->assertNotifyConfigurationChangedWasCalled()); + const auto stylusInfo = findDeviceByName(stylus->getName()); + ASSERT_TRUE(stylusInfo); - disableDevice(deviceId); - mReader->loopOnce(); + ASSERT_EQ(AINPUT_SOURCE_STYLUS | AINPUT_SOURCE_KEYBOARD, stylusInfo->getSources()); - ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyDeviceResetWasCalled(&resetArgs)); - ASSERT_EQ(deviceId, resetArgs.deviceId); - ASSERT_FALSE(device->isEnabled()); - ASSERT_FALSE(mFakeEventHub->isDeviceEnabled(eventHubIds[0])); - ASSERT_FALSE(mFakeEventHub->isDeviceEnabled(eventHubIds[1])); + const auto touchscreenId = mDeviceInfo.getId(); - enableDevice(deviceId); - mReader->loopOnce(); + // Start a finger gesture and ensure a finger pointer is generated for it, without waiting for + // pressure data from the external stylus. + mDevice->sendSlot(FIRST_SLOT); + mDevice->sendTrackingId(FIRST_TRACKING_ID); + mDevice->sendToolType(MT_TOOL_FINGER); + mDevice->sendDown(centerPoint); + auto waitUntil = std::chrono::system_clock::now() + + std::chrono::milliseconds(ns2ms(EXTERNAL_STYLUS_DATA_TIMEOUT)); + mDevice->sendSync(); + ASSERT_NO_FATAL_FAILURE( + mTestListener + ->assertNotifyMotionWasCalled(AllOf(WithMotionAction(AMOTION_EVENT_ACTION_DOWN), + WithToolType( + ToolType::FINGER), + WithButtonState(0), + WithDeviceId(touchscreenId), + WithPressure(1.f)), + waitUntil)); - ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyDeviceResetWasCalled(&resetArgs)); - ASSERT_EQ(deviceId, resetArgs.deviceId); - ASSERT_TRUE(device->isEnabled()); - ASSERT_TRUE(mFakeEventHub->isDeviceEnabled(eventHubIds[0])); - ASSERT_TRUE(mFakeEventHub->isDeviceEnabled(eventHubIds[1])); + // The external stylus did not generate any events. + ASSERT_NO_FATAL_FAILURE(mTestListener->assertNotifyMotionWasNotCalled()); + ASSERT_NO_FATAL_FAILURE(mTestListener->assertNotifyKeyWasNotCalled()); } -TEST_F(InputReaderTest, GetKeyCodeState_ForwardsRequestsToSubdeviceMappers) { - constexpr int32_t deviceId = END_RESERVED_ID + 1000; - constexpr ftl::Flags deviceClass = InputDeviceClass::KEYBOARD; - constexpr int32_t eventHubIds[2] = {END_RESERVED_ID, END_RESERVED_ID + 1}; - // Add two subdevices to device - std::shared_ptr device = mReader->newDevice(deviceId, "fake"); - FakeInputMapper& mapperDevice1 = - device->addMapper(eventHubIds[0], AINPUT_SOURCE_KEYBOARD); - FakeInputMapper& mapperDevice2 = - device->addMapper(eventHubIds[1], AINPUT_SOURCE_KEYBOARD); - mReader->pushNextDevice(device); - mReader->pushNextDevice(device); - ASSERT_NO_FATAL_FAILURE(addDevice(eventHubIds[0], "fake1", deviceClass, nullptr)); - ASSERT_NO_FATAL_FAILURE(addDevice(eventHubIds[1], "fake2", deviceClass, nullptr)); +// --- InputDeviceTest --- +class InputDeviceTest : public testing::Test { +protected: + static const char* DEVICE_NAME; + static const char* DEVICE_LOCATION; + static const int32_t DEVICE_ID; + static const int32_t DEVICE_GENERATION; + static const int32_t DEVICE_CONTROLLER_NUMBER; + static const ftl::Flags DEVICE_CLASSES; + static const int32_t EVENTHUB_ID; + static const std::string DEVICE_BLUETOOTH_ADDRESS; - mapperDevice1.setKeyCodeState(AKEYCODE_A, AKEY_STATE_DOWN); - mapperDevice2.setKeyCodeState(AKEYCODE_B, AKEY_STATE_DOWN); + std::shared_ptr mFakeEventHub; + sp mFakePolicy; + std::unique_ptr mFakeListener; + std::unique_ptr mReader; + std::shared_ptr mDevice; - ASSERT_EQ(AKEY_STATE_DOWN, - mReader->getKeyCodeState(deviceId, AINPUT_SOURCE_KEYBOARD, AKEYCODE_A)); - ASSERT_EQ(AKEY_STATE_DOWN, - mReader->getKeyCodeState(deviceId, AINPUT_SOURCE_KEYBOARD, AKEYCODE_B)); - ASSERT_EQ(AKEY_STATE_UNKNOWN, - mReader->getKeyCodeState(deviceId, AINPUT_SOURCE_KEYBOARD, AKEYCODE_C)); -} + void SetUp() override { + mFakeEventHub = std::make_unique(); + mFakePolicy = sp::make(); + mFakeListener = std::make_unique(); + mReader = std::make_unique(mFakeEventHub, mFakePolicy, + *mFakeListener); + InputDeviceIdentifier identifier; + identifier.name = DEVICE_NAME; + identifier.location = DEVICE_LOCATION; + identifier.bluetoothAddress = DEVICE_BLUETOOTH_ADDRESS; + mDevice = std::make_shared(mReader->getContext(), DEVICE_ID, DEVICE_GENERATION, + identifier); + mReader->pushNextDevice(mDevice); + mFakeEventHub->addDevice(EVENTHUB_ID, DEVICE_NAME, ftl::Flags(0)); + mReader->loopOnce(); + } -TEST_F(InputReaderTest, ChangingPointerCaptureNotifiesInputListener) { - NotifyPointerCaptureChangedArgs args; + void TearDown() override { + mFakeListener.reset(); + mFakePolicy.clear(); + } +}; - auto request = mFakePolicy->setPointerCapture(true); - mReader->requestRefreshConfiguration(InputReaderConfiguration::CHANGE_POINTER_CAPTURE); - mReader->loopOnce(); - mFakeListener->assertNotifyCaptureWasCalled(&args); - ASSERT_TRUE(args.request.enable) << "Pointer Capture should be enabled."; - ASSERT_EQ(args.request, request) << "Pointer Capture sequence number should match."; +const char* InputDeviceTest::DEVICE_NAME = "device"; +const char* InputDeviceTest::DEVICE_LOCATION = "USB1"; +const int32_t InputDeviceTest::DEVICE_ID = END_RESERVED_ID + 1000; +const int32_t InputDeviceTest::DEVICE_GENERATION = 2; +const int32_t InputDeviceTest::DEVICE_CONTROLLER_NUMBER = 0; +const ftl::Flags InputDeviceTest::DEVICE_CLASSES = + InputDeviceClass::KEYBOARD | InputDeviceClass::TOUCH | InputDeviceClass::JOYSTICK; +const int32_t InputDeviceTest::EVENTHUB_ID = 1; +const std::string InputDeviceTest::DEVICE_BLUETOOTH_ADDRESS = "11:AA:22:BB:33:CC"; - mFakePolicy->setPointerCapture(false); - mReader->requestRefreshConfiguration(InputReaderConfiguration::CHANGE_POINTER_CAPTURE); - mReader->loopOnce(); - mFakeListener->assertNotifyCaptureWasCalled(&args); - ASSERT_FALSE(args.request.enable) << "Pointer Capture should be disabled."; +TEST_F(InputDeviceTest, ImmutableProperties) { + ASSERT_EQ(DEVICE_ID, mDevice->getId()); + ASSERT_STREQ(DEVICE_NAME, mDevice->getName().c_str()); + ASSERT_EQ(ftl::Flags(0), mDevice->getClasses()); +} - // Verify that the Pointer Capture state is not updated when the configuration value - // does not change. - mReader->requestRefreshConfiguration(InputReaderConfiguration::CHANGE_POINTER_CAPTURE); - mReader->loopOnce(); - mFakeListener->assertNotifyCaptureWasNotCalled(); +TEST_F(InputDeviceTest, WhenDeviceCreated_EnabledIsFalse) { + ASSERT_EQ(mDevice->isEnabled(), false); } -class FakeVibratorInputMapper : public FakeInputMapper { -public: - FakeVibratorInputMapper(InputDeviceContext& deviceContext, uint32_t sources) - : FakeInputMapper(deviceContext, sources) {} +TEST_F(InputDeviceTest, WhenNoMappersAreRegistered_DeviceIsIgnored) { + // Configuration. + InputReaderConfiguration config; + std::list unused = mDevice->configure(ARBITRARY_TIME, config, /*changes=*/{}); - std::vector getVibratorIds() override { return getDeviceContext().getVibratorIds(); } -}; + // Reset. + unused += mDevice->reset(ARBITRARY_TIME); -TEST_F(InputReaderTest, VibratorGetVibratorIds) { - constexpr int32_t deviceId = END_RESERVED_ID + 1000; - ftl::Flags deviceClass = - InputDeviceClass::KEYBOARD | InputDeviceClass::VIBRATOR; - constexpr int32_t eventHubId = 1; - const char* DEVICE_LOCATION = "BLUETOOTH"; - std::shared_ptr device = mReader->newDevice(deviceId, "fake", DEVICE_LOCATION); - FakeVibratorInputMapper& mapper = - device->addMapper(eventHubId, AINPUT_SOURCE_KEYBOARD); - mReader->pushNextDevice(device); + NotifyDeviceResetArgs resetArgs; + ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyDeviceResetWasCalled(&resetArgs)); + ASSERT_EQ(ARBITRARY_TIME, resetArgs.eventTime); + ASSERT_EQ(DEVICE_ID, resetArgs.deviceId); - ASSERT_NO_FATAL_FAILURE(addDevice(eventHubId, "fake", deviceClass, nullptr)); - ASSERT_NO_FATAL_FAILURE(mapper.assertConfigureWasCalled()); + // Metadata. + ASSERT_TRUE(mDevice->isIgnored()); + ASSERT_EQ(AINPUT_SOURCE_UNKNOWN, mDevice->getSources()); - ASSERT_EQ(mapper.getVibratorIds().size(), 2U); - ASSERT_EQ(mReader->getVibratorIds(deviceId).size(), 2U); -} + InputDeviceInfo info = mDevice->getDeviceInfo(); + ASSERT_EQ(DEVICE_ID, info.getId()); + ASSERT_STREQ(DEVICE_NAME, info.getIdentifier().name.c_str()); + ASSERT_EQ(AINPUT_KEYBOARD_TYPE_NONE, info.getKeyboardType()); + ASSERT_EQ(AINPUT_SOURCE_UNKNOWN, info.getSources()); -// --- FakePeripheralController --- + // State queries. + ASSERT_EQ(0, mDevice->getMetaState()); -class FakePeripheralController : public PeripheralControllerInterface { -public: - FakePeripheralController(InputDeviceContext& deviceContext) : mDeviceContext(deviceContext) {} + ASSERT_EQ(AKEY_STATE_UNKNOWN, mDevice->getKeyCodeState(AINPUT_SOURCE_KEYBOARD, 0)) + << "Ignored device should return unknown key code state."; + ASSERT_EQ(AKEY_STATE_UNKNOWN, mDevice->getScanCodeState(AINPUT_SOURCE_KEYBOARD, 0)) + << "Ignored device should return unknown scan code state."; + ASSERT_EQ(AKEY_STATE_UNKNOWN, mDevice->getSwitchState(AINPUT_SOURCE_KEYBOARD, 0)) + << "Ignored device should return unknown switch state."; - ~FakePeripheralController() override {} + const std::vector keyCodes{AKEYCODE_A, AKEYCODE_B}; + uint8_t flags[2] = { 0, 1 }; + ASSERT_FALSE(mDevice->markSupportedKeyCodes(AINPUT_SOURCE_KEYBOARD, keyCodes, flags)) + << "Ignored device should never mark any key codes."; + ASSERT_EQ(0, flags[0]) << "Flag for unsupported key should be unchanged."; + ASSERT_EQ(1, flags[1]) << "Flag for unsupported key should be unchanged."; +} - int32_t getEventHubId() const { return getDeviceContext().getEventHubId(); } +TEST_F(InputDeviceTest, WhenMappersAreRegistered_DeviceIsNotIgnoredAndForwardsRequestsToMappers) { + // Configuration. + mFakeEventHub->addConfigurationProperty(EVENTHUB_ID, "key", "value"); - void populateDeviceInfo(InputDeviceInfo* deviceInfo) override {} + FakeInputMapper& mapper1 = + mDevice->addMapper(EVENTHUB_ID, mFakePolicy->getReaderConfiguration(), + AINPUT_SOURCE_KEYBOARD); + mapper1.setKeyboardType(AINPUT_KEYBOARD_TYPE_ALPHABETIC); + mapper1.setMetaState(AMETA_ALT_ON); + mapper1.addSupportedKeyCode(AKEYCODE_A); + mapper1.addSupportedKeyCode(AKEYCODE_B); + mapper1.setKeyCodeState(AKEYCODE_A, AKEY_STATE_DOWN); + mapper1.setKeyCodeState(AKEYCODE_B, AKEY_STATE_UP); + mapper1.setScanCodeState(2, AKEY_STATE_DOWN); + mapper1.setScanCodeState(3, AKEY_STATE_UP); + mapper1.setSwitchState(4, AKEY_STATE_DOWN); - void dump(std::string& dump) override {} + FakeInputMapper& mapper2 = + mDevice->addMapper(EVENTHUB_ID, mFakePolicy->getReaderConfiguration(), + AINPUT_SOURCE_TOUCHSCREEN); + mapper2.setMetaState(AMETA_SHIFT_ON); - std::optional getBatteryCapacity(int32_t batteryId) override { - return getDeviceContext().getBatteryCapacity(batteryId); - } + InputReaderConfiguration config; + std::list unused = mDevice->configure(ARBITRARY_TIME, config, /*changes=*/{}); - std::optional getBatteryStatus(int32_t batteryId) override { - return getDeviceContext().getBatteryStatus(batteryId); - } + std::optional propertyValue = mDevice->getConfiguration().getString("key"); + ASSERT_TRUE(propertyValue.has_value()) + << "Device should have read configuration during configuration phase."; + ASSERT_EQ("value", *propertyValue); - bool setLightColor(int32_t lightId, int32_t color) override { - getDeviceContext().setLightBrightness(lightId, color >> 24); - return true; - } + ASSERT_NO_FATAL_FAILURE(mapper1.assertConfigureWasCalled()); + ASSERT_NO_FATAL_FAILURE(mapper2.assertConfigureWasCalled()); - std::optional getLightColor(int32_t lightId) override { - std::optional result = getDeviceContext().getLightBrightness(lightId); - if (!result.has_value()) { - return std::nullopt; - } - return result.value() << 24; - } + // Reset + unused += mDevice->reset(ARBITRARY_TIME); + ASSERT_NO_FATAL_FAILURE(mapper1.assertResetWasCalled()); + ASSERT_NO_FATAL_FAILURE(mapper2.assertResetWasCalled()); - bool setLightPlayerId(int32_t lightId, int32_t playerId) override { return true; } + NotifyDeviceResetArgs resetArgs; + ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyDeviceResetWasCalled(&resetArgs)); + ASSERT_EQ(ARBITRARY_TIME, resetArgs.eventTime); + ASSERT_EQ(DEVICE_ID, resetArgs.deviceId); - std::optional getLightPlayerId(int32_t lightId) override { return std::nullopt; } + // Metadata. + ASSERT_FALSE(mDevice->isIgnored()); + ASSERT_EQ(uint32_t(AINPUT_SOURCE_KEYBOARD | AINPUT_SOURCE_TOUCHSCREEN), mDevice->getSources()); -private: - InputDeviceContext& mDeviceContext; - inline int32_t getDeviceId() { return mDeviceContext.getId(); } - inline InputDeviceContext& getDeviceContext() { return mDeviceContext; } - inline InputDeviceContext& getDeviceContext() const { return mDeviceContext; } -}; + InputDeviceInfo info = mDevice->getDeviceInfo(); + ASSERT_EQ(DEVICE_ID, info.getId()); + ASSERT_STREQ(DEVICE_NAME, info.getIdentifier().name.c_str()); + ASSERT_EQ(AINPUT_KEYBOARD_TYPE_ALPHABETIC, info.getKeyboardType()); + ASSERT_EQ(uint32_t(AINPUT_SOURCE_KEYBOARD | AINPUT_SOURCE_TOUCHSCREEN), info.getSources()); -TEST_F(InputReaderTest, BatteryGetCapacity) { - constexpr int32_t deviceId = END_RESERVED_ID + 1000; - ftl::Flags deviceClass = - InputDeviceClass::KEYBOARD | InputDeviceClass::BATTERY; - constexpr int32_t eventHubId = 1; - const char* DEVICE_LOCATION = "BLUETOOTH"; - std::shared_ptr device = mReader->newDevice(deviceId, "fake", DEVICE_LOCATION); - FakePeripheralController& controller = - device->addController(eventHubId); - mReader->pushNextDevice(device); - - ASSERT_NO_FATAL_FAILURE(addDevice(eventHubId, "fake", deviceClass, nullptr)); - - ASSERT_EQ(controller.getBatteryCapacity(DEFAULT_BATTERY), BATTERY_CAPACITY); - ASSERT_EQ(mReader->getBatteryCapacity(deviceId), BATTERY_CAPACITY); -} + // State queries. + ASSERT_EQ(AMETA_ALT_ON | AMETA_SHIFT_ON, mDevice->getMetaState()) + << "Should query mappers and combine meta states."; -TEST_F(InputReaderTest, BatteryGetStatus) { - constexpr int32_t deviceId = END_RESERVED_ID + 1000; - ftl::Flags deviceClass = - InputDeviceClass::KEYBOARD | InputDeviceClass::BATTERY; - constexpr int32_t eventHubId = 1; - const char* DEVICE_LOCATION = "BLUETOOTH"; - std::shared_ptr device = mReader->newDevice(deviceId, "fake", DEVICE_LOCATION); - FakePeripheralController& controller = - device->addController(eventHubId); - mReader->pushNextDevice(device); + ASSERT_EQ(AKEY_STATE_UNKNOWN, mDevice->getKeyCodeState(AINPUT_SOURCE_TRACKBALL, AKEYCODE_A)) + << "Should return unknown key code state when source not supported."; + ASSERT_EQ(AKEY_STATE_UNKNOWN, mDevice->getScanCodeState(AINPUT_SOURCE_TRACKBALL, AKEYCODE_A)) + << "Should return unknown scan code state when source not supported."; + ASSERT_EQ(AKEY_STATE_UNKNOWN, mDevice->getSwitchState(AINPUT_SOURCE_TRACKBALL, AKEYCODE_A)) + << "Should return unknown switch state when source not supported."; - ASSERT_NO_FATAL_FAILURE(addDevice(eventHubId, "fake", deviceClass, nullptr)); + ASSERT_EQ(AKEY_STATE_DOWN, mDevice->getKeyCodeState(AINPUT_SOURCE_KEYBOARD, AKEYCODE_A)) + << "Should query mapper when source is supported."; + ASSERT_EQ(AKEY_STATE_UP, mDevice->getScanCodeState(AINPUT_SOURCE_KEYBOARD, 3)) + << "Should query mapper when source is supported."; + ASSERT_EQ(AKEY_STATE_DOWN, mDevice->getSwitchState(AINPUT_SOURCE_KEYBOARD, 4)) + << "Should query mapper when source is supported."; - ASSERT_EQ(controller.getBatteryStatus(DEFAULT_BATTERY), BATTERY_STATUS); - ASSERT_EQ(mReader->getBatteryStatus(deviceId), BATTERY_STATUS); -} + const std::vector keyCodes{AKEYCODE_A, AKEYCODE_B, AKEYCODE_1, AKEYCODE_2}; + uint8_t flags[4] = { 0, 0, 0, 1 }; + ASSERT_FALSE(mDevice->markSupportedKeyCodes(AINPUT_SOURCE_TRACKBALL, keyCodes, flags)) + << "Should do nothing when source is unsupported."; + ASSERT_EQ(0, flags[0]) << "Flag should be unchanged when source is unsupported."; + ASSERT_EQ(0, flags[1]) << "Flag should be unchanged when source is unsupported."; + ASSERT_EQ(0, flags[2]) << "Flag should be unchanged when source is unsupported."; + ASSERT_EQ(1, flags[3]) << "Flag should be unchanged when source is unsupported."; -TEST_F(InputReaderTest, LightGetColor) { - constexpr int32_t deviceId = END_RESERVED_ID + 1000; - ftl::Flags deviceClass = InputDeviceClass::KEYBOARD | InputDeviceClass::LIGHT; - constexpr int32_t eventHubId = 1; - const char* DEVICE_LOCATION = "BLUETOOTH"; - std::shared_ptr device = mReader->newDevice(deviceId, "fake", DEVICE_LOCATION); - FakePeripheralController& controller = - device->addController(eventHubId); - mReader->pushNextDevice(device); - RawLightInfo info = {.id = 1, - .name = "Mono", - .maxBrightness = 255, - .flags = InputLightClass::BRIGHTNESS, - .path = ""}; - mFakeEventHub->addRawLightInfo(1 /* rawId */, std::move(info)); - mFakeEventHub->fakeLightBrightness(1 /* rawId */, 0x55); + ASSERT_TRUE(mDevice->markSupportedKeyCodes(AINPUT_SOURCE_KEYBOARD, keyCodes, flags)) + << "Should query mapper when source is supported."; + ASSERT_EQ(1, flags[0]) << "Flag for supported key should be set."; + ASSERT_EQ(1, flags[1]) << "Flag for supported key should be set."; + ASSERT_EQ(0, flags[2]) << "Flag for unsupported key should be unchanged."; + ASSERT_EQ(1, flags[3]) << "Flag for unsupported key should be unchanged."; - ASSERT_NO_FATAL_FAILURE(addDevice(eventHubId, "fake", deviceClass, nullptr)); + // Event handling. + RawEvent event; + event.deviceId = EVENTHUB_ID; + unused += mDevice->process(&event, 1); - ASSERT_TRUE(controller.setLightColor(1 /* lightId */, LIGHT_BRIGHTNESS)); - ASSERT_EQ(controller.getLightColor(1 /* lightId */), LIGHT_BRIGHTNESS); - ASSERT_TRUE(mReader->setLightColor(deviceId, 1 /* lightId */, LIGHT_BRIGHTNESS)); - ASSERT_EQ(mReader->getLightColor(deviceId, 1 /* lightId */), LIGHT_BRIGHTNESS); + ASSERT_NO_FATAL_FAILURE(mapper1.assertProcessWasCalled()); + ASSERT_NO_FATAL_FAILURE(mapper2.assertProcessWasCalled()); } -// --- InputReaderIntegrationTest --- +// A single input device is associated with a specific display. Check that: +// 1. Device is disabled if the viewport corresponding to the associated display is not found +// 2. Device is disabled when setEnabled API is called +TEST_F(InputDeviceTest, Configure_AssignsDisplayPort) { + mDevice->addMapper(EVENTHUB_ID, mFakePolicy->getReaderConfiguration(), + AINPUT_SOURCE_TOUCHSCREEN); -// These tests create and interact with the InputReader only through its interface. -// The InputReader is started during SetUp(), which starts its processing in its own -// thread. The tests use linux uinput to emulate input devices. -// NOTE: Interacting with the physical device while these tests are running may cause -// the tests to fail. -class InputReaderIntegrationTest : public testing::Test { -protected: - std::unique_ptr mTestListener; - sp mFakePolicy; - std::unique_ptr mReader; + // First Configuration. + std::list unused = + mDevice->configure(ARBITRARY_TIME, mFakePolicy->getReaderConfiguration(), + /*changes=*/{}); - std::shared_ptr mFakePointerController; + // Device should be enabled by default. + ASSERT_TRUE(mDevice->isEnabled()); - void SetUp() override { - mFakePolicy = new FakeInputReaderPolicy(); - mFakePointerController = std::make_shared(); - mFakePolicy->setPointerController(mFakePointerController); - mTestListener = std::make_unique(2000ms /*eventHappenedTimeout*/, - 30ms /*eventDidNotHappenTimeout*/); + // Prepare associated info. + constexpr uint8_t hdmi = 1; + const std::string UNIQUE_ID = "local:1"; - mReader = std::make_unique(std::make_shared(), mFakePolicy, - *mTestListener); - ASSERT_EQ(mReader->start(), OK); + mFakePolicy->addInputPortAssociation(DEVICE_LOCATION, hdmi); + unused += mDevice->configure(ARBITRARY_TIME, mFakePolicy->getReaderConfiguration(), + InputReaderConfiguration::Change::DISPLAY_INFO); + // Device should be disabled because it is associated with a specific display via + // input port <-> display port association, but the corresponding display is not found + ASSERT_FALSE(mDevice->isEnabled()); - // Since this test is run on a real device, all the input devices connected - // to the test device will show up in mReader. We wait for those input devices to - // show up before beginning the tests. - ASSERT_NO_FATAL_FAILURE(mFakePolicy->assertInputDevicesChanged()); - ASSERT_NO_FATAL_FAILURE(mTestListener->assertNotifyConfigurationChangedWasCalled()); - } + // Prepare displays. + mFakePolicy->addDisplayViewport(SECONDARY_DISPLAY_ID, DISPLAY_WIDTH, DISPLAY_HEIGHT, + ui::ROTATION_0, /*isActive=*/true, UNIQUE_ID, hdmi, + ViewportType::INTERNAL); + unused += mDevice->configure(ARBITRARY_TIME, mFakePolicy->getReaderConfiguration(), + InputReaderConfiguration::Change::DISPLAY_INFO); + ASSERT_TRUE(mDevice->isEnabled()); - void TearDown() override { - ASSERT_EQ(mReader->stop(), OK); - mReader.reset(); - mTestListener.reset(); - mFakePolicy.clear(); - } -}; + // Device should be disabled after set disable. + mFakePolicy->addDisabledDevice(mDevice->getId()); + unused += mDevice->configure(ARBITRARY_TIME, mFakePolicy->getReaderConfiguration(), + InputReaderConfiguration::Change::ENABLED_STATE); + ASSERT_FALSE(mDevice->isEnabled()); -TEST_F(InputReaderIntegrationTest, TestInvalidDevice) { - // An invalid input device that is only used for this test. - class InvalidUinputDevice : public UinputDevice { - public: - InvalidUinputDevice() : UinputDevice("Invalid Device") {} + // Device should still be disabled even found the associated display. + unused += mDevice->configure(ARBITRARY_TIME, mFakePolicy->getReaderConfiguration(), + InputReaderConfiguration::Change::DISPLAY_INFO); + ASSERT_FALSE(mDevice->isEnabled()); +} - private: - void configureDevice(int fd, uinput_user_dev* device) override {} - }; +TEST_F(InputDeviceTest, Configure_AssignsDisplayUniqueId) { + // Device should be enabled by default. + mFakePolicy->clearViewports(); + mDevice->addMapper(EVENTHUB_ID, mFakePolicy->getReaderConfiguration(), + AINPUT_SOURCE_KEYBOARD); + std::list unused = + mDevice->configure(ARBITRARY_TIME, mFakePolicy->getReaderConfiguration(), + /*changes=*/{}); + ASSERT_TRUE(mDevice->isEnabled()); - const size_t numDevices = mFakePolicy->getInputDevices().size(); + // Device should be disabled because it is associated with a specific display, but the + // corresponding display is not found. + mFakePolicy->addInputUniqueIdAssociation(DEVICE_LOCATION, DISPLAY_UNIQUE_ID); + unused += mDevice->configure(ARBITRARY_TIME, mFakePolicy->getReaderConfiguration(), + InputReaderConfiguration::Change::DISPLAY_INFO); + ASSERT_FALSE(mDevice->isEnabled()); - // UinputDevice does not set any event or key bits, so InputReader should not - // consider it as a valid device. - std::unique_ptr invalidDevice = createUinputDevice(); - ASSERT_NO_FATAL_FAILURE(mFakePolicy->assertInputDevicesNotChanged()); - ASSERT_NO_FATAL_FAILURE(mTestListener->assertNotifyConfigurationChangedWasNotCalled()); - ASSERT_EQ(numDevices, mFakePolicy->getInputDevices().size()); + // Device should be enabled when a display is found. + mFakePolicy->addDisplayViewport(SECONDARY_DISPLAY_ID, DISPLAY_WIDTH, DISPLAY_HEIGHT, + ui::ROTATION_0, /* isActive= */ true, DISPLAY_UNIQUE_ID, + NO_PORT, ViewportType::INTERNAL); + unused += mDevice->configure(ARBITRARY_TIME, mFakePolicy->getReaderConfiguration(), + InputReaderConfiguration::Change::DISPLAY_INFO); + ASSERT_TRUE(mDevice->isEnabled()); - invalidDevice.reset(); - ASSERT_NO_FATAL_FAILURE(mFakePolicy->assertInputDevicesNotChanged()); - ASSERT_NO_FATAL_FAILURE(mTestListener->assertNotifyConfigurationChangedWasNotCalled()); - ASSERT_EQ(numDevices, mFakePolicy->getInputDevices().size()); + // Device should be disabled after set disable. + mFakePolicy->addDisabledDevice(mDevice->getId()); + unused += mDevice->configure(ARBITRARY_TIME, mFakePolicy->getReaderConfiguration(), + InputReaderConfiguration::Change::ENABLED_STATE); + ASSERT_FALSE(mDevice->isEnabled()); + + // Device should still be disabled even found the associated display. + unused += mDevice->configure(ARBITRARY_TIME, mFakePolicy->getReaderConfiguration(), + InputReaderConfiguration::Change::DISPLAY_INFO); + ASSERT_FALSE(mDevice->isEnabled()); } -TEST_F(InputReaderIntegrationTest, AddNewDevice) { - const size_t initialNumDevices = mFakePolicy->getInputDevices().size(); +TEST_F(InputDeviceTest, Configure_UniqueId_CorrectlyMatches) { + mFakePolicy->clearViewports(); + mDevice->addMapper(EVENTHUB_ID, mFakePolicy->getReaderConfiguration(), + AINPUT_SOURCE_KEYBOARD); + std::list unused = + mDevice->configure(ARBITRARY_TIME, mFakePolicy->getReaderConfiguration(), + /*changes=*/{}); - std::unique_ptr keyboard = createUinputDevice(); - ASSERT_NO_FATAL_FAILURE(mFakePolicy->assertInputDevicesChanged()); - ASSERT_NO_FATAL_FAILURE(mTestListener->assertNotifyConfigurationChangedWasCalled()); - ASSERT_EQ(initialNumDevices + 1, mFakePolicy->getInputDevices().size()); + mFakePolicy->addInputUniqueIdAssociation(DEVICE_LOCATION, DISPLAY_UNIQUE_ID); + mFakePolicy->addDisplayViewport(SECONDARY_DISPLAY_ID, DISPLAY_WIDTH, DISPLAY_HEIGHT, + ui::ROTATION_0, /* isActive= */ true, DISPLAY_UNIQUE_ID, + NO_PORT, ViewportType::INTERNAL); + unused += mDevice->configure(ARBITRARY_TIME, mFakePolicy->getReaderConfiguration(), + InputReaderConfiguration::Change::DISPLAY_INFO); + ASSERT_EQ(DISPLAY_UNIQUE_ID, mDevice->getAssociatedDisplayUniqueId()); +} - // Find the test device by its name. - const std::vector inputDevices = mFakePolicy->getInputDevices(); - const auto& it = - std::find_if(inputDevices.begin(), inputDevices.end(), - [&keyboard](const InputDeviceInfo& info) { - return info.getIdentifier().name == keyboard->getName(); - }); +/** + * This test reproduces a crash caused by a dangling reference that remains after device is added + * and removed. The reference is accessed in InputDevice::dump(..); + */ +TEST_F(InputDeviceTest, DumpDoesNotCrash) { + constexpr int32_t TEST_EVENTHUB_ID = 10; + mFakeEventHub->addDevice(TEST_EVENTHUB_ID, "Test EventHub device", InputDeviceClass::BATTERY); - ASSERT_NE(it, inputDevices.end()); - ASSERT_EQ(AINPUT_KEYBOARD_TYPE_NON_ALPHABETIC, it->getKeyboardType()); - ASSERT_EQ(AINPUT_SOURCE_KEYBOARD, it->getSources()); - ASSERT_EQ(0U, it->getMotionRanges().size()); + InputDevice device(mReader->getContext(), /*id=*/1, /*generation=*/2, /*identifier=*/{}); + device.addEventHubDevice(TEST_EVENTHUB_ID, mFakePolicy->getReaderConfiguration()); + device.removeEventHubDevice(TEST_EVENTHUB_ID); + std::string dumpStr, eventHubDevStr; + device.dump(dumpStr, eventHubDevStr); +} - keyboard.reset(); - ASSERT_NO_FATAL_FAILURE(mFakePolicy->assertInputDevicesChanged()); - ASSERT_NO_FATAL_FAILURE(mTestListener->assertNotifyConfigurationChangedWasCalled()); - ASSERT_EQ(initialNumDevices, mFakePolicy->getInputDevices().size()); +TEST_F(InputDeviceTest, GetBluetoothAddress) { + const auto& address = mReader->getBluetoothAddress(DEVICE_ID); + ASSERT_TRUE(address); + ASSERT_EQ(DEVICE_BLUETOOTH_ADDRESS, *address); } -TEST_F(InputReaderIntegrationTest, SendsEventsToInputListener) { - std::unique_ptr keyboard = createUinputDevice(); - ASSERT_NO_FATAL_FAILURE(mFakePolicy->assertInputDevicesChanged()); +// --- SwitchInputMapperTest --- - NotifyConfigurationChangedArgs configChangedArgs; - ASSERT_NO_FATAL_FAILURE( - mTestListener->assertNotifyConfigurationChangedWasCalled(&configChangedArgs)); - int32_t prevId = configChangedArgs.id; - nsecs_t prevTimestamp = configChangedArgs.eventTime; +class SwitchInputMapperTest : public InputMapperTest { +protected: +}; - NotifyKeyArgs keyArgs; - keyboard->pressAndReleaseHomeKey(); - ASSERT_NO_FATAL_FAILURE(mTestListener->assertNotifyKeyWasCalled(&keyArgs)); - ASSERT_EQ(AKEY_EVENT_ACTION_DOWN, keyArgs.action); - ASSERT_NE(prevId, keyArgs.id); - prevId = keyArgs.id; - ASSERT_LE(prevTimestamp, keyArgs.eventTime); - ASSERT_LE(keyArgs.eventTime, keyArgs.readTime); - prevTimestamp = keyArgs.eventTime; +TEST_F(SwitchInputMapperTest, GetSources) { + SwitchInputMapper& mapper = constructAndAddMapper(); - ASSERT_NO_FATAL_FAILURE(mTestListener->assertNotifyKeyWasCalled(&keyArgs)); - ASSERT_EQ(AKEY_EVENT_ACTION_UP, keyArgs.action); - ASSERT_NE(prevId, keyArgs.id); - ASSERT_LE(prevTimestamp, keyArgs.eventTime); - ASSERT_LE(keyArgs.eventTime, keyArgs.readTime); + ASSERT_EQ(uint32_t(AINPUT_SOURCE_SWITCH), mapper.getSources()); } -/** - * The Steam controller sends BTN_GEAR_DOWN and BTN_GEAR_UP for the two "paddle" buttons - * on the back. In this test, we make sure that BTN_GEAR_DOWN / BTN_WHEEL and BTN_GEAR_UP - * are passed to the listener. - */ -static_assert(BTN_GEAR_DOWN == BTN_WHEEL); -TEST_F(InputReaderIntegrationTest, SendsGearDownAndUpToInputListener) { - std::unique_ptr controller = createUinputDevice(); - ASSERT_NO_FATAL_FAILURE(mFakePolicy->assertInputDevicesChanged()); - NotifyKeyArgs keyArgs; +TEST_F(SwitchInputMapperTest, GetSwitchState) { + SwitchInputMapper& mapper = constructAndAddMapper(); - controller->pressAndReleaseKey(BTN_GEAR_DOWN); - ASSERT_NO_FATAL_FAILURE(mTestListener->assertNotifyKeyWasCalled(&keyArgs)); // ACTION_DOWN - ASSERT_NO_FATAL_FAILURE(mTestListener->assertNotifyKeyWasCalled(&keyArgs)); // ACTION_UP - ASSERT_EQ(BTN_GEAR_DOWN, keyArgs.scanCode); + mFakeEventHub->setSwitchState(EVENTHUB_ID, SW_LID, 1); + ASSERT_EQ(1, mapper.getSwitchState(AINPUT_SOURCE_ANY, SW_LID)); - controller->pressAndReleaseKey(BTN_GEAR_UP); - ASSERT_NO_FATAL_FAILURE(mTestListener->assertNotifyKeyWasCalled(&keyArgs)); // ACTION_DOWN - ASSERT_NO_FATAL_FAILURE(mTestListener->assertNotifyKeyWasCalled(&keyArgs)); // ACTION_UP - ASSERT_EQ(BTN_GEAR_UP, keyArgs.scanCode); + mFakeEventHub->setSwitchState(EVENTHUB_ID, SW_LID, 0); + ASSERT_EQ(0, mapper.getSwitchState(AINPUT_SOURCE_ANY, SW_LID)); } -// --- TouchProcessTest --- -class TouchIntegrationTest : public InputReaderIntegrationTest { +TEST_F(SwitchInputMapperTest, Process) { + SwitchInputMapper& mapper = constructAndAddMapper(); + std::list out; + out = process(mapper, ARBITRARY_TIME, READ_TIME, EV_SW, SW_LID, 1); + ASSERT_TRUE(out.empty()); + out = process(mapper, ARBITRARY_TIME, READ_TIME, EV_SW, SW_JACK_PHYSICAL_INSERT, 1); + ASSERT_TRUE(out.empty()); + out = process(mapper, ARBITRARY_TIME, READ_TIME, EV_SW, SW_HEADPHONE_INSERT, 0); + ASSERT_TRUE(out.empty()); + out = process(mapper, ARBITRARY_TIME, READ_TIME, EV_SYN, SYN_REPORT, 0); + + ASSERT_EQ(1u, out.size()); + const NotifySwitchArgs& args = std::get(*out.begin()); + ASSERT_EQ(ARBITRARY_TIME, args.eventTime); + ASSERT_EQ((1U << SW_LID) | (1U << SW_JACK_PHYSICAL_INSERT), args.switchValues); + ASSERT_EQ((1U << SW_LID) | (1U << SW_JACK_PHYSICAL_INSERT) | (1 << SW_HEADPHONE_INSERT), + args.switchMask); + ASSERT_EQ(uint32_t(0), args.policyFlags); +} + +// --- VibratorInputMapperTest --- +class VibratorInputMapperTest : public InputMapperTest { protected: - const std::string UNIQUE_ID = "local:0"; + void SetUp() override { InputMapperTest::SetUp(DEVICE_CLASSES | InputDeviceClass::VIBRATOR); } +}; - void SetUp() override { - InputReaderIntegrationTest::SetUp(); - // At least add an internal display. - setDisplayInfoAndReconfigure(DISPLAY_ID, DISPLAY_WIDTH, DISPLAY_HEIGHT, - DISPLAY_ORIENTATION_0, UNIQUE_ID, NO_PORT, - ViewportType::INTERNAL); - - mDevice = createUinputDevice(Rect(0, 0, DISPLAY_WIDTH, DISPLAY_HEIGHT)); - ASSERT_NO_FATAL_FAILURE(mFakePolicy->assertInputDevicesChanged()); - ASSERT_NO_FATAL_FAILURE(mTestListener->assertNotifyConfigurationChangedWasCalled()); - } +TEST_F(VibratorInputMapperTest, GetSources) { + VibratorInputMapper& mapper = constructAndAddMapper(); - void setDisplayInfoAndReconfigure(int32_t displayId, int32_t width, int32_t height, - int32_t orientation, const std::string& uniqueId, - std::optional physicalPort, - ViewportType viewportType) { - mFakePolicy->addDisplayViewport(displayId, width, height, orientation, true /*isActive*/, - uniqueId, physicalPort, viewportType); - mReader->requestRefreshConfiguration(InputReaderConfiguration::CHANGE_DISPLAY_INFO); - } + ASSERT_EQ(AINPUT_SOURCE_UNKNOWN, mapper.getSources()); +} - void assertReceivedMotion(int32_t action, const std::vector& points) { - NotifyMotionArgs args; - ASSERT_NO_FATAL_FAILURE(mTestListener->assertNotifyMotionWasCalled(&args)); - EXPECT_EQ(action, args.action); - ASSERT_EQ(points.size(), args.pointerCount); - for (size_t i = 0; i < args.pointerCount; i++) { - EXPECT_EQ(points[i].x, args.pointerCoords[i].getX()); - EXPECT_EQ(points[i].y, args.pointerCoords[i].getY()); - } - } +TEST_F(VibratorInputMapperTest, GetVibratorIds) { + VibratorInputMapper& mapper = constructAndAddMapper(); - std::unique_ptr mDevice; -}; + ASSERT_EQ(mapper.getVibratorIds().size(), 2U); +} -TEST_F(TouchIntegrationTest, InputEvent_ProcessSingleTouch) { - NotifyMotionArgs args; - const Point centerPoint = mDevice->getCenterPoint(); +TEST_F(VibratorInputMapperTest, Vibrate) { + constexpr uint8_t DEFAULT_AMPLITUDE = 192; + constexpr int32_t VIBRATION_TOKEN = 100; + VibratorInputMapper& mapper = constructAndAddMapper(); - // ACTION_DOWN - mDevice->sendTrackingId(FIRST_TRACKING_ID); - mDevice->sendDown(centerPoint); - mDevice->sendSync(); - ASSERT_NO_FATAL_FAILURE(mTestListener->assertNotifyMotionWasCalled(&args)); - ASSERT_EQ(AMOTION_EVENT_ACTION_DOWN, args.action); + VibrationElement pattern(2); + VibrationSequence sequence(2); + pattern.duration = std::chrono::milliseconds(200); + pattern.channels = {{/*vibratorId=*/0, DEFAULT_AMPLITUDE / 2}, + {/*vibratorId=*/1, DEFAULT_AMPLITUDE}}; + sequence.addElement(pattern); + pattern.duration = std::chrono::milliseconds(500); + pattern.channels = {{/*vibratorId=*/0, DEFAULT_AMPLITUDE / 4}, + {/*vibratorId=*/1, DEFAULT_AMPLITUDE}}; + sequence.addElement(pattern); - // ACTION_MOVE - mDevice->sendMove(centerPoint + Point(1, 1)); - mDevice->sendSync(); - ASSERT_NO_FATAL_FAILURE(mTestListener->assertNotifyMotionWasCalled(&args)); - ASSERT_EQ(AMOTION_EVENT_ACTION_MOVE, args.action); + std::vector timings = {0, 1}; + std::vector amplitudes = {DEFAULT_AMPLITUDE, DEFAULT_AMPLITUDE / 2}; - // ACTION_UP - mDevice->sendUp(); - mDevice->sendSync(); - ASSERT_NO_FATAL_FAILURE(mTestListener->assertNotifyMotionWasCalled(&args)); - ASSERT_EQ(AMOTION_EVENT_ACTION_UP, args.action); + ASSERT_FALSE(mapper.isVibrating()); + // Start vibrating + std::list out = mapper.vibrate(sequence, /*repeat=*/-1, VIBRATION_TOKEN); + ASSERT_TRUE(mapper.isVibrating()); + // Verify vibrator state listener was notified. + mReader->loopOnce(); + ASSERT_EQ(1u, out.size()); + const NotifyVibratorStateArgs& vibrateArgs = std::get(*out.begin()); + ASSERT_EQ(DEVICE_ID, vibrateArgs.deviceId); + ASSERT_TRUE(vibrateArgs.isOn); + // Stop vibrating + out = mapper.cancelVibrate(VIBRATION_TOKEN); + ASSERT_FALSE(mapper.isVibrating()); + // Verify vibrator state listener was notified. + mReader->loopOnce(); + ASSERT_EQ(1u, out.size()); + const NotifyVibratorStateArgs& cancelArgs = std::get(*out.begin()); + ASSERT_EQ(DEVICE_ID, cancelArgs.deviceId); + ASSERT_FALSE(cancelArgs.isOn); } -TEST_F(TouchIntegrationTest, InputEvent_ProcessMultiTouch) { - NotifyMotionArgs args; - const Point centerPoint = mDevice->getCenterPoint(); +// --- SensorInputMapperTest --- - // ACTION_DOWN - mDevice->sendSlot(FIRST_SLOT); - mDevice->sendTrackingId(FIRST_TRACKING_ID); - mDevice->sendDown(centerPoint); - mDevice->sendSync(); - ASSERT_NO_FATAL_FAILURE(mTestListener->assertNotifyMotionWasCalled(&args)); - ASSERT_EQ(AMOTION_EVENT_ACTION_DOWN, args.action); +class SensorInputMapperTest : public InputMapperTest { +protected: + static const int32_t ACCEL_RAW_MIN; + static const int32_t ACCEL_RAW_MAX; + static const int32_t ACCEL_RAW_FUZZ; + static const int32_t ACCEL_RAW_FLAT; + static const int32_t ACCEL_RAW_RESOLUTION; - // ACTION_POINTER_DOWN (Second slot) - const Point secondPoint = centerPoint + Point(100, 100); - mDevice->sendSlot(SECOND_SLOT); - mDevice->sendTrackingId(SECOND_TRACKING_ID); - mDevice->sendDown(secondPoint); - mDevice->sendSync(); - ASSERT_NO_FATAL_FAILURE(mTestListener->assertNotifyMotionWasCalled(&args)); - ASSERT_EQ(ACTION_POINTER_1_DOWN, args.action); + static const int32_t GYRO_RAW_MIN; + static const int32_t GYRO_RAW_MAX; + static const int32_t GYRO_RAW_FUZZ; + static const int32_t GYRO_RAW_FLAT; + static const int32_t GYRO_RAW_RESOLUTION; - // ACTION_MOVE (Second slot) - mDevice->sendMove(secondPoint + Point(1, 1)); - mDevice->sendSync(); - ASSERT_NO_FATAL_FAILURE(mTestListener->assertNotifyMotionWasCalled(&args)); - ASSERT_EQ(AMOTION_EVENT_ACTION_MOVE, args.action); + static const float GRAVITY_MS2_UNIT; + static const float DEGREE_RADIAN_UNIT; - // ACTION_POINTER_UP (Second slot) - mDevice->sendPointerUp(); - mDevice->sendSync(); - ASSERT_NO_FATAL_FAILURE(mTestListener->assertNotifyMotionWasCalled(&args)); - ASSERT_EQ(ACTION_POINTER_1_UP, args.action); + void prepareAccelAxes(); + void prepareGyroAxes(); + void setAccelProperties(); + void setGyroProperties(); + void SetUp() override { InputMapperTest::SetUp(DEVICE_CLASSES | InputDeviceClass::SENSOR); } +}; - // ACTION_UP - mDevice->sendSlot(FIRST_SLOT); - mDevice->sendUp(); - mDevice->sendSync(); - ASSERT_NO_FATAL_FAILURE(mTestListener->assertNotifyMotionWasCalled(&args)); - ASSERT_EQ(AMOTION_EVENT_ACTION_UP, args.action); -} +const int32_t SensorInputMapperTest::ACCEL_RAW_MIN = -32768; +const int32_t SensorInputMapperTest::ACCEL_RAW_MAX = 32768; +const int32_t SensorInputMapperTest::ACCEL_RAW_FUZZ = 16; +const int32_t SensorInputMapperTest::ACCEL_RAW_FLAT = 0; +const int32_t SensorInputMapperTest::ACCEL_RAW_RESOLUTION = 8192; -/** - * What happens when a pointer goes up while another pointer moves in the same frame? Are POINTER_UP - * events guaranteed to contain the same data as a preceding MOVE, or can they contain different - * data? - * In this test, we try to send a change in coordinates in Pointer 0 in the same frame as the - * liftoff of Pointer 1. We check that POINTER_UP event is generated first, and the MOVE event - * for Pointer 0 only is generated after. - * Suppose we are only interested in learning the movement of Pointer 0. If we only observe MOVE - * events, we will not miss any information. - * Even though the Pointer 1 up event contains updated Pointer 0 coordinates, there is another MOVE - * event generated afterwards that contains the newest movement of pointer 0. - * This is important for palm rejection. If there is a subsequent InputListener stage that detects - * palms, and wants to cancel Pointer 1, then it is safe to simply drop POINTER_1_UP event without - * losing information about non-palm pointers. - */ -TEST_F(TouchIntegrationTest, MultiTouch_PointerMoveAndSecondPointerUp) { - NotifyMotionArgs args; - const Point centerPoint = mDevice->getCenterPoint(); +const int32_t SensorInputMapperTest::GYRO_RAW_MIN = -2097152; +const int32_t SensorInputMapperTest::GYRO_RAW_MAX = 2097152; +const int32_t SensorInputMapperTest::GYRO_RAW_FUZZ = 16; +const int32_t SensorInputMapperTest::GYRO_RAW_FLAT = 0; +const int32_t SensorInputMapperTest::GYRO_RAW_RESOLUTION = 1024; - // ACTION_DOWN - mDevice->sendSlot(FIRST_SLOT); - mDevice->sendTrackingId(FIRST_TRACKING_ID); - mDevice->sendDown(centerPoint); - mDevice->sendSync(); - assertReceivedMotion(AMOTION_EVENT_ACTION_DOWN, {centerPoint}); +const float SensorInputMapperTest::GRAVITY_MS2_UNIT = 9.80665f; +const float SensorInputMapperTest::DEGREE_RADIAN_UNIT = 0.0174533f; - // ACTION_POINTER_DOWN (Second slot) - const Point secondPoint = centerPoint + Point(100, 100); - mDevice->sendSlot(SECOND_SLOT); - mDevice->sendTrackingId(SECOND_TRACKING_ID); - mDevice->sendDown(secondPoint); - mDevice->sendSync(); - assertReceivedMotion(ACTION_POINTER_1_DOWN, {centerPoint, secondPoint}); +void SensorInputMapperTest::prepareAccelAxes() { + mFakeEventHub->addAbsoluteAxis(EVENTHUB_ID, ABS_X, ACCEL_RAW_MIN, ACCEL_RAW_MAX, ACCEL_RAW_FUZZ, + ACCEL_RAW_FLAT, ACCEL_RAW_RESOLUTION); + mFakeEventHub->addAbsoluteAxis(EVENTHUB_ID, ABS_Y, ACCEL_RAW_MIN, ACCEL_RAW_MAX, ACCEL_RAW_FUZZ, + ACCEL_RAW_FLAT, ACCEL_RAW_RESOLUTION); + mFakeEventHub->addAbsoluteAxis(EVENTHUB_ID, ABS_Z, ACCEL_RAW_MIN, ACCEL_RAW_MAX, ACCEL_RAW_FUZZ, + ACCEL_RAW_FLAT, ACCEL_RAW_RESOLUTION); +} - // ACTION_MOVE (First slot) - mDevice->sendSlot(FIRST_SLOT); - mDevice->sendMove(centerPoint + Point(5, 5)); - // ACTION_POINTER_UP (Second slot) - mDevice->sendSlot(SECOND_SLOT); - mDevice->sendPointerUp(); - // Send a single sync for the above 2 pointer updates - mDevice->sendSync(); +void SensorInputMapperTest::prepareGyroAxes() { + mFakeEventHub->addAbsoluteAxis(EVENTHUB_ID, ABS_RX, GYRO_RAW_MIN, GYRO_RAW_MAX, GYRO_RAW_FUZZ, + GYRO_RAW_FLAT, GYRO_RAW_RESOLUTION); + mFakeEventHub->addAbsoluteAxis(EVENTHUB_ID, ABS_RY, GYRO_RAW_MIN, GYRO_RAW_MAX, GYRO_RAW_FUZZ, + GYRO_RAW_FLAT, GYRO_RAW_RESOLUTION); + mFakeEventHub->addAbsoluteAxis(EVENTHUB_ID, ABS_RZ, GYRO_RAW_MIN, GYRO_RAW_MAX, GYRO_RAW_FUZZ, + GYRO_RAW_FLAT, GYRO_RAW_RESOLUTION); +} - // First, we should get POINTER_UP for the second pointer - assertReceivedMotion(ACTION_POINTER_1_UP, - {/*first pointer */ centerPoint + Point(5, 5), - /*second pointer*/ secondPoint}); +void SensorInputMapperTest::setAccelProperties() { + mFakeEventHub->addSensorAxis(EVENTHUB_ID, /* absCode */ 0, InputDeviceSensorType::ACCELEROMETER, + /* sensorDataIndex */ 0); + mFakeEventHub->addSensorAxis(EVENTHUB_ID, /* absCode */ 1, InputDeviceSensorType::ACCELEROMETER, + /* sensorDataIndex */ 1); + mFakeEventHub->addSensorAxis(EVENTHUB_ID, /* absCode */ 2, InputDeviceSensorType::ACCELEROMETER, + /* sensorDataIndex */ 2); + mFakeEventHub->setMscEvent(EVENTHUB_ID, MSC_TIMESTAMP); + addConfigurationProperty("sensor.accelerometer.reportingMode", "0"); + addConfigurationProperty("sensor.accelerometer.maxDelay", "100000"); + addConfigurationProperty("sensor.accelerometer.minDelay", "5000"); + addConfigurationProperty("sensor.accelerometer.power", "1.5"); +} - // Next, the MOVE event for the first pointer - assertReceivedMotion(AMOTION_EVENT_ACTION_MOVE, {centerPoint + Point(5, 5)}); +void SensorInputMapperTest::setGyroProperties() { + mFakeEventHub->addSensorAxis(EVENTHUB_ID, /* absCode */ 3, InputDeviceSensorType::GYROSCOPE, + /* sensorDataIndex */ 0); + mFakeEventHub->addSensorAxis(EVENTHUB_ID, /* absCode */ 4, InputDeviceSensorType::GYROSCOPE, + /* sensorDataIndex */ 1); + mFakeEventHub->addSensorAxis(EVENTHUB_ID, /* absCode */ 5, InputDeviceSensorType::GYROSCOPE, + /* sensorDataIndex */ 2); + mFakeEventHub->setMscEvent(EVENTHUB_ID, MSC_TIMESTAMP); + addConfigurationProperty("sensor.gyroscope.reportingMode", "0"); + addConfigurationProperty("sensor.gyroscope.maxDelay", "100000"); + addConfigurationProperty("sensor.gyroscope.minDelay", "5000"); + addConfigurationProperty("sensor.gyroscope.power", "0.8"); } -/** - * Similar scenario as above. The difference is that when the second pointer goes up, it will first - * move, and then it will go up, all in the same frame. - * In this scenario, the movement of the second pointer just prior to liftoff is ignored, and never - * gets sent to the listener. - */ -TEST_F(TouchIntegrationTest, MultiTouch_PointerMoveAndSecondPointerMoveAndUp) { - NotifyMotionArgs args; - const Point centerPoint = mDevice->getCenterPoint(); +TEST_F(SensorInputMapperTest, GetSources) { + SensorInputMapper& mapper = constructAndAddMapper(); - // ACTION_DOWN - mDevice->sendSlot(FIRST_SLOT); - mDevice->sendTrackingId(FIRST_TRACKING_ID); - mDevice->sendDown(centerPoint); - mDevice->sendSync(); - assertReceivedMotion(AMOTION_EVENT_ACTION_DOWN, {centerPoint}); + ASSERT_EQ(static_cast(AINPUT_SOURCE_SENSOR), mapper.getSources()); +} - // ACTION_POINTER_DOWN (Second slot) - const Point secondPoint = centerPoint + Point(100, 100); - mDevice->sendSlot(SECOND_SLOT); - mDevice->sendTrackingId(SECOND_TRACKING_ID); - mDevice->sendDown(secondPoint); - mDevice->sendSync(); - assertReceivedMotion(ACTION_POINTER_1_DOWN, {centerPoint, secondPoint}); +TEST_F(SensorInputMapperTest, ProcessAccelerometerSensor) { + setAccelProperties(); + prepareAccelAxes(); + SensorInputMapper& mapper = constructAndAddMapper(); - // ACTION_MOVE (First slot) - mDevice->sendSlot(FIRST_SLOT); - mDevice->sendMove(centerPoint + Point(5, 5)); - // ACTION_POINTER_UP (Second slot) - mDevice->sendSlot(SECOND_SLOT); - mDevice->sendMove(secondPoint + Point(6, 6)); - mDevice->sendPointerUp(); - // Send a single sync for the above 2 pointer updates - mDevice->sendSync(); + ASSERT_TRUE(mapper.enableSensor(InputDeviceSensorType::ACCELEROMETER, + std::chrono::microseconds(10000), + std::chrono::microseconds(0))); + ASSERT_TRUE(mFakeEventHub->isDeviceEnabled(EVENTHUB_ID)); + process(mapper, ARBITRARY_TIME, READ_TIME, EV_ABS, ABS_X, 20000); + process(mapper, ARBITRARY_TIME, READ_TIME, EV_ABS, ABS_Y, -20000); + process(mapper, ARBITRARY_TIME, READ_TIME, EV_ABS, ABS_Z, 40000); + process(mapper, ARBITRARY_TIME, READ_TIME, EV_MSC, MSC_TIMESTAMP, 1000); + process(mapper, ARBITRARY_TIME, READ_TIME, EV_SYN, SYN_REPORT, 0); - // First, we should get POINTER_UP for the second pointer - // The movement of the second pointer during the liftoff frame is ignored. - // The coordinates 'secondPoint + Point(6, 6)' are never sent to the listener. - assertReceivedMotion(ACTION_POINTER_1_UP, - {/*first pointer */ centerPoint + Point(5, 5), - /*second pointer*/ secondPoint}); + NotifySensorArgs args; + std::vector values = {20000.0f / ACCEL_RAW_RESOLUTION * GRAVITY_MS2_UNIT, + -20000.0f / ACCEL_RAW_RESOLUTION * GRAVITY_MS2_UNIT, + 40000.0f / ACCEL_RAW_RESOLUTION * GRAVITY_MS2_UNIT}; - // Next, the MOVE event for the first pointer - assertReceivedMotion(AMOTION_EVENT_ACTION_MOVE, {centerPoint + Point(5, 5)}); + ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifySensorWasCalled(&args)); + ASSERT_EQ(args.source, AINPUT_SOURCE_SENSOR); + ASSERT_EQ(args.deviceId, DEVICE_ID); + ASSERT_EQ(args.sensorType, InputDeviceSensorType::ACCELEROMETER); + ASSERT_EQ(args.accuracy, InputDeviceSensorAccuracy::ACCURACY_HIGH); + ASSERT_EQ(args.hwTimestamp, ARBITRARY_TIME); + ASSERT_EQ(args.values, values); + mapper.flushSensor(InputDeviceSensorType::ACCELEROMETER); } -TEST_F(TouchIntegrationTest, InputEvent_ProcessPalm) { - NotifyMotionArgs args; - const Point centerPoint = mDevice->getCenterPoint(); - - // ACTION_DOWN - mDevice->sendSlot(FIRST_SLOT); - mDevice->sendTrackingId(FIRST_TRACKING_ID); - mDevice->sendDown(centerPoint); - mDevice->sendSync(); - ASSERT_NO_FATAL_FAILURE(mTestListener->assertNotifyMotionWasCalled(&args)); - ASSERT_EQ(AMOTION_EVENT_ACTION_DOWN, args.action); - - // ACTION_POINTER_DOWN (second slot) - const Point secondPoint = centerPoint + Point(100, 100); - mDevice->sendSlot(SECOND_SLOT); - mDevice->sendTrackingId(SECOND_TRACKING_ID); - mDevice->sendDown(secondPoint); - mDevice->sendSync(); - ASSERT_NO_FATAL_FAILURE(mTestListener->assertNotifyMotionWasCalled(&args)); - ASSERT_EQ(ACTION_POINTER_1_DOWN, args.action); - - // ACTION_MOVE (second slot) - mDevice->sendMove(secondPoint + Point(1, 1)); - mDevice->sendSync(); - ASSERT_NO_FATAL_FAILURE(mTestListener->assertNotifyMotionWasCalled(&args)); - ASSERT_EQ(AMOTION_EVENT_ACTION_MOVE, args.action); - - // Send MT_TOOL_PALM (second slot), which indicates that the touch IC has determined this to be - // a palm event. - // Expect to receive the ACTION_POINTER_UP with cancel flag. - mDevice->sendToolType(MT_TOOL_PALM); - mDevice->sendSync(); - ASSERT_NO_FATAL_FAILURE(mTestListener->assertNotifyMotionWasCalled(&args)); - ASSERT_EQ(ACTION_POINTER_1_UP, args.action); - ASSERT_EQ(AMOTION_EVENT_FLAG_CANCELED, args.flags); +TEST_F(SensorInputMapperTest, ProcessGyroscopeSensor) { + setGyroProperties(); + prepareGyroAxes(); + SensorInputMapper& mapper = constructAndAddMapper(); - // Send up to second slot, expect first slot send moving. - mDevice->sendPointerUp(); - mDevice->sendSync(); - ASSERT_NO_FATAL_FAILURE(mTestListener->assertNotifyMotionWasCalled(&args)); - ASSERT_EQ(AMOTION_EVENT_ACTION_MOVE, args.action); + ASSERT_TRUE(mapper.enableSensor(InputDeviceSensorType::GYROSCOPE, + std::chrono::microseconds(10000), + std::chrono::microseconds(0))); + ASSERT_TRUE(mFakeEventHub->isDeviceEnabled(EVENTHUB_ID)); + process(mapper, ARBITRARY_TIME, READ_TIME, EV_ABS, ABS_RX, 20000); + process(mapper, ARBITRARY_TIME, READ_TIME, EV_ABS, ABS_RY, -20000); + process(mapper, ARBITRARY_TIME, READ_TIME, EV_ABS, ABS_RZ, 40000); + process(mapper, ARBITRARY_TIME, READ_TIME, EV_MSC, MSC_TIMESTAMP, 1000); + process(mapper, ARBITRARY_TIME, READ_TIME, EV_SYN, SYN_REPORT, 0); - // Send ACTION_UP (first slot) - mDevice->sendSlot(FIRST_SLOT); - mDevice->sendUp(); - mDevice->sendSync(); + NotifySensorArgs args; + std::vector values = {20000.0f / GYRO_RAW_RESOLUTION * DEGREE_RADIAN_UNIT, + -20000.0f / GYRO_RAW_RESOLUTION * DEGREE_RADIAN_UNIT, + 40000.0f / GYRO_RAW_RESOLUTION * DEGREE_RADIAN_UNIT}; - ASSERT_NO_FATAL_FAILURE(mTestListener->assertNotifyMotionWasCalled(&args)); - ASSERT_EQ(AMOTION_EVENT_ACTION_UP, args.action); + ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifySensorWasCalled(&args)); + ASSERT_EQ(args.source, AINPUT_SOURCE_SENSOR); + ASSERT_EQ(args.deviceId, DEVICE_ID); + ASSERT_EQ(args.sensorType, InputDeviceSensorType::GYROSCOPE); + ASSERT_EQ(args.accuracy, InputDeviceSensorAccuracy::ACCURACY_HIGH); + ASSERT_EQ(args.hwTimestamp, ARBITRARY_TIME); + ASSERT_EQ(args.values, values); + mapper.flushSensor(InputDeviceSensorType::GYROSCOPE); } -// --- InputDeviceTest --- -class InputDeviceTest : public testing::Test { -protected: - static const char* DEVICE_NAME; - static const char* DEVICE_LOCATION; - static const int32_t DEVICE_ID; - static const int32_t DEVICE_GENERATION; - static const int32_t DEVICE_CONTROLLER_NUMBER; - static const ftl::Flags DEVICE_CLASSES; - static const int32_t EVENTHUB_ID; - - std::shared_ptr mFakeEventHub; - sp mFakePolicy; - std::unique_ptr mFakeListener; - std::unique_ptr mReader; - std::shared_ptr mDevice; +// --- KeyboardInputMapperTest --- - void SetUp() override { - mFakeEventHub = std::make_unique(); - mFakePolicy = new FakeInputReaderPolicy(); - mFakeListener = std::make_unique(); - mReader = std::make_unique(mFakeEventHub, mFakePolicy, - *mFakeListener); - InputDeviceIdentifier identifier; - identifier.name = DEVICE_NAME; - identifier.location = DEVICE_LOCATION; - mDevice = std::make_shared(mReader->getContext(), DEVICE_ID, DEVICE_GENERATION, - identifier); - mReader->pushNextDevice(mDevice); - mFakeEventHub->addDevice(EVENTHUB_ID, DEVICE_NAME, ftl::Flags(0)); - mReader->loopOnce(); - } +class KeyboardInputMapperTest : public InputMapperTest { +protected: + const std::string UNIQUE_ID = "local:0"; + const KeyboardLayoutInfo DEVICE_KEYBOARD_LAYOUT_INFO = KeyboardLayoutInfo("en-US", "qwerty"); + void prepareDisplay(ui::Rotation orientation); - void TearDown() override { - mFakeListener.reset(); - mFakePolicy.clear(); - } + void testDPadKeyRotation(KeyboardInputMapper& mapper, int32_t originalScanCode, + int32_t originalKeyCode, int32_t rotatedKeyCode, + int32_t displayId = ADISPLAY_ID_NONE); }; -const char* InputDeviceTest::DEVICE_NAME = "device"; -const char* InputDeviceTest::DEVICE_LOCATION = "USB1"; -const int32_t InputDeviceTest::DEVICE_ID = END_RESERVED_ID + 1000; -const int32_t InputDeviceTest::DEVICE_GENERATION = 2; -const int32_t InputDeviceTest::DEVICE_CONTROLLER_NUMBER = 0; -const ftl::Flags InputDeviceTest::DEVICE_CLASSES = - InputDeviceClass::KEYBOARD | InputDeviceClass::TOUCH | InputDeviceClass::JOYSTICK; -const int32_t InputDeviceTest::EVENTHUB_ID = 1; - -TEST_F(InputDeviceTest, ImmutableProperties) { - ASSERT_EQ(DEVICE_ID, mDevice->getId()); - ASSERT_STREQ(DEVICE_NAME, mDevice->getName().c_str()); - ASSERT_EQ(ftl::Flags(0), mDevice->getClasses()); +/* Similar to setDisplayInfoAndReconfigure, but pre-populates all parameters except for the + * orientation. + */ +void KeyboardInputMapperTest::prepareDisplay(ui::Rotation orientation) { + setDisplayInfoAndReconfigure(DISPLAY_ID, DISPLAY_WIDTH, DISPLAY_HEIGHT, orientation, UNIQUE_ID, + NO_PORT, ViewportType::INTERNAL); } -TEST_F(InputDeviceTest, WhenDeviceCreated_EnabledIsFalse) { - ASSERT_EQ(mDevice->isEnabled(), false); -} +void KeyboardInputMapperTest::testDPadKeyRotation(KeyboardInputMapper& mapper, + int32_t originalScanCode, int32_t originalKeyCode, + int32_t rotatedKeyCode, int32_t displayId) { + NotifyKeyArgs args; -TEST_F(InputDeviceTest, WhenNoMappersAreRegistered_DeviceIsIgnored) { - // Configuration. - InputReaderConfiguration config; - mDevice->configure(ARBITRARY_TIME, &config, 0); + process(mapper, ARBITRARY_TIME, READ_TIME, EV_KEY, originalScanCode, 1); + ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyKeyWasCalled(&args)); + ASSERT_EQ(AKEY_EVENT_ACTION_DOWN, args.action); + ASSERT_EQ(originalScanCode, args.scanCode); + ASSERT_EQ(rotatedKeyCode, args.keyCode); + ASSERT_EQ(displayId, args.displayId); - // Reset. - mDevice->reset(ARBITRARY_TIME); + process(mapper, ARBITRARY_TIME, READ_TIME, EV_KEY, originalScanCode, 0); + ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyKeyWasCalled(&args)); + ASSERT_EQ(AKEY_EVENT_ACTION_UP, args.action); + ASSERT_EQ(originalScanCode, args.scanCode); + ASSERT_EQ(rotatedKeyCode, args.keyCode); + ASSERT_EQ(displayId, args.displayId); +} - NotifyDeviceResetArgs resetArgs; - ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyDeviceResetWasCalled(&resetArgs)); - ASSERT_EQ(ARBITRARY_TIME, resetArgs.eventTime); - ASSERT_EQ(DEVICE_ID, resetArgs.deviceId); +TEST_F(KeyboardInputMapperTest, GetSources) { + KeyboardInputMapper& mapper = + constructAndAddMapper(AINPUT_SOURCE_KEYBOARD, + AINPUT_KEYBOARD_TYPE_ALPHABETIC); - // Metadata. - ASSERT_TRUE(mDevice->isIgnored()); - ASSERT_EQ(AINPUT_SOURCE_UNKNOWN, mDevice->getSources()); + ASSERT_EQ(AINPUT_SOURCE_KEYBOARD, mapper.getSources()); +} - InputDeviceInfo info = mDevice->getDeviceInfo(); - ASSERT_EQ(DEVICE_ID, info.getId()); - ASSERT_STREQ(DEVICE_NAME, info.getIdentifier().name.c_str()); - ASSERT_EQ(AINPUT_KEYBOARD_TYPE_NONE, info.getKeyboardType()); - ASSERT_EQ(AINPUT_SOURCE_UNKNOWN, info.getSources()); +TEST_F(KeyboardInputMapperTest, Process_SimpleKeyPress) { + const int32_t USAGE_A = 0x070004; + const int32_t USAGE_UNKNOWN = 0x07ffff; + mFakeEventHub->addKey(EVENTHUB_ID, KEY_HOME, 0, AKEYCODE_HOME, POLICY_FLAG_WAKE); + mFakeEventHub->addKey(EVENTHUB_ID, 0, USAGE_A, AKEYCODE_A, POLICY_FLAG_WAKE); + mFakeEventHub->addKey(EVENTHUB_ID, 0, KEY_NUMLOCK, AKEYCODE_NUM_LOCK, POLICY_FLAG_WAKE); + mFakeEventHub->addKey(EVENTHUB_ID, 0, KEY_CAPSLOCK, AKEYCODE_CAPS_LOCK, POLICY_FLAG_WAKE); + mFakeEventHub->addKey(EVENTHUB_ID, 0, KEY_SCROLLLOCK, AKEYCODE_SCROLL_LOCK, POLICY_FLAG_WAKE); - // State queries. - ASSERT_EQ(0, mDevice->getMetaState()); + KeyboardInputMapper& mapper = + constructAndAddMapper(AINPUT_SOURCE_KEYBOARD, + AINPUT_KEYBOARD_TYPE_ALPHABETIC); + // Initial metastate is AMETA_NONE. + ASSERT_EQ(AMETA_NONE, mapper.getMetaState()); - ASSERT_EQ(AKEY_STATE_UNKNOWN, mDevice->getKeyCodeState(AINPUT_SOURCE_KEYBOARD, 0)) - << "Ignored device should return unknown key code state."; - ASSERT_EQ(AKEY_STATE_UNKNOWN, mDevice->getScanCodeState(AINPUT_SOURCE_KEYBOARD, 0)) - << "Ignored device should return unknown scan code state."; - ASSERT_EQ(AKEY_STATE_UNKNOWN, mDevice->getSwitchState(AINPUT_SOURCE_KEYBOARD, 0)) - << "Ignored device should return unknown switch state."; + // Key down by scan code. + process(mapper, ARBITRARY_TIME, READ_TIME, EV_KEY, KEY_HOME, 1); + NotifyKeyArgs args; + ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyKeyWasCalled(&args)); + ASSERT_EQ(DEVICE_ID, args.deviceId); + ASSERT_EQ(AINPUT_SOURCE_KEYBOARD, args.source); + ASSERT_EQ(ARBITRARY_TIME, args.eventTime); + ASSERT_EQ(AKEY_EVENT_ACTION_DOWN, args.action); + ASSERT_EQ(AKEYCODE_HOME, args.keyCode); + ASSERT_EQ(KEY_HOME, args.scanCode); + ASSERT_EQ(AMETA_NONE, args.metaState); + ASSERT_EQ(AKEY_EVENT_FLAG_FROM_SYSTEM, args.flags); + ASSERT_EQ(POLICY_FLAG_WAKE, args.policyFlags); + ASSERT_EQ(ARBITRARY_TIME, args.downTime); - const int32_t keyCodes[2] = { AKEYCODE_A, AKEYCODE_B }; - uint8_t flags[2] = { 0, 1 }; - ASSERT_FALSE(mDevice->markSupportedKeyCodes(AINPUT_SOURCE_KEYBOARD, 2, keyCodes, flags)) - << "Ignored device should never mark any key codes."; - ASSERT_EQ(0, flags[0]) << "Flag for unsupported key should be unchanged."; - ASSERT_EQ(1, flags[1]) << "Flag for unsupported key should be unchanged."; -} + // Key up by scan code. + process(mapper, ARBITRARY_TIME + 1, READ_TIME, EV_KEY, KEY_HOME, 0); + ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyKeyWasCalled(&args)); + ASSERT_EQ(DEVICE_ID, args.deviceId); + ASSERT_EQ(AINPUT_SOURCE_KEYBOARD, args.source); + ASSERT_EQ(ARBITRARY_TIME + 1, args.eventTime); + ASSERT_EQ(AKEY_EVENT_ACTION_UP, args.action); + ASSERT_EQ(AKEYCODE_HOME, args.keyCode); + ASSERT_EQ(KEY_HOME, args.scanCode); + ASSERT_EQ(AMETA_NONE, args.metaState); + ASSERT_EQ(AKEY_EVENT_FLAG_FROM_SYSTEM, args.flags); + ASSERT_EQ(POLICY_FLAG_WAKE, args.policyFlags); + ASSERT_EQ(ARBITRARY_TIME, args.downTime); -TEST_F(InputDeviceTest, WhenMappersAreRegistered_DeviceIsNotIgnoredAndForwardsRequestsToMappers) { - // Configuration. - mFakeEventHub->addConfigurationProperty(EVENTHUB_ID, String8("key"), String8("value")); - - FakeInputMapper& mapper1 = - mDevice->addMapper(EVENTHUB_ID, AINPUT_SOURCE_KEYBOARD); - mapper1.setKeyboardType(AINPUT_KEYBOARD_TYPE_ALPHABETIC); - mapper1.setMetaState(AMETA_ALT_ON); - mapper1.addSupportedKeyCode(AKEYCODE_A); - mapper1.addSupportedKeyCode(AKEYCODE_B); - mapper1.setKeyCodeState(AKEYCODE_A, AKEY_STATE_DOWN); - mapper1.setKeyCodeState(AKEYCODE_B, AKEY_STATE_UP); - mapper1.setScanCodeState(2, AKEY_STATE_DOWN); - mapper1.setScanCodeState(3, AKEY_STATE_UP); - mapper1.setSwitchState(4, AKEY_STATE_DOWN); - - FakeInputMapper& mapper2 = - mDevice->addMapper(EVENTHUB_ID, AINPUT_SOURCE_TOUCHSCREEN); - mapper2.setMetaState(AMETA_SHIFT_ON); - - InputReaderConfiguration config; - mDevice->configure(ARBITRARY_TIME, &config, 0); - - String8 propertyValue; - ASSERT_TRUE(mDevice->getConfiguration().tryGetProperty(String8("key"), propertyValue)) - << "Device should have read configuration during configuration phase."; - ASSERT_STREQ("value", propertyValue.c_str()); - - ASSERT_NO_FATAL_FAILURE(mapper1.assertConfigureWasCalled()); - ASSERT_NO_FATAL_FAILURE(mapper2.assertConfigureWasCalled()); + // Key down by usage code. + process(mapper, ARBITRARY_TIME, READ_TIME, EV_MSC, MSC_SCAN, USAGE_A); + process(mapper, ARBITRARY_TIME, READ_TIME, EV_KEY, 0, 1); + ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyKeyWasCalled(&args)); + ASSERT_EQ(DEVICE_ID, args.deviceId); + ASSERT_EQ(AINPUT_SOURCE_KEYBOARD, args.source); + ASSERT_EQ(ARBITRARY_TIME, args.eventTime); + ASSERT_EQ(AKEY_EVENT_ACTION_DOWN, args.action); + ASSERT_EQ(AKEYCODE_A, args.keyCode); + ASSERT_EQ(0, args.scanCode); + ASSERT_EQ(AMETA_NONE, args.metaState); + ASSERT_EQ(AKEY_EVENT_FLAG_FROM_SYSTEM, args.flags); + ASSERT_EQ(POLICY_FLAG_WAKE, args.policyFlags); + ASSERT_EQ(ARBITRARY_TIME, args.downTime); - // Reset - mDevice->reset(ARBITRARY_TIME); - ASSERT_NO_FATAL_FAILURE(mapper1.assertResetWasCalled()); - ASSERT_NO_FATAL_FAILURE(mapper2.assertResetWasCalled()); + // Key up by usage code. + process(mapper, ARBITRARY_TIME, READ_TIME, EV_MSC, MSC_SCAN, USAGE_A); + process(mapper, ARBITRARY_TIME + 1, READ_TIME, EV_KEY, 0, 0); + ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyKeyWasCalled(&args)); + ASSERT_EQ(DEVICE_ID, args.deviceId); + ASSERT_EQ(AINPUT_SOURCE_KEYBOARD, args.source); + ASSERT_EQ(ARBITRARY_TIME + 1, args.eventTime); + ASSERT_EQ(AKEY_EVENT_ACTION_UP, args.action); + ASSERT_EQ(AKEYCODE_A, args.keyCode); + ASSERT_EQ(0, args.scanCode); + ASSERT_EQ(AMETA_NONE, args.metaState); + ASSERT_EQ(AKEY_EVENT_FLAG_FROM_SYSTEM, args.flags); + ASSERT_EQ(POLICY_FLAG_WAKE, args.policyFlags); + ASSERT_EQ(ARBITRARY_TIME, args.downTime); - NotifyDeviceResetArgs resetArgs; - ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyDeviceResetWasCalled(&resetArgs)); - ASSERT_EQ(ARBITRARY_TIME, resetArgs.eventTime); - ASSERT_EQ(DEVICE_ID, resetArgs.deviceId); + // Key down with unknown scan code or usage code. + process(mapper, ARBITRARY_TIME, READ_TIME, EV_MSC, MSC_SCAN, USAGE_UNKNOWN); + process(mapper, ARBITRARY_TIME, READ_TIME, EV_KEY, KEY_UNKNOWN, 1); + ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyKeyWasCalled(&args)); + ASSERT_EQ(DEVICE_ID, args.deviceId); + ASSERT_EQ(AINPUT_SOURCE_KEYBOARD, args.source); + ASSERT_EQ(ARBITRARY_TIME, args.eventTime); + ASSERT_EQ(AKEY_EVENT_ACTION_DOWN, args.action); + ASSERT_EQ(0, args.keyCode); + ASSERT_EQ(KEY_UNKNOWN, args.scanCode); + ASSERT_EQ(AMETA_NONE, args.metaState); + ASSERT_EQ(AKEY_EVENT_FLAG_FROM_SYSTEM, args.flags); + ASSERT_EQ(0U, args.policyFlags); + ASSERT_EQ(ARBITRARY_TIME, args.downTime); - // Metadata. - ASSERT_FALSE(mDevice->isIgnored()); - ASSERT_EQ(uint32_t(AINPUT_SOURCE_KEYBOARD | AINPUT_SOURCE_TOUCHSCREEN), mDevice->getSources()); + // Key up with unknown scan code or usage code. + process(mapper, ARBITRARY_TIME, READ_TIME, EV_MSC, MSC_SCAN, USAGE_UNKNOWN); + process(mapper, ARBITRARY_TIME + 1, READ_TIME, EV_KEY, KEY_UNKNOWN, 0); + ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyKeyWasCalled(&args)); + ASSERT_EQ(DEVICE_ID, args.deviceId); + ASSERT_EQ(AINPUT_SOURCE_KEYBOARD, args.source); + ASSERT_EQ(ARBITRARY_TIME + 1, args.eventTime); + ASSERT_EQ(AKEY_EVENT_ACTION_UP, args.action); + ASSERT_EQ(0, args.keyCode); + ASSERT_EQ(KEY_UNKNOWN, args.scanCode); + ASSERT_EQ(AMETA_NONE, args.metaState); + ASSERT_EQ(AKEY_EVENT_FLAG_FROM_SYSTEM, args.flags); + ASSERT_EQ(0U, args.policyFlags); + ASSERT_EQ(ARBITRARY_TIME, args.downTime); +} - InputDeviceInfo info = mDevice->getDeviceInfo(); - ASSERT_EQ(DEVICE_ID, info.getId()); - ASSERT_STREQ(DEVICE_NAME, info.getIdentifier().name.c_str()); - ASSERT_EQ(AINPUT_KEYBOARD_TYPE_ALPHABETIC, info.getKeyboardType()); - ASSERT_EQ(uint32_t(AINPUT_SOURCE_KEYBOARD | AINPUT_SOURCE_TOUCHSCREEN), info.getSources()); +TEST_F(KeyboardInputMapperTest, Process_KeyRemapping) { + mFakeEventHub->addKey(EVENTHUB_ID, KEY_A, 0, AKEYCODE_A, 0); + mFakeEventHub->addKey(EVENTHUB_ID, KEY_B, 0, AKEYCODE_B, 0); + mFakeEventHub->addKeyRemapping(EVENTHUB_ID, AKEYCODE_A, AKEYCODE_B); - // State queries. - ASSERT_EQ(AMETA_ALT_ON | AMETA_SHIFT_ON, mDevice->getMetaState()) - << "Should query mappers and combine meta states."; + KeyboardInputMapper& mapper = + constructAndAddMapper(AINPUT_SOURCE_KEYBOARD, + AINPUT_KEYBOARD_TYPE_ALPHABETIC); - ASSERT_EQ(AKEY_STATE_UNKNOWN, mDevice->getKeyCodeState(AINPUT_SOURCE_TRACKBALL, AKEYCODE_A)) - << "Should return unknown key code state when source not supported."; - ASSERT_EQ(AKEY_STATE_UNKNOWN, mDevice->getScanCodeState(AINPUT_SOURCE_TRACKBALL, AKEYCODE_A)) - << "Should return unknown scan code state when source not supported."; - ASSERT_EQ(AKEY_STATE_UNKNOWN, mDevice->getSwitchState(AINPUT_SOURCE_TRACKBALL, AKEYCODE_A)) - << "Should return unknown switch state when source not supported."; + // Key down by scan code. + process(mapper, ARBITRARY_TIME, READ_TIME, EV_KEY, KEY_A, 1); + NotifyKeyArgs args; + ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyKeyWasCalled(&args)); + ASSERT_EQ(AKEYCODE_B, args.keyCode); - ASSERT_EQ(AKEY_STATE_DOWN, mDevice->getKeyCodeState(AINPUT_SOURCE_KEYBOARD, AKEYCODE_A)) - << "Should query mapper when source is supported."; - ASSERT_EQ(AKEY_STATE_UP, mDevice->getScanCodeState(AINPUT_SOURCE_KEYBOARD, 3)) - << "Should query mapper when source is supported."; - ASSERT_EQ(AKEY_STATE_DOWN, mDevice->getSwitchState(AINPUT_SOURCE_KEYBOARD, 4)) - << "Should query mapper when source is supported."; + // Key up by scan code. + process(mapper, ARBITRARY_TIME + 1, READ_TIME, EV_KEY, KEY_A, 0); + ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyKeyWasCalled(&args)); + ASSERT_EQ(AKEYCODE_B, args.keyCode); +} - const int32_t keyCodes[4] = { AKEYCODE_A, AKEYCODE_B, AKEYCODE_1, AKEYCODE_2 }; - uint8_t flags[4] = { 0, 0, 0, 1 }; - ASSERT_FALSE(mDevice->markSupportedKeyCodes(AINPUT_SOURCE_TRACKBALL, 4, keyCodes, flags)) - << "Should do nothing when source is unsupported."; - ASSERT_EQ(0, flags[0]) << "Flag should be unchanged when source is unsupported."; - ASSERT_EQ(0, flags[1]) << "Flag should be unchanged when source is unsupported."; - ASSERT_EQ(0, flags[2]) << "Flag should be unchanged when source is unsupported."; - ASSERT_EQ(1, flags[3]) << "Flag should be unchanged when source is unsupported."; +/** + * Ensure that the readTime is set to the time when the EV_KEY is received. + */ +TEST_F(KeyboardInputMapperTest, Process_SendsReadTime) { + mFakeEventHub->addKey(EVENTHUB_ID, KEY_HOME, 0, AKEYCODE_HOME, POLICY_FLAG_WAKE); - ASSERT_TRUE(mDevice->markSupportedKeyCodes(AINPUT_SOURCE_KEYBOARD, 4, keyCodes, flags)) - << "Should query mapper when source is supported."; - ASSERT_EQ(1, flags[0]) << "Flag for supported key should be set."; - ASSERT_EQ(1, flags[1]) << "Flag for supported key should be set."; - ASSERT_EQ(0, flags[2]) << "Flag for unsupported key should be unchanged."; - ASSERT_EQ(1, flags[3]) << "Flag for unsupported key should be unchanged."; + KeyboardInputMapper& mapper = + constructAndAddMapper(AINPUT_SOURCE_KEYBOARD, + AINPUT_KEYBOARD_TYPE_ALPHABETIC); + NotifyKeyArgs args; - // Event handling. - RawEvent event; - event.deviceId = EVENTHUB_ID; - mDevice->process(&event, 1); + // Key down + process(mapper, ARBITRARY_TIME, /*readTime=*/12, EV_KEY, KEY_HOME, 1); + ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyKeyWasCalled(&args)); + ASSERT_EQ(12, args.readTime); - ASSERT_NO_FATAL_FAILURE(mapper1.assertProcessWasCalled()); - ASSERT_NO_FATAL_FAILURE(mapper2.assertProcessWasCalled()); + // Key up + process(mapper, ARBITRARY_TIME, /*readTime=*/15, EV_KEY, KEY_HOME, 1); + ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyKeyWasCalled(&args)); + ASSERT_EQ(15, args.readTime); } -// A single input device is associated with a specific display. Check that: -// 1. Device is disabled if the viewport corresponding to the associated display is not found -// 2. Device is disabled when setEnabled API is called -TEST_F(InputDeviceTest, Configure_AssignsDisplayPort) { - mDevice->addMapper(EVENTHUB_ID, AINPUT_SOURCE_TOUCHSCREEN); - - // First Configuration. - mDevice->configure(ARBITRARY_TIME, mFakePolicy->getReaderConfiguration(), 0); +TEST_F(KeyboardInputMapperTest, Process_ShouldUpdateMetaState) { + mFakeEventHub->addKey(EVENTHUB_ID, KEY_LEFTSHIFT, 0, AKEYCODE_SHIFT_LEFT, 0); + mFakeEventHub->addKey(EVENTHUB_ID, KEY_A, 0, AKEYCODE_A, 0); + mFakeEventHub->addKey(EVENTHUB_ID, 0, KEY_NUMLOCK, AKEYCODE_NUM_LOCK, 0); + mFakeEventHub->addKey(EVENTHUB_ID, 0, KEY_CAPSLOCK, AKEYCODE_CAPS_LOCK, 0); + mFakeEventHub->addKey(EVENTHUB_ID, 0, KEY_SCROLLLOCK, AKEYCODE_SCROLL_LOCK, 0); - // Device should be enabled by default. - ASSERT_TRUE(mDevice->isEnabled()); + KeyboardInputMapper& mapper = + constructAndAddMapper(AINPUT_SOURCE_KEYBOARD, + AINPUT_KEYBOARD_TYPE_ALPHABETIC); - // Prepare associated info. - constexpr uint8_t hdmi = 1; - const std::string UNIQUE_ID = "local:1"; + // Initial metastate is AMETA_NONE. + ASSERT_EQ(AMETA_NONE, mapper.getMetaState()); - mFakePolicy->addInputPortAssociation(DEVICE_LOCATION, hdmi); - mDevice->configure(ARBITRARY_TIME, mFakePolicy->getReaderConfiguration(), - InputReaderConfiguration::CHANGE_DISPLAY_INFO); - // Device should be disabled because it is associated with a specific display via - // input port <-> display port association, but the corresponding display is not found - ASSERT_FALSE(mDevice->isEnabled()); + // Metakey down. + process(mapper, ARBITRARY_TIME, READ_TIME, EV_KEY, KEY_LEFTSHIFT, 1); + NotifyKeyArgs args; + ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyKeyWasCalled(&args)); + ASSERT_EQ(AMETA_SHIFT_LEFT_ON | AMETA_SHIFT_ON, args.metaState); + ASSERT_EQ(AMETA_SHIFT_LEFT_ON | AMETA_SHIFT_ON, mapper.getMetaState()); + ASSERT_NO_FATAL_FAILURE(mReader->getContext()->assertUpdateGlobalMetaStateWasCalled()); - // Prepare displays. - mFakePolicy->addDisplayViewport(SECONDARY_DISPLAY_ID, DISPLAY_WIDTH, DISPLAY_HEIGHT, - DISPLAY_ORIENTATION_0, true /*isActive*/, UNIQUE_ID, hdmi, - ViewportType::INTERNAL); - mDevice->configure(ARBITRARY_TIME, mFakePolicy->getReaderConfiguration(), - InputReaderConfiguration::CHANGE_DISPLAY_INFO); - ASSERT_TRUE(mDevice->isEnabled()); + // Key down. + process(mapper, ARBITRARY_TIME + 1, READ_TIME, EV_KEY, KEY_A, 1); + ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyKeyWasCalled(&args)); + ASSERT_EQ(AMETA_SHIFT_LEFT_ON | AMETA_SHIFT_ON, args.metaState); + ASSERT_EQ(AMETA_SHIFT_LEFT_ON | AMETA_SHIFT_ON, mapper.getMetaState()); - // Device should be disabled after set disable. - mFakePolicy->addDisabledDevice(mDevice->getId()); - mDevice->configure(ARBITRARY_TIME, mFakePolicy->getReaderConfiguration(), - InputReaderConfiguration::CHANGE_ENABLED_STATE); - ASSERT_FALSE(mDevice->isEnabled()); + // Key up. + process(mapper, ARBITRARY_TIME + 2, READ_TIME, EV_KEY, KEY_A, 0); + ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyKeyWasCalled(&args)); + ASSERT_EQ(AMETA_SHIFT_LEFT_ON | AMETA_SHIFT_ON, args.metaState); + ASSERT_EQ(AMETA_SHIFT_LEFT_ON | AMETA_SHIFT_ON, mapper.getMetaState()); - // Device should still be disabled even found the associated display. - mDevice->configure(ARBITRARY_TIME, mFakePolicy->getReaderConfiguration(), - InputReaderConfiguration::CHANGE_DISPLAY_INFO); - ASSERT_FALSE(mDevice->isEnabled()); + // Metakey up. + process(mapper, ARBITRARY_TIME + 3, READ_TIME, EV_KEY, KEY_LEFTSHIFT, 0); + ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyKeyWasCalled(&args)); + ASSERT_EQ(AMETA_NONE, args.metaState); + ASSERT_EQ(AMETA_NONE, mapper.getMetaState()); + ASSERT_NO_FATAL_FAILURE(mReader->getContext()->assertUpdateGlobalMetaStateWasCalled()); } -TEST_F(InputDeviceTest, Configure_AssignsDisplayUniqueId) { - // Device should be enabled by default. - mFakePolicy->clearViewports(); - mDevice->addMapper(EVENTHUB_ID, AINPUT_SOURCE_KEYBOARD); - mDevice->configure(ARBITRARY_TIME, mFakePolicy->getReaderConfiguration(), 0); - ASSERT_TRUE(mDevice->isEnabled()); - - // Device should be disabled because it is associated with a specific display, but the - // corresponding display is not found. - mFakePolicy->addInputUniqueIdAssociation(DEVICE_LOCATION, DISPLAY_UNIQUE_ID); - mDevice->configure(ARBITRARY_TIME, mFakePolicy->getReaderConfiguration(), - InputReaderConfiguration::CHANGE_DISPLAY_INFO); - ASSERT_FALSE(mDevice->isEnabled()); - - // Device should be enabled when a display is found. - mFakePolicy->addDisplayViewport(SECONDARY_DISPLAY_ID, DISPLAY_WIDTH, DISPLAY_HEIGHT, - DISPLAY_ORIENTATION_0, /* isActive= */ true, DISPLAY_UNIQUE_ID, - NO_PORT, ViewportType::INTERNAL); - mDevice->configure(ARBITRARY_TIME, mFakePolicy->getReaderConfiguration(), - InputReaderConfiguration::CHANGE_DISPLAY_INFO); - ASSERT_TRUE(mDevice->isEnabled()); +TEST_F(KeyboardInputMapperTest, Process_WhenNotOrientationAware_ShouldNotRotateDPad) { + mFakeEventHub->addKey(EVENTHUB_ID, KEY_UP, 0, AKEYCODE_DPAD_UP, 0); + mFakeEventHub->addKey(EVENTHUB_ID, KEY_RIGHT, 0, AKEYCODE_DPAD_RIGHT, 0); + mFakeEventHub->addKey(EVENTHUB_ID, KEY_DOWN, 0, AKEYCODE_DPAD_DOWN, 0); + mFakeEventHub->addKey(EVENTHUB_ID, KEY_LEFT, 0, AKEYCODE_DPAD_LEFT, 0); - // Device should be disabled after set disable. - mFakePolicy->addDisabledDevice(mDevice->getId()); - mDevice->configure(ARBITRARY_TIME, mFakePolicy->getReaderConfiguration(), - InputReaderConfiguration::CHANGE_ENABLED_STATE); - ASSERT_FALSE(mDevice->isEnabled()); + KeyboardInputMapper& mapper = + constructAndAddMapper(AINPUT_SOURCE_KEYBOARD, + AINPUT_KEYBOARD_TYPE_ALPHABETIC); - // Device should still be disabled even found the associated display. - mDevice->configure(ARBITRARY_TIME, mFakePolicy->getReaderConfiguration(), - InputReaderConfiguration::CHANGE_DISPLAY_INFO); - ASSERT_FALSE(mDevice->isEnabled()); + prepareDisplay(ui::ROTATION_90); + ASSERT_NO_FATAL_FAILURE(testDPadKeyRotation(mapper, + KEY_UP, AKEYCODE_DPAD_UP, AKEYCODE_DPAD_UP)); + ASSERT_NO_FATAL_FAILURE(testDPadKeyRotation(mapper, + KEY_RIGHT, AKEYCODE_DPAD_RIGHT, AKEYCODE_DPAD_RIGHT)); + ASSERT_NO_FATAL_FAILURE(testDPadKeyRotation(mapper, + KEY_DOWN, AKEYCODE_DPAD_DOWN, AKEYCODE_DPAD_DOWN)); + ASSERT_NO_FATAL_FAILURE(testDPadKeyRotation(mapper, + KEY_LEFT, AKEYCODE_DPAD_LEFT, AKEYCODE_DPAD_LEFT)); } -TEST_F(InputDeviceTest, Configure_UniqueId_CorrectlyMatches) { - mFakePolicy->clearViewports(); - mDevice->addMapper(EVENTHUB_ID, AINPUT_SOURCE_KEYBOARD); - mDevice->configure(ARBITRARY_TIME, mFakePolicy->getReaderConfiguration(), 0); +TEST_F(KeyboardInputMapperTest, Process_WhenOrientationAware_ShouldRotateDPad) { + mFakeEventHub->addKey(EVENTHUB_ID, KEY_UP, 0, AKEYCODE_DPAD_UP, 0); + mFakeEventHub->addKey(EVENTHUB_ID, KEY_RIGHT, 0, AKEYCODE_DPAD_RIGHT, 0); + mFakeEventHub->addKey(EVENTHUB_ID, KEY_DOWN, 0, AKEYCODE_DPAD_DOWN, 0); + mFakeEventHub->addKey(EVENTHUB_ID, KEY_LEFT, 0, AKEYCODE_DPAD_LEFT, 0); - mFakePolicy->addInputUniqueIdAssociation(DEVICE_LOCATION, DISPLAY_UNIQUE_ID); - mFakePolicy->addDisplayViewport(SECONDARY_DISPLAY_ID, DISPLAY_WIDTH, DISPLAY_HEIGHT, - DISPLAY_ORIENTATION_0, /* isActive= */ true, DISPLAY_UNIQUE_ID, - NO_PORT, ViewportType::INTERNAL); - mDevice->configure(ARBITRARY_TIME, mFakePolicy->getReaderConfiguration(), - InputReaderConfiguration::CHANGE_DISPLAY_INFO); - ASSERT_EQ(DISPLAY_UNIQUE_ID, mDevice->getAssociatedDisplayUniqueId()); -} + addConfigurationProperty("keyboard.orientationAware", "1"); + KeyboardInputMapper& mapper = + constructAndAddMapper(AINPUT_SOURCE_KEYBOARD, + AINPUT_KEYBOARD_TYPE_ALPHABETIC); -/** - * This test reproduces a crash caused by a dangling reference that remains after device is added - * and removed. The reference is accessed in InputDevice::dump(..); - */ -TEST_F(InputDeviceTest, DumpDoesNotCrash) { - constexpr int32_t TEST_EVENTHUB_ID = 10; - mFakeEventHub->addDevice(TEST_EVENTHUB_ID, "Test EventHub device", InputDeviceClass::BATTERY); - - InputDevice device(mReader->getContext(), 1 /*id*/, 2 /*generation*/, {} /*identifier*/); - device.addEventHubDevice(TEST_EVENTHUB_ID, true /*populateMappers*/); - device.removeEventHubDevice(TEST_EVENTHUB_ID); - std::string dumpStr, eventHubDevStr; - device.dump(dumpStr, eventHubDevStr); -} - -// --- InputMapperTest --- - -class InputMapperTest : public testing::Test { -protected: - static const char* DEVICE_NAME; - static const char* DEVICE_LOCATION; - static const int32_t DEVICE_ID; - static const int32_t DEVICE_GENERATION; - static const int32_t DEVICE_CONTROLLER_NUMBER; - static const ftl::Flags DEVICE_CLASSES; - static const int32_t EVENTHUB_ID; - - std::shared_ptr mFakeEventHub; - sp mFakePolicy; - std::unique_ptr mFakeListener; - std::unique_ptr mReader; - std::shared_ptr mDevice; - - virtual void SetUp(ftl::Flags classes) { - mFakeEventHub = std::make_unique(); - mFakePolicy = new FakeInputReaderPolicy(); - mFakeListener = std::make_unique(); - mReader = std::make_unique(mFakeEventHub, mFakePolicy, - *mFakeListener); - mDevice = newDevice(DEVICE_ID, DEVICE_NAME, DEVICE_LOCATION, EVENTHUB_ID, classes); - // Consume the device reset notification generated when adding a new device. - mFakeListener->assertNotifyDeviceResetWasCalled(); - } + prepareDisplay(ui::ROTATION_0); + ASSERT_NO_FATAL_FAILURE( + testDPadKeyRotation(mapper, KEY_UP, AKEYCODE_DPAD_UP, AKEYCODE_DPAD_UP, DISPLAY_ID)); + ASSERT_NO_FATAL_FAILURE(testDPadKeyRotation(mapper, KEY_RIGHT, AKEYCODE_DPAD_RIGHT, + AKEYCODE_DPAD_RIGHT, DISPLAY_ID)); + ASSERT_NO_FATAL_FAILURE(testDPadKeyRotation(mapper, KEY_DOWN, AKEYCODE_DPAD_DOWN, + AKEYCODE_DPAD_DOWN, DISPLAY_ID)); + ASSERT_NO_FATAL_FAILURE(testDPadKeyRotation(mapper, KEY_LEFT, AKEYCODE_DPAD_LEFT, + AKEYCODE_DPAD_LEFT, DISPLAY_ID)); - void SetUp() override { - SetUp(DEVICE_CLASSES); - } + clearViewports(); + prepareDisplay(ui::ROTATION_90); + ASSERT_NO_FATAL_FAILURE( + testDPadKeyRotation(mapper, KEY_UP, AKEYCODE_DPAD_UP, AKEYCODE_DPAD_LEFT, DISPLAY_ID)); + ASSERT_NO_FATAL_FAILURE(testDPadKeyRotation(mapper, KEY_RIGHT, AKEYCODE_DPAD_RIGHT, + AKEYCODE_DPAD_UP, DISPLAY_ID)); + ASSERT_NO_FATAL_FAILURE(testDPadKeyRotation(mapper, KEY_DOWN, AKEYCODE_DPAD_DOWN, + AKEYCODE_DPAD_RIGHT, DISPLAY_ID)); + ASSERT_NO_FATAL_FAILURE(testDPadKeyRotation(mapper, KEY_LEFT, AKEYCODE_DPAD_LEFT, + AKEYCODE_DPAD_DOWN, DISPLAY_ID)); - void TearDown() override { - mFakeListener.reset(); - mFakePolicy.clear(); - } + clearViewports(); + prepareDisplay(ui::ROTATION_180); + ASSERT_NO_FATAL_FAILURE( + testDPadKeyRotation(mapper, KEY_UP, AKEYCODE_DPAD_UP, AKEYCODE_DPAD_DOWN, DISPLAY_ID)); + ASSERT_NO_FATAL_FAILURE(testDPadKeyRotation(mapper, KEY_RIGHT, AKEYCODE_DPAD_RIGHT, + AKEYCODE_DPAD_LEFT, DISPLAY_ID)); + ASSERT_NO_FATAL_FAILURE(testDPadKeyRotation(mapper, KEY_DOWN, AKEYCODE_DPAD_DOWN, + AKEYCODE_DPAD_UP, DISPLAY_ID)); + ASSERT_NO_FATAL_FAILURE(testDPadKeyRotation(mapper, KEY_LEFT, AKEYCODE_DPAD_LEFT, + AKEYCODE_DPAD_RIGHT, DISPLAY_ID)); - void addConfigurationProperty(const char* key, const char* value) { - mFakeEventHub->addConfigurationProperty(EVENTHUB_ID, String8(key), String8(value)); - } + clearViewports(); + prepareDisplay(ui::ROTATION_270); + ASSERT_NO_FATAL_FAILURE( + testDPadKeyRotation(mapper, KEY_UP, AKEYCODE_DPAD_UP, AKEYCODE_DPAD_RIGHT, DISPLAY_ID)); + ASSERT_NO_FATAL_FAILURE(testDPadKeyRotation(mapper, KEY_RIGHT, AKEYCODE_DPAD_RIGHT, + AKEYCODE_DPAD_DOWN, DISPLAY_ID)); + ASSERT_NO_FATAL_FAILURE(testDPadKeyRotation(mapper, KEY_DOWN, AKEYCODE_DPAD_DOWN, + AKEYCODE_DPAD_LEFT, DISPLAY_ID)); + ASSERT_NO_FATAL_FAILURE(testDPadKeyRotation(mapper, KEY_LEFT, AKEYCODE_DPAD_LEFT, + AKEYCODE_DPAD_UP, DISPLAY_ID)); - void configureDevice(uint32_t changes) { - if (!changes || - (changes & - (InputReaderConfiguration::CHANGE_DISPLAY_INFO | - InputReaderConfiguration::CHANGE_POINTER_CAPTURE))) { - mReader->requestRefreshConfiguration(changes); - mReader->loopOnce(); - } - mDevice->configure(ARBITRARY_TIME, mFakePolicy->getReaderConfiguration(), changes); - // Loop the reader to flush the input listener queue. - mReader->loopOnce(); - } + // Special case: if orientation changes while key is down, we still emit the same keycode + // in the key up as we did in the key down. + NotifyKeyArgs args; + clearViewports(); + prepareDisplay(ui::ROTATION_270); + process(mapper, ARBITRARY_TIME, READ_TIME, EV_KEY, KEY_UP, 1); + ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyKeyWasCalled(&args)); + ASSERT_EQ(AKEY_EVENT_ACTION_DOWN, args.action); + ASSERT_EQ(KEY_UP, args.scanCode); + ASSERT_EQ(AKEYCODE_DPAD_RIGHT, args.keyCode); - std::shared_ptr newDevice(int32_t deviceId, const std::string& name, - const std::string& location, int32_t eventHubId, - ftl::Flags classes) { - InputDeviceIdentifier identifier; - identifier.name = name; - identifier.location = location; - std::shared_ptr device = - std::make_shared(mReader->getContext(), deviceId, DEVICE_GENERATION, - identifier); - mReader->pushNextDevice(device); - mFakeEventHub->addDevice(eventHubId, name, classes); - mReader->loopOnce(); - return device; - } + clearViewports(); + prepareDisplay(ui::ROTATION_180); + process(mapper, ARBITRARY_TIME, READ_TIME, EV_KEY, KEY_UP, 0); + ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyKeyWasCalled(&args)); + ASSERT_EQ(AKEY_EVENT_ACTION_UP, args.action); + ASSERT_EQ(KEY_UP, args.scanCode); + ASSERT_EQ(AKEYCODE_DPAD_RIGHT, args.keyCode); +} - template - T& addMapperAndConfigure(Args... args) { - T& mapper = mDevice->addMapper(EVENTHUB_ID, args...); - configureDevice(0); - mDevice->reset(ARBITRARY_TIME); - mapper.reset(ARBITRARY_TIME); - // Loop the reader to flush the input listener queue. - mReader->loopOnce(); - return mapper; - } +TEST_F(KeyboardInputMapperTest, DisplayIdConfigurationChange_NotOrientationAware) { + // If the keyboard is not orientation aware, + // key events should not be associated with a specific display id + mFakeEventHub->addKey(EVENTHUB_ID, KEY_UP, 0, AKEYCODE_DPAD_UP, 0); - void setDisplayInfoAndReconfigure(int32_t displayId, int32_t width, int32_t height, - int32_t orientation, const std::string& uniqueId, - std::optional physicalPort, ViewportType viewportType) { - mFakePolicy->addDisplayViewport(displayId, width, height, orientation, true /*isActive*/, - uniqueId, physicalPort, viewportType); - configureDevice(InputReaderConfiguration::CHANGE_DISPLAY_INFO); - } + KeyboardInputMapper& mapper = + constructAndAddMapper(AINPUT_SOURCE_KEYBOARD, + AINPUT_KEYBOARD_TYPE_ALPHABETIC); + NotifyKeyArgs args; - void clearViewports() { - mFakePolicy->clearViewports(); - } + // Display id should be ADISPLAY_ID_NONE 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(ADISPLAY_ID_NONE, args.displayId); - void process(InputMapper& mapper, nsecs_t when, nsecs_t readTime, int32_t type, int32_t code, - int32_t value) { - RawEvent event; - event.when = when; - event.readTime = readTime; - event.deviceId = mapper.getDeviceContext().getEventHubId(); - event.type = type; - event.code = code; - event.value = value; - mapper.process(&event); - // Loop the reader to flush the input listener queue. - mReader->loopOnce(); - } + 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); +} - static void assertMotionRange(const InputDeviceInfo& info, - int32_t axis, uint32_t source, float min, float max, float flat, float fuzz) { - const InputDeviceInfo::MotionRange* range = info.getMotionRange(axis, source); - ASSERT_TRUE(range != nullptr) << "Axis: " << axis << " Source: " << source; - ASSERT_EQ(axis, range->axis) << "Axis: " << axis << " Source: " << source; - ASSERT_EQ(source, range->source) << "Axis: " << axis << " Source: " << source; - ASSERT_NEAR(min, range->min, EPSILON) << "Axis: " << axis << " Source: " << source; - ASSERT_NEAR(max, range->max, EPSILON) << "Axis: " << axis << " Source: " << source; - ASSERT_NEAR(flat, range->flat, EPSILON) << "Axis: " << axis << " Source: " << source; - ASSERT_NEAR(fuzz, range->fuzz, EPSILON) << "Axis: " << axis << " Source: " << source; - } +TEST_F(KeyboardInputMapperTest, DisplayIdConfigurationChange_OrientationAware) { + // If the keyboard is orientation aware, + // key events should be associated with the internal viewport + mFakeEventHub->addKey(EVENTHUB_ID, KEY_UP, 0, AKEYCODE_DPAD_UP, 0); - static void assertPointerCoords(const PointerCoords& coords, float x, float y, float pressure, - float size, float touchMajor, float touchMinor, float toolMajor, - float toolMinor, float orientation, float distance, - float scaledAxisEpsilon = 1.f) { - ASSERT_NEAR(x, coords.getAxisValue(AMOTION_EVENT_AXIS_X), scaledAxisEpsilon); - ASSERT_NEAR(y, coords.getAxisValue(AMOTION_EVENT_AXIS_Y), scaledAxisEpsilon); - ASSERT_NEAR(pressure, coords.getAxisValue(AMOTION_EVENT_AXIS_PRESSURE), EPSILON); - ASSERT_NEAR(size, coords.getAxisValue(AMOTION_EVENT_AXIS_SIZE), EPSILON); - ASSERT_NEAR(touchMajor, coords.getAxisValue(AMOTION_EVENT_AXIS_TOUCH_MAJOR), - scaledAxisEpsilon); - ASSERT_NEAR(touchMinor, coords.getAxisValue(AMOTION_EVENT_AXIS_TOUCH_MINOR), - scaledAxisEpsilon); - ASSERT_NEAR(toolMajor, coords.getAxisValue(AMOTION_EVENT_AXIS_TOOL_MAJOR), - scaledAxisEpsilon); - ASSERT_NEAR(toolMinor, coords.getAxisValue(AMOTION_EVENT_AXIS_TOOL_MINOR), - scaledAxisEpsilon); - ASSERT_NEAR(orientation, coords.getAxisValue(AMOTION_EVENT_AXIS_ORIENTATION), EPSILON); - ASSERT_NEAR(distance, coords.getAxisValue(AMOTION_EVENT_AXIS_DISTANCE), EPSILON); - } + addConfigurationProperty("keyboard.orientationAware", "1"); + KeyboardInputMapper& mapper = + constructAndAddMapper(AINPUT_SOURCE_KEYBOARD, + AINPUT_KEYBOARD_TYPE_ALPHABETIC); + NotifyKeyArgs args; - static void assertPosition(const FakePointerController& controller, float x, float y) { - float actualX, actualY; - controller.getPosition(&actualX, &actualY); - ASSERT_NEAR(x, actualX, 1); - ASSERT_NEAR(y, actualY, 1); - } -}; + // Display id should be ADISPLAY_ID_NONE without any display configuration. + // ^--- already checked by the previous test -const char* InputMapperTest::DEVICE_NAME = "device"; -const char* InputMapperTest::DEVICE_LOCATION = "USB1"; -const int32_t InputMapperTest::DEVICE_ID = END_RESERVED_ID + 1000; -const int32_t InputMapperTest::DEVICE_GENERATION = 2; -const int32_t InputMapperTest::DEVICE_CONTROLLER_NUMBER = 0; -const ftl::Flags InputMapperTest::DEVICE_CLASSES = - ftl::Flags(0); // not needed for current tests -const int32_t InputMapperTest::EVENTHUB_ID = 1; + setDisplayInfoAndReconfigure(DISPLAY_ID, DISPLAY_WIDTH, DISPLAY_HEIGHT, ui::ROTATION_0, + UNIQUE_ID, NO_PORT, ViewportType::INTERNAL); + 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(DISPLAY_ID, args.displayId); -// --- SwitchInputMapperTest --- + constexpr int32_t newDisplayId = 2; + clearViewports(); + setDisplayInfoAndReconfigure(newDisplayId, DISPLAY_WIDTH, DISPLAY_HEIGHT, ui::ROTATION_0, + UNIQUE_ID, NO_PORT, ViewportType::INTERNAL); + 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(newDisplayId, args.displayId); +} -class SwitchInputMapperTest : public InputMapperTest { -protected: -}; +TEST_F(KeyboardInputMapperTest, GetKeyCodeState) { + KeyboardInputMapper& mapper = + constructAndAddMapper(AINPUT_SOURCE_KEYBOARD, + AINPUT_KEYBOARD_TYPE_ALPHABETIC); -TEST_F(SwitchInputMapperTest, GetSources) { - SwitchInputMapper& mapper = addMapperAndConfigure(); + mFakeEventHub->setKeyCodeState(EVENTHUB_ID, AKEYCODE_A, 1); + ASSERT_EQ(1, mapper.getKeyCodeState(AINPUT_SOURCE_ANY, AKEYCODE_A)); - ASSERT_EQ(uint32_t(AINPUT_SOURCE_SWITCH), mapper.getSources()); + mFakeEventHub->setKeyCodeState(EVENTHUB_ID, AKEYCODE_A, 0); + ASSERT_EQ(0, mapper.getKeyCodeState(AINPUT_SOURCE_ANY, AKEYCODE_A)); } -TEST_F(SwitchInputMapperTest, GetSwitchState) { - SwitchInputMapper& mapper = addMapperAndConfigure(); +TEST_F(KeyboardInputMapperTest, GetKeyCodeForKeyLocation) { + KeyboardInputMapper& mapper = + constructAndAddMapper(AINPUT_SOURCE_KEYBOARD, + AINPUT_KEYBOARD_TYPE_ALPHABETIC); - mFakeEventHub->setSwitchState(EVENTHUB_ID, SW_LID, 1); - ASSERT_EQ(1, mapper.getSwitchState(AINPUT_SOURCE_ANY, SW_LID)); + mFakeEventHub->addKeyCodeMapping(EVENTHUB_ID, AKEYCODE_Y, AKEYCODE_Z); + ASSERT_EQ(AKEYCODE_Z, mapper.getKeyCodeForKeyLocation(AKEYCODE_Y)) + << "If a mapping is available, the result is equal to the mapping"; - mFakeEventHub->setSwitchState(EVENTHUB_ID, SW_LID, 0); - ASSERT_EQ(0, mapper.getSwitchState(AINPUT_SOURCE_ANY, SW_LID)); + ASSERT_EQ(AKEYCODE_A, mapper.getKeyCodeForKeyLocation(AKEYCODE_A)) + << "If no mapping is available, the result is the key location"; } -TEST_F(SwitchInputMapperTest, Process) { - SwitchInputMapper& mapper = addMapperAndConfigure(); +TEST_F(KeyboardInputMapperTest, GetScanCodeState) { + KeyboardInputMapper& mapper = + constructAndAddMapper(AINPUT_SOURCE_KEYBOARD, + AINPUT_KEYBOARD_TYPE_ALPHABETIC); - process(mapper, ARBITRARY_TIME, READ_TIME, EV_SW, SW_LID, 1); - process(mapper, ARBITRARY_TIME, READ_TIME, EV_SW, SW_JACK_PHYSICAL_INSERT, 1); - process(mapper, ARBITRARY_TIME, READ_TIME, EV_SW, SW_HEADPHONE_INSERT, 0); - process(mapper, ARBITRARY_TIME, READ_TIME, EV_SYN, SYN_REPORT, 0); + mFakeEventHub->setScanCodeState(EVENTHUB_ID, KEY_A, 1); + ASSERT_EQ(1, mapper.getScanCodeState(AINPUT_SOURCE_ANY, KEY_A)); - NotifySwitchArgs args; - ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifySwitchWasCalled(&args)); - ASSERT_EQ(ARBITRARY_TIME, args.eventTime); - ASSERT_EQ((1U << SW_LID) | (1U << SW_JACK_PHYSICAL_INSERT), args.switchValues); - ASSERT_EQ((1U << SW_LID) | (1U << SW_JACK_PHYSICAL_INSERT) | (1 << SW_HEADPHONE_INSERT), - args.switchMask); - ASSERT_EQ(uint32_t(0), args.policyFlags); + mFakeEventHub->setScanCodeState(EVENTHUB_ID, KEY_A, 0); + ASSERT_EQ(0, mapper.getScanCodeState(AINPUT_SOURCE_ANY, KEY_A)); } -// --- VibratorInputMapperTest --- -class VibratorInputMapperTest : public InputMapperTest { -protected: - void SetUp() override { InputMapperTest::SetUp(DEVICE_CLASSES | InputDeviceClass::VIBRATOR); } -}; +TEST_F(KeyboardInputMapperTest, MarkSupportedKeyCodes) { + KeyboardInputMapper& mapper = + constructAndAddMapper(AINPUT_SOURCE_KEYBOARD, + AINPUT_KEYBOARD_TYPE_ALPHABETIC); -TEST_F(VibratorInputMapperTest, GetSources) { - VibratorInputMapper& mapper = addMapperAndConfigure(); + mFakeEventHub->addKey(EVENTHUB_ID, KEY_A, 0, AKEYCODE_A, 0); - ASSERT_EQ(AINPUT_SOURCE_UNKNOWN, mapper.getSources()); + uint8_t flags[2] = { 0, 0 }; + ASSERT_TRUE(mapper.markSupportedKeyCodes(AINPUT_SOURCE_ANY, {AKEYCODE_A, AKEYCODE_B}, flags)); + ASSERT_TRUE(flags[0]); + ASSERT_FALSE(flags[1]); } -TEST_F(VibratorInputMapperTest, GetVibratorIds) { - VibratorInputMapper& mapper = addMapperAndConfigure(); - - ASSERT_EQ(mapper.getVibratorIds().size(), 2U); -} +TEST_F(KeyboardInputMapperTest, Process_LockedKeysShouldToggleMetaStateAndLeds) { + mFakeEventHub->addLed(EVENTHUB_ID, LED_CAPSL, true /*initially on*/); + mFakeEventHub->addLed(EVENTHUB_ID, LED_NUML, false /*initially off*/); + mFakeEventHub->addLed(EVENTHUB_ID, LED_SCROLLL, false /*initially off*/); + mFakeEventHub->addKey(EVENTHUB_ID, KEY_CAPSLOCK, 0, AKEYCODE_CAPS_LOCK, 0); + mFakeEventHub->addKey(EVENTHUB_ID, KEY_NUMLOCK, 0, AKEYCODE_NUM_LOCK, 0); + mFakeEventHub->addKey(EVENTHUB_ID, KEY_SCROLLLOCK, 0, AKEYCODE_SCROLL_LOCK, 0); -TEST_F(VibratorInputMapperTest, Vibrate) { - constexpr uint8_t DEFAULT_AMPLITUDE = 192; - constexpr int32_t VIBRATION_TOKEN = 100; - VibratorInputMapper& mapper = addMapperAndConfigure(); + KeyboardInputMapper& mapper = + constructAndAddMapper(AINPUT_SOURCE_KEYBOARD, + AINPUT_KEYBOARD_TYPE_ALPHABETIC); + // Initial metastate is AMETA_NONE. + ASSERT_EQ(AMETA_NONE, mapper.getMetaState()); - VibrationElement pattern(2); - VibrationSequence sequence(2); - pattern.duration = std::chrono::milliseconds(200); - pattern.channels = {{0 /* vibratorId */, DEFAULT_AMPLITUDE / 2}, - {1 /* vibratorId */, DEFAULT_AMPLITUDE}}; - sequence.addElement(pattern); - pattern.duration = std::chrono::milliseconds(500); - pattern.channels = {{0 /* vibratorId */, DEFAULT_AMPLITUDE / 4}, - {1 /* vibratorId */, DEFAULT_AMPLITUDE}}; - sequence.addElement(pattern); + // Initialization should have turned all of the lights off. + ASSERT_FALSE(mFakeEventHub->getLedState(EVENTHUB_ID, LED_CAPSL)); + ASSERT_FALSE(mFakeEventHub->getLedState(EVENTHUB_ID, LED_NUML)); + ASSERT_FALSE(mFakeEventHub->getLedState(EVENTHUB_ID, LED_SCROLLL)); - std::vector timings = {0, 1}; - std::vector amplitudes = {DEFAULT_AMPLITUDE, DEFAULT_AMPLITUDE / 2}; + // Toggle caps lock on. + process(mapper, ARBITRARY_TIME, READ_TIME, EV_KEY, KEY_CAPSLOCK, 1); + process(mapper, ARBITRARY_TIME, READ_TIME, EV_KEY, KEY_CAPSLOCK, 0); + ASSERT_TRUE(mFakeEventHub->getLedState(EVENTHUB_ID, LED_CAPSL)); + ASSERT_FALSE(mFakeEventHub->getLedState(EVENTHUB_ID, LED_NUML)); + ASSERT_FALSE(mFakeEventHub->getLedState(EVENTHUB_ID, LED_SCROLLL)); + ASSERT_EQ(AMETA_CAPS_LOCK_ON, mapper.getMetaState()); - ASSERT_FALSE(mapper.isVibrating()); - // Start vibrating - mapper.vibrate(sequence, -1 /* repeat */, VIBRATION_TOKEN); - ASSERT_TRUE(mapper.isVibrating()); - // Verify vibrator state listener was notified. - mReader->loopOnce(); - NotifyVibratorStateArgs args; - ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyVibratorStateWasCalled(&args)); - ASSERT_EQ(DEVICE_ID, args.deviceId); - ASSERT_TRUE(args.isOn); - // Stop vibrating - mapper.cancelVibrate(VIBRATION_TOKEN); - ASSERT_FALSE(mapper.isVibrating()); - // Verify vibrator state listener was notified. - mReader->loopOnce(); - ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyVibratorStateWasCalled(&args)); - ASSERT_EQ(DEVICE_ID, args.deviceId); - ASSERT_FALSE(args.isOn); -} + // Toggle num lock on. + process(mapper, ARBITRARY_TIME, READ_TIME, EV_KEY, KEY_NUMLOCK, 1); + process(mapper, ARBITRARY_TIME, READ_TIME, EV_KEY, KEY_NUMLOCK, 0); + ASSERT_TRUE(mFakeEventHub->getLedState(EVENTHUB_ID, LED_CAPSL)); + ASSERT_TRUE(mFakeEventHub->getLedState(EVENTHUB_ID, LED_NUML)); + ASSERT_FALSE(mFakeEventHub->getLedState(EVENTHUB_ID, LED_SCROLLL)); + ASSERT_EQ(AMETA_CAPS_LOCK_ON | AMETA_NUM_LOCK_ON, mapper.getMetaState()); -// --- SensorInputMapperTest --- + // Toggle caps lock off. + process(mapper, ARBITRARY_TIME, READ_TIME, EV_KEY, KEY_CAPSLOCK, 1); + process(mapper, ARBITRARY_TIME, READ_TIME, EV_KEY, KEY_CAPSLOCK, 0); + ASSERT_FALSE(mFakeEventHub->getLedState(EVENTHUB_ID, LED_CAPSL)); + ASSERT_TRUE(mFakeEventHub->getLedState(EVENTHUB_ID, LED_NUML)); + ASSERT_FALSE(mFakeEventHub->getLedState(EVENTHUB_ID, LED_SCROLLL)); + ASSERT_EQ(AMETA_NUM_LOCK_ON, mapper.getMetaState()); -class SensorInputMapperTest : public InputMapperTest { -protected: - static const int32_t ACCEL_RAW_MIN; - static const int32_t ACCEL_RAW_MAX; - static const int32_t ACCEL_RAW_FUZZ; - static const int32_t ACCEL_RAW_FLAT; - static const int32_t ACCEL_RAW_RESOLUTION; + // Toggle scroll lock on. + process(mapper, ARBITRARY_TIME, READ_TIME, EV_KEY, KEY_SCROLLLOCK, 1); + process(mapper, ARBITRARY_TIME, READ_TIME, EV_KEY, KEY_SCROLLLOCK, 0); + ASSERT_FALSE(mFakeEventHub->getLedState(EVENTHUB_ID, LED_CAPSL)); + ASSERT_TRUE(mFakeEventHub->getLedState(EVENTHUB_ID, LED_NUML)); + ASSERT_TRUE(mFakeEventHub->getLedState(EVENTHUB_ID, LED_SCROLLL)); + ASSERT_EQ(AMETA_NUM_LOCK_ON | AMETA_SCROLL_LOCK_ON, mapper.getMetaState()); - static const int32_t GYRO_RAW_MIN; - static const int32_t GYRO_RAW_MAX; - static const int32_t GYRO_RAW_FUZZ; - static const int32_t GYRO_RAW_FLAT; - static const int32_t GYRO_RAW_RESOLUTION; + // Toggle num lock off. + process(mapper, ARBITRARY_TIME, READ_TIME, EV_KEY, KEY_NUMLOCK, 1); + process(mapper, ARBITRARY_TIME, READ_TIME, EV_KEY, KEY_NUMLOCK, 0); + ASSERT_FALSE(mFakeEventHub->getLedState(EVENTHUB_ID, LED_CAPSL)); + ASSERT_FALSE(mFakeEventHub->getLedState(EVENTHUB_ID, LED_NUML)); + ASSERT_TRUE(mFakeEventHub->getLedState(EVENTHUB_ID, LED_SCROLLL)); + ASSERT_EQ(AMETA_SCROLL_LOCK_ON, mapper.getMetaState()); - static const float GRAVITY_MS2_UNIT; - static const float DEGREE_RADIAN_UNIT; + // Toggle scroll lock off. + process(mapper, ARBITRARY_TIME, READ_TIME, EV_KEY, KEY_SCROLLLOCK, 1); + process(mapper, ARBITRARY_TIME, READ_TIME, EV_KEY, KEY_SCROLLLOCK, 0); + ASSERT_FALSE(mFakeEventHub->getLedState(EVENTHUB_ID, LED_CAPSL)); + ASSERT_FALSE(mFakeEventHub->getLedState(EVENTHUB_ID, LED_NUML)); + ASSERT_FALSE(mFakeEventHub->getLedState(EVENTHUB_ID, LED_SCROLLL)); + ASSERT_EQ(AMETA_NONE, mapper.getMetaState()); +} - void prepareAccelAxes(); - void prepareGyroAxes(); - void setAccelProperties(); - void setGyroProperties(); - void SetUp() override { InputMapperTest::SetUp(DEVICE_CLASSES | InputDeviceClass::SENSOR); } -}; +TEST_F(KeyboardInputMapperTest, NoMetaStateWhenMetaKeysNotPresent) { + mFakeEventHub->addKey(EVENTHUB_ID, BTN_A, 0, AKEYCODE_BUTTON_A, 0); + mFakeEventHub->addKey(EVENTHUB_ID, BTN_B, 0, AKEYCODE_BUTTON_B, 0); + mFakeEventHub->addKey(EVENTHUB_ID, BTN_X, 0, AKEYCODE_BUTTON_X, 0); + mFakeEventHub->addKey(EVENTHUB_ID, BTN_Y, 0, AKEYCODE_BUTTON_Y, 0); -const int32_t SensorInputMapperTest::ACCEL_RAW_MIN = -32768; -const int32_t SensorInputMapperTest::ACCEL_RAW_MAX = 32768; -const int32_t SensorInputMapperTest::ACCEL_RAW_FUZZ = 16; -const int32_t SensorInputMapperTest::ACCEL_RAW_FLAT = 0; -const int32_t SensorInputMapperTest::ACCEL_RAW_RESOLUTION = 8192; + KeyboardInputMapper& mapper = + constructAndAddMapper(AINPUT_SOURCE_KEYBOARD, + AINPUT_KEYBOARD_TYPE_NON_ALPHABETIC); -const int32_t SensorInputMapperTest::GYRO_RAW_MIN = -2097152; -const int32_t SensorInputMapperTest::GYRO_RAW_MAX = 2097152; -const int32_t SensorInputMapperTest::GYRO_RAW_FUZZ = 16; -const int32_t SensorInputMapperTest::GYRO_RAW_FLAT = 0; -const int32_t SensorInputMapperTest::GYRO_RAW_RESOLUTION = 1024; + // Meta state should be AMETA_NONE after reset + std::list unused = mapper.reset(ARBITRARY_TIME); + ASSERT_EQ(AMETA_NONE, mapper.getMetaState()); + // Meta state should be AMETA_NONE with update, as device doesn't have the keys. + mapper.updateMetaState(AKEYCODE_NUM_LOCK); + ASSERT_EQ(AMETA_NONE, mapper.getMetaState()); -const float SensorInputMapperTest::GRAVITY_MS2_UNIT = 9.80665f; -const float SensorInputMapperTest::DEGREE_RADIAN_UNIT = 0.0174533f; + NotifyKeyArgs args; + // Press button "A" + process(mapper, ARBITRARY_TIME, READ_TIME, EV_KEY, BTN_A, 1); + ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyKeyWasCalled(&args)); + ASSERT_EQ(AMETA_NONE, args.metaState); + ASSERT_EQ(AMETA_NONE, mapper.getMetaState()); + ASSERT_EQ(AKEY_EVENT_ACTION_DOWN, args.action); + ASSERT_EQ(AKEYCODE_BUTTON_A, args.keyCode); -void SensorInputMapperTest::prepareAccelAxes() { - mFakeEventHub->addAbsoluteAxis(EVENTHUB_ID, ABS_X, ACCEL_RAW_MIN, ACCEL_RAW_MAX, ACCEL_RAW_FUZZ, - ACCEL_RAW_FLAT, ACCEL_RAW_RESOLUTION); - mFakeEventHub->addAbsoluteAxis(EVENTHUB_ID, ABS_Y, ACCEL_RAW_MIN, ACCEL_RAW_MAX, ACCEL_RAW_FUZZ, - ACCEL_RAW_FLAT, ACCEL_RAW_RESOLUTION); - mFakeEventHub->addAbsoluteAxis(EVENTHUB_ID, ABS_Z, ACCEL_RAW_MIN, ACCEL_RAW_MAX, ACCEL_RAW_FUZZ, - ACCEL_RAW_FLAT, ACCEL_RAW_RESOLUTION); + // Button up. + process(mapper, ARBITRARY_TIME + 2, READ_TIME, EV_KEY, BTN_A, 0); + ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyKeyWasCalled(&args)); + ASSERT_EQ(AMETA_NONE, args.metaState); + ASSERT_EQ(AMETA_NONE, mapper.getMetaState()); + ASSERT_EQ(AKEY_EVENT_ACTION_UP, args.action); + ASSERT_EQ(AKEYCODE_BUTTON_A, args.keyCode); } -void SensorInputMapperTest::prepareGyroAxes() { - mFakeEventHub->addAbsoluteAxis(EVENTHUB_ID, ABS_RX, GYRO_RAW_MIN, GYRO_RAW_MAX, GYRO_RAW_FUZZ, - GYRO_RAW_FLAT, GYRO_RAW_RESOLUTION); - mFakeEventHub->addAbsoluteAxis(EVENTHUB_ID, ABS_RY, GYRO_RAW_MIN, GYRO_RAW_MAX, GYRO_RAW_FUZZ, - GYRO_RAW_FLAT, GYRO_RAW_RESOLUTION); - mFakeEventHub->addAbsoluteAxis(EVENTHUB_ID, ABS_RZ, GYRO_RAW_MIN, GYRO_RAW_MAX, GYRO_RAW_FUZZ, - GYRO_RAW_FLAT, GYRO_RAW_RESOLUTION); -} +TEST_F(KeyboardInputMapperTest, Configure_AssignsDisplayPort) { + // keyboard 1. + mFakeEventHub->addKey(EVENTHUB_ID, KEY_UP, 0, AKEYCODE_DPAD_UP, 0); + mFakeEventHub->addKey(EVENTHUB_ID, KEY_RIGHT, 0, AKEYCODE_DPAD_RIGHT, 0); + mFakeEventHub->addKey(EVENTHUB_ID, KEY_DOWN, 0, AKEYCODE_DPAD_DOWN, 0); + mFakeEventHub->addKey(EVENTHUB_ID, KEY_LEFT, 0, AKEYCODE_DPAD_LEFT, 0); -void SensorInputMapperTest::setAccelProperties() { - mFakeEventHub->addSensorAxis(EVENTHUB_ID, /* absCode */ 0, InputDeviceSensorType::ACCELEROMETER, - /* sensorDataIndex */ 0); - mFakeEventHub->addSensorAxis(EVENTHUB_ID, /* absCode */ 1, InputDeviceSensorType::ACCELEROMETER, - /* sensorDataIndex */ 1); - mFakeEventHub->addSensorAxis(EVENTHUB_ID, /* absCode */ 2, InputDeviceSensorType::ACCELEROMETER, - /* sensorDataIndex */ 2); - mFakeEventHub->setMscEvent(EVENTHUB_ID, MSC_TIMESTAMP); - addConfigurationProperty("sensor.accelerometer.reportingMode", "0"); - addConfigurationProperty("sensor.accelerometer.maxDelay", "100000"); - addConfigurationProperty("sensor.accelerometer.minDelay", "5000"); - addConfigurationProperty("sensor.accelerometer.power", "1.5"); -} + // keyboard 2. + const std::string USB2 = "USB2"; + const std::string DEVICE_NAME2 = "KEYBOARD2"; + 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)); -void SensorInputMapperTest::setGyroProperties() { - mFakeEventHub->addSensorAxis(EVENTHUB_ID, /* absCode */ 3, InputDeviceSensorType::GYROSCOPE, - /* sensorDataIndex */ 0); - mFakeEventHub->addSensorAxis(EVENTHUB_ID, /* absCode */ 4, InputDeviceSensorType::GYROSCOPE, - /* sensorDataIndex */ 1); - mFakeEventHub->addSensorAxis(EVENTHUB_ID, /* absCode */ 5, InputDeviceSensorType::GYROSCOPE, - /* sensorDataIndex */ 2); - mFakeEventHub->setMscEvent(EVENTHUB_ID, MSC_TIMESTAMP); - addConfigurationProperty("sensor.gyroscope.reportingMode", "0"); - addConfigurationProperty("sensor.gyroscope.maxDelay", "100000"); - addConfigurationProperty("sensor.gyroscope.minDelay", "5000"); - addConfigurationProperty("sensor.gyroscope.power", "0.8"); -} + mFakeEventHub->addKey(SECOND_EVENTHUB_ID, KEY_UP, 0, AKEYCODE_DPAD_UP, 0); + mFakeEventHub->addKey(SECOND_EVENTHUB_ID, KEY_RIGHT, 0, AKEYCODE_DPAD_RIGHT, 0); + mFakeEventHub->addKey(SECOND_EVENTHUB_ID, KEY_DOWN, 0, AKEYCODE_DPAD_DOWN, 0); + mFakeEventHub->addKey(SECOND_EVENTHUB_ID, KEY_LEFT, 0, AKEYCODE_DPAD_LEFT, 0); -TEST_F(SensorInputMapperTest, GetSources) { - SensorInputMapper& mapper = addMapperAndConfigure(); + KeyboardInputMapper& mapper = + constructAndAddMapper(AINPUT_SOURCE_KEYBOARD, + AINPUT_KEYBOARD_TYPE_ALPHABETIC); - ASSERT_EQ(static_cast(AINPUT_SOURCE_SENSOR), mapper.getSources()); -} + device2->addEmptyEventHubDevice(SECOND_EVENTHUB_ID); + KeyboardInputMapper& mapper2 = + device2->constructAndAddMapper(SECOND_EVENTHUB_ID, + mFakePolicy + ->getReaderConfiguration(), + AINPUT_SOURCE_KEYBOARD, + AINPUT_KEYBOARD_TYPE_ALPHABETIC); + std::list unused = + device2->configure(ARBITRARY_TIME, mFakePolicy->getReaderConfiguration(), + /*changes=*/{}); + unused += device2->reset(ARBITRARY_TIME); -TEST_F(SensorInputMapperTest, ProcessAccelerometerSensor) { - setAccelProperties(); - prepareAccelAxes(); - SensorInputMapper& mapper = addMapperAndConfigure(); + // Prepared displays and associated info. + constexpr uint8_t hdmi1 = 0; + constexpr uint8_t hdmi2 = 1; + const std::string SECONDARY_UNIQUE_ID = "local:1"; - ASSERT_TRUE(mapper.enableSensor(InputDeviceSensorType::ACCELEROMETER, - std::chrono::microseconds(10000), - std::chrono::microseconds(0))); - ASSERT_TRUE(mFakeEventHub->isDeviceEnabled(EVENTHUB_ID)); - process(mapper, ARBITRARY_TIME, READ_TIME, EV_ABS, ABS_X, 20000); - process(mapper, ARBITRARY_TIME, READ_TIME, EV_ABS, ABS_Y, -20000); - process(mapper, ARBITRARY_TIME, READ_TIME, EV_ABS, ABS_Z, 40000); - process(mapper, ARBITRARY_TIME, READ_TIME, EV_MSC, MSC_TIMESTAMP, 1000); - process(mapper, ARBITRARY_TIME, READ_TIME, EV_SYN, SYN_REPORT, 0); + mFakePolicy->addInputPortAssociation(DEVICE_LOCATION, hdmi1); + mFakePolicy->addInputPortAssociation(USB2, hdmi2); - NotifySensorArgs args; - std::vector values = {20000.0f / ACCEL_RAW_RESOLUTION * GRAVITY_MS2_UNIT, - -20000.0f / ACCEL_RAW_RESOLUTION * GRAVITY_MS2_UNIT, - 40000.0f / ACCEL_RAW_RESOLUTION * GRAVITY_MS2_UNIT}; + // No associated display viewport found, should disable the device. + unused += device2->configure(ARBITRARY_TIME, mFakePolicy->getReaderConfiguration(), + InputReaderConfiguration::Change::DISPLAY_INFO); + ASSERT_FALSE(device2->isEnabled()); - ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifySensorWasCalled(&args)); - ASSERT_EQ(args.source, AINPUT_SOURCE_SENSOR); - ASSERT_EQ(args.deviceId, DEVICE_ID); - ASSERT_EQ(args.sensorType, InputDeviceSensorType::ACCELEROMETER); - ASSERT_EQ(args.accuracy, InputDeviceSensorAccuracy::ACCURACY_HIGH); - ASSERT_EQ(args.hwTimestamp, ARBITRARY_TIME); - ASSERT_EQ(args.values, values); - mapper.flushSensor(InputDeviceSensorType::ACCELEROMETER); -} + // Prepare second display. + constexpr int32_t newDisplayId = 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, + SECONDARY_UNIQUE_ID, hdmi2, ViewportType::EXTERNAL); + // Default device will reconfigure above, need additional reconfiguration for another device. + unused += device2->configure(ARBITRARY_TIME, mFakePolicy->getReaderConfiguration(), + InputReaderConfiguration::Change::DISPLAY_INFO); -TEST_F(SensorInputMapperTest, ProcessGyroscopeSensor) { - setGyroProperties(); - prepareGyroAxes(); - SensorInputMapper& mapper = addMapperAndConfigure(); + // Device should be enabled after the associated display is found. + ASSERT_TRUE(mDevice->isEnabled()); + ASSERT_TRUE(device2->isEnabled()); - ASSERT_TRUE(mapper.enableSensor(InputDeviceSensorType::GYROSCOPE, - std::chrono::microseconds(10000), - std::chrono::microseconds(0))); - ASSERT_TRUE(mFakeEventHub->isDeviceEnabled(EVENTHUB_ID)); - process(mapper, ARBITRARY_TIME, READ_TIME, EV_ABS, ABS_RX, 20000); - process(mapper, ARBITRARY_TIME, READ_TIME, EV_ABS, ABS_RY, -20000); - process(mapper, ARBITRARY_TIME, READ_TIME, EV_ABS, ABS_RZ, 40000); - process(mapper, ARBITRARY_TIME, READ_TIME, EV_MSC, MSC_TIMESTAMP, 1000); - process(mapper, ARBITRARY_TIME, READ_TIME, EV_SYN, SYN_REPORT, 0); + // Test pad key events + ASSERT_NO_FATAL_FAILURE( + testDPadKeyRotation(mapper, KEY_UP, AKEYCODE_DPAD_UP, AKEYCODE_DPAD_UP, DISPLAY_ID)); + ASSERT_NO_FATAL_FAILURE(testDPadKeyRotation(mapper, KEY_RIGHT, AKEYCODE_DPAD_RIGHT, + AKEYCODE_DPAD_RIGHT, DISPLAY_ID)); + ASSERT_NO_FATAL_FAILURE(testDPadKeyRotation(mapper, KEY_DOWN, AKEYCODE_DPAD_DOWN, + AKEYCODE_DPAD_DOWN, DISPLAY_ID)); + ASSERT_NO_FATAL_FAILURE(testDPadKeyRotation(mapper, KEY_LEFT, AKEYCODE_DPAD_LEFT, + AKEYCODE_DPAD_LEFT, DISPLAY_ID)); - NotifySensorArgs args; - std::vector values = {20000.0f / GYRO_RAW_RESOLUTION * DEGREE_RADIAN_UNIT, - -20000.0f / GYRO_RAW_RESOLUTION * DEGREE_RADIAN_UNIT, - 40000.0f / GYRO_RAW_RESOLUTION * DEGREE_RADIAN_UNIT}; + ASSERT_NO_FATAL_FAILURE( + testDPadKeyRotation(mapper2, KEY_UP, AKEYCODE_DPAD_UP, AKEYCODE_DPAD_UP, newDisplayId)); + ASSERT_NO_FATAL_FAILURE(testDPadKeyRotation(mapper2, KEY_RIGHT, AKEYCODE_DPAD_RIGHT, + AKEYCODE_DPAD_RIGHT, newDisplayId)); + ASSERT_NO_FATAL_FAILURE(testDPadKeyRotation(mapper2, KEY_DOWN, AKEYCODE_DPAD_DOWN, + AKEYCODE_DPAD_DOWN, newDisplayId)); + ASSERT_NO_FATAL_FAILURE(testDPadKeyRotation(mapper2, KEY_LEFT, AKEYCODE_DPAD_LEFT, + AKEYCODE_DPAD_LEFT, newDisplayId)); +} - ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifySensorWasCalled(&args)); - ASSERT_EQ(args.source, AINPUT_SOURCE_SENSOR); - ASSERT_EQ(args.deviceId, DEVICE_ID); - ASSERT_EQ(args.sensorType, InputDeviceSensorType::GYROSCOPE); - ASSERT_EQ(args.accuracy, InputDeviceSensorAccuracy::ACCURACY_HIGH); - ASSERT_EQ(args.hwTimestamp, ARBITRARY_TIME); - ASSERT_EQ(args.values, values); - mapper.flushSensor(InputDeviceSensorType::GYROSCOPE); -} +TEST_F(KeyboardInputMapperTest, Process_LockedKeysShouldToggleAfterReattach) { + mFakeEventHub->addLed(EVENTHUB_ID, LED_CAPSL, true /*initially on*/); + mFakeEventHub->addLed(EVENTHUB_ID, LED_NUML, false /*initially off*/); + mFakeEventHub->addLed(EVENTHUB_ID, LED_SCROLLL, false /*initially off*/); + mFakeEventHub->addKey(EVENTHUB_ID, KEY_CAPSLOCK, 0, AKEYCODE_CAPS_LOCK, 0); + mFakeEventHub->addKey(EVENTHUB_ID, KEY_NUMLOCK, 0, AKEYCODE_NUM_LOCK, 0); + mFakeEventHub->addKey(EVENTHUB_ID, KEY_SCROLLLOCK, 0, AKEYCODE_SCROLL_LOCK, 0); -// --- KeyboardInputMapperTest --- + KeyboardInputMapper& mapper = + constructAndAddMapper(AINPUT_SOURCE_KEYBOARD, + AINPUT_KEYBOARD_TYPE_ALPHABETIC); + // Initial metastate is AMETA_NONE. + ASSERT_EQ(AMETA_NONE, mapper.getMetaState()); -class KeyboardInputMapperTest : public InputMapperTest { -protected: - const std::string UNIQUE_ID = "local:0"; + // Initialization should have turned all of the lights off. + ASSERT_FALSE(mFakeEventHub->getLedState(EVENTHUB_ID, LED_CAPSL)); + ASSERT_FALSE(mFakeEventHub->getLedState(EVENTHUB_ID, LED_NUML)); + ASSERT_FALSE(mFakeEventHub->getLedState(EVENTHUB_ID, LED_SCROLLL)); - void prepareDisplay(int32_t orientation); + // Toggle caps lock on. + process(mapper, ARBITRARY_TIME, READ_TIME, EV_KEY, KEY_CAPSLOCK, 1); + process(mapper, ARBITRARY_TIME, READ_TIME, EV_KEY, KEY_CAPSLOCK, 0); + ASSERT_TRUE(mFakeEventHub->getLedState(EVENTHUB_ID, LED_CAPSL)); + ASSERT_EQ(AMETA_CAPS_LOCK_ON, mapper.getMetaState()); - void testDPadKeyRotation(KeyboardInputMapper& mapper, int32_t originalScanCode, - int32_t originalKeyCode, int32_t rotatedKeyCode, - int32_t displayId = ADISPLAY_ID_NONE); -}; + // Toggle num lock on. + process(mapper, ARBITRARY_TIME, READ_TIME, EV_KEY, KEY_NUMLOCK, 1); + process(mapper, ARBITRARY_TIME, READ_TIME, EV_KEY, KEY_NUMLOCK, 0); + ASSERT_TRUE(mFakeEventHub->getLedState(EVENTHUB_ID, LED_NUML)); + ASSERT_EQ(AMETA_CAPS_LOCK_ON | AMETA_NUM_LOCK_ON, mapper.getMetaState()); -/* Similar to setDisplayInfoAndReconfigure, but pre-populates all parameters except for the - * orientation. - */ -void KeyboardInputMapperTest::prepareDisplay(int32_t orientation) { - setDisplayInfoAndReconfigure(DISPLAY_ID, DISPLAY_WIDTH, DISPLAY_HEIGHT, orientation, UNIQUE_ID, - NO_PORT, ViewportType::INTERNAL); -} + // Toggle scroll lock on. + process(mapper, ARBITRARY_TIME, READ_TIME, EV_KEY, KEY_SCROLLLOCK, 1); + process(mapper, ARBITRARY_TIME, READ_TIME, EV_KEY, KEY_SCROLLLOCK, 0); + ASSERT_TRUE(mFakeEventHub->getLedState(EVENTHUB_ID, LED_SCROLLL)); + ASSERT_EQ(AMETA_CAPS_LOCK_ON | AMETA_NUM_LOCK_ON | AMETA_SCROLL_LOCK_ON, mapper.getMetaState()); -void KeyboardInputMapperTest::testDPadKeyRotation(KeyboardInputMapper& mapper, - int32_t originalScanCode, int32_t originalKeyCode, - int32_t rotatedKeyCode, int32_t displayId) { - NotifyKeyArgs args; + mFakeEventHub->removeDevice(EVENTHUB_ID); + mReader->loopOnce(); - process(mapper, ARBITRARY_TIME, READ_TIME, EV_KEY, originalScanCode, 1); - ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyKeyWasCalled(&args)); - ASSERT_EQ(AKEY_EVENT_ACTION_DOWN, args.action); - ASSERT_EQ(originalScanCode, args.scanCode); - ASSERT_EQ(rotatedKeyCode, args.keyCode); - ASSERT_EQ(displayId, args.displayId); + // keyboard 2 should default toggle keys. + const std::string USB2 = "USB2"; + const std::string DEVICE_NAME2 = "KEYBOARD2"; + 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->addLed(SECOND_EVENTHUB_ID, LED_CAPSL, true /*initially on*/); + mFakeEventHub->addLed(SECOND_EVENTHUB_ID, LED_NUML, false /*initially off*/); + mFakeEventHub->addLed(SECOND_EVENTHUB_ID, LED_SCROLLL, false /*initially off*/); + mFakeEventHub->addKey(SECOND_EVENTHUB_ID, KEY_CAPSLOCK, 0, AKEYCODE_CAPS_LOCK, 0); + mFakeEventHub->addKey(SECOND_EVENTHUB_ID, KEY_NUMLOCK, 0, AKEYCODE_NUM_LOCK, 0); + mFakeEventHub->addKey(SECOND_EVENTHUB_ID, KEY_SCROLLLOCK, 0, AKEYCODE_SCROLL_LOCK, 0); - process(mapper, ARBITRARY_TIME, READ_TIME, EV_KEY, originalScanCode, 0); - ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyKeyWasCalled(&args)); - ASSERT_EQ(AKEY_EVENT_ACTION_UP, args.action); - ASSERT_EQ(originalScanCode, args.scanCode); - ASSERT_EQ(rotatedKeyCode, args.keyCode); - ASSERT_EQ(displayId, args.displayId); + device2->addEmptyEventHubDevice(SECOND_EVENTHUB_ID); + KeyboardInputMapper& mapper2 = + device2->constructAndAddMapper(SECOND_EVENTHUB_ID, + mFakePolicy + ->getReaderConfiguration(), + AINPUT_SOURCE_KEYBOARD, + AINPUT_KEYBOARD_TYPE_ALPHABETIC); + std::list unused = + device2->configure(ARBITRARY_TIME, mFakePolicy->getReaderConfiguration(), + /*changes=*/{}); + unused += device2->reset(ARBITRARY_TIME); + + ASSERT_TRUE(mFakeEventHub->getLedState(SECOND_EVENTHUB_ID, LED_CAPSL)); + ASSERT_TRUE(mFakeEventHub->getLedState(SECOND_EVENTHUB_ID, LED_NUML)); + ASSERT_TRUE(mFakeEventHub->getLedState(SECOND_EVENTHUB_ID, LED_SCROLLL)); + ASSERT_EQ(AMETA_CAPS_LOCK_ON | AMETA_NUM_LOCK_ON | AMETA_SCROLL_LOCK_ON, + mapper2.getMetaState()); } -TEST_F(KeyboardInputMapperTest, GetSources) { +TEST_F(KeyboardInputMapperTest, Process_toggleCapsLockState) { + mFakeEventHub->addKey(EVENTHUB_ID, KEY_CAPSLOCK, 0, AKEYCODE_CAPS_LOCK, 0); + mFakeEventHub->addKey(EVENTHUB_ID, KEY_NUMLOCK, 0, AKEYCODE_NUM_LOCK, 0); + 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); KeyboardInputMapper& mapper = - addMapperAndConfigure(AINPUT_SOURCE_KEYBOARD, + constructAndAddMapper(AINPUT_SOURCE_KEYBOARD, AINPUT_KEYBOARD_TYPE_ALPHABETIC); + // Initial metastate is AMETA_NONE. + ASSERT_EQ(AMETA_NONE, mapper.getMetaState()); - ASSERT_EQ(AINPUT_SOURCE_KEYBOARD, mapper.getSources()); + mReader->toggleCapsLockState(DEVICE_ID); + ASSERT_EQ(AMETA_CAPS_LOCK_ON, mapper.getMetaState()); } -TEST_F(KeyboardInputMapperTest, Process_SimpleKeyPress) { +TEST_F(KeyboardInputMapperTest, Process_LockedKeysShouldToggleInMultiDevices) { + // keyboard 1. + mFakeEventHub->addLed(EVENTHUB_ID, LED_CAPSL, true /*initially on*/); + mFakeEventHub->addLed(EVENTHUB_ID, LED_NUML, false /*initially off*/); + mFakeEventHub->addLed(EVENTHUB_ID, LED_SCROLLL, false /*initially off*/); + mFakeEventHub->addKey(EVENTHUB_ID, KEY_CAPSLOCK, 0, AKEYCODE_CAPS_LOCK, 0); + mFakeEventHub->addKey(EVENTHUB_ID, KEY_NUMLOCK, 0, AKEYCODE_NUM_LOCK, 0); + mFakeEventHub->addKey(EVENTHUB_ID, KEY_SCROLLLOCK, 0, AKEYCODE_SCROLL_LOCK, 0); + + KeyboardInputMapper& mapper1 = + constructAndAddMapper(AINPUT_SOURCE_KEYBOARD, + AINPUT_KEYBOARD_TYPE_ALPHABETIC); + + // keyboard 2. + const std::string USB2 = "USB2"; + const std::string DEVICE_NAME2 = "KEYBOARD2"; + 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->addLed(SECOND_EVENTHUB_ID, LED_CAPSL, true /*initially on*/); + mFakeEventHub->addLed(SECOND_EVENTHUB_ID, LED_NUML, false /*initially off*/); + mFakeEventHub->addLed(SECOND_EVENTHUB_ID, LED_SCROLLL, false /*initially off*/); + mFakeEventHub->addKey(SECOND_EVENTHUB_ID, KEY_CAPSLOCK, 0, AKEYCODE_CAPS_LOCK, 0); + mFakeEventHub->addKey(SECOND_EVENTHUB_ID, KEY_NUMLOCK, 0, AKEYCODE_NUM_LOCK, 0); + mFakeEventHub->addKey(SECOND_EVENTHUB_ID, KEY_SCROLLLOCK, 0, AKEYCODE_SCROLL_LOCK, 0); + + device2->addEmptyEventHubDevice(SECOND_EVENTHUB_ID); + KeyboardInputMapper& mapper2 = + device2->constructAndAddMapper(SECOND_EVENTHUB_ID, + mFakePolicy + ->getReaderConfiguration(), + AINPUT_SOURCE_KEYBOARD, + AINPUT_KEYBOARD_TYPE_ALPHABETIC); + std::list unused = + device2->configure(ARBITRARY_TIME, mFakePolicy->getReaderConfiguration(), + /*changes=*/{}); + unused += device2->reset(ARBITRARY_TIME); + + // Initial metastate is AMETA_NONE. + ASSERT_EQ(AMETA_NONE, mapper1.getMetaState()); + ASSERT_EQ(AMETA_NONE, mapper2.getMetaState()); + + // Toggle num lock on and off. + process(mapper1, ARBITRARY_TIME, READ_TIME, EV_KEY, KEY_NUMLOCK, 1); + process(mapper1, ARBITRARY_TIME, READ_TIME, EV_KEY, KEY_NUMLOCK, 0); + ASSERT_TRUE(mFakeEventHub->getLedState(EVENTHUB_ID, LED_NUML)); + ASSERT_EQ(AMETA_NUM_LOCK_ON, mapper1.getMetaState()); + ASSERT_EQ(AMETA_NUM_LOCK_ON, mapper2.getMetaState()); + + process(mapper1, ARBITRARY_TIME, READ_TIME, EV_KEY, KEY_NUMLOCK, 1); + process(mapper1, ARBITRARY_TIME, READ_TIME, EV_KEY, KEY_NUMLOCK, 0); + ASSERT_FALSE(mFakeEventHub->getLedState(EVENTHUB_ID, LED_NUML)); + ASSERT_EQ(AMETA_NONE, mapper1.getMetaState()); + ASSERT_EQ(AMETA_NONE, mapper2.getMetaState()); + + // Toggle caps lock on and off. + process(mapper1, ARBITRARY_TIME, READ_TIME, EV_KEY, KEY_CAPSLOCK, 1); + process(mapper1, ARBITRARY_TIME, READ_TIME, EV_KEY, KEY_CAPSLOCK, 0); + ASSERT_TRUE(mFakeEventHub->getLedState(EVENTHUB_ID, LED_CAPSL)); + ASSERT_EQ(AMETA_CAPS_LOCK_ON, mapper1.getMetaState()); + ASSERT_EQ(AMETA_CAPS_LOCK_ON, mapper2.getMetaState()); + + process(mapper1, ARBITRARY_TIME, READ_TIME, EV_KEY, KEY_CAPSLOCK, 1); + process(mapper1, ARBITRARY_TIME, READ_TIME, EV_KEY, KEY_CAPSLOCK, 0); + ASSERT_FALSE(mFakeEventHub->getLedState(EVENTHUB_ID, LED_CAPSL)); + ASSERT_EQ(AMETA_NONE, mapper1.getMetaState()); + ASSERT_EQ(AMETA_NONE, mapper2.getMetaState()); + + // Toggle scroll lock on and off. + process(mapper1, ARBITRARY_TIME, READ_TIME, EV_KEY, KEY_SCROLLLOCK, 1); + process(mapper1, ARBITRARY_TIME, READ_TIME, EV_KEY, KEY_SCROLLLOCK, 0); + ASSERT_TRUE(mFakeEventHub->getLedState(EVENTHUB_ID, LED_SCROLLL)); + ASSERT_EQ(AMETA_SCROLL_LOCK_ON, mapper1.getMetaState()); + ASSERT_EQ(AMETA_SCROLL_LOCK_ON, mapper2.getMetaState()); + + process(mapper1, ARBITRARY_TIME, READ_TIME, EV_KEY, KEY_SCROLLLOCK, 1); + process(mapper1, ARBITRARY_TIME, READ_TIME, EV_KEY, KEY_SCROLLLOCK, 0); + ASSERT_FALSE(mFakeEventHub->getLedState(EVENTHUB_ID, LED_SCROLLL)); + ASSERT_EQ(AMETA_NONE, mapper1.getMetaState()); + ASSERT_EQ(AMETA_NONE, mapper2.getMetaState()); +} + +TEST_F(KeyboardInputMapperTest, Process_DisabledDevice) { const int32_t USAGE_A = 0x070004; - const int32_t USAGE_UNKNOWN = 0x07ffff; mFakeEventHub->addKey(EVENTHUB_ID, KEY_HOME, 0, AKEYCODE_HOME, POLICY_FLAG_WAKE); mFakeEventHub->addKey(EVENTHUB_ID, 0, USAGE_A, AKEYCODE_A, POLICY_FLAG_WAKE); - mFakeEventHub->addKey(EVENTHUB_ID, 0, KEY_NUMLOCK, AKEYCODE_NUM_LOCK, POLICY_FLAG_WAKE); - mFakeEventHub->addKey(EVENTHUB_ID, 0, KEY_CAPSLOCK, AKEYCODE_CAPS_LOCK, POLICY_FLAG_WAKE); - mFakeEventHub->addKey(EVENTHUB_ID, 0, KEY_SCROLLLOCK, AKEYCODE_SCROLL_LOCK, POLICY_FLAG_WAKE); KeyboardInputMapper& mapper = - addMapperAndConfigure(AINPUT_SOURCE_KEYBOARD, + constructAndAddMapper(AINPUT_SOURCE_KEYBOARD, AINPUT_KEYBOARD_TYPE_ALPHABETIC); - // Initial metastate is AMETA_NONE. - ASSERT_EQ(AMETA_NONE, mapper.getMetaState()); - // Key down by scan code. process(mapper, ARBITRARY_TIME, READ_TIME, EV_KEY, KEY_HOME, 1); NotifyKeyArgs args; @@ -3437,3694 +3672,3956 @@ TEST_F(KeyboardInputMapperTest, Process_SimpleKeyPress) { ASSERT_EQ(AKEY_EVENT_ACTION_DOWN, args.action); ASSERT_EQ(AKEYCODE_HOME, args.keyCode); ASSERT_EQ(KEY_HOME, args.scanCode); - ASSERT_EQ(AMETA_NONE, args.metaState); ASSERT_EQ(AKEY_EVENT_FLAG_FROM_SYSTEM, args.flags); - ASSERT_EQ(POLICY_FLAG_WAKE, args.policyFlags); - ASSERT_EQ(ARBITRARY_TIME, args.downTime); - // Key up by scan code. - process(mapper, ARBITRARY_TIME + 1, READ_TIME, EV_KEY, KEY_HOME, 0); + // Disable device, it should synthesize cancellation events for down events. + mFakePolicy->addDisabledDevice(DEVICE_ID); + configureDevice(InputReaderConfiguration::Change::ENABLED_STATE); + ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyKeyWasCalled(&args)); - ASSERT_EQ(DEVICE_ID, args.deviceId); - ASSERT_EQ(AINPUT_SOURCE_KEYBOARD, args.source); - ASSERT_EQ(ARBITRARY_TIME + 1, args.eventTime); ASSERT_EQ(AKEY_EVENT_ACTION_UP, args.action); ASSERT_EQ(AKEYCODE_HOME, args.keyCode); ASSERT_EQ(KEY_HOME, args.scanCode); - ASSERT_EQ(AMETA_NONE, args.metaState); - ASSERT_EQ(AKEY_EVENT_FLAG_FROM_SYSTEM, args.flags); + ASSERT_EQ(AKEY_EVENT_FLAG_FROM_SYSTEM | AKEY_EVENT_FLAG_CANCELED, args.flags); +} + +TEST_F(KeyboardInputMapperTest, Configure_AssignKeyboardLayoutInfo) { + constructAndAddMapper(AINPUT_SOURCE_KEYBOARD, + AINPUT_KEYBOARD_TYPE_ALPHABETIC); + std::list unused = + mDevice->configure(ARBITRARY_TIME, mFakePolicy->getReaderConfiguration(), + /*changes=*/{}); + + uint32_t generation = mReader->getContext()->getGeneration(); + mFakePolicy->addKeyboardLayoutAssociation(DEVICE_LOCATION, DEVICE_KEYBOARD_LAYOUT_INFO); + + unused += mDevice->configure(ARBITRARY_TIME, mFakePolicy->getReaderConfiguration(), + InputReaderConfiguration::Change::KEYBOARD_LAYOUT_ASSOCIATION); + + InputDeviceInfo deviceInfo = mDevice->getDeviceInfo(); + ASSERT_EQ(DEVICE_KEYBOARD_LAYOUT_INFO.languageTag, + deviceInfo.getKeyboardLayoutInfo()->languageTag); + ASSERT_EQ(DEVICE_KEYBOARD_LAYOUT_INFO.layoutType, + deviceInfo.getKeyboardLayoutInfo()->layoutType); + ASSERT_TRUE(mReader->getContext()->getGeneration() != generation); + + // Call change layout association with the same values: Generation shouldn't change + generation = mReader->getContext()->getGeneration(); + mFakePolicy->addKeyboardLayoutAssociation(DEVICE_LOCATION, DEVICE_KEYBOARD_LAYOUT_INFO); + unused += mDevice->configure(ARBITRARY_TIME, mFakePolicy->getReaderConfiguration(), + InputReaderConfiguration::Change::KEYBOARD_LAYOUT_ASSOCIATION); + ASSERT_TRUE(mReader->getContext()->getGeneration() == generation); +} + +TEST_F(KeyboardInputMapperTest, LayoutInfoCorrectlyMapped) { + mFakeEventHub->setRawLayoutInfo(EVENTHUB_ID, + RawLayoutInfo{.languageTag = "en", .layoutType = "extended"}); + + // Configuration + constructAndAddMapper(AINPUT_SOURCE_KEYBOARD, + AINPUT_KEYBOARD_TYPE_ALPHABETIC); + InputReaderConfiguration config; + std::list unused = mDevice->configure(ARBITRARY_TIME, config, /*changes=*/{}); + + ASSERT_EQ("en", mDevice->getDeviceInfo().getKeyboardLayoutInfo()->languageTag); + ASSERT_EQ("extended", mDevice->getDeviceInfo().getKeyboardLayoutInfo()->layoutType); +} + +// --- KeyboardInputMapperTest_ExternalDevice --- + +class KeyboardInputMapperTest_ExternalDevice : public InputMapperTest { +protected: + void SetUp() override { InputMapperTest::SetUp(DEVICE_CLASSES | InputDeviceClass::EXTERNAL); } +}; + +TEST_F(KeyboardInputMapperTest_ExternalDevice, WakeBehavior) { + // For external devices, keys will trigger wake on key down. Media keys should also trigger + // wake if triggered from external devices. + + mFakeEventHub->addKey(EVENTHUB_ID, KEY_HOME, 0, AKEYCODE_HOME, 0); + mFakeEventHub->addKey(EVENTHUB_ID, KEY_PLAY, 0, AKEYCODE_MEDIA_PLAY, 0); + mFakeEventHub->addKey(EVENTHUB_ID, KEY_PLAYPAUSE, 0, AKEYCODE_MEDIA_PLAY_PAUSE, + POLICY_FLAG_WAKE); + + KeyboardInputMapper& mapper = + constructAndAddMapper(AINPUT_SOURCE_KEYBOARD, + AINPUT_KEYBOARD_TYPE_ALPHABETIC); + + process(mapper, ARBITRARY_TIME, READ_TIME, EV_KEY, KEY_HOME, 1); + NotifyKeyArgs args; + ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyKeyWasCalled(&args)); ASSERT_EQ(POLICY_FLAG_WAKE, args.policyFlags); - ASSERT_EQ(ARBITRARY_TIME, args.downTime); - // Key down by usage code. - process(mapper, ARBITRARY_TIME, READ_TIME, EV_MSC, MSC_SCAN, USAGE_A); - process(mapper, ARBITRARY_TIME, READ_TIME, EV_KEY, 0, 1); + process(mapper, ARBITRARY_TIME + 1, READ_TIME, EV_KEY, KEY_HOME, 0); + ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyKeyWasCalled(&args)); + ASSERT_EQ(uint32_t(0), args.policyFlags); + + process(mapper, ARBITRARY_TIME, READ_TIME, EV_KEY, KEY_PLAY, 1); ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyKeyWasCalled(&args)); - ASSERT_EQ(DEVICE_ID, args.deviceId); - ASSERT_EQ(AINPUT_SOURCE_KEYBOARD, args.source); - ASSERT_EQ(ARBITRARY_TIME, args.eventTime); - ASSERT_EQ(AKEY_EVENT_ACTION_DOWN, args.action); - ASSERT_EQ(AKEYCODE_A, args.keyCode); - ASSERT_EQ(0, args.scanCode); - ASSERT_EQ(AMETA_NONE, args.metaState); - ASSERT_EQ(AKEY_EVENT_FLAG_FROM_SYSTEM, args.flags); ASSERT_EQ(POLICY_FLAG_WAKE, args.policyFlags); - ASSERT_EQ(ARBITRARY_TIME, args.downTime); - // Key up by usage code. - process(mapper, ARBITRARY_TIME, READ_TIME, EV_MSC, MSC_SCAN, USAGE_A); - process(mapper, ARBITRARY_TIME + 1, READ_TIME, EV_KEY, 0, 0); + process(mapper, ARBITRARY_TIME + 1, READ_TIME, EV_KEY, KEY_PLAY, 0); ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyKeyWasCalled(&args)); - ASSERT_EQ(DEVICE_ID, args.deviceId); - ASSERT_EQ(AINPUT_SOURCE_KEYBOARD, args.source); - ASSERT_EQ(ARBITRARY_TIME + 1, args.eventTime); - ASSERT_EQ(AKEY_EVENT_ACTION_UP, args.action); - ASSERT_EQ(AKEYCODE_A, args.keyCode); - ASSERT_EQ(0, args.scanCode); - ASSERT_EQ(AMETA_NONE, args.metaState); - ASSERT_EQ(AKEY_EVENT_FLAG_FROM_SYSTEM, args.flags); - ASSERT_EQ(POLICY_FLAG_WAKE, args.policyFlags); - ASSERT_EQ(ARBITRARY_TIME, args.downTime); + ASSERT_EQ(uint32_t(0), args.policyFlags); - // Key down with unknown scan code or usage code. - process(mapper, ARBITRARY_TIME, READ_TIME, EV_MSC, MSC_SCAN, USAGE_UNKNOWN); - process(mapper, ARBITRARY_TIME, READ_TIME, EV_KEY, KEY_UNKNOWN, 1); + process(mapper, ARBITRARY_TIME, READ_TIME, EV_KEY, KEY_PLAYPAUSE, 1); ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyKeyWasCalled(&args)); - ASSERT_EQ(DEVICE_ID, args.deviceId); - ASSERT_EQ(AINPUT_SOURCE_KEYBOARD, args.source); - ASSERT_EQ(ARBITRARY_TIME, args.eventTime); - ASSERT_EQ(AKEY_EVENT_ACTION_DOWN, args.action); - ASSERT_EQ(0, args.keyCode); - ASSERT_EQ(KEY_UNKNOWN, args.scanCode); - ASSERT_EQ(AMETA_NONE, args.metaState); - ASSERT_EQ(AKEY_EVENT_FLAG_FROM_SYSTEM, args.flags); - ASSERT_EQ(0U, args.policyFlags); - ASSERT_EQ(ARBITRARY_TIME, args.downTime); + ASSERT_EQ(POLICY_FLAG_WAKE, args.policyFlags); - // Key up with unknown scan code or usage code. - process(mapper, ARBITRARY_TIME, READ_TIME, EV_MSC, MSC_SCAN, USAGE_UNKNOWN); - process(mapper, ARBITRARY_TIME + 1, READ_TIME, EV_KEY, KEY_UNKNOWN, 0); + process(mapper, ARBITRARY_TIME + 1, READ_TIME, EV_KEY, KEY_PLAYPAUSE, 0); ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyKeyWasCalled(&args)); - ASSERT_EQ(DEVICE_ID, args.deviceId); - ASSERT_EQ(AINPUT_SOURCE_KEYBOARD, args.source); - ASSERT_EQ(ARBITRARY_TIME + 1, args.eventTime); - ASSERT_EQ(AKEY_EVENT_ACTION_UP, args.action); - ASSERT_EQ(0, args.keyCode); - ASSERT_EQ(KEY_UNKNOWN, args.scanCode); - ASSERT_EQ(AMETA_NONE, args.metaState); - ASSERT_EQ(AKEY_EVENT_FLAG_FROM_SYSTEM, args.flags); - ASSERT_EQ(0U, args.policyFlags); - ASSERT_EQ(ARBITRARY_TIME, args.downTime); + ASSERT_EQ(POLICY_FLAG_WAKE, args.policyFlags); } -/** - * Ensure that the readTime is set to the time when the EV_KEY is received. - */ -TEST_F(KeyboardInputMapperTest, Process_SendsReadTime) { +TEST_F(KeyboardInputMapperTest_ExternalDevice, 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); + mFakeEventHub->addKey(EVENTHUB_ID, KEY_DOWN, 0, AKEYCODE_DPAD_DOWN, 0); + mFakeEventHub->addKey(EVENTHUB_ID, KEY_PLAY, 0, AKEYCODE_MEDIA_PLAY, POLICY_FLAG_WAKE); + addConfigurationProperty("keyboard.doNotWakeByDefault", "1"); KeyboardInputMapper& mapper = - addMapperAndConfigure(AINPUT_SOURCE_KEYBOARD, + constructAndAddMapper(AINPUT_SOURCE_KEYBOARD, AINPUT_KEYBOARD_TYPE_ALPHABETIC); - NotifyKeyArgs args; - // Key down - process(mapper, ARBITRARY_TIME, 12 /*readTime*/, EV_KEY, KEY_HOME, 1); + process(mapper, ARBITRARY_TIME, READ_TIME, EV_KEY, KEY_HOME, 1); + NotifyKeyArgs args; ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyKeyWasCalled(&args)); - ASSERT_EQ(12, args.readTime); + ASSERT_EQ(POLICY_FLAG_WAKE, args.policyFlags); - // Key up - process(mapper, ARBITRARY_TIME, 15 /*readTime*/, EV_KEY, KEY_HOME, 1); + process(mapper, ARBITRARY_TIME + 1, READ_TIME, EV_KEY, KEY_HOME, 0); ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyKeyWasCalled(&args)); - ASSERT_EQ(15, args.readTime); -} - -TEST_F(KeyboardInputMapperTest, Process_ShouldUpdateMetaState) { - mFakeEventHub->addKey(EVENTHUB_ID, KEY_LEFTSHIFT, 0, AKEYCODE_SHIFT_LEFT, 0); - mFakeEventHub->addKey(EVENTHUB_ID, KEY_A, 0, AKEYCODE_A, 0); - mFakeEventHub->addKey(EVENTHUB_ID, 0, KEY_NUMLOCK, AKEYCODE_NUM_LOCK, 0); - mFakeEventHub->addKey(EVENTHUB_ID, 0, KEY_CAPSLOCK, AKEYCODE_CAPS_LOCK, 0); - mFakeEventHub->addKey(EVENTHUB_ID, 0, KEY_SCROLLLOCK, AKEYCODE_SCROLL_LOCK, 0); - - KeyboardInputMapper& mapper = - addMapperAndConfigure(AINPUT_SOURCE_KEYBOARD, - AINPUT_KEYBOARD_TYPE_ALPHABETIC); - - // Initial metastate is AMETA_NONE. - ASSERT_EQ(AMETA_NONE, mapper.getMetaState()); + ASSERT_EQ(POLICY_FLAG_WAKE, args.policyFlags); - // Metakey down. - process(mapper, ARBITRARY_TIME, READ_TIME, EV_KEY, KEY_LEFTSHIFT, 1); - NotifyKeyArgs args; + process(mapper, ARBITRARY_TIME, READ_TIME, EV_KEY, KEY_DOWN, 1); ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyKeyWasCalled(&args)); - ASSERT_EQ(AMETA_SHIFT_LEFT_ON | AMETA_SHIFT_ON, args.metaState); - ASSERT_EQ(AMETA_SHIFT_LEFT_ON | AMETA_SHIFT_ON, mapper.getMetaState()); - ASSERT_NO_FATAL_FAILURE(mReader->getContext()->assertUpdateGlobalMetaStateWasCalled()); + ASSERT_EQ(uint32_t(0), args.policyFlags); - // Key down. - process(mapper, ARBITRARY_TIME + 1, READ_TIME, EV_KEY, KEY_A, 1); + process(mapper, ARBITRARY_TIME + 1, READ_TIME, EV_KEY, KEY_DOWN, 0); ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyKeyWasCalled(&args)); - ASSERT_EQ(AMETA_SHIFT_LEFT_ON | AMETA_SHIFT_ON, args.metaState); - ASSERT_EQ(AMETA_SHIFT_LEFT_ON | AMETA_SHIFT_ON, mapper.getMetaState()); + ASSERT_EQ(uint32_t(0), args.policyFlags); - // Key up. - process(mapper, ARBITRARY_TIME + 2, READ_TIME, EV_KEY, KEY_A, 0); + process(mapper, ARBITRARY_TIME, READ_TIME, EV_KEY, KEY_PLAY, 1); ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyKeyWasCalled(&args)); - ASSERT_EQ(AMETA_SHIFT_LEFT_ON | AMETA_SHIFT_ON, args.metaState); - ASSERT_EQ(AMETA_SHIFT_LEFT_ON | AMETA_SHIFT_ON, mapper.getMetaState()); + ASSERT_EQ(POLICY_FLAG_WAKE, args.policyFlags); - // Metakey up. - process(mapper, ARBITRARY_TIME + 3, READ_TIME, EV_KEY, KEY_LEFTSHIFT, 0); + process(mapper, ARBITRARY_TIME + 1, READ_TIME, EV_KEY, KEY_PLAY, 0); ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyKeyWasCalled(&args)); - ASSERT_EQ(AMETA_NONE, args.metaState); - ASSERT_EQ(AMETA_NONE, mapper.getMetaState()); - ASSERT_NO_FATAL_FAILURE(mReader->getContext()->assertUpdateGlobalMetaStateWasCalled()); + ASSERT_EQ(POLICY_FLAG_WAKE, args.policyFlags); } -TEST_F(KeyboardInputMapperTest, Process_WhenNotOrientationAware_ShouldNotRotateDPad) { - mFakeEventHub->addKey(EVENTHUB_ID, KEY_UP, 0, AKEYCODE_DPAD_UP, 0); - mFakeEventHub->addKey(EVENTHUB_ID, KEY_RIGHT, 0, AKEYCODE_DPAD_RIGHT, 0); - mFakeEventHub->addKey(EVENTHUB_ID, KEY_DOWN, 0, AKEYCODE_DPAD_DOWN, 0); - mFakeEventHub->addKey(EVENTHUB_ID, KEY_LEFT, 0, AKEYCODE_DPAD_LEFT, 0); +// --- CursorInputMapperTest --- - KeyboardInputMapper& mapper = - addMapperAndConfigure(AINPUT_SOURCE_KEYBOARD, - AINPUT_KEYBOARD_TYPE_ALPHABETIC); +class CursorInputMapperTest : public InputMapperTest { +protected: + static const int32_t TRACKBALL_MOVEMENT_THRESHOLD; - prepareDisplay(DISPLAY_ORIENTATION_90); - ASSERT_NO_FATAL_FAILURE(testDPadKeyRotation(mapper, - KEY_UP, AKEYCODE_DPAD_UP, AKEYCODE_DPAD_UP)); - ASSERT_NO_FATAL_FAILURE(testDPadKeyRotation(mapper, - KEY_RIGHT, AKEYCODE_DPAD_RIGHT, AKEYCODE_DPAD_RIGHT)); - ASSERT_NO_FATAL_FAILURE(testDPadKeyRotation(mapper, - KEY_DOWN, AKEYCODE_DPAD_DOWN, AKEYCODE_DPAD_DOWN)); - ASSERT_NO_FATAL_FAILURE(testDPadKeyRotation(mapper, - KEY_LEFT, AKEYCODE_DPAD_LEFT, AKEYCODE_DPAD_LEFT)); -} + std::shared_ptr mFakePointerController; -TEST_F(KeyboardInputMapperTest, Process_WhenOrientationAware_ShouldRotateDPad) { - mFakeEventHub->addKey(EVENTHUB_ID, KEY_UP, 0, AKEYCODE_DPAD_UP, 0); - mFakeEventHub->addKey(EVENTHUB_ID, KEY_RIGHT, 0, AKEYCODE_DPAD_RIGHT, 0); - mFakeEventHub->addKey(EVENTHUB_ID, KEY_DOWN, 0, AKEYCODE_DPAD_DOWN, 0); - mFakeEventHub->addKey(EVENTHUB_ID, KEY_LEFT, 0, AKEYCODE_DPAD_LEFT, 0); + void SetUp() override { + InputMapperTest::SetUp(); - addConfigurationProperty("keyboard.orientationAware", "1"); - KeyboardInputMapper& mapper = - addMapperAndConfigure(AINPUT_SOURCE_KEYBOARD, - AINPUT_KEYBOARD_TYPE_ALPHABETIC); + mFakePointerController = std::make_shared(); + mFakePolicy->setPointerController(mFakePointerController); + } - prepareDisplay(DISPLAY_ORIENTATION_0); - ASSERT_NO_FATAL_FAILURE( - testDPadKeyRotation(mapper, KEY_UP, AKEYCODE_DPAD_UP, AKEYCODE_DPAD_UP, DISPLAY_ID)); - ASSERT_NO_FATAL_FAILURE(testDPadKeyRotation(mapper, KEY_RIGHT, AKEYCODE_DPAD_RIGHT, - AKEYCODE_DPAD_RIGHT, DISPLAY_ID)); - ASSERT_NO_FATAL_FAILURE(testDPadKeyRotation(mapper, KEY_DOWN, AKEYCODE_DPAD_DOWN, - AKEYCODE_DPAD_DOWN, DISPLAY_ID)); - ASSERT_NO_FATAL_FAILURE(testDPadKeyRotation(mapper, KEY_LEFT, AKEYCODE_DPAD_LEFT, - AKEYCODE_DPAD_LEFT, DISPLAY_ID)); + void testMotionRotation(CursorInputMapper& mapper, int32_t originalX, int32_t originalY, + int32_t rotatedX, int32_t rotatedY); - clearViewports(); - prepareDisplay(DISPLAY_ORIENTATION_90); - ASSERT_NO_FATAL_FAILURE( - testDPadKeyRotation(mapper, KEY_UP, AKEYCODE_DPAD_UP, AKEYCODE_DPAD_LEFT, DISPLAY_ID)); - ASSERT_NO_FATAL_FAILURE(testDPadKeyRotation(mapper, KEY_RIGHT, AKEYCODE_DPAD_RIGHT, - AKEYCODE_DPAD_UP, DISPLAY_ID)); - ASSERT_NO_FATAL_FAILURE(testDPadKeyRotation(mapper, KEY_DOWN, AKEYCODE_DPAD_DOWN, - AKEYCODE_DPAD_RIGHT, DISPLAY_ID)); - ASSERT_NO_FATAL_FAILURE(testDPadKeyRotation(mapper, KEY_LEFT, AKEYCODE_DPAD_LEFT, - AKEYCODE_DPAD_DOWN, DISPLAY_ID)); + void prepareDisplay(ui::Rotation orientation) { + setDisplayInfoAndReconfigure(DISPLAY_ID, DISPLAY_WIDTH, DISPLAY_HEIGHT, orientation, + DISPLAY_UNIQUE_ID, NO_PORT, ViewportType::INTERNAL); + } - clearViewports(); - prepareDisplay(DISPLAY_ORIENTATION_180); - ASSERT_NO_FATAL_FAILURE( - testDPadKeyRotation(mapper, KEY_UP, AKEYCODE_DPAD_UP, AKEYCODE_DPAD_DOWN, DISPLAY_ID)); - ASSERT_NO_FATAL_FAILURE(testDPadKeyRotation(mapper, KEY_RIGHT, AKEYCODE_DPAD_RIGHT, - AKEYCODE_DPAD_LEFT, DISPLAY_ID)); - ASSERT_NO_FATAL_FAILURE(testDPadKeyRotation(mapper, KEY_DOWN, AKEYCODE_DPAD_DOWN, - AKEYCODE_DPAD_UP, DISPLAY_ID)); - ASSERT_NO_FATAL_FAILURE(testDPadKeyRotation(mapper, KEY_LEFT, AKEYCODE_DPAD_LEFT, - AKEYCODE_DPAD_RIGHT, DISPLAY_ID)); + void prepareSecondaryDisplay() { + setDisplayInfoAndReconfigure(SECONDARY_DISPLAY_ID, DISPLAY_WIDTH, DISPLAY_HEIGHT, + ui::ROTATION_0, SECONDARY_DISPLAY_UNIQUE_ID, NO_PORT, + ViewportType::EXTERNAL); + } - clearViewports(); - prepareDisplay(DISPLAY_ORIENTATION_270); - ASSERT_NO_FATAL_FAILURE( - testDPadKeyRotation(mapper, KEY_UP, AKEYCODE_DPAD_UP, AKEYCODE_DPAD_RIGHT, DISPLAY_ID)); - ASSERT_NO_FATAL_FAILURE(testDPadKeyRotation(mapper, KEY_RIGHT, AKEYCODE_DPAD_RIGHT, - AKEYCODE_DPAD_DOWN, DISPLAY_ID)); - ASSERT_NO_FATAL_FAILURE(testDPadKeyRotation(mapper, KEY_DOWN, AKEYCODE_DPAD_DOWN, - AKEYCODE_DPAD_LEFT, DISPLAY_ID)); - ASSERT_NO_FATAL_FAILURE(testDPadKeyRotation(mapper, KEY_LEFT, AKEYCODE_DPAD_LEFT, - AKEYCODE_DPAD_UP, DISPLAY_ID)); + static void assertCursorPointerCoords(const PointerCoords& coords, float x, float y, + float pressure) { + ASSERT_NO_FATAL_FAILURE(assertPointerCoords(coords, x, y, pressure, 0.0f, 0.0f, 0.0f, 0.0f, + 0.0f, 0.0f, 0.0f, EPSILON)); + } +}; - // Special case: if orientation changes while key is down, we still emit the same keycode - // in the key up as we did in the key down. - NotifyKeyArgs args; - clearViewports(); - prepareDisplay(DISPLAY_ORIENTATION_270); - process(mapper, ARBITRARY_TIME, READ_TIME, EV_KEY, KEY_UP, 1); - ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyKeyWasCalled(&args)); - ASSERT_EQ(AKEY_EVENT_ACTION_DOWN, args.action); - ASSERT_EQ(KEY_UP, args.scanCode); - ASSERT_EQ(AKEYCODE_DPAD_RIGHT, args.keyCode); +const int32_t CursorInputMapperTest::TRACKBALL_MOVEMENT_THRESHOLD = 6; - clearViewports(); - prepareDisplay(DISPLAY_ORIENTATION_180); - process(mapper, ARBITRARY_TIME, READ_TIME, EV_KEY, KEY_UP, 0); - ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyKeyWasCalled(&args)); - ASSERT_EQ(AKEY_EVENT_ACTION_UP, args.action); - ASSERT_EQ(KEY_UP, args.scanCode); - ASSERT_EQ(AKEYCODE_DPAD_RIGHT, args.keyCode); +void CursorInputMapperTest::testMotionRotation(CursorInputMapper& mapper, int32_t originalX, + int32_t originalY, int32_t rotatedX, + int32_t rotatedY) { + NotifyMotionArgs args; + + process(mapper, ARBITRARY_TIME, READ_TIME, EV_REL, REL_X, originalX); + process(mapper, ARBITRARY_TIME, READ_TIME, EV_REL, REL_Y, originalY); + process(mapper, ARBITRARY_TIME, READ_TIME, EV_SYN, SYN_REPORT, 0); + ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&args)); + ASSERT_EQ(AMOTION_EVENT_ACTION_MOVE, args.action); + ASSERT_NO_FATAL_FAILURE( + assertCursorPointerCoords(args.pointerCoords[0], + float(rotatedX) / TRACKBALL_MOVEMENT_THRESHOLD, + float(rotatedY) / TRACKBALL_MOVEMENT_THRESHOLD, 0.0f)); } -TEST_F(KeyboardInputMapperTest, DisplayIdConfigurationChange_NotOrientationAware) { - // If the keyboard is not orientation aware, - // key events should not be associated with a specific display id - mFakeEventHub->addKey(EVENTHUB_ID, KEY_UP, 0, AKEYCODE_DPAD_UP, 0); +TEST_F(CursorInputMapperTest, WhenModeIsPointer_GetSources_ReturnsMouse) { + addConfigurationProperty("cursor.mode", "pointer"); + CursorInputMapper& mapper = constructAndAddMapper(); - KeyboardInputMapper& mapper = - addMapperAndConfigure(AINPUT_SOURCE_KEYBOARD, - AINPUT_KEYBOARD_TYPE_ALPHABETIC); - NotifyKeyArgs args; + ASSERT_EQ(AINPUT_SOURCE_MOUSE, mapper.getSources()); +} - // Display id should be ADISPLAY_ID_NONE 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(ADISPLAY_ID_NONE, args.displayId); +TEST_F(CursorInputMapperTest, WhenModeIsNavigation_GetSources_ReturnsTrackball) { + addConfigurationProperty("cursor.mode", "navigation"); + CursorInputMapper& mapper = constructAndAddMapper(); - prepareDisplay(DISPLAY_ORIENTATION_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(AINPUT_SOURCE_TRACKBALL, mapper.getSources()); } -TEST_F(KeyboardInputMapperTest, DisplayIdConfigurationChange_OrientationAware) { - // If the keyboard is orientation aware, - // key events should be associated with the internal viewport - mFakeEventHub->addKey(EVENTHUB_ID, KEY_UP, 0, AKEYCODE_DPAD_UP, 0); - - addConfigurationProperty("keyboard.orientationAware", "1"); - KeyboardInputMapper& mapper = - addMapperAndConfigure(AINPUT_SOURCE_KEYBOARD, - AINPUT_KEYBOARD_TYPE_ALPHABETIC); - NotifyKeyArgs args; +TEST_F(CursorInputMapperTest, WhenModeIsPointer_PopulateDeviceInfo_ReturnsRangeFromPointerController) { + addConfigurationProperty("cursor.mode", "pointer"); + CursorInputMapper& mapper = constructAndAddMapper(); - // Display id should be ADISPLAY_ID_NONE without any display configuration. - // ^--- already checked by the previous test + InputDeviceInfo info; + mapper.populateDeviceInfo(info); - setDisplayInfoAndReconfigure(DISPLAY_ID, DISPLAY_WIDTH, DISPLAY_HEIGHT, DISPLAY_ORIENTATION_0, - UNIQUE_ID, NO_PORT, ViewportType::INTERNAL); - 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(DISPLAY_ID, args.displayId); - - constexpr int32_t newDisplayId = 2; - clearViewports(); - setDisplayInfoAndReconfigure(newDisplayId, DISPLAY_WIDTH, DISPLAY_HEIGHT, DISPLAY_ORIENTATION_0, - UNIQUE_ID, NO_PORT, ViewportType::INTERNAL); - 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(newDisplayId, args.displayId); -} + // Initially there may not be a valid motion range. + ASSERT_EQ(nullptr, info.getMotionRange(AINPUT_MOTION_RANGE_X, AINPUT_SOURCE_MOUSE)); + ASSERT_EQ(nullptr, info.getMotionRange(AINPUT_MOTION_RANGE_Y, AINPUT_SOURCE_MOUSE)); + ASSERT_NO_FATAL_FAILURE(assertMotionRange(info, + AINPUT_MOTION_RANGE_PRESSURE, AINPUT_SOURCE_MOUSE, 0.0f, 1.0f, 0.0f, 0.0f)); -TEST_F(KeyboardInputMapperTest, GetKeyCodeState) { - KeyboardInputMapper& mapper = - addMapperAndConfigure(AINPUT_SOURCE_KEYBOARD, - AINPUT_KEYBOARD_TYPE_ALPHABETIC); + // When the bounds are set, then there should be a valid motion range. + mFakePointerController->setBounds(1, 2, 800 - 1, 480 - 1); - mFakeEventHub->setKeyCodeState(EVENTHUB_ID, AKEYCODE_A, 1); - ASSERT_EQ(1, mapper.getKeyCodeState(AINPUT_SOURCE_ANY, AKEYCODE_A)); + InputDeviceInfo info2; + mapper.populateDeviceInfo(info2); - mFakeEventHub->setKeyCodeState(EVENTHUB_ID, AKEYCODE_A, 0); - ASSERT_EQ(0, mapper.getKeyCodeState(AINPUT_SOURCE_ANY, AKEYCODE_A)); + ASSERT_NO_FATAL_FAILURE(assertMotionRange(info2, + AINPUT_MOTION_RANGE_X, AINPUT_SOURCE_MOUSE, + 1, 800 - 1, 0.0f, 0.0f)); + ASSERT_NO_FATAL_FAILURE(assertMotionRange(info2, + AINPUT_MOTION_RANGE_Y, AINPUT_SOURCE_MOUSE, + 2, 480 - 1, 0.0f, 0.0f)); + ASSERT_NO_FATAL_FAILURE(assertMotionRange(info2, + AINPUT_MOTION_RANGE_PRESSURE, AINPUT_SOURCE_MOUSE, + 0.0f, 1.0f, 0.0f, 0.0f)); } -TEST_F(KeyboardInputMapperTest, GetKeyCodeForKeyLocation) { - KeyboardInputMapper& mapper = - addMapperAndConfigure(AINPUT_SOURCE_KEYBOARD, - AINPUT_KEYBOARD_TYPE_ALPHABETIC); +TEST_F(CursorInputMapperTest, WhenModeIsNavigation_PopulateDeviceInfo_ReturnsScaledRange) { + addConfigurationProperty("cursor.mode", "navigation"); + CursorInputMapper& mapper = constructAndAddMapper(); - mFakeEventHub->addKeyCodeMapping(EVENTHUB_ID, AKEYCODE_Y, AKEYCODE_Z); - ASSERT_EQ(AKEYCODE_Z, mapper.getKeyCodeForKeyLocation(AKEYCODE_Y)) - << "If a mapping is available, the result is equal to the mapping"; + InputDeviceInfo info; + mapper.populateDeviceInfo(info); - ASSERT_EQ(AKEYCODE_A, mapper.getKeyCodeForKeyLocation(AKEYCODE_A)) - << "If no mapping is available, the result is the key location"; + ASSERT_NO_FATAL_FAILURE(assertMotionRange(info, + AINPUT_MOTION_RANGE_X, AINPUT_SOURCE_TRACKBALL, + -1.0f, 1.0f, 0.0f, 1.0f / TRACKBALL_MOVEMENT_THRESHOLD)); + ASSERT_NO_FATAL_FAILURE(assertMotionRange(info, + AINPUT_MOTION_RANGE_Y, AINPUT_SOURCE_TRACKBALL, + -1.0f, 1.0f, 0.0f, 1.0f / TRACKBALL_MOVEMENT_THRESHOLD)); + ASSERT_NO_FATAL_FAILURE(assertMotionRange(info, + AINPUT_MOTION_RANGE_PRESSURE, AINPUT_SOURCE_TRACKBALL, + 0.0f, 1.0f, 0.0f, 0.0f)); } -TEST_F(KeyboardInputMapperTest, GetScanCodeState) { - KeyboardInputMapper& mapper = - addMapperAndConfigure(AINPUT_SOURCE_KEYBOARD, - AINPUT_KEYBOARD_TYPE_ALPHABETIC); +TEST_F(CursorInputMapperTest, Process_ShouldSetAllFieldsAndIncludeGlobalMetaState) { + addConfigurationProperty("cursor.mode", "navigation"); + CursorInputMapper& mapper = constructAndAddMapper(); - mFakeEventHub->setScanCodeState(EVENTHUB_ID, KEY_A, 1); - ASSERT_EQ(1, mapper.getScanCodeState(AINPUT_SOURCE_ANY, KEY_A)); + mReader->getContext()->setGlobalMetaState(AMETA_SHIFT_LEFT_ON | AMETA_SHIFT_ON); - mFakeEventHub->setScanCodeState(EVENTHUB_ID, KEY_A, 0); - ASSERT_EQ(0, mapper.getScanCodeState(AINPUT_SOURCE_ANY, KEY_A)); -} + NotifyMotionArgs args; -TEST_F(KeyboardInputMapperTest, MarkSupportedKeyCodes) { - KeyboardInputMapper& mapper = - addMapperAndConfigure(AINPUT_SOURCE_KEYBOARD, - AINPUT_KEYBOARD_TYPE_ALPHABETIC); + // Button press. + // Mostly testing non x/y behavior here so we don't need to check again elsewhere. + process(mapper, ARBITRARY_TIME, READ_TIME, EV_KEY, BTN_MOUSE, 1); + process(mapper, ARBITRARY_TIME, READ_TIME, EV_SYN, SYN_REPORT, 0); + ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&args)); + ASSERT_EQ(ARBITRARY_TIME, args.eventTime); + ASSERT_EQ(DEVICE_ID, args.deviceId); + ASSERT_EQ(AINPUT_SOURCE_TRACKBALL, args.source); + ASSERT_EQ(uint32_t(0), args.policyFlags); + ASSERT_EQ(AMOTION_EVENT_ACTION_DOWN, args.action); + ASSERT_EQ(0, args.flags); + ASSERT_EQ(AMETA_SHIFT_LEFT_ON | AMETA_SHIFT_ON, args.metaState); + ASSERT_EQ(AMOTION_EVENT_BUTTON_PRIMARY, args.buttonState); + ASSERT_EQ(0, args.edgeFlags); + ASSERT_EQ(uint32_t(1), args.pointerCount); + ASSERT_EQ(0, args.pointerProperties[0].id); + ASSERT_EQ(ToolType::MOUSE, args.pointerProperties[0].toolType); + ASSERT_NO_FATAL_FAILURE(assertCursorPointerCoords(args.pointerCoords[0], 0.0f, 0.0f, 1.0f)); + ASSERT_EQ(TRACKBALL_MOVEMENT_THRESHOLD, args.xPrecision); + ASSERT_EQ(TRACKBALL_MOVEMENT_THRESHOLD, args.yPrecision); + ASSERT_EQ(ARBITRARY_TIME, args.downTime); - mFakeEventHub->addKey(EVENTHUB_ID, KEY_A, 0, AKEYCODE_A, 0); + ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&args)); + ASSERT_EQ(ARBITRARY_TIME, args.eventTime); + ASSERT_EQ(DEVICE_ID, args.deviceId); + ASSERT_EQ(AINPUT_SOURCE_TRACKBALL, args.source); + ASSERT_EQ(uint32_t(0), args.policyFlags); + ASSERT_EQ(AMOTION_EVENT_ACTION_BUTTON_PRESS, args.action); + ASSERT_EQ(0, args.flags); + ASSERT_EQ(AMETA_SHIFT_LEFT_ON | AMETA_SHIFT_ON, args.metaState); + ASSERT_EQ(AMOTION_EVENT_BUTTON_PRIMARY, args.buttonState); + ASSERT_EQ(0, args.edgeFlags); + ASSERT_EQ(uint32_t(1), args.pointerCount); + ASSERT_EQ(0, args.pointerProperties[0].id); + ASSERT_EQ(ToolType::MOUSE, args.pointerProperties[0].toolType); + ASSERT_NO_FATAL_FAILURE(assertCursorPointerCoords(args.pointerCoords[0], 0.0f, 0.0f, 1.0f)); + ASSERT_EQ(TRACKBALL_MOVEMENT_THRESHOLD, args.xPrecision); + ASSERT_EQ(TRACKBALL_MOVEMENT_THRESHOLD, args.yPrecision); + ASSERT_EQ(ARBITRARY_TIME, args.downTime); - const int32_t keyCodes[2] = { AKEYCODE_A, AKEYCODE_B }; - uint8_t flags[2] = { 0, 0 }; - ASSERT_TRUE(mapper.markSupportedKeyCodes(AINPUT_SOURCE_ANY, 1, keyCodes, flags)); - ASSERT_TRUE(flags[0]); - ASSERT_FALSE(flags[1]); + // Button release. Should have same down time. + process(mapper, ARBITRARY_TIME + 1, READ_TIME, EV_KEY, BTN_MOUSE, 0); + process(mapper, ARBITRARY_TIME + 1, READ_TIME, EV_SYN, SYN_REPORT, 0); + ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&args)); + ASSERT_EQ(ARBITRARY_TIME + 1, args.eventTime); + ASSERT_EQ(DEVICE_ID, args.deviceId); + ASSERT_EQ(AINPUT_SOURCE_TRACKBALL, args.source); + ASSERT_EQ(uint32_t(0), args.policyFlags); + ASSERT_EQ(AMOTION_EVENT_ACTION_BUTTON_RELEASE, args.action); + ASSERT_EQ(0, args.flags); + ASSERT_EQ(AMETA_SHIFT_LEFT_ON | AMETA_SHIFT_ON, args.metaState); + ASSERT_EQ(0, args.buttonState); + ASSERT_EQ(0, args.edgeFlags); + ASSERT_EQ(uint32_t(1), args.pointerCount); + ASSERT_EQ(0, args.pointerProperties[0].id); + ASSERT_EQ(ToolType::MOUSE, args.pointerProperties[0].toolType); + ASSERT_NO_FATAL_FAILURE(assertCursorPointerCoords(args.pointerCoords[0], 0.0f, 0.0f, 0.0f)); + ASSERT_EQ(TRACKBALL_MOVEMENT_THRESHOLD, args.xPrecision); + ASSERT_EQ(TRACKBALL_MOVEMENT_THRESHOLD, args.yPrecision); + ASSERT_EQ(ARBITRARY_TIME, args.downTime); + + ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&args)); + ASSERT_EQ(ARBITRARY_TIME + 1, args.eventTime); + ASSERT_EQ(DEVICE_ID, args.deviceId); + ASSERT_EQ(AINPUT_SOURCE_TRACKBALL, args.source); + ASSERT_EQ(uint32_t(0), args.policyFlags); + ASSERT_EQ(AMOTION_EVENT_ACTION_UP, args.action); + ASSERT_EQ(0, args.flags); + ASSERT_EQ(AMETA_SHIFT_LEFT_ON | AMETA_SHIFT_ON, args.metaState); + ASSERT_EQ(0, args.buttonState); + ASSERT_EQ(0, args.edgeFlags); + ASSERT_EQ(uint32_t(1), args.pointerCount); + ASSERT_EQ(0, args.pointerProperties[0].id); + ASSERT_EQ(ToolType::MOUSE, args.pointerProperties[0].toolType); + ASSERT_NO_FATAL_FAILURE(assertCursorPointerCoords(args.pointerCoords[0], 0.0f, 0.0f, 0.0f)); + ASSERT_EQ(TRACKBALL_MOVEMENT_THRESHOLD, args.xPrecision); + ASSERT_EQ(TRACKBALL_MOVEMENT_THRESHOLD, args.yPrecision); + ASSERT_EQ(ARBITRARY_TIME, args.downTime); } -TEST_F(KeyboardInputMapperTest, Process_LockedKeysShouldToggleMetaStateAndLeds) { - mFakeEventHub->addLed(EVENTHUB_ID, LED_CAPSL, true /*initially on*/); - mFakeEventHub->addLed(EVENTHUB_ID, LED_NUML, false /*initially off*/); - mFakeEventHub->addLed(EVENTHUB_ID, LED_SCROLLL, false /*initially off*/); - mFakeEventHub->addKey(EVENTHUB_ID, KEY_CAPSLOCK, 0, AKEYCODE_CAPS_LOCK, 0); - mFakeEventHub->addKey(EVENTHUB_ID, KEY_NUMLOCK, 0, AKEYCODE_NUM_LOCK, 0); - mFakeEventHub->addKey(EVENTHUB_ID, KEY_SCROLLLOCK, 0, AKEYCODE_SCROLL_LOCK, 0); +TEST_F(CursorInputMapperTest, Process_ShouldHandleIndependentXYUpdates) { + addConfigurationProperty("cursor.mode", "navigation"); + CursorInputMapper& mapper = constructAndAddMapper(); - KeyboardInputMapper& mapper = - addMapperAndConfigure(AINPUT_SOURCE_KEYBOARD, - AINPUT_KEYBOARD_TYPE_ALPHABETIC); - // Initial metastate is AMETA_NONE. - ASSERT_EQ(AMETA_NONE, mapper.getMetaState()); + NotifyMotionArgs args; - // Initialization should have turned all of the lights off. - ASSERT_FALSE(mFakeEventHub->getLedState(EVENTHUB_ID, LED_CAPSL)); - ASSERT_FALSE(mFakeEventHub->getLedState(EVENTHUB_ID, LED_NUML)); - ASSERT_FALSE(mFakeEventHub->getLedState(EVENTHUB_ID, LED_SCROLLL)); + // Motion in X but not Y. + process(mapper, ARBITRARY_TIME, READ_TIME, EV_REL, REL_X, 1); + process(mapper, ARBITRARY_TIME, READ_TIME, EV_SYN, SYN_REPORT, 0); + ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&args)); + ASSERT_EQ(AMOTION_EVENT_ACTION_MOVE, args.action); + ASSERT_NO_FATAL_FAILURE(assertCursorPointerCoords(args.pointerCoords[0], + 1.0f / TRACKBALL_MOVEMENT_THRESHOLD, 0.0f, + 0.0f)); - // Toggle caps lock on. - process(mapper, ARBITRARY_TIME, READ_TIME, EV_KEY, KEY_CAPSLOCK, 1); - process(mapper, ARBITRARY_TIME, READ_TIME, EV_KEY, KEY_CAPSLOCK, 0); - ASSERT_TRUE(mFakeEventHub->getLedState(EVENTHUB_ID, LED_CAPSL)); - ASSERT_FALSE(mFakeEventHub->getLedState(EVENTHUB_ID, LED_NUML)); - ASSERT_FALSE(mFakeEventHub->getLedState(EVENTHUB_ID, LED_SCROLLL)); - ASSERT_EQ(AMETA_CAPS_LOCK_ON, mapper.getMetaState()); + // Motion in Y but not X. + process(mapper, ARBITRARY_TIME, READ_TIME, EV_REL, REL_Y, -2); + process(mapper, ARBITRARY_TIME, READ_TIME, EV_SYN, SYN_REPORT, 0); + ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&args)); + ASSERT_EQ(AMOTION_EVENT_ACTION_MOVE, args.action); + ASSERT_NO_FATAL_FAILURE(assertCursorPointerCoords(args.pointerCoords[0], 0.0f, + -2.0f / TRACKBALL_MOVEMENT_THRESHOLD, 0.0f)); +} - // Toggle num lock on. - process(mapper, ARBITRARY_TIME, READ_TIME, EV_KEY, KEY_NUMLOCK, 1); - process(mapper, ARBITRARY_TIME, READ_TIME, EV_KEY, KEY_NUMLOCK, 0); - ASSERT_TRUE(mFakeEventHub->getLedState(EVENTHUB_ID, LED_CAPSL)); - ASSERT_TRUE(mFakeEventHub->getLedState(EVENTHUB_ID, LED_NUML)); - ASSERT_FALSE(mFakeEventHub->getLedState(EVENTHUB_ID, LED_SCROLLL)); - ASSERT_EQ(AMETA_CAPS_LOCK_ON | AMETA_NUM_LOCK_ON, mapper.getMetaState()); +TEST_F(CursorInputMapperTest, Process_ShouldHandleIndependentButtonUpdates) { + addConfigurationProperty("cursor.mode", "navigation"); + CursorInputMapper& mapper = constructAndAddMapper(); - // Toggle caps lock off. - process(mapper, ARBITRARY_TIME, READ_TIME, EV_KEY, KEY_CAPSLOCK, 1); - process(mapper, ARBITRARY_TIME, READ_TIME, EV_KEY, KEY_CAPSLOCK, 0); - ASSERT_FALSE(mFakeEventHub->getLedState(EVENTHUB_ID, LED_CAPSL)); - ASSERT_TRUE(mFakeEventHub->getLedState(EVENTHUB_ID, LED_NUML)); - ASSERT_FALSE(mFakeEventHub->getLedState(EVENTHUB_ID, LED_SCROLLL)); - ASSERT_EQ(AMETA_NUM_LOCK_ON, mapper.getMetaState()); + NotifyMotionArgs args; - // Toggle scroll lock on. - process(mapper, ARBITRARY_TIME, READ_TIME, EV_KEY, KEY_SCROLLLOCK, 1); - process(mapper, ARBITRARY_TIME, READ_TIME, EV_KEY, KEY_SCROLLLOCK, 0); - ASSERT_FALSE(mFakeEventHub->getLedState(EVENTHUB_ID, LED_CAPSL)); - ASSERT_TRUE(mFakeEventHub->getLedState(EVENTHUB_ID, LED_NUML)); - ASSERT_TRUE(mFakeEventHub->getLedState(EVENTHUB_ID, LED_SCROLLL)); - ASSERT_EQ(AMETA_NUM_LOCK_ON | AMETA_SCROLL_LOCK_ON, mapper.getMetaState()); + // Button press. + process(mapper, ARBITRARY_TIME, READ_TIME, EV_KEY, BTN_MOUSE, 1); + process(mapper, ARBITRARY_TIME, READ_TIME, EV_SYN, SYN_REPORT, 0); + ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&args)); + ASSERT_EQ(AMOTION_EVENT_ACTION_DOWN, args.action); + ASSERT_NO_FATAL_FAILURE(assertCursorPointerCoords(args.pointerCoords[0], 0.0f, 0.0f, 1.0f)); - // Toggle num lock off. - process(mapper, ARBITRARY_TIME, READ_TIME, EV_KEY, KEY_NUMLOCK, 1); - process(mapper, ARBITRARY_TIME, READ_TIME, EV_KEY, KEY_NUMLOCK, 0); - ASSERT_FALSE(mFakeEventHub->getLedState(EVENTHUB_ID, LED_CAPSL)); - ASSERT_FALSE(mFakeEventHub->getLedState(EVENTHUB_ID, LED_NUML)); - ASSERT_TRUE(mFakeEventHub->getLedState(EVENTHUB_ID, LED_SCROLLL)); - ASSERT_EQ(AMETA_SCROLL_LOCK_ON, mapper.getMetaState()); + ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&args)); + ASSERT_EQ(AMOTION_EVENT_ACTION_BUTTON_PRESS, args.action); + ASSERT_NO_FATAL_FAILURE(assertCursorPointerCoords(args.pointerCoords[0], 0.0f, 0.0f, 1.0f)); - // Toggle scroll lock off. - process(mapper, ARBITRARY_TIME, READ_TIME, EV_KEY, KEY_SCROLLLOCK, 1); - process(mapper, ARBITRARY_TIME, READ_TIME, EV_KEY, KEY_SCROLLLOCK, 0); - ASSERT_FALSE(mFakeEventHub->getLedState(EVENTHUB_ID, LED_CAPSL)); - ASSERT_FALSE(mFakeEventHub->getLedState(EVENTHUB_ID, LED_NUML)); - ASSERT_FALSE(mFakeEventHub->getLedState(EVENTHUB_ID, LED_SCROLLL)); - ASSERT_EQ(AMETA_NONE, mapper.getMetaState()); + // Button release. + process(mapper, ARBITRARY_TIME, READ_TIME, EV_KEY, BTN_MOUSE, 0); + process(mapper, ARBITRARY_TIME, READ_TIME, EV_SYN, SYN_REPORT, 0); + ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&args)); + ASSERT_EQ(AMOTION_EVENT_ACTION_BUTTON_RELEASE, args.action); + ASSERT_NO_FATAL_FAILURE(assertCursorPointerCoords(args.pointerCoords[0], 0.0f, 0.0f, 0.0f)); + + ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&args)); + ASSERT_EQ(AMOTION_EVENT_ACTION_UP, args.action); + ASSERT_NO_FATAL_FAILURE(assertCursorPointerCoords(args.pointerCoords[0], 0.0f, 0.0f, 0.0f)); } -TEST_F(KeyboardInputMapperTest, NoMetaStateWhenMetaKeysNotPresent) { - mFakeEventHub->addKey(EVENTHUB_ID, BTN_A, 0, AKEYCODE_BUTTON_A, 0); - mFakeEventHub->addKey(EVENTHUB_ID, BTN_B, 0, AKEYCODE_BUTTON_B, 0); - mFakeEventHub->addKey(EVENTHUB_ID, BTN_X, 0, AKEYCODE_BUTTON_X, 0); - mFakeEventHub->addKey(EVENTHUB_ID, BTN_Y, 0, AKEYCODE_BUTTON_Y, 0); +TEST_F(CursorInputMapperTest, Process_ShouldHandleCombinedXYAndButtonUpdates) { + addConfigurationProperty("cursor.mode", "navigation"); + CursorInputMapper& mapper = constructAndAddMapper(); - KeyboardInputMapper& mapper = - addMapperAndConfigure(AINPUT_SOURCE_KEYBOARD, - AINPUT_KEYBOARD_TYPE_NON_ALPHABETIC); + NotifyMotionArgs args; - // Meta state should be AMETA_NONE after reset - mapper.reset(ARBITRARY_TIME); - ASSERT_EQ(AMETA_NONE, mapper.getMetaState()); - // Meta state should be AMETA_NONE with update, as device doesn't have the keys. - mapper.updateMetaState(AKEYCODE_NUM_LOCK); - ASSERT_EQ(AMETA_NONE, mapper.getMetaState()); + // Combined X, Y and Button. + process(mapper, ARBITRARY_TIME, READ_TIME, EV_REL, REL_X, 1); + process(mapper, ARBITRARY_TIME, READ_TIME, EV_REL, REL_Y, -2); + process(mapper, ARBITRARY_TIME, READ_TIME, EV_KEY, BTN_MOUSE, 1); + process(mapper, ARBITRARY_TIME, READ_TIME, EV_SYN, SYN_REPORT, 0); + ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&args)); + ASSERT_EQ(AMOTION_EVENT_ACTION_DOWN, args.action); + ASSERT_NO_FATAL_FAILURE(assertCursorPointerCoords(args.pointerCoords[0], + 1.0f / TRACKBALL_MOVEMENT_THRESHOLD, + -2.0f / TRACKBALL_MOVEMENT_THRESHOLD, 1.0f)); - NotifyKeyArgs args; - // Press button "A" - process(mapper, ARBITRARY_TIME, READ_TIME, EV_KEY, BTN_A, 1); - ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyKeyWasCalled(&args)); - ASSERT_EQ(AMETA_NONE, args.metaState); - ASSERT_EQ(AMETA_NONE, mapper.getMetaState()); - ASSERT_EQ(AKEY_EVENT_ACTION_DOWN, args.action); - ASSERT_EQ(AKEYCODE_BUTTON_A, args.keyCode); + ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&args)); + ASSERT_EQ(AMOTION_EVENT_ACTION_BUTTON_PRESS, args.action); + ASSERT_NO_FATAL_FAILURE(assertCursorPointerCoords(args.pointerCoords[0], + 1.0f / TRACKBALL_MOVEMENT_THRESHOLD, + -2.0f / TRACKBALL_MOVEMENT_THRESHOLD, 1.0f)); - // Button up. - process(mapper, ARBITRARY_TIME + 2, READ_TIME, EV_KEY, BTN_A, 0); - ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyKeyWasCalled(&args)); - ASSERT_EQ(AMETA_NONE, args.metaState); - ASSERT_EQ(AMETA_NONE, mapper.getMetaState()); - ASSERT_EQ(AKEY_EVENT_ACTION_UP, args.action); - ASSERT_EQ(AKEYCODE_BUTTON_A, args.keyCode); + // Move X, Y a bit while pressed. + process(mapper, ARBITRARY_TIME, READ_TIME, EV_REL, REL_X, 2); + process(mapper, ARBITRARY_TIME, READ_TIME, EV_REL, REL_Y, 1); + process(mapper, ARBITRARY_TIME, READ_TIME, EV_SYN, SYN_REPORT, 0); + ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&args)); + ASSERT_EQ(AMOTION_EVENT_ACTION_MOVE, args.action); + ASSERT_NO_FATAL_FAILURE(assertCursorPointerCoords(args.pointerCoords[0], + 2.0f / TRACKBALL_MOVEMENT_THRESHOLD, + 1.0f / TRACKBALL_MOVEMENT_THRESHOLD, 1.0f)); + + // Release Button. + process(mapper, ARBITRARY_TIME, READ_TIME, EV_KEY, BTN_MOUSE, 0); + process(mapper, ARBITRARY_TIME, READ_TIME, EV_SYN, SYN_REPORT, 0); + ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&args)); + ASSERT_EQ(AMOTION_EVENT_ACTION_BUTTON_RELEASE, args.action); + ASSERT_NO_FATAL_FAILURE(assertCursorPointerCoords(args.pointerCoords[0], 0.0f, 0.0f, 0.0f)); + + ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&args)); + ASSERT_EQ(AMOTION_EVENT_ACTION_UP, args.action); + ASSERT_NO_FATAL_FAILURE(assertCursorPointerCoords(args.pointerCoords[0], 0.0f, 0.0f, 0.0f)); } -TEST_F(KeyboardInputMapperTest, Configure_AssignsDisplayPort) { - // keyboard 1. - mFakeEventHub->addKey(EVENTHUB_ID, KEY_UP, 0, AKEYCODE_DPAD_UP, 0); - mFakeEventHub->addKey(EVENTHUB_ID, KEY_RIGHT, 0, AKEYCODE_DPAD_RIGHT, 0); - mFakeEventHub->addKey(EVENTHUB_ID, KEY_DOWN, 0, AKEYCODE_DPAD_DOWN, 0); - mFakeEventHub->addKey(EVENTHUB_ID, KEY_LEFT, 0, AKEYCODE_DPAD_LEFT, 0); +TEST_F(CursorInputMapperTest, Process_WhenOrientationAware_ShouldNotRotateMotions) { + mFakePolicy->addInputUniqueIdAssociation(DEVICE_LOCATION, DISPLAY_UNIQUE_ID); + addConfigurationProperty("cursor.mode", "navigation"); + // InputReader works in the un-rotated coordinate space, so orientation-aware devices do not + // need to be rotated. + addConfigurationProperty("cursor.orientationAware", "1"); + CursorInputMapper& mapper = constructAndAddMapper(); - // keyboard 2. - const std::string USB2 = "USB2"; - const std::string DEVICE_NAME2 = "KEYBOARD2"; - 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)); + prepareDisplay(ui::ROTATION_90); + ASSERT_NO_FATAL_FAILURE(testMotionRotation(mapper, 0, 1, 0, 1)); + ASSERT_NO_FATAL_FAILURE(testMotionRotation(mapper, 1, 1, 1, 1)); + ASSERT_NO_FATAL_FAILURE(testMotionRotation(mapper, 1, 0, 1, 0)); + ASSERT_NO_FATAL_FAILURE(testMotionRotation(mapper, 1, -1, 1, -1)); + ASSERT_NO_FATAL_FAILURE(testMotionRotation(mapper, 0, -1, 0, -1)); + ASSERT_NO_FATAL_FAILURE(testMotionRotation(mapper, -1, -1, -1, -1)); + ASSERT_NO_FATAL_FAILURE(testMotionRotation(mapper, -1, 0, -1, 0)); + ASSERT_NO_FATAL_FAILURE(testMotionRotation(mapper, -1, 1, -1, 1)); +} - mFakeEventHub->addKey(SECOND_EVENTHUB_ID, KEY_UP, 0, AKEYCODE_DPAD_UP, 0); - mFakeEventHub->addKey(SECOND_EVENTHUB_ID, KEY_RIGHT, 0, AKEYCODE_DPAD_RIGHT, 0); - mFakeEventHub->addKey(SECOND_EVENTHUB_ID, KEY_DOWN, 0, AKEYCODE_DPAD_DOWN, 0); - mFakeEventHub->addKey(SECOND_EVENTHUB_ID, KEY_LEFT, 0, AKEYCODE_DPAD_LEFT, 0); +TEST_F(CursorInputMapperTest, Process_WhenNotOrientationAware_ShouldRotateMotions) { + mFakePolicy->addInputUniqueIdAssociation(DEVICE_LOCATION, DISPLAY_UNIQUE_ID); + addConfigurationProperty("cursor.mode", "navigation"); + // Since InputReader works in the un-rotated coordinate space, only devices that are not + // orientation-aware are affected by display rotation. + CursorInputMapper& mapper = constructAndAddMapper(); - KeyboardInputMapper& mapper = - addMapperAndConfigure(AINPUT_SOURCE_KEYBOARD, - AINPUT_KEYBOARD_TYPE_ALPHABETIC); + clearViewports(); + prepareDisplay(ui::ROTATION_0); + ASSERT_NO_FATAL_FAILURE(testMotionRotation(mapper, 0, 1, 0, 1)); + ASSERT_NO_FATAL_FAILURE(testMotionRotation(mapper, 1, 1, 1, 1)); + ASSERT_NO_FATAL_FAILURE(testMotionRotation(mapper, 1, 0, 1, 0)); + ASSERT_NO_FATAL_FAILURE(testMotionRotation(mapper, 1, -1, 1, -1)); + ASSERT_NO_FATAL_FAILURE(testMotionRotation(mapper, 0, -1, 0, -1)); + ASSERT_NO_FATAL_FAILURE(testMotionRotation(mapper, -1, -1, -1, -1)); + ASSERT_NO_FATAL_FAILURE(testMotionRotation(mapper, -1, 0, -1, 0)); + ASSERT_NO_FATAL_FAILURE(testMotionRotation(mapper, -1, 1, -1, 1)); - KeyboardInputMapper& mapper2 = - device2->addMapper(SECOND_EVENTHUB_ID, AINPUT_SOURCE_KEYBOARD, - AINPUT_KEYBOARD_TYPE_ALPHABETIC); - device2->configure(ARBITRARY_TIME, mFakePolicy->getReaderConfiguration(), 0 /*changes*/); - device2->reset(ARBITRARY_TIME); + clearViewports(); + prepareDisplay(ui::ROTATION_90); + ASSERT_NO_FATAL_FAILURE(testMotionRotation(mapper, 0, 1, -1, 0)); + ASSERT_NO_FATAL_FAILURE(testMotionRotation(mapper, 1, 1, -1, 1)); + ASSERT_NO_FATAL_FAILURE(testMotionRotation(mapper, 1, 0, 0, 1)); + ASSERT_NO_FATAL_FAILURE(testMotionRotation(mapper, 1, -1, 1, 1)); + ASSERT_NO_FATAL_FAILURE(testMotionRotation(mapper, 0, -1, 1, 0)); + ASSERT_NO_FATAL_FAILURE(testMotionRotation(mapper, -1, -1, 1, -1)); + ASSERT_NO_FATAL_FAILURE(testMotionRotation(mapper, -1, 0, 0, -1)); + ASSERT_NO_FATAL_FAILURE(testMotionRotation(mapper, -1, 1, -1, -1)); - // Prepared displays and associated info. - constexpr uint8_t hdmi1 = 0; - constexpr uint8_t hdmi2 = 1; - const std::string SECONDARY_UNIQUE_ID = "local:1"; + clearViewports(); + prepareDisplay(ui::ROTATION_180); + ASSERT_NO_FATAL_FAILURE(testMotionRotation(mapper, 0, 1, 0, -1)); + ASSERT_NO_FATAL_FAILURE(testMotionRotation(mapper, 1, 1, -1, -1)); + ASSERT_NO_FATAL_FAILURE(testMotionRotation(mapper, 1, 0, -1, 0)); + ASSERT_NO_FATAL_FAILURE(testMotionRotation(mapper, 1, -1, -1, 1)); + ASSERT_NO_FATAL_FAILURE(testMotionRotation(mapper, 0, -1, 0, 1)); + ASSERT_NO_FATAL_FAILURE(testMotionRotation(mapper, -1, -1, 1, 1)); + ASSERT_NO_FATAL_FAILURE(testMotionRotation(mapper, -1, 0, 1, 0)); + ASSERT_NO_FATAL_FAILURE(testMotionRotation(mapper, -1, 1, 1, -1)); - mFakePolicy->addInputPortAssociation(DEVICE_LOCATION, hdmi1); - mFakePolicy->addInputPortAssociation(USB2, hdmi2); + clearViewports(); + prepareDisplay(ui::ROTATION_270); + ASSERT_NO_FATAL_FAILURE(testMotionRotation(mapper, 0, 1, 1, 0)); + ASSERT_NO_FATAL_FAILURE(testMotionRotation(mapper, 1, 1, 1, -1)); + ASSERT_NO_FATAL_FAILURE(testMotionRotation(mapper, 1, 0, 0, -1)); + ASSERT_NO_FATAL_FAILURE(testMotionRotation(mapper, 1, -1, -1, -1)); + ASSERT_NO_FATAL_FAILURE(testMotionRotation(mapper, 0, -1, -1, 0)); + ASSERT_NO_FATAL_FAILURE(testMotionRotation(mapper, -1, -1, -1, 1)); + ASSERT_NO_FATAL_FAILURE(testMotionRotation(mapper, -1, 0, 0, 1)); + ASSERT_NO_FATAL_FAILURE(testMotionRotation(mapper, -1, 1, 1, 1)); +} - // No associated display viewport found, should disable the device. - device2->configure(ARBITRARY_TIME, mFakePolicy->getReaderConfiguration(), - InputReaderConfiguration::CHANGE_DISPLAY_INFO); - ASSERT_FALSE(device2->isEnabled()); +TEST_F(CursorInputMapperTest, Process_ShouldHandleAllButtons) { + addConfigurationProperty("cursor.mode", "pointer"); + CursorInputMapper& mapper = constructAndAddMapper(); - // Prepare second display. - constexpr int32_t newDisplayId = 2; - setDisplayInfoAndReconfigure(DISPLAY_ID, DISPLAY_WIDTH, DISPLAY_HEIGHT, DISPLAY_ORIENTATION_0, - UNIQUE_ID, hdmi1, ViewportType::INTERNAL); - setDisplayInfoAndReconfigure(newDisplayId, DISPLAY_WIDTH, DISPLAY_HEIGHT, DISPLAY_ORIENTATION_0, - SECONDARY_UNIQUE_ID, hdmi2, ViewportType::EXTERNAL); - // Default device will reconfigure above, need additional reconfiguration for another device. - device2->configure(ARBITRARY_TIME, mFakePolicy->getReaderConfiguration(), - InputReaderConfiguration::CHANGE_DISPLAY_INFO); + mFakePointerController->setBounds(0, 0, 800 - 1, 480 - 1); + mFakePointerController->setPosition(100, 200); - // Device should be enabled after the associated display is found. - ASSERT_TRUE(mDevice->isEnabled()); - ASSERT_TRUE(device2->isEnabled()); + NotifyMotionArgs motionArgs; + NotifyKeyArgs keyArgs; - // Test pad key events + // press BTN_LEFT, release BTN_LEFT + process(mapper, ARBITRARY_TIME, READ_TIME, EV_KEY, BTN_LEFT, 1); + process(mapper, ARBITRARY_TIME, READ_TIME, EV_SYN, SYN_REPORT, 0); + ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs)); + ASSERT_EQ(AMOTION_EVENT_ACTION_DOWN, motionArgs.action); + ASSERT_EQ(AMOTION_EVENT_BUTTON_PRIMARY, motionArgs.buttonState); ASSERT_NO_FATAL_FAILURE( - testDPadKeyRotation(mapper, KEY_UP, AKEYCODE_DPAD_UP, AKEYCODE_DPAD_UP, DISPLAY_ID)); - ASSERT_NO_FATAL_FAILURE(testDPadKeyRotation(mapper, KEY_RIGHT, AKEYCODE_DPAD_RIGHT, - AKEYCODE_DPAD_RIGHT, DISPLAY_ID)); - ASSERT_NO_FATAL_FAILURE(testDPadKeyRotation(mapper, KEY_DOWN, AKEYCODE_DPAD_DOWN, - AKEYCODE_DPAD_DOWN, DISPLAY_ID)); - ASSERT_NO_FATAL_FAILURE(testDPadKeyRotation(mapper, KEY_LEFT, AKEYCODE_DPAD_LEFT, - AKEYCODE_DPAD_LEFT, DISPLAY_ID)); + assertCursorPointerCoords(motionArgs.pointerCoords[0], 100.0f, 200.0f, 1.0f)); + ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs)); + ASSERT_EQ(AMOTION_EVENT_ACTION_BUTTON_PRESS, motionArgs.action); + ASSERT_EQ(AMOTION_EVENT_BUTTON_PRIMARY, motionArgs.buttonState); ASSERT_NO_FATAL_FAILURE( - testDPadKeyRotation(mapper2, KEY_UP, AKEYCODE_DPAD_UP, AKEYCODE_DPAD_UP, newDisplayId)); - ASSERT_NO_FATAL_FAILURE(testDPadKeyRotation(mapper2, KEY_RIGHT, AKEYCODE_DPAD_RIGHT, - AKEYCODE_DPAD_RIGHT, newDisplayId)); - ASSERT_NO_FATAL_FAILURE(testDPadKeyRotation(mapper2, KEY_DOWN, AKEYCODE_DPAD_DOWN, - AKEYCODE_DPAD_DOWN, newDisplayId)); - ASSERT_NO_FATAL_FAILURE(testDPadKeyRotation(mapper2, KEY_LEFT, AKEYCODE_DPAD_LEFT, - AKEYCODE_DPAD_LEFT, newDisplayId)); -} - -TEST_F(KeyboardInputMapperTest, Process_LockedKeysShouldToggleAfterReattach) { - mFakeEventHub->addLed(EVENTHUB_ID, LED_CAPSL, true /*initially on*/); - mFakeEventHub->addLed(EVENTHUB_ID, LED_NUML, false /*initially off*/); - mFakeEventHub->addLed(EVENTHUB_ID, LED_SCROLLL, false /*initially off*/); - mFakeEventHub->addKey(EVENTHUB_ID, KEY_CAPSLOCK, 0, AKEYCODE_CAPS_LOCK, 0); - mFakeEventHub->addKey(EVENTHUB_ID, KEY_NUMLOCK, 0, AKEYCODE_NUM_LOCK, 0); - mFakeEventHub->addKey(EVENTHUB_ID, KEY_SCROLLLOCK, 0, AKEYCODE_SCROLL_LOCK, 0); + assertCursorPointerCoords(motionArgs.pointerCoords[0], 100.0f, 200.0f, 1.0f)); - KeyboardInputMapper& mapper = - addMapperAndConfigure(AINPUT_SOURCE_KEYBOARD, - AINPUT_KEYBOARD_TYPE_ALPHABETIC); - // Initial metastate is AMETA_NONE. - ASSERT_EQ(AMETA_NONE, mapper.getMetaState()); + process(mapper, ARBITRARY_TIME, READ_TIME, EV_KEY, BTN_LEFT, 0); + process(mapper, ARBITRARY_TIME, READ_TIME, EV_SYN, SYN_REPORT, 0); + ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs)); + ASSERT_EQ(AMOTION_EVENT_ACTION_BUTTON_RELEASE, motionArgs.action); + ASSERT_EQ(0, motionArgs.buttonState); + ASSERT_NO_FATAL_FAILURE( + assertCursorPointerCoords(motionArgs.pointerCoords[0], 100.0f, 200.0f, 0.0f)); - // Initialization should have turned all of the lights off. - ASSERT_FALSE(mFakeEventHub->getLedState(EVENTHUB_ID, LED_CAPSL)); - ASSERT_FALSE(mFakeEventHub->getLedState(EVENTHUB_ID, LED_NUML)); - ASSERT_FALSE(mFakeEventHub->getLedState(EVENTHUB_ID, LED_SCROLLL)); + ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs)); + ASSERT_EQ(AMOTION_EVENT_ACTION_UP, motionArgs.action); + ASSERT_EQ(0, motionArgs.buttonState); + ASSERT_NO_FATAL_FAILURE( + assertCursorPointerCoords(motionArgs.pointerCoords[0], 100.0f, 200.0f, 0.0f)); - // Toggle caps lock on. - process(mapper, ARBITRARY_TIME, READ_TIME, EV_KEY, KEY_CAPSLOCK, 1); - process(mapper, ARBITRARY_TIME, READ_TIME, EV_KEY, KEY_CAPSLOCK, 0); - ASSERT_TRUE(mFakeEventHub->getLedState(EVENTHUB_ID, LED_CAPSL)); - ASSERT_EQ(AMETA_CAPS_LOCK_ON, mapper.getMetaState()); + ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs)); + ASSERT_EQ(AMOTION_EVENT_ACTION_HOVER_MOVE, motionArgs.action); + ASSERT_EQ(0, motionArgs.buttonState); + ASSERT_NO_FATAL_FAILURE( + assertCursorPointerCoords(motionArgs.pointerCoords[0], 100.0f, 200.0f, 0.0f)); - // Toggle num lock on. - process(mapper, ARBITRARY_TIME, READ_TIME, EV_KEY, KEY_NUMLOCK, 1); - process(mapper, ARBITRARY_TIME, READ_TIME, EV_KEY, KEY_NUMLOCK, 0); - ASSERT_TRUE(mFakeEventHub->getLedState(EVENTHUB_ID, LED_NUML)); - ASSERT_EQ(AMETA_CAPS_LOCK_ON | AMETA_NUM_LOCK_ON, mapper.getMetaState()); + // press BTN_RIGHT + BTN_MIDDLE, release BTN_RIGHT, release BTN_MIDDLE + process(mapper, ARBITRARY_TIME, READ_TIME, EV_KEY, BTN_RIGHT, 1); + process(mapper, ARBITRARY_TIME, READ_TIME, EV_KEY, BTN_MIDDLE, 1); + process(mapper, ARBITRARY_TIME, READ_TIME, EV_SYN, SYN_REPORT, 0); + ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs)); + ASSERT_EQ(AMOTION_EVENT_ACTION_DOWN, motionArgs.action); + ASSERT_EQ(AMOTION_EVENT_BUTTON_SECONDARY | AMOTION_EVENT_BUTTON_TERTIARY, + motionArgs.buttonState); + ASSERT_NO_FATAL_FAILURE( + assertCursorPointerCoords(motionArgs.pointerCoords[0], 100.0f, 200.0f, 1.0f)); - // Toggle scroll lock on. - process(mapper, ARBITRARY_TIME, READ_TIME, EV_KEY, KEY_SCROLLLOCK, 1); - process(mapper, ARBITRARY_TIME, READ_TIME, EV_KEY, KEY_SCROLLLOCK, 0); - ASSERT_TRUE(mFakeEventHub->getLedState(EVENTHUB_ID, LED_SCROLLL)); - ASSERT_EQ(AMETA_CAPS_LOCK_ON | AMETA_NUM_LOCK_ON | AMETA_SCROLL_LOCK_ON, mapper.getMetaState()); + ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs)); + ASSERT_EQ(AMOTION_EVENT_ACTION_BUTTON_PRESS, motionArgs.action); + ASSERT_EQ(AMOTION_EVENT_BUTTON_TERTIARY, motionArgs.buttonState); + ASSERT_NO_FATAL_FAILURE( + assertCursorPointerCoords(motionArgs.pointerCoords[0], 100.0f, 200.0f, 1.0f)); - mFakeEventHub->removeDevice(EVENTHUB_ID); - mReader->loopOnce(); + ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs)); + ASSERT_EQ(AMOTION_EVENT_ACTION_BUTTON_PRESS, motionArgs.action); + ASSERT_EQ(AMOTION_EVENT_BUTTON_SECONDARY | AMOTION_EVENT_BUTTON_TERTIARY, + motionArgs.buttonState); + ASSERT_NO_FATAL_FAILURE( + assertCursorPointerCoords(motionArgs.pointerCoords[0], 100.0f, 200.0f, 1.0f)); - // keyboard 2 should default toggle keys. - const std::string USB2 = "USB2"; - const std::string DEVICE_NAME2 = "KEYBOARD2"; - 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->addLed(SECOND_EVENTHUB_ID, LED_CAPSL, true /*initially on*/); - mFakeEventHub->addLed(SECOND_EVENTHUB_ID, LED_NUML, false /*initially off*/); - mFakeEventHub->addLed(SECOND_EVENTHUB_ID, LED_SCROLLL, false /*initially off*/); - mFakeEventHub->addKey(SECOND_EVENTHUB_ID, KEY_CAPSLOCK, 0, AKEYCODE_CAPS_LOCK, 0); - mFakeEventHub->addKey(SECOND_EVENTHUB_ID, KEY_NUMLOCK, 0, AKEYCODE_NUM_LOCK, 0); - mFakeEventHub->addKey(SECOND_EVENTHUB_ID, KEY_SCROLLLOCK, 0, AKEYCODE_SCROLL_LOCK, 0); + process(mapper, ARBITRARY_TIME, READ_TIME, EV_KEY, BTN_RIGHT, 0); + process(mapper, ARBITRARY_TIME, READ_TIME, EV_SYN, SYN_REPORT, 0); + ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs)); + ASSERT_EQ(AMOTION_EVENT_ACTION_BUTTON_RELEASE, motionArgs.action); + ASSERT_EQ(AMOTION_EVENT_BUTTON_TERTIARY, motionArgs.buttonState); + ASSERT_NO_FATAL_FAILURE( + assertCursorPointerCoords(motionArgs.pointerCoords[0], 100.0f, 200.0f, 1.0f)); - KeyboardInputMapper& mapper2 = - device2->addMapper(SECOND_EVENTHUB_ID, AINPUT_SOURCE_KEYBOARD, - AINPUT_KEYBOARD_TYPE_ALPHABETIC); - device2->configure(ARBITRARY_TIME, mFakePolicy->getReaderConfiguration(), 0 /*changes*/); - device2->reset(ARBITRARY_TIME); + ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs)); + ASSERT_EQ(AMOTION_EVENT_ACTION_MOVE, motionArgs.action); + ASSERT_EQ(AMOTION_EVENT_BUTTON_TERTIARY, motionArgs.buttonState); + ASSERT_NO_FATAL_FAILURE( + assertCursorPointerCoords(motionArgs.pointerCoords[0], 100.0f, 200.0f, 1.0f)); - ASSERT_TRUE(mFakeEventHub->getLedState(SECOND_EVENTHUB_ID, LED_CAPSL)); - ASSERT_TRUE(mFakeEventHub->getLedState(SECOND_EVENTHUB_ID, LED_NUML)); - ASSERT_TRUE(mFakeEventHub->getLedState(SECOND_EVENTHUB_ID, LED_SCROLLL)); - ASSERT_EQ(AMETA_CAPS_LOCK_ON | AMETA_NUM_LOCK_ON | AMETA_SCROLL_LOCK_ON, - mapper2.getMetaState()); -} + process(mapper, ARBITRARY_TIME, READ_TIME, EV_KEY, BTN_MIDDLE, 0); + process(mapper, ARBITRARY_TIME, READ_TIME, EV_SYN, SYN_REPORT, 0); + ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs)); + ASSERT_EQ(AMOTION_EVENT_ACTION_BUTTON_RELEASE, motionArgs.action); + ASSERT_EQ(0, motionArgs.buttonState); + ASSERT_NO_FATAL_FAILURE( + assertCursorPointerCoords(motionArgs.pointerCoords[0], 100.0f, 200.0f, 0.0f)); + process(mapper, ARBITRARY_TIME, READ_TIME, EV_KEY, BTN_MIDDLE, 0); + process(mapper, ARBITRARY_TIME, READ_TIME, EV_SYN, SYN_REPORT, 0); -TEST_F(KeyboardInputMapperTest, Process_toggleCapsLockState) { - mFakeEventHub->addKey(EVENTHUB_ID, KEY_CAPSLOCK, 0, AKEYCODE_CAPS_LOCK, 0); - mFakeEventHub->addKey(EVENTHUB_ID, KEY_NUMLOCK, 0, AKEYCODE_NUM_LOCK, 0); - mFakeEventHub->addKey(EVENTHUB_ID, KEY_SCROLLLOCK, 0, AKEYCODE_SCROLL_LOCK, 0); + ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs)); + ASSERT_EQ(0, motionArgs.buttonState); + ASSERT_EQ(AMOTION_EVENT_ACTION_UP, motionArgs.action); + ASSERT_NO_FATAL_FAILURE( + assertCursorPointerCoords(motionArgs.pointerCoords[0], 100.0f, 200.0f, 0.0f)); - // Suppose we have two mappers. (DPAD + KEYBOARD) - addMapperAndConfigure(AINPUT_SOURCE_DPAD, - AINPUT_KEYBOARD_TYPE_NON_ALPHABETIC); - KeyboardInputMapper& mapper = - addMapperAndConfigure(AINPUT_SOURCE_KEYBOARD, - AINPUT_KEYBOARD_TYPE_ALPHABETIC); - // Initial metastate is AMETA_NONE. - ASSERT_EQ(AMETA_NONE, mapper.getMetaState()); + ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs)); + ASSERT_EQ(0, motionArgs.buttonState); + ASSERT_EQ(AMOTION_EVENT_ACTION_HOVER_MOVE, motionArgs.action); + ASSERT_NO_FATAL_FAILURE( + assertCursorPointerCoords(motionArgs.pointerCoords[0], 100.0f, 200.0f, 0.0f)); - mReader->toggleCapsLockState(DEVICE_ID); - ASSERT_EQ(AMETA_CAPS_LOCK_ON, mapper.getMetaState()); -} + // press BTN_BACK, release BTN_BACK + process(mapper, ARBITRARY_TIME, READ_TIME, EV_KEY, BTN_BACK, 1); + process(mapper, ARBITRARY_TIME, READ_TIME, EV_SYN, SYN_REPORT, 0); + ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyKeyWasCalled(&keyArgs)); + ASSERT_EQ(AKEY_EVENT_ACTION_DOWN, keyArgs.action); + ASSERT_EQ(AKEYCODE_BACK, keyArgs.keyCode); -TEST_F(KeyboardInputMapperTest, Process_LockedKeysShouldToggleInMultiDevices) { - // keyboard 1. - mFakeEventHub->addLed(EVENTHUB_ID, LED_CAPSL, true /*initially on*/); - mFakeEventHub->addLed(EVENTHUB_ID, LED_NUML, false /*initially off*/); - mFakeEventHub->addLed(EVENTHUB_ID, LED_SCROLLL, false /*initially off*/); - mFakeEventHub->addKey(EVENTHUB_ID, KEY_CAPSLOCK, 0, AKEYCODE_CAPS_LOCK, 0); - mFakeEventHub->addKey(EVENTHUB_ID, KEY_NUMLOCK, 0, AKEYCODE_NUM_LOCK, 0); - mFakeEventHub->addKey(EVENTHUB_ID, KEY_SCROLLLOCK, 0, AKEYCODE_SCROLL_LOCK, 0); + ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs)); + ASSERT_EQ(AMOTION_EVENT_ACTION_HOVER_MOVE, motionArgs.action); + ASSERT_EQ(AMOTION_EVENT_BUTTON_BACK, motionArgs.buttonState); + ASSERT_NO_FATAL_FAILURE( + assertCursorPointerCoords(motionArgs.pointerCoords[0], 100.0f, 200.0f, 0.0f)); - KeyboardInputMapper& mapper1 = - addMapperAndConfigure(AINPUT_SOURCE_KEYBOARD, - AINPUT_KEYBOARD_TYPE_ALPHABETIC); + ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs)); + ASSERT_EQ(AMOTION_EVENT_ACTION_BUTTON_PRESS, motionArgs.action); + ASSERT_EQ(AMOTION_EVENT_BUTTON_BACK, motionArgs.buttonState); + ASSERT_NO_FATAL_FAILURE( + assertCursorPointerCoords(motionArgs.pointerCoords[0], 100.0f, 200.0f, 0.0f)); - // keyboard 2. - const std::string USB2 = "USB2"; - const std::string DEVICE_NAME2 = "KEYBOARD2"; - 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->addLed(SECOND_EVENTHUB_ID, LED_CAPSL, true /*initially on*/); - mFakeEventHub->addLed(SECOND_EVENTHUB_ID, LED_NUML, false /*initially off*/); - mFakeEventHub->addLed(SECOND_EVENTHUB_ID, LED_SCROLLL, false /*initially off*/); - mFakeEventHub->addKey(SECOND_EVENTHUB_ID, KEY_CAPSLOCK, 0, AKEYCODE_CAPS_LOCK, 0); - mFakeEventHub->addKey(SECOND_EVENTHUB_ID, KEY_NUMLOCK, 0, AKEYCODE_NUM_LOCK, 0); - mFakeEventHub->addKey(SECOND_EVENTHUB_ID, KEY_SCROLLLOCK, 0, AKEYCODE_SCROLL_LOCK, 0); + process(mapper, ARBITRARY_TIME, READ_TIME, EV_KEY, BTN_BACK, 0); + process(mapper, ARBITRARY_TIME, READ_TIME, EV_SYN, SYN_REPORT, 0); + ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs)); + ASSERT_EQ(AMOTION_EVENT_ACTION_BUTTON_RELEASE, motionArgs.action); + ASSERT_EQ(0, motionArgs.buttonState); + ASSERT_NO_FATAL_FAILURE( + assertCursorPointerCoords(motionArgs.pointerCoords[0], 100.0f, 200.0f, 0.0f)); - KeyboardInputMapper& mapper2 = - device2->addMapper(SECOND_EVENTHUB_ID, AINPUT_SOURCE_KEYBOARD, - AINPUT_KEYBOARD_TYPE_ALPHABETIC); - device2->configure(ARBITRARY_TIME, mFakePolicy->getReaderConfiguration(), 0 /*changes*/); - device2->reset(ARBITRARY_TIME); + ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs)); + ASSERT_EQ(AMOTION_EVENT_ACTION_HOVER_MOVE, motionArgs.action); + ASSERT_EQ(0, motionArgs.buttonState); - // Initial metastate is AMETA_NONE. - ASSERT_EQ(AMETA_NONE, mapper1.getMetaState()); - ASSERT_EQ(AMETA_NONE, mapper2.getMetaState()); + ASSERT_NO_FATAL_FAILURE( + assertCursorPointerCoords(motionArgs.pointerCoords[0], 100.0f, 200.0f, 0.0f)); + ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyKeyWasCalled(&keyArgs)); + ASSERT_EQ(AKEY_EVENT_ACTION_UP, keyArgs.action); + ASSERT_EQ(AKEYCODE_BACK, keyArgs.keyCode); - // Toggle num lock on and off. - process(mapper1, ARBITRARY_TIME, READ_TIME, EV_KEY, KEY_NUMLOCK, 1); - process(mapper1, ARBITRARY_TIME, READ_TIME, EV_KEY, KEY_NUMLOCK, 0); - ASSERT_TRUE(mFakeEventHub->getLedState(EVENTHUB_ID, LED_NUML)); - ASSERT_EQ(AMETA_NUM_LOCK_ON, mapper1.getMetaState()); - ASSERT_EQ(AMETA_NUM_LOCK_ON, mapper2.getMetaState()); + // press BTN_SIDE, release BTN_SIDE + process(mapper, ARBITRARY_TIME, READ_TIME, EV_KEY, BTN_SIDE, 1); + process(mapper, ARBITRARY_TIME, READ_TIME, EV_SYN, SYN_REPORT, 0); + ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyKeyWasCalled(&keyArgs)); + ASSERT_EQ(AKEY_EVENT_ACTION_DOWN, keyArgs.action); + ASSERT_EQ(AKEYCODE_BACK, keyArgs.keyCode); - process(mapper1, ARBITRARY_TIME, READ_TIME, EV_KEY, KEY_NUMLOCK, 1); - process(mapper1, ARBITRARY_TIME, READ_TIME, EV_KEY, KEY_NUMLOCK, 0); - ASSERT_FALSE(mFakeEventHub->getLedState(EVENTHUB_ID, LED_NUML)); - ASSERT_EQ(AMETA_NONE, mapper1.getMetaState()); - ASSERT_EQ(AMETA_NONE, mapper2.getMetaState()); + ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs)); + ASSERT_EQ(AMOTION_EVENT_ACTION_HOVER_MOVE, motionArgs.action); + ASSERT_EQ(AMOTION_EVENT_BUTTON_BACK, motionArgs.buttonState); + ASSERT_NO_FATAL_FAILURE( + assertCursorPointerCoords(motionArgs.pointerCoords[0], 100.0f, 200.0f, 0.0f)); - // Toggle caps lock on and off. - process(mapper1, ARBITRARY_TIME, READ_TIME, EV_KEY, KEY_CAPSLOCK, 1); - process(mapper1, ARBITRARY_TIME, READ_TIME, EV_KEY, KEY_CAPSLOCK, 0); - ASSERT_TRUE(mFakeEventHub->getLedState(EVENTHUB_ID, LED_CAPSL)); - ASSERT_EQ(AMETA_CAPS_LOCK_ON, mapper1.getMetaState()); - ASSERT_EQ(AMETA_CAPS_LOCK_ON, mapper2.getMetaState()); + ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs)); + ASSERT_EQ(AMOTION_EVENT_ACTION_BUTTON_PRESS, motionArgs.action); + ASSERT_EQ(AMOTION_EVENT_BUTTON_BACK, motionArgs.buttonState); + ASSERT_NO_FATAL_FAILURE( + assertCursorPointerCoords(motionArgs.pointerCoords[0], 100.0f, 200.0f, 0.0f)); - process(mapper1, ARBITRARY_TIME, READ_TIME, EV_KEY, KEY_CAPSLOCK, 1); - process(mapper1, ARBITRARY_TIME, READ_TIME, EV_KEY, KEY_CAPSLOCK, 0); - ASSERT_FALSE(mFakeEventHub->getLedState(EVENTHUB_ID, LED_CAPSL)); - ASSERT_EQ(AMETA_NONE, mapper1.getMetaState()); - ASSERT_EQ(AMETA_NONE, mapper2.getMetaState()); + process(mapper, ARBITRARY_TIME, READ_TIME, EV_KEY, BTN_SIDE, 0); + process(mapper, ARBITRARY_TIME, READ_TIME, EV_SYN, SYN_REPORT, 0); + ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs)); + ASSERT_EQ(AMOTION_EVENT_ACTION_BUTTON_RELEASE, motionArgs.action); + ASSERT_EQ(0, motionArgs.buttonState); + ASSERT_NO_FATAL_FAILURE( + assertCursorPointerCoords(motionArgs.pointerCoords[0], 100.0f, 200.0f, 0.0f)); - // Toggle scroll lock on and off. - process(mapper1, ARBITRARY_TIME, READ_TIME, EV_KEY, KEY_SCROLLLOCK, 1); - process(mapper1, ARBITRARY_TIME, READ_TIME, EV_KEY, KEY_SCROLLLOCK, 0); - ASSERT_TRUE(mFakeEventHub->getLedState(EVENTHUB_ID, LED_SCROLLL)); - ASSERT_EQ(AMETA_SCROLL_LOCK_ON, mapper1.getMetaState()); - ASSERT_EQ(AMETA_SCROLL_LOCK_ON, mapper2.getMetaState()); + ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs)); + ASSERT_EQ(AMOTION_EVENT_ACTION_HOVER_MOVE, motionArgs.action); + ASSERT_EQ(0, motionArgs.buttonState); + ASSERT_NO_FATAL_FAILURE( + assertCursorPointerCoords(motionArgs.pointerCoords[0], 100.0f, 200.0f, 0.0f)); - process(mapper1, ARBITRARY_TIME, READ_TIME, EV_KEY, KEY_SCROLLLOCK, 1); - process(mapper1, ARBITRARY_TIME, READ_TIME, EV_KEY, KEY_SCROLLLOCK, 0); - ASSERT_FALSE(mFakeEventHub->getLedState(EVENTHUB_ID, LED_SCROLLL)); - ASSERT_EQ(AMETA_NONE, mapper1.getMetaState()); - ASSERT_EQ(AMETA_NONE, mapper2.getMetaState()); -} + ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyKeyWasCalled(&keyArgs)); + ASSERT_EQ(AKEY_EVENT_ACTION_UP, keyArgs.action); + ASSERT_EQ(AKEYCODE_BACK, keyArgs.keyCode); -// --- KeyboardInputMapperTest_ExternalDevice --- + // press BTN_FORWARD, release BTN_FORWARD + process(mapper, ARBITRARY_TIME, READ_TIME, EV_KEY, BTN_FORWARD, 1); + process(mapper, ARBITRARY_TIME, READ_TIME, EV_SYN, SYN_REPORT, 0); + ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyKeyWasCalled(&keyArgs)); + ASSERT_EQ(AKEY_EVENT_ACTION_DOWN, keyArgs.action); + ASSERT_EQ(AKEYCODE_FORWARD, keyArgs.keyCode); -class KeyboardInputMapperTest_ExternalDevice : public InputMapperTest { -protected: - void SetUp() override { InputMapperTest::SetUp(DEVICE_CLASSES | InputDeviceClass::EXTERNAL); } -}; + ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs)); + ASSERT_EQ(AMOTION_EVENT_ACTION_HOVER_MOVE, motionArgs.action); + ASSERT_EQ(AMOTION_EVENT_BUTTON_FORWARD, motionArgs.buttonState); + ASSERT_NO_FATAL_FAILURE( + assertCursorPointerCoords(motionArgs.pointerCoords[0], 100.0f, 200.0f, 0.0f)); -TEST_F(KeyboardInputMapperTest_ExternalDevice, WakeBehavior) { - // For external devices, non-media keys will trigger wake on key down. Media keys need to be - // marked as WAKE in the keylayout file to trigger wake. + ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs)); + ASSERT_EQ(AMOTION_EVENT_ACTION_BUTTON_PRESS, motionArgs.action); + ASSERT_EQ(AMOTION_EVENT_BUTTON_FORWARD, motionArgs.buttonState); + ASSERT_NO_FATAL_FAILURE( + assertCursorPointerCoords(motionArgs.pointerCoords[0], 100.0f, 200.0f, 0.0f)); - mFakeEventHub->addKey(EVENTHUB_ID, KEY_HOME, 0, AKEYCODE_HOME, 0); - mFakeEventHub->addKey(EVENTHUB_ID, KEY_PLAY, 0, AKEYCODE_MEDIA_PLAY, 0); - mFakeEventHub->addKey(EVENTHUB_ID, KEY_PLAYPAUSE, 0, AKEYCODE_MEDIA_PLAY_PAUSE, - POLICY_FLAG_WAKE); + process(mapper, ARBITRARY_TIME, READ_TIME, EV_KEY, BTN_FORWARD, 0); + process(mapper, ARBITRARY_TIME, READ_TIME, EV_SYN, SYN_REPORT, 0); + ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs)); + ASSERT_EQ(AMOTION_EVENT_ACTION_BUTTON_RELEASE, motionArgs.action); + ASSERT_EQ(0, motionArgs.buttonState); + ASSERT_NO_FATAL_FAILURE( + assertCursorPointerCoords(motionArgs.pointerCoords[0], 100.0f, 200.0f, 0.0f)); - KeyboardInputMapper& mapper = - addMapperAndConfigure(AINPUT_SOURCE_KEYBOARD, - AINPUT_KEYBOARD_TYPE_ALPHABETIC); + ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs)); + ASSERT_EQ(AMOTION_EVENT_ACTION_HOVER_MOVE, motionArgs.action); + ASSERT_EQ(0, motionArgs.buttonState); + ASSERT_NO_FATAL_FAILURE( + assertCursorPointerCoords(motionArgs.pointerCoords[0], 100.0f, 200.0f, 0.0f)); - process(mapper, ARBITRARY_TIME, READ_TIME, EV_KEY, KEY_HOME, 1); - NotifyKeyArgs args; - ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyKeyWasCalled(&args)); - ASSERT_EQ(POLICY_FLAG_WAKE, args.policyFlags); + ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyKeyWasCalled(&keyArgs)); + ASSERT_EQ(AKEY_EVENT_ACTION_UP, keyArgs.action); + ASSERT_EQ(AKEYCODE_FORWARD, keyArgs.keyCode); - process(mapper, ARBITRARY_TIME + 1, READ_TIME, EV_KEY, KEY_HOME, 0); - ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyKeyWasCalled(&args)); - ASSERT_EQ(uint32_t(0), args.policyFlags); + // press BTN_EXTRA, release BTN_EXTRA + process(mapper, ARBITRARY_TIME, READ_TIME, EV_KEY, BTN_EXTRA, 1); + process(mapper, ARBITRARY_TIME, READ_TIME, EV_SYN, SYN_REPORT, 0); + ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyKeyWasCalled(&keyArgs)); + ASSERT_EQ(AKEY_EVENT_ACTION_DOWN, keyArgs.action); + ASSERT_EQ(AKEYCODE_FORWARD, keyArgs.keyCode); - process(mapper, ARBITRARY_TIME, READ_TIME, EV_KEY, KEY_PLAY, 1); - ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyKeyWasCalled(&args)); - ASSERT_EQ(uint32_t(0), args.policyFlags); + ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs)); + ASSERT_EQ(AMOTION_EVENT_ACTION_HOVER_MOVE, motionArgs.action); + ASSERT_EQ(AMOTION_EVENT_BUTTON_FORWARD, motionArgs.buttonState); + ASSERT_NO_FATAL_FAILURE( + assertCursorPointerCoords(motionArgs.pointerCoords[0], 100.0f, 200.0f, 0.0f)); - process(mapper, ARBITRARY_TIME + 1, READ_TIME, EV_KEY, KEY_PLAY, 0); - ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyKeyWasCalled(&args)); - ASSERT_EQ(uint32_t(0), args.policyFlags); + ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs)); + ASSERT_EQ(AMOTION_EVENT_ACTION_BUTTON_PRESS, motionArgs.action); + ASSERT_EQ(AMOTION_EVENT_BUTTON_FORWARD, motionArgs.buttonState); + ASSERT_NO_FATAL_FAILURE( + assertCursorPointerCoords(motionArgs.pointerCoords[0], 100.0f, 200.0f, 0.0f)); - process(mapper, ARBITRARY_TIME, READ_TIME, EV_KEY, KEY_PLAYPAUSE, 1); - ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyKeyWasCalled(&args)); - ASSERT_EQ(POLICY_FLAG_WAKE, args.policyFlags); + process(mapper, ARBITRARY_TIME, READ_TIME, EV_KEY, BTN_EXTRA, 0); + process(mapper, ARBITRARY_TIME, READ_TIME, EV_SYN, SYN_REPORT, 0); + ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs)); + ASSERT_EQ(AMOTION_EVENT_ACTION_BUTTON_RELEASE, motionArgs.action); + ASSERT_EQ(0, motionArgs.buttonState); + ASSERT_NO_FATAL_FAILURE( + assertCursorPointerCoords(motionArgs.pointerCoords[0], 100.0f, 200.0f, 0.0f)); - process(mapper, ARBITRARY_TIME + 1, READ_TIME, EV_KEY, KEY_PLAYPAUSE, 0); - ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyKeyWasCalled(&args)); - ASSERT_EQ(POLICY_FLAG_WAKE, args.policyFlags); + ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs)); + ASSERT_EQ(AMOTION_EVENT_ACTION_HOVER_MOVE, motionArgs.action); + ASSERT_EQ(0, motionArgs.buttonState); + ASSERT_NO_FATAL_FAILURE( + assertCursorPointerCoords(motionArgs.pointerCoords[0], 100.0f, 200.0f, 0.0f)); + + ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyKeyWasCalled(&keyArgs)); + ASSERT_EQ(AKEY_EVENT_ACTION_UP, keyArgs.action); + ASSERT_EQ(AKEYCODE_FORWARD, keyArgs.keyCode); } -TEST_F(KeyboardInputMapperTest_ExternalDevice, DoNotWakeByDefaultBehavior) { - // Tv Remote key's wake behavior is prescribed by the keylayout file. +TEST_F(CursorInputMapperTest, Process_WhenModeIsPointer_ShouldMoveThePointerAround) { + addConfigurationProperty("cursor.mode", "pointer"); + CursorInputMapper& mapper = constructAndAddMapper(); - mFakeEventHub->addKey(EVENTHUB_ID, KEY_HOME, 0, AKEYCODE_HOME, POLICY_FLAG_WAKE); - mFakeEventHub->addKey(EVENTHUB_ID, KEY_DOWN, 0, AKEYCODE_DPAD_DOWN, 0); - mFakeEventHub->addKey(EVENTHUB_ID, KEY_PLAY, 0, AKEYCODE_MEDIA_PLAY, POLICY_FLAG_WAKE); + mFakePointerController->setBounds(0, 0, 800 - 1, 480 - 1); + mFakePointerController->setPosition(100, 200); - addConfigurationProperty("keyboard.doNotWakeByDefault", "1"); - KeyboardInputMapper& mapper = - addMapperAndConfigure(AINPUT_SOURCE_KEYBOARD, - AINPUT_KEYBOARD_TYPE_ALPHABETIC); + NotifyMotionArgs args; - process(mapper, ARBITRARY_TIME, READ_TIME, EV_KEY, KEY_HOME, 1); - NotifyKeyArgs args; - ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyKeyWasCalled(&args)); - ASSERT_EQ(POLICY_FLAG_WAKE, args.policyFlags); + process(mapper, ARBITRARY_TIME, READ_TIME, EV_REL, REL_X, 10); + process(mapper, ARBITRARY_TIME, READ_TIME, EV_REL, REL_Y, 20); + process(mapper, ARBITRARY_TIME, READ_TIME, EV_SYN, SYN_REPORT, 0); + ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&args)); + ASSERT_EQ(AINPUT_SOURCE_MOUSE, args.source); + ASSERT_EQ(AMOTION_EVENT_ACTION_HOVER_MOVE, args.action); + ASSERT_NO_FATAL_FAILURE(assertPointerCoords(args.pointerCoords[0], + 110.0f, 220.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f)); + ASSERT_NO_FATAL_FAILURE(mFakePointerController->assertPosition(110.0f, 220.0f)); +} - process(mapper, ARBITRARY_TIME + 1, READ_TIME, EV_KEY, KEY_HOME, 0); - ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyKeyWasCalled(&args)); - ASSERT_EQ(POLICY_FLAG_WAKE, args.policyFlags); +TEST_F(CursorInputMapperTest, Process_PointerCapture) { + addConfigurationProperty("cursor.mode", "pointer"); + mFakePolicy->setPointerCapture(true); + CursorInputMapper& mapper = constructAndAddMapper(); - process(mapper, ARBITRARY_TIME, READ_TIME, EV_KEY, KEY_DOWN, 1); - ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyKeyWasCalled(&args)); - ASSERT_EQ(uint32_t(0), args.policyFlags); + NotifyDeviceResetArgs resetArgs; + ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyDeviceResetWasCalled(&resetArgs)); + ASSERT_EQ(ARBITRARY_TIME, resetArgs.eventTime); + ASSERT_EQ(DEVICE_ID, resetArgs.deviceId); - process(mapper, ARBITRARY_TIME + 1, READ_TIME, EV_KEY, KEY_DOWN, 0); - ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyKeyWasCalled(&args)); - ASSERT_EQ(uint32_t(0), args.policyFlags); + mFakePointerController->setBounds(0, 0, 800 - 1, 480 - 1); + mFakePointerController->setPosition(100, 200); - process(mapper, ARBITRARY_TIME, READ_TIME, EV_KEY, KEY_PLAY, 1); - ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyKeyWasCalled(&args)); - ASSERT_EQ(POLICY_FLAG_WAKE, args.policyFlags); + NotifyMotionArgs args; - process(mapper, ARBITRARY_TIME + 1, READ_TIME, EV_KEY, KEY_PLAY, 0); - ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyKeyWasCalled(&args)); - ASSERT_EQ(POLICY_FLAG_WAKE, args.policyFlags); -} + // Move. + process(mapper, ARBITRARY_TIME, READ_TIME, EV_REL, REL_X, 10); + process(mapper, ARBITRARY_TIME, READ_TIME, EV_REL, REL_Y, 20); + process(mapper, ARBITRARY_TIME, READ_TIME, EV_SYN, SYN_REPORT, 0); + ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&args)); + ASSERT_EQ(AINPUT_SOURCE_MOUSE_RELATIVE, args.source); + ASSERT_EQ(AMOTION_EVENT_ACTION_MOVE, args.action); + ASSERT_NO_FATAL_FAILURE(assertPointerCoords(args.pointerCoords[0], + 10.0f, 20.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f)); + ASSERT_NO_FATAL_FAILURE(mFakePointerController->assertPosition(100.0f, 200.0f)); -// --- CursorInputMapperTest --- + // Button press. + process(mapper, ARBITRARY_TIME, READ_TIME, EV_KEY, BTN_MOUSE, 1); + process(mapper, ARBITRARY_TIME, READ_TIME, EV_SYN, SYN_REPORT, 0); + ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&args)); + ASSERT_EQ(AINPUT_SOURCE_MOUSE_RELATIVE, args.source); + ASSERT_EQ(AMOTION_EVENT_ACTION_DOWN, args.action); + ASSERT_NO_FATAL_FAILURE(assertPointerCoords(args.pointerCoords[0], + 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f)); + ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&args)); + ASSERT_EQ(AINPUT_SOURCE_MOUSE_RELATIVE, args.source); + ASSERT_EQ(AMOTION_EVENT_ACTION_BUTTON_PRESS, args.action); + ASSERT_NO_FATAL_FAILURE(assertPointerCoords(args.pointerCoords[0], + 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f)); -class CursorInputMapperTest : public InputMapperTest { -protected: - static const int32_t TRACKBALL_MOVEMENT_THRESHOLD; + // Button release. + process(mapper, ARBITRARY_TIME + 2, READ_TIME, EV_KEY, BTN_MOUSE, 0); + process(mapper, ARBITRARY_TIME + 2, READ_TIME, EV_SYN, SYN_REPORT, 0); + ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&args)); + ASSERT_EQ(AINPUT_SOURCE_MOUSE_RELATIVE, args.source); + ASSERT_EQ(AMOTION_EVENT_ACTION_BUTTON_RELEASE, args.action); + ASSERT_NO_FATAL_FAILURE(assertPointerCoords(args.pointerCoords[0], + 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f)); + ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&args)); + ASSERT_EQ(AINPUT_SOURCE_MOUSE_RELATIVE, args.source); + ASSERT_EQ(AMOTION_EVENT_ACTION_UP, args.action); + ASSERT_NO_FATAL_FAILURE(assertPointerCoords(args.pointerCoords[0], + 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f)); - std::shared_ptr mFakePointerController; + // Another move. + process(mapper, ARBITRARY_TIME, READ_TIME, EV_REL, REL_X, 30); + process(mapper, ARBITRARY_TIME, READ_TIME, EV_REL, REL_Y, 40); + process(mapper, ARBITRARY_TIME, READ_TIME, EV_SYN, SYN_REPORT, 0); + ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&args)); + ASSERT_EQ(AINPUT_SOURCE_MOUSE_RELATIVE, args.source); + ASSERT_EQ(AMOTION_EVENT_ACTION_MOVE, args.action); + ASSERT_NO_FATAL_FAILURE(assertPointerCoords(args.pointerCoords[0], + 30.0f, 40.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f)); + ASSERT_NO_FATAL_FAILURE(mFakePointerController->assertPosition(100.0f, 200.0f)); - void SetUp() override { - InputMapperTest::SetUp(); + // Disable pointer capture and check that the device generation got bumped + // and events are generated the usual way. + const uint32_t generation = mReader->getContext()->getGeneration(); + mFakePolicy->setPointerCapture(false); + configureDevice(InputReaderConfiguration::Change::POINTER_CAPTURE); + ASSERT_TRUE(mReader->getContext()->getGeneration() != generation); - mFakePointerController = std::make_shared(); - mFakePolicy->setPointerController(mFakePointerController); - } + ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyDeviceResetWasCalled(&resetArgs)); + ASSERT_EQ(DEVICE_ID, resetArgs.deviceId); - void testMotionRotation(CursorInputMapper& mapper, int32_t originalX, int32_t originalY, - int32_t rotatedX, int32_t rotatedY); + process(mapper, ARBITRARY_TIME, READ_TIME, EV_REL, REL_X, 10); + process(mapper, ARBITRARY_TIME, READ_TIME, EV_REL, REL_Y, 20); + process(mapper, ARBITRARY_TIME, READ_TIME, EV_SYN, SYN_REPORT, 0); + ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&args)); + ASSERT_EQ(AINPUT_SOURCE_MOUSE, args.source); + ASSERT_EQ(AMOTION_EVENT_ACTION_HOVER_MOVE, args.action); + ASSERT_NO_FATAL_FAILURE(assertPointerCoords(args.pointerCoords[0], + 110.0f, 220.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f)); + ASSERT_NO_FATAL_FAILURE(mFakePointerController->assertPosition(110.0f, 220.0f)); +} - void prepareDisplay(int32_t orientation) { - setDisplayInfoAndReconfigure(DISPLAY_ID, DISPLAY_WIDTH, DISPLAY_HEIGHT, orientation, - DISPLAY_UNIQUE_ID, NO_PORT, ViewportType::INTERNAL); - } +/** + * When Pointer Capture is enabled, we expect to report unprocessed relative movements, so any + * pointer acceleration or speed processing should not be applied. + */ +TEST_F(CursorInputMapperTest, PointerCaptureDisablesVelocityProcessing) { + addConfigurationProperty("cursor.mode", "pointer"); + const VelocityControlParameters testParams(/*scale=*/5.f, /*low threshold=*/0.f, + /*high threshold=*/100.f, /*acceleration=*/10.f); + mFakePolicy->setVelocityControlParams(testParams); + CursorInputMapper& mapper = constructAndAddMapper(); - void prepareSecondaryDisplay() { - setDisplayInfoAndReconfigure(SECONDARY_DISPLAY_ID, DISPLAY_WIDTH, DISPLAY_HEIGHT, - DISPLAY_ORIENTATION_0, SECONDARY_DISPLAY_UNIQUE_ID, NO_PORT, - ViewportType::EXTERNAL); - } + NotifyDeviceResetArgs resetArgs; + ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyDeviceResetWasCalled(&resetArgs)); + ASSERT_EQ(ARBITRARY_TIME, resetArgs.eventTime); + ASSERT_EQ(DEVICE_ID, resetArgs.deviceId); - static void assertCursorPointerCoords(const PointerCoords& coords, float x, float y, - float pressure) { - ASSERT_NO_FATAL_FAILURE(assertPointerCoords(coords, x, y, pressure, 0.0f, 0.0f, 0.0f, 0.0f, - 0.0f, 0.0f, 0.0f, EPSILON)); - } -}; + NotifyMotionArgs args; -const int32_t CursorInputMapperTest::TRACKBALL_MOVEMENT_THRESHOLD = 6; + // Move and verify scale is applied. + process(mapper, ARBITRARY_TIME, READ_TIME, EV_REL, REL_X, 10); + process(mapper, ARBITRARY_TIME, READ_TIME, EV_REL, REL_Y, 20); + process(mapper, ARBITRARY_TIME, READ_TIME, EV_SYN, SYN_REPORT, 0); + ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&args)); + ASSERT_EQ(AINPUT_SOURCE_MOUSE, args.source); + ASSERT_EQ(AMOTION_EVENT_ACTION_HOVER_MOVE, args.action); + const float relX = args.pointerCoords[0].getAxisValue(AMOTION_EVENT_AXIS_RELATIVE_X); + const float relY = args.pointerCoords[0].getAxisValue(AMOTION_EVENT_AXIS_RELATIVE_Y); + ASSERT_GT(relX, 10); + ASSERT_GT(relY, 20); -void CursorInputMapperTest::testMotionRotation(CursorInputMapper& mapper, int32_t originalX, - int32_t originalY, int32_t rotatedX, - int32_t rotatedY) { - NotifyMotionArgs args; + // Enable Pointer Capture + mFakePolicy->setPointerCapture(true); + configureDevice(InputReaderConfiguration::Change::POINTER_CAPTURE); + NotifyPointerCaptureChangedArgs captureArgs; + ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyCaptureWasCalled(&captureArgs)); + ASSERT_TRUE(captureArgs.request.enable); - process(mapper, ARBITRARY_TIME, READ_TIME, EV_REL, REL_X, originalX); - process(mapper, ARBITRARY_TIME, READ_TIME, EV_REL, REL_Y, originalY); + // Move and verify scale is not applied. + process(mapper, ARBITRARY_TIME, READ_TIME, EV_REL, REL_X, 10); + process(mapper, ARBITRARY_TIME, READ_TIME, EV_REL, REL_Y, 20); process(mapper, ARBITRARY_TIME, READ_TIME, EV_SYN, SYN_REPORT, 0); ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&args)); + ASSERT_EQ(AINPUT_SOURCE_MOUSE_RELATIVE, args.source); ASSERT_EQ(AMOTION_EVENT_ACTION_MOVE, args.action); - ASSERT_NO_FATAL_FAILURE( - assertCursorPointerCoords(args.pointerCoords[0], - float(rotatedX) / TRACKBALL_MOVEMENT_THRESHOLD, - float(rotatedY) / TRACKBALL_MOVEMENT_THRESHOLD, 0.0f)); + ASSERT_EQ(10, args.pointerCoords[0].getX()); + ASSERT_EQ(20, args.pointerCoords[0].getY()); } -TEST_F(CursorInputMapperTest, WhenModeIsPointer_GetSources_ReturnsMouse) { +TEST_F(CursorInputMapperTest, PointerCaptureDisablesOrientationChanges) { addConfigurationProperty("cursor.mode", "pointer"); - CursorInputMapper& mapper = addMapperAndConfigure(); + CursorInputMapper& mapper = constructAndAddMapper(); - ASSERT_EQ(AINPUT_SOURCE_MOUSE, mapper.getSources()); -} + NotifyDeviceResetArgs resetArgs; + ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyDeviceResetWasCalled(&resetArgs)); + ASSERT_EQ(ARBITRARY_TIME, resetArgs.eventTime); + ASSERT_EQ(DEVICE_ID, resetArgs.deviceId); -TEST_F(CursorInputMapperTest, WhenModeIsNavigation_GetSources_ReturnsTrackball) { - addConfigurationProperty("cursor.mode", "navigation"); - CursorInputMapper& mapper = addMapperAndConfigure(); + // Ensure the display is rotated. + prepareDisplay(ui::ROTATION_90); - ASSERT_EQ(AINPUT_SOURCE_TRACKBALL, mapper.getSources()); -} + NotifyMotionArgs args; -TEST_F(CursorInputMapperTest, WhenModeIsPointer_PopulateDeviceInfo_ReturnsRangeFromPointerController) { - addConfigurationProperty("cursor.mode", "pointer"); - CursorInputMapper& mapper = addMapperAndConfigure(); + // Verify that the coordinates are rotated. + process(mapper, ARBITRARY_TIME, READ_TIME, EV_REL, REL_X, 10); + process(mapper, ARBITRARY_TIME, READ_TIME, EV_REL, REL_Y, 20); + process(mapper, ARBITRARY_TIME, READ_TIME, EV_SYN, SYN_REPORT, 0); + ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&args)); + ASSERT_EQ(AINPUT_SOURCE_MOUSE, args.source); + ASSERT_EQ(AMOTION_EVENT_ACTION_HOVER_MOVE, args.action); + ASSERT_EQ(-20, args.pointerCoords[0].getAxisValue(AMOTION_EVENT_AXIS_RELATIVE_X)); + ASSERT_EQ(10, args.pointerCoords[0].getAxisValue(AMOTION_EVENT_AXIS_RELATIVE_Y)); - InputDeviceInfo info; - mapper.populateDeviceInfo(&info); + // Enable Pointer Capture. + mFakePolicy->setPointerCapture(true); + configureDevice(InputReaderConfiguration::Change::POINTER_CAPTURE); + NotifyPointerCaptureChangedArgs captureArgs; + ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyCaptureWasCalled(&captureArgs)); + ASSERT_TRUE(captureArgs.request.enable); - // Initially there may not be a valid motion range. - ASSERT_EQ(nullptr, info.getMotionRange(AINPUT_MOTION_RANGE_X, AINPUT_SOURCE_MOUSE)); - ASSERT_EQ(nullptr, info.getMotionRange(AINPUT_MOTION_RANGE_Y, AINPUT_SOURCE_MOUSE)); - ASSERT_NO_FATAL_FAILURE(assertMotionRange(info, - AINPUT_MOTION_RANGE_PRESSURE, AINPUT_SOURCE_MOUSE, 0.0f, 1.0f, 0.0f, 0.0f)); + // Move and verify rotation is not applied. + process(mapper, ARBITRARY_TIME, READ_TIME, EV_REL, REL_X, 10); + process(mapper, ARBITRARY_TIME, READ_TIME, EV_REL, REL_Y, 20); + process(mapper, ARBITRARY_TIME, READ_TIME, EV_SYN, SYN_REPORT, 0); + ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&args)); + ASSERT_EQ(AINPUT_SOURCE_MOUSE_RELATIVE, args.source); + ASSERT_EQ(AMOTION_EVENT_ACTION_MOVE, args.action); + ASSERT_EQ(10, args.pointerCoords[0].getX()); + ASSERT_EQ(20, args.pointerCoords[0].getY()); +} - // When the bounds are set, then there should be a valid motion range. - mFakePointerController->setBounds(1, 2, 800 - 1, 480 - 1); +TEST_F(CursorInputMapperTest, ConfigureDisplayId_NoAssociatedViewport) { + CursorInputMapper& mapper = constructAndAddMapper(); - InputDeviceInfo info2; - mapper.populateDeviceInfo(&info2); + // Set up the default display. + prepareDisplay(ui::ROTATION_90); - ASSERT_NO_FATAL_FAILURE(assertMotionRange(info2, - AINPUT_MOTION_RANGE_X, AINPUT_SOURCE_MOUSE, - 1, 800 - 1, 0.0f, 0.0f)); - ASSERT_NO_FATAL_FAILURE(assertMotionRange(info2, - AINPUT_MOTION_RANGE_Y, AINPUT_SOURCE_MOUSE, - 2, 480 - 1, 0.0f, 0.0f)); - ASSERT_NO_FATAL_FAILURE(assertMotionRange(info2, - AINPUT_MOTION_RANGE_PRESSURE, AINPUT_SOURCE_MOUSE, - 0.0f, 1.0f, 0.0f, 0.0f)); -} - -TEST_F(CursorInputMapperTest, WhenModeIsNavigation_PopulateDeviceInfo_ReturnsScaledRange) { - addConfigurationProperty("cursor.mode", "navigation"); - CursorInputMapper& mapper = addMapperAndConfigure(); + // Set up the secondary display as the display on which the pointer should be shown. + // The InputDevice is not associated with any display. + prepareSecondaryDisplay(); + mFakePolicy->setDefaultPointerDisplayId(SECONDARY_DISPLAY_ID); + configureDevice(InputReaderConfiguration::Change::DISPLAY_INFO); - InputDeviceInfo info; - mapper.populateDeviceInfo(&info); + mFakePointerController->setBounds(0, 0, DISPLAY_WIDTH - 1, DISPLAY_HEIGHT - 1); + mFakePointerController->setPosition(100, 200); - ASSERT_NO_FATAL_FAILURE(assertMotionRange(info, - AINPUT_MOTION_RANGE_X, AINPUT_SOURCE_TRACKBALL, - -1.0f, 1.0f, 0.0f, 1.0f / TRACKBALL_MOVEMENT_THRESHOLD)); - ASSERT_NO_FATAL_FAILURE(assertMotionRange(info, - AINPUT_MOTION_RANGE_Y, AINPUT_SOURCE_TRACKBALL, - -1.0f, 1.0f, 0.0f, 1.0f / TRACKBALL_MOVEMENT_THRESHOLD)); - ASSERT_NO_FATAL_FAILURE(assertMotionRange(info, - AINPUT_MOTION_RANGE_PRESSURE, AINPUT_SOURCE_TRACKBALL, - 0.0f, 1.0f, 0.0f, 0.0f)); + // Ensure input events are generated for the secondary display. + process(mapper, ARBITRARY_TIME, READ_TIME, EV_REL, REL_X, 10); + process(mapper, ARBITRARY_TIME, READ_TIME, EV_REL, REL_Y, 20); + process(mapper, ARBITRARY_TIME, READ_TIME, EV_SYN, SYN_REPORT, 0); + ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled( + AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_MOVE), + WithSource(AINPUT_SOURCE_MOUSE), WithDisplayId(SECONDARY_DISPLAY_ID), + WithCoords(110.0f, 220.0f)))); + ASSERT_NO_FATAL_FAILURE(mFakePointerController->assertPosition(110.0f, 220.0f)); } -TEST_F(CursorInputMapperTest, Process_ShouldSetAllFieldsAndIncludeGlobalMetaState) { - addConfigurationProperty("cursor.mode", "navigation"); - CursorInputMapper& mapper = addMapperAndConfigure(); - - mReader->getContext()->setGlobalMetaState(AMETA_SHIFT_LEFT_ON | AMETA_SHIFT_ON); - - NotifyMotionArgs args; +TEST_F(CursorInputMapperTest, ConfigureDisplayId_WithAssociatedViewport) { + CursorInputMapper& mapper = constructAndAddMapper(); - // Button press. - // Mostly testing non x/y behavior here so we don't need to check again elsewhere. - process(mapper, ARBITRARY_TIME, READ_TIME, EV_KEY, BTN_MOUSE, 1); - process(mapper, ARBITRARY_TIME, READ_TIME, EV_SYN, SYN_REPORT, 0); - ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&args)); - ASSERT_EQ(ARBITRARY_TIME, args.eventTime); - ASSERT_EQ(DEVICE_ID, args.deviceId); - ASSERT_EQ(AINPUT_SOURCE_TRACKBALL, args.source); - ASSERT_EQ(uint32_t(0), args.policyFlags); - ASSERT_EQ(AMOTION_EVENT_ACTION_DOWN, args.action); - ASSERT_EQ(0, args.flags); - ASSERT_EQ(AMETA_SHIFT_LEFT_ON | AMETA_SHIFT_ON, args.metaState); - ASSERT_EQ(AMOTION_EVENT_BUTTON_PRIMARY, args.buttonState); - ASSERT_EQ(0, args.edgeFlags); - ASSERT_EQ(uint32_t(1), args.pointerCount); - ASSERT_EQ(0, args.pointerProperties[0].id); - ASSERT_EQ(AMOTION_EVENT_TOOL_TYPE_MOUSE, args.pointerProperties[0].toolType); - ASSERT_NO_FATAL_FAILURE(assertCursorPointerCoords(args.pointerCoords[0], 0.0f, 0.0f, 1.0f)); - ASSERT_EQ(TRACKBALL_MOVEMENT_THRESHOLD, args.xPrecision); - ASSERT_EQ(TRACKBALL_MOVEMENT_THRESHOLD, args.yPrecision); - ASSERT_EQ(ARBITRARY_TIME, args.downTime); + // Set up the default display. + prepareDisplay(ui::ROTATION_90); - ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&args)); - ASSERT_EQ(ARBITRARY_TIME, args.eventTime); - ASSERT_EQ(DEVICE_ID, args.deviceId); - ASSERT_EQ(AINPUT_SOURCE_TRACKBALL, args.source); - ASSERT_EQ(uint32_t(0), args.policyFlags); - ASSERT_EQ(AMOTION_EVENT_ACTION_BUTTON_PRESS, args.action); - ASSERT_EQ(0, args.flags); - ASSERT_EQ(AMETA_SHIFT_LEFT_ON | AMETA_SHIFT_ON, args.metaState); - ASSERT_EQ(AMOTION_EVENT_BUTTON_PRIMARY, args.buttonState); - ASSERT_EQ(0, args.edgeFlags); - ASSERT_EQ(uint32_t(1), args.pointerCount); - ASSERT_EQ(0, args.pointerProperties[0].id); - ASSERT_EQ(AMOTION_EVENT_TOOL_TYPE_MOUSE, args.pointerProperties[0].toolType); - ASSERT_NO_FATAL_FAILURE(assertCursorPointerCoords(args.pointerCoords[0], 0.0f, 0.0f, 1.0f)); - ASSERT_EQ(TRACKBALL_MOVEMENT_THRESHOLD, args.xPrecision); - ASSERT_EQ(TRACKBALL_MOVEMENT_THRESHOLD, args.yPrecision); - ASSERT_EQ(ARBITRARY_TIME, args.downTime); + // Set up the secondary display as the display on which the pointer should be shown, + // and associate the InputDevice with the secondary display. + prepareSecondaryDisplay(); + mFakePolicy->setDefaultPointerDisplayId(SECONDARY_DISPLAY_ID); + mFakePolicy->addInputUniqueIdAssociation(DEVICE_LOCATION, SECONDARY_DISPLAY_UNIQUE_ID); + configureDevice(InputReaderConfiguration::Change::DISPLAY_INFO); - // Button release. Should have same down time. - process(mapper, ARBITRARY_TIME + 1, READ_TIME, EV_KEY, BTN_MOUSE, 0); - process(mapper, ARBITRARY_TIME + 1, READ_TIME, EV_SYN, SYN_REPORT, 0); - ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&args)); - ASSERT_EQ(ARBITRARY_TIME + 1, args.eventTime); - ASSERT_EQ(DEVICE_ID, args.deviceId); - ASSERT_EQ(AINPUT_SOURCE_TRACKBALL, args.source); - ASSERT_EQ(uint32_t(0), args.policyFlags); - ASSERT_EQ(AMOTION_EVENT_ACTION_BUTTON_RELEASE, args.action); - ASSERT_EQ(0, args.flags); - ASSERT_EQ(AMETA_SHIFT_LEFT_ON | AMETA_SHIFT_ON, args.metaState); - ASSERT_EQ(0, args.buttonState); - ASSERT_EQ(0, args.edgeFlags); - ASSERT_EQ(uint32_t(1), args.pointerCount); - ASSERT_EQ(0, args.pointerProperties[0].id); - ASSERT_EQ(AMOTION_EVENT_TOOL_TYPE_MOUSE, args.pointerProperties[0].toolType); - ASSERT_NO_FATAL_FAILURE(assertCursorPointerCoords(args.pointerCoords[0], 0.0f, 0.0f, 0.0f)); - ASSERT_EQ(TRACKBALL_MOVEMENT_THRESHOLD, args.xPrecision); - ASSERT_EQ(TRACKBALL_MOVEMENT_THRESHOLD, args.yPrecision); - ASSERT_EQ(ARBITRARY_TIME, args.downTime); + mFakePointerController->setBounds(0, 0, DISPLAY_WIDTH - 1, DISPLAY_HEIGHT - 1); + mFakePointerController->setPosition(100, 200); - ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&args)); - ASSERT_EQ(ARBITRARY_TIME + 1, args.eventTime); - ASSERT_EQ(DEVICE_ID, args.deviceId); - ASSERT_EQ(AINPUT_SOURCE_TRACKBALL, args.source); - ASSERT_EQ(uint32_t(0), args.policyFlags); - ASSERT_EQ(AMOTION_EVENT_ACTION_UP, args.action); - ASSERT_EQ(0, args.flags); - ASSERT_EQ(AMETA_SHIFT_LEFT_ON | AMETA_SHIFT_ON, args.metaState); - ASSERT_EQ(0, args.buttonState); - ASSERT_EQ(0, args.edgeFlags); - ASSERT_EQ(uint32_t(1), args.pointerCount); - ASSERT_EQ(0, args.pointerProperties[0].id); - ASSERT_EQ(AMOTION_EVENT_TOOL_TYPE_MOUSE, args.pointerProperties[0].toolType); - ASSERT_NO_FATAL_FAILURE(assertCursorPointerCoords(args.pointerCoords[0], 0.0f, 0.0f, 0.0f)); - ASSERT_EQ(TRACKBALL_MOVEMENT_THRESHOLD, args.xPrecision); - ASSERT_EQ(TRACKBALL_MOVEMENT_THRESHOLD, args.yPrecision); - ASSERT_EQ(ARBITRARY_TIME, args.downTime); + process(mapper, ARBITRARY_TIME, READ_TIME, EV_REL, REL_X, 10); + process(mapper, ARBITRARY_TIME, READ_TIME, EV_REL, REL_Y, 20); + process(mapper, ARBITRARY_TIME, READ_TIME, EV_SYN, SYN_REPORT, 0); + ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled( + AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_MOVE), + WithSource(AINPUT_SOURCE_MOUSE), WithDisplayId(SECONDARY_DISPLAY_ID), + WithCoords(110.0f, 220.0f)))); + ASSERT_NO_FATAL_FAILURE(mFakePointerController->assertPosition(110.0f, 220.0f)); } -TEST_F(CursorInputMapperTest, Process_ShouldHandleIndependentXYUpdates) { - addConfigurationProperty("cursor.mode", "navigation"); - CursorInputMapper& mapper = addMapperAndConfigure(); +TEST_F(CursorInputMapperTest, ConfigureDisplayId_IgnoresEventsForMismatchedPointerDisplay) { + CursorInputMapper& mapper = constructAndAddMapper(); - NotifyMotionArgs args; + // Set up the default display as the display on which the pointer should be shown. + prepareDisplay(ui::ROTATION_90); + mFakePolicy->setDefaultPointerDisplayId(DISPLAY_ID); - // Motion in X but not Y. - process(mapper, ARBITRARY_TIME, READ_TIME, EV_REL, REL_X, 1); - process(mapper, ARBITRARY_TIME, READ_TIME, EV_SYN, SYN_REPORT, 0); - ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&args)); - ASSERT_EQ(AMOTION_EVENT_ACTION_MOVE, args.action); - ASSERT_NO_FATAL_FAILURE(assertCursorPointerCoords(args.pointerCoords[0], - 1.0f / TRACKBALL_MOVEMENT_THRESHOLD, 0.0f, - 0.0f)); + // Associate the InputDevice with the secondary display. + prepareSecondaryDisplay(); + mFakePolicy->addInputUniqueIdAssociation(DEVICE_LOCATION, SECONDARY_DISPLAY_UNIQUE_ID); + configureDevice(InputReaderConfiguration::Change::DISPLAY_INFO); - // Motion in Y but not X. - process(mapper, ARBITRARY_TIME, READ_TIME, EV_REL, REL_Y, -2); + // The mapper should not generate any events because it is associated with a display that is + // different from the pointer display. + process(mapper, ARBITRARY_TIME, READ_TIME, EV_REL, REL_X, 10); + process(mapper, ARBITRARY_TIME, READ_TIME, EV_REL, REL_Y, 20); process(mapper, ARBITRARY_TIME, READ_TIME, EV_SYN, SYN_REPORT, 0); - ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&args)); - ASSERT_EQ(AMOTION_EVENT_ACTION_MOVE, args.action); - ASSERT_NO_FATAL_FAILURE(assertCursorPointerCoords(args.pointerCoords[0], 0.0f, - -2.0f / TRACKBALL_MOVEMENT_THRESHOLD, 0.0f)); + ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasNotCalled()); } -TEST_F(CursorInputMapperTest, Process_ShouldHandleIndependentButtonUpdates) { - addConfigurationProperty("cursor.mode", "navigation"); - CursorInputMapper& mapper = addMapperAndConfigure(); +// --- BluetoothCursorInputMapperTest --- - NotifyMotionArgs args; +class BluetoothCursorInputMapperTest : public CursorInputMapperTest { +protected: + void SetUp() override { + InputMapperTest::SetUp(DEVICE_CLASSES | InputDeviceClass::EXTERNAL, BUS_BLUETOOTH); - // Button press. - process(mapper, ARBITRARY_TIME, READ_TIME, EV_KEY, BTN_MOUSE, 1); - process(mapper, ARBITRARY_TIME, READ_TIME, EV_SYN, SYN_REPORT, 0); - ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&args)); - ASSERT_EQ(AMOTION_EVENT_ACTION_DOWN, args.action); - ASSERT_NO_FATAL_FAILURE(assertCursorPointerCoords(args.pointerCoords[0], 0.0f, 0.0f, 1.0f)); + mFakePointerController = std::make_shared(); + mFakePolicy->setPointerController(mFakePointerController); + } +}; - ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&args)); - ASSERT_EQ(AMOTION_EVENT_ACTION_BUTTON_PRESS, args.action); - ASSERT_NO_FATAL_FAILURE(assertCursorPointerCoords(args.pointerCoords[0], 0.0f, 0.0f, 1.0f)); +TEST_F(BluetoothCursorInputMapperTest, TimestampSmoothening) { + addConfigurationProperty("cursor.mode", "pointer"); + CursorInputMapper& mapper = constructAndAddMapper(); - // Button release. - process(mapper, ARBITRARY_TIME, READ_TIME, EV_KEY, BTN_MOUSE, 0); - process(mapper, ARBITRARY_TIME, READ_TIME, EV_SYN, SYN_REPORT, 0); - ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&args)); - ASSERT_EQ(AMOTION_EVENT_ACTION_BUTTON_RELEASE, args.action); - ASSERT_NO_FATAL_FAILURE(assertCursorPointerCoords(args.pointerCoords[0], 0.0f, 0.0f, 0.0f)); + nsecs_t kernelEventTime = ARBITRARY_TIME; + nsecs_t expectedEventTime = ARBITRARY_TIME; + process(mapper, kernelEventTime, READ_TIME, EV_REL, REL_X, 1); + process(mapper, kernelEventTime, READ_TIME, EV_SYN, SYN_REPORT, 0); + ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled( + AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_MOVE), + WithEventTime(expectedEventTime)))); - ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&args)); - ASSERT_EQ(AMOTION_EVENT_ACTION_UP, args.action); - ASSERT_NO_FATAL_FAILURE(assertCursorPointerCoords(args.pointerCoords[0], 0.0f, 0.0f, 0.0f)); -} + // Process several events that come in quick succession, according to their timestamps. + for (int i = 0; i < 3; i++) { + constexpr static nsecs_t delta = ms2ns(1); + static_assert(delta < MIN_BLUETOOTH_TIMESTAMP_DELTA); + kernelEventTime += delta; + expectedEventTime += MIN_BLUETOOTH_TIMESTAMP_DELTA; -TEST_F(CursorInputMapperTest, Process_ShouldHandleCombinedXYAndButtonUpdates) { - addConfigurationProperty("cursor.mode", "navigation"); - CursorInputMapper& mapper = addMapperAndConfigure(); + process(mapper, kernelEventTime, READ_TIME, EV_REL, REL_X, 1); + process(mapper, kernelEventTime, READ_TIME, EV_SYN, SYN_REPORT, 0); + ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled( + AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_MOVE), + WithEventTime(expectedEventTime)))); + } +} - NotifyMotionArgs args; +TEST_F(BluetoothCursorInputMapperTest, TimestampSmootheningIsCapped) { + addConfigurationProperty("cursor.mode", "pointer"); + CursorInputMapper& mapper = constructAndAddMapper(); - // Combined X, Y and Button. + nsecs_t expectedEventTime = ARBITRARY_TIME; process(mapper, ARBITRARY_TIME, READ_TIME, EV_REL, REL_X, 1); - process(mapper, ARBITRARY_TIME, READ_TIME, EV_REL, REL_Y, -2); - process(mapper, ARBITRARY_TIME, READ_TIME, EV_KEY, BTN_MOUSE, 1); process(mapper, ARBITRARY_TIME, READ_TIME, EV_SYN, SYN_REPORT, 0); - ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&args)); - ASSERT_EQ(AMOTION_EVENT_ACTION_DOWN, args.action); - ASSERT_NO_FATAL_FAILURE(assertCursorPointerCoords(args.pointerCoords[0], - 1.0f / TRACKBALL_MOVEMENT_THRESHOLD, - -2.0f / TRACKBALL_MOVEMENT_THRESHOLD, 1.0f)); - - ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&args)); - ASSERT_EQ(AMOTION_EVENT_ACTION_BUTTON_PRESS, args.action); - ASSERT_NO_FATAL_FAILURE(assertCursorPointerCoords(args.pointerCoords[0], - 1.0f / TRACKBALL_MOVEMENT_THRESHOLD, - -2.0f / TRACKBALL_MOVEMENT_THRESHOLD, 1.0f)); + ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled( + AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_MOVE), + WithEventTime(expectedEventTime)))); - // Move X, Y a bit while pressed. - process(mapper, ARBITRARY_TIME, READ_TIME, EV_REL, REL_X, 2); - process(mapper, ARBITRARY_TIME, READ_TIME, EV_REL, REL_Y, 1); - process(mapper, ARBITRARY_TIME, READ_TIME, EV_SYN, SYN_REPORT, 0); - ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&args)); - ASSERT_EQ(AMOTION_EVENT_ACTION_MOVE, args.action); - ASSERT_NO_FATAL_FAILURE(assertCursorPointerCoords(args.pointerCoords[0], - 2.0f / TRACKBALL_MOVEMENT_THRESHOLD, - 1.0f / TRACKBALL_MOVEMENT_THRESHOLD, 1.0f)); + // Process several events with the same timestamp from the kernel. + // Ensure that we do not generate events too far into the future. + constexpr static int32_t numEvents = + MAX_BLUETOOTH_SMOOTHING_DELTA / MIN_BLUETOOTH_TIMESTAMP_DELTA; + for (int i = 0; i < numEvents; i++) { + expectedEventTime += MIN_BLUETOOTH_TIMESTAMP_DELTA; - // Release Button. - process(mapper, ARBITRARY_TIME, READ_TIME, EV_KEY, BTN_MOUSE, 0); - process(mapper, ARBITRARY_TIME, READ_TIME, EV_SYN, SYN_REPORT, 0); - ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&args)); - ASSERT_EQ(AMOTION_EVENT_ACTION_BUTTON_RELEASE, args.action); - ASSERT_NO_FATAL_FAILURE(assertCursorPointerCoords(args.pointerCoords[0], 0.0f, 0.0f, 0.0f)); + process(mapper, ARBITRARY_TIME, READ_TIME, EV_REL, REL_X, 1); + process(mapper, ARBITRARY_TIME, READ_TIME, EV_SYN, SYN_REPORT, 0); + ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled( + AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_MOVE), + WithEventTime(expectedEventTime)))); + } - ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&args)); - ASSERT_EQ(AMOTION_EVENT_ACTION_UP, args.action); - ASSERT_NO_FATAL_FAILURE(assertCursorPointerCoords(args.pointerCoords[0], 0.0f, 0.0f, 0.0f)); + // By processing more events with the same timestamp, we should not generate events with a + // timestamp that is more than the specified max time delta from the timestamp at its injection. + const nsecs_t cappedEventTime = ARBITRARY_TIME + MAX_BLUETOOTH_SMOOTHING_DELTA; + for (int i = 0; i < 3; i++) { + process(mapper, ARBITRARY_TIME, READ_TIME, EV_REL, REL_X, 1); + process(mapper, ARBITRARY_TIME, READ_TIME, EV_SYN, SYN_REPORT, 0); + ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled( + AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_MOVE), + WithEventTime(cappedEventTime)))); + } } -TEST_F(CursorInputMapperTest, Process_WhenOrientationAware_ShouldNotRotateMotions) { - mFakePolicy->addInputUniqueIdAssociation(DEVICE_LOCATION, DISPLAY_UNIQUE_ID); - addConfigurationProperty("cursor.mode", "navigation"); - // InputReader works in the un-rotated coordinate space, so orientation-aware devices do not - // need to be rotated. - addConfigurationProperty("cursor.orientationAware", "1"); - CursorInputMapper& mapper = addMapperAndConfigure(); +TEST_F(BluetoothCursorInputMapperTest, TimestampSmootheningNotUsed) { + addConfigurationProperty("cursor.mode", "pointer"); + CursorInputMapper& mapper = constructAndAddMapper(); - prepareDisplay(DISPLAY_ORIENTATION_90); - ASSERT_NO_FATAL_FAILURE(testMotionRotation(mapper, 0, 1, 0, 1)); - ASSERT_NO_FATAL_FAILURE(testMotionRotation(mapper, 1, 1, 1, 1)); - ASSERT_NO_FATAL_FAILURE(testMotionRotation(mapper, 1, 0, 1, 0)); - ASSERT_NO_FATAL_FAILURE(testMotionRotation(mapper, 1, -1, 1, -1)); - ASSERT_NO_FATAL_FAILURE(testMotionRotation(mapper, 0, -1, 0, -1)); - ASSERT_NO_FATAL_FAILURE(testMotionRotation(mapper, -1, -1, -1, -1)); - ASSERT_NO_FATAL_FAILURE(testMotionRotation(mapper, -1, 0, -1, 0)); - ASSERT_NO_FATAL_FAILURE(testMotionRotation(mapper, -1, 1, -1, 1)); + nsecs_t kernelEventTime = ARBITRARY_TIME; + nsecs_t expectedEventTime = ARBITRARY_TIME; + process(mapper, kernelEventTime, READ_TIME, EV_REL, REL_X, 1); + process(mapper, kernelEventTime, READ_TIME, EV_SYN, SYN_REPORT, 0); + ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled( + AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_MOVE), + WithEventTime(expectedEventTime)))); + + // If the next event has a timestamp that is sufficiently spaced out so that Bluetooth timestamp + // smoothening is not needed, its timestamp is not affected. + kernelEventTime += MAX_BLUETOOTH_SMOOTHING_DELTA + ms2ns(1); + expectedEventTime = kernelEventTime; + + process(mapper, kernelEventTime, READ_TIME, EV_REL, REL_X, 1); + process(mapper, kernelEventTime, READ_TIME, EV_SYN, SYN_REPORT, 0); + ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled( + AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_MOVE), + WithEventTime(expectedEventTime)))); } -TEST_F(CursorInputMapperTest, Process_WhenNotOrientationAware_ShouldRotateMotions) { - mFakePolicy->addInputUniqueIdAssociation(DEVICE_LOCATION, DISPLAY_UNIQUE_ID); - addConfigurationProperty("cursor.mode", "navigation"); - // Since InputReader works in the un-rotated coordinate space, only devices that are not - // orientation-aware are affected by display rotation. - CursorInputMapper& mapper = addMapperAndConfigure(); +// --- TouchInputMapperTest --- - clearViewports(); - prepareDisplay(DISPLAY_ORIENTATION_0); - ASSERT_NO_FATAL_FAILURE(testMotionRotation(mapper, 0, 1, 0, 1)); - ASSERT_NO_FATAL_FAILURE(testMotionRotation(mapper, 1, 1, 1, 1)); - ASSERT_NO_FATAL_FAILURE(testMotionRotation(mapper, 1, 0, 1, 0)); - ASSERT_NO_FATAL_FAILURE(testMotionRotation(mapper, 1, -1, 1, -1)); - ASSERT_NO_FATAL_FAILURE(testMotionRotation(mapper, 0, -1, 0, -1)); - ASSERT_NO_FATAL_FAILURE(testMotionRotation(mapper, -1, -1, -1, -1)); - ASSERT_NO_FATAL_FAILURE(testMotionRotation(mapper, -1, 0, -1, 0)); - ASSERT_NO_FATAL_FAILURE(testMotionRotation(mapper, -1, 1, -1, 1)); - - clearViewports(); - prepareDisplay(DISPLAY_ORIENTATION_90); - ASSERT_NO_FATAL_FAILURE(testMotionRotation(mapper, 0, 1, -1, 0)); - ASSERT_NO_FATAL_FAILURE(testMotionRotation(mapper, 1, 1, -1, 1)); - ASSERT_NO_FATAL_FAILURE(testMotionRotation(mapper, 1, 0, 0, 1)); - ASSERT_NO_FATAL_FAILURE(testMotionRotation(mapper, 1, -1, 1, 1)); - ASSERT_NO_FATAL_FAILURE(testMotionRotation(mapper, 0, -1, 1, 0)); - ASSERT_NO_FATAL_FAILURE(testMotionRotation(mapper, -1, -1, 1, -1)); - ASSERT_NO_FATAL_FAILURE(testMotionRotation(mapper, -1, 0, 0, -1)); - ASSERT_NO_FATAL_FAILURE(testMotionRotation(mapper, -1, 1, -1, -1)); - - clearViewports(); - prepareDisplay(DISPLAY_ORIENTATION_180); - ASSERT_NO_FATAL_FAILURE(testMotionRotation(mapper, 0, 1, 0, -1)); - ASSERT_NO_FATAL_FAILURE(testMotionRotation(mapper, 1, 1, -1, -1)); - ASSERT_NO_FATAL_FAILURE(testMotionRotation(mapper, 1, 0, -1, 0)); - ASSERT_NO_FATAL_FAILURE(testMotionRotation(mapper, 1, -1, -1, 1)); - ASSERT_NO_FATAL_FAILURE(testMotionRotation(mapper, 0, -1, 0, 1)); - ASSERT_NO_FATAL_FAILURE(testMotionRotation(mapper, -1, -1, 1, 1)); - ASSERT_NO_FATAL_FAILURE(testMotionRotation(mapper, -1, 0, 1, 0)); - ASSERT_NO_FATAL_FAILURE(testMotionRotation(mapper, -1, 1, 1, -1)); - - clearViewports(); - prepareDisplay(DISPLAY_ORIENTATION_270); - ASSERT_NO_FATAL_FAILURE(testMotionRotation(mapper, 0, 1, 1, 0)); - ASSERT_NO_FATAL_FAILURE(testMotionRotation(mapper, 1, 1, 1, -1)); - ASSERT_NO_FATAL_FAILURE(testMotionRotation(mapper, 1, 0, 0, -1)); - ASSERT_NO_FATAL_FAILURE(testMotionRotation(mapper, 1, -1, -1, -1)); - ASSERT_NO_FATAL_FAILURE(testMotionRotation(mapper, 0, -1, -1, 0)); - ASSERT_NO_FATAL_FAILURE(testMotionRotation(mapper, -1, -1, -1, 1)); - ASSERT_NO_FATAL_FAILURE(testMotionRotation(mapper, -1, 0, 0, 1)); - ASSERT_NO_FATAL_FAILURE(testMotionRotation(mapper, -1, 1, 1, 1)); -} +class TouchInputMapperTest : public InputMapperTest { +protected: + static const int32_t RAW_X_MIN; + static const int32_t RAW_X_MAX; + static const int32_t RAW_Y_MIN; + static const int32_t RAW_Y_MAX; + static const int32_t RAW_TOUCH_MIN; + static const int32_t RAW_TOUCH_MAX; + static const int32_t RAW_TOOL_MIN; + static const int32_t RAW_TOOL_MAX; + static const int32_t RAW_PRESSURE_MIN; + static const int32_t RAW_PRESSURE_MAX; + static const int32_t RAW_ORIENTATION_MIN; + static const int32_t RAW_ORIENTATION_MAX; + static const int32_t RAW_DISTANCE_MIN; + static const int32_t RAW_DISTANCE_MAX; + static const int32_t RAW_TILT_MIN; + static const int32_t RAW_TILT_MAX; + static const int32_t RAW_ID_MIN; + static const int32_t RAW_ID_MAX; + static const int32_t RAW_SLOT_MIN; + static const int32_t RAW_SLOT_MAX; + static const float X_PRECISION; + static const float Y_PRECISION; + static const float X_PRECISION_VIRTUAL; + static const float Y_PRECISION_VIRTUAL; -TEST_F(CursorInputMapperTest, Process_ShouldHandleAllButtons) { - addConfigurationProperty("cursor.mode", "pointer"); - CursorInputMapper& mapper = addMapperAndConfigure(); + static const float GEOMETRIC_SCALE; + static const TouchAffineTransformation AFFINE_TRANSFORM; - mFakePointerController->setBounds(0, 0, 800 - 1, 480 - 1); - mFakePointerController->setPosition(100, 200); - mFakePointerController->setButtonState(0); + static const VirtualKeyDefinition VIRTUAL_KEYS[2]; - NotifyMotionArgs motionArgs; - NotifyKeyArgs keyArgs; + const std::string UNIQUE_ID = "local:0"; + const std::string SECONDARY_UNIQUE_ID = "local:1"; - // press BTN_LEFT, release BTN_LEFT - process(mapper, ARBITRARY_TIME, READ_TIME, EV_KEY, BTN_LEFT, 1); - process(mapper, ARBITRARY_TIME, READ_TIME, EV_SYN, SYN_REPORT, 0); - ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs)); - ASSERT_EQ(AMOTION_EVENT_ACTION_DOWN, motionArgs.action); - ASSERT_EQ(AMOTION_EVENT_BUTTON_PRIMARY, motionArgs.buttonState); - ASSERT_EQ(AMOTION_EVENT_BUTTON_PRIMARY, mFakePointerController->getButtonState()); - ASSERT_NO_FATAL_FAILURE( - assertCursorPointerCoords(motionArgs.pointerCoords[0], 100.0f, 200.0f, 1.0f)); + enum Axes { + POSITION = 1 << 0, + TOUCH = 1 << 1, + TOOL = 1 << 2, + PRESSURE = 1 << 3, + ORIENTATION = 1 << 4, + MINOR = 1 << 5, + ID = 1 << 6, + DISTANCE = 1 << 7, + TILT = 1 << 8, + SLOT = 1 << 9, + TOOL_TYPE = 1 << 10, + }; - ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs)); - ASSERT_EQ(AMOTION_EVENT_ACTION_BUTTON_PRESS, motionArgs.action); - ASSERT_EQ(AMOTION_EVENT_BUTTON_PRIMARY, motionArgs.buttonState); - ASSERT_EQ(AMOTION_EVENT_BUTTON_PRIMARY, mFakePointerController->getButtonState()); - ASSERT_NO_FATAL_FAILURE( - assertCursorPointerCoords(motionArgs.pointerCoords[0], 100.0f, 200.0f, 1.0f)); + void prepareDisplay(ui::Rotation orientation, std::optional port = NO_PORT); + void prepareSecondaryDisplay(ViewportType type, std::optional port = NO_PORT); + void prepareVirtualDisplay(ui::Rotation orientation); + void prepareVirtualKeys(); + void prepareLocationCalibration(); + int32_t toRawX(float displayX); + int32_t toRawY(float displayY); + int32_t toRotatedRawX(float displayX); + int32_t toRotatedRawY(float displayY); + float toCookedX(float rawX, float rawY); + float toCookedY(float rawX, float rawY); + float toDisplayX(int32_t rawX); + float toDisplayX(int32_t rawX, int32_t displayWidth); + float toDisplayY(int32_t rawY); + float toDisplayY(int32_t rawY, int32_t displayHeight); - process(mapper, ARBITRARY_TIME, READ_TIME, EV_KEY, BTN_LEFT, 0); - process(mapper, ARBITRARY_TIME, READ_TIME, EV_SYN, SYN_REPORT, 0); - ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs)); - ASSERT_EQ(AMOTION_EVENT_ACTION_BUTTON_RELEASE, motionArgs.action); - ASSERT_EQ(0, motionArgs.buttonState); - ASSERT_EQ(0, mFakePointerController->getButtonState()); - ASSERT_NO_FATAL_FAILURE( - assertCursorPointerCoords(motionArgs.pointerCoords[0], 100.0f, 200.0f, 0.0f)); +}; - ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs)); - ASSERT_EQ(AMOTION_EVENT_ACTION_UP, motionArgs.action); - ASSERT_EQ(0, motionArgs.buttonState); - ASSERT_EQ(0, mFakePointerController->getButtonState()); - ASSERT_NO_FATAL_FAILURE( - assertCursorPointerCoords(motionArgs.pointerCoords[0], 100.0f, 200.0f, 0.0f)); +const int32_t TouchInputMapperTest::RAW_X_MIN = 25; +const int32_t TouchInputMapperTest::RAW_X_MAX = 1019; +const int32_t TouchInputMapperTest::RAW_Y_MIN = 30; +const int32_t TouchInputMapperTest::RAW_Y_MAX = 1009; +const int32_t TouchInputMapperTest::RAW_TOUCH_MIN = 0; +const int32_t TouchInputMapperTest::RAW_TOUCH_MAX = 31; +const int32_t TouchInputMapperTest::RAW_TOOL_MIN = 0; +const int32_t TouchInputMapperTest::RAW_TOOL_MAX = 15; +const int32_t TouchInputMapperTest::RAW_PRESSURE_MIN = 0; +const int32_t TouchInputMapperTest::RAW_PRESSURE_MAX = 255; +const int32_t TouchInputMapperTest::RAW_ORIENTATION_MIN = -7; +const int32_t TouchInputMapperTest::RAW_ORIENTATION_MAX = 7; +const int32_t TouchInputMapperTest::RAW_DISTANCE_MIN = 0; +const int32_t TouchInputMapperTest::RAW_DISTANCE_MAX = 7; +const int32_t TouchInputMapperTest::RAW_TILT_MIN = 0; +const int32_t TouchInputMapperTest::RAW_TILT_MAX = 150; +const int32_t TouchInputMapperTest::RAW_ID_MIN = 0; +const int32_t TouchInputMapperTest::RAW_ID_MAX = 9; +const int32_t TouchInputMapperTest::RAW_SLOT_MIN = 0; +const int32_t TouchInputMapperTest::RAW_SLOT_MAX = 9; +const float TouchInputMapperTest::X_PRECISION = float(RAW_X_MAX - RAW_X_MIN + 1) / DISPLAY_WIDTH; +const float TouchInputMapperTest::Y_PRECISION = float(RAW_Y_MAX - RAW_Y_MIN + 1) / DISPLAY_HEIGHT; +const float TouchInputMapperTest::X_PRECISION_VIRTUAL = + float(RAW_X_MAX - RAW_X_MIN + 1) / VIRTUAL_DISPLAY_WIDTH; +const float TouchInputMapperTest::Y_PRECISION_VIRTUAL = + float(RAW_Y_MAX - RAW_Y_MIN + 1) / VIRTUAL_DISPLAY_HEIGHT; +const TouchAffineTransformation TouchInputMapperTest::AFFINE_TRANSFORM = + TouchAffineTransformation(1, -2, 3, -4, 5, -6); - ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs)); - ASSERT_EQ(AMOTION_EVENT_ACTION_HOVER_MOVE, motionArgs.action); - ASSERT_EQ(0, motionArgs.buttonState); - ASSERT_EQ(0, mFakePointerController->getButtonState()); - ASSERT_NO_FATAL_FAILURE( - assertCursorPointerCoords(motionArgs.pointerCoords[0], 100.0f, 200.0f, 0.0f)); +const float TouchInputMapperTest::GEOMETRIC_SCALE = + avg(float(DISPLAY_WIDTH) / (RAW_X_MAX - RAW_X_MIN + 1), + float(DISPLAY_HEIGHT) / (RAW_Y_MAX - RAW_Y_MIN + 1)); - // press BTN_RIGHT + BTN_MIDDLE, release BTN_RIGHT, release BTN_MIDDLE - process(mapper, ARBITRARY_TIME, READ_TIME, EV_KEY, BTN_RIGHT, 1); - process(mapper, ARBITRARY_TIME, READ_TIME, EV_KEY, BTN_MIDDLE, 1); - process(mapper, ARBITRARY_TIME, READ_TIME, EV_SYN, SYN_REPORT, 0); - ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs)); - ASSERT_EQ(AMOTION_EVENT_ACTION_DOWN, motionArgs.action); - ASSERT_EQ(AMOTION_EVENT_BUTTON_SECONDARY | AMOTION_EVENT_BUTTON_TERTIARY, - motionArgs.buttonState); - ASSERT_EQ(AMOTION_EVENT_BUTTON_SECONDARY | AMOTION_EVENT_BUTTON_TERTIARY, - mFakePointerController->getButtonState()); - ASSERT_NO_FATAL_FAILURE( - assertCursorPointerCoords(motionArgs.pointerCoords[0], 100.0f, 200.0f, 1.0f)); +const VirtualKeyDefinition TouchInputMapperTest::VIRTUAL_KEYS[2] = { + { KEY_HOME, 60, DISPLAY_HEIGHT + 15, 20, 20 }, + { KEY_MENU, DISPLAY_HEIGHT - 60, DISPLAY_WIDTH + 15, 20, 20 }, +}; - ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs)); - ASSERT_EQ(AMOTION_EVENT_ACTION_BUTTON_PRESS, motionArgs.action); - ASSERT_EQ(AMOTION_EVENT_BUTTON_TERTIARY, motionArgs.buttonState); - ASSERT_EQ(AMOTION_EVENT_BUTTON_SECONDARY | AMOTION_EVENT_BUTTON_TERTIARY, - mFakePointerController->getButtonState()); - ASSERT_NO_FATAL_FAILURE( - assertCursorPointerCoords(motionArgs.pointerCoords[0], 100.0f, 200.0f, 1.0f)); +void TouchInputMapperTest::prepareDisplay(ui::Rotation orientation, std::optional port) { + setDisplayInfoAndReconfigure(DISPLAY_ID, DISPLAY_WIDTH, DISPLAY_HEIGHT, orientation, UNIQUE_ID, + port, ViewportType::INTERNAL); +} - ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs)); - ASSERT_EQ(AMOTION_EVENT_ACTION_BUTTON_PRESS, motionArgs.action); - ASSERT_EQ(AMOTION_EVENT_BUTTON_SECONDARY | AMOTION_EVENT_BUTTON_TERTIARY, - motionArgs.buttonState); - ASSERT_EQ(AMOTION_EVENT_BUTTON_SECONDARY | AMOTION_EVENT_BUTTON_TERTIARY, - mFakePointerController->getButtonState()); - ASSERT_NO_FATAL_FAILURE( - assertCursorPointerCoords(motionArgs.pointerCoords[0], 100.0f, 200.0f, 1.0f)); +void TouchInputMapperTest::prepareSecondaryDisplay(ViewportType type, std::optional port) { + setDisplayInfoAndReconfigure(SECONDARY_DISPLAY_ID, DISPLAY_WIDTH, DISPLAY_HEIGHT, + ui::ROTATION_0, SECONDARY_UNIQUE_ID, port, type); +} - process(mapper, ARBITRARY_TIME, READ_TIME, EV_KEY, BTN_RIGHT, 0); - process(mapper, ARBITRARY_TIME, READ_TIME, EV_SYN, SYN_REPORT, 0); - ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs)); - ASSERT_EQ(AMOTION_EVENT_ACTION_BUTTON_RELEASE, motionArgs.action); - ASSERT_EQ(AMOTION_EVENT_BUTTON_TERTIARY, motionArgs.buttonState); - ASSERT_EQ(AMOTION_EVENT_BUTTON_TERTIARY, mFakePointerController->getButtonState()); - ASSERT_NO_FATAL_FAILURE( - assertCursorPointerCoords(motionArgs.pointerCoords[0], 100.0f, 200.0f, 1.0f)); +void TouchInputMapperTest::prepareVirtualDisplay(ui::Rotation orientation) { + setDisplayInfoAndReconfigure(VIRTUAL_DISPLAY_ID, VIRTUAL_DISPLAY_WIDTH, VIRTUAL_DISPLAY_HEIGHT, + orientation, VIRTUAL_DISPLAY_UNIQUE_ID, NO_PORT, + ViewportType::VIRTUAL); +} - ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs)); - ASSERT_EQ(AMOTION_EVENT_ACTION_MOVE, motionArgs.action); - ASSERT_EQ(AMOTION_EVENT_BUTTON_TERTIARY, motionArgs.buttonState); - ASSERT_EQ(AMOTION_EVENT_BUTTON_TERTIARY, mFakePointerController->getButtonState()); - ASSERT_NO_FATAL_FAILURE( - assertCursorPointerCoords(motionArgs.pointerCoords[0], 100.0f, 200.0f, 1.0f)); +void TouchInputMapperTest::prepareVirtualKeys() { + mFakeEventHub->addVirtualKeyDefinition(EVENTHUB_ID, VIRTUAL_KEYS[0]); + mFakeEventHub->addVirtualKeyDefinition(EVENTHUB_ID, VIRTUAL_KEYS[1]); + mFakeEventHub->addKey(EVENTHUB_ID, KEY_HOME, 0, AKEYCODE_HOME, POLICY_FLAG_WAKE); + mFakeEventHub->addKey(EVENTHUB_ID, KEY_MENU, 0, AKEYCODE_MENU, POLICY_FLAG_WAKE); +} - process(mapper, ARBITRARY_TIME, READ_TIME, EV_KEY, BTN_MIDDLE, 0); - process(mapper, ARBITRARY_TIME, READ_TIME, EV_SYN, SYN_REPORT, 0); - ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs)); - ASSERT_EQ(AMOTION_EVENT_ACTION_BUTTON_RELEASE, motionArgs.action); - ASSERT_EQ(0, motionArgs.buttonState); - ASSERT_EQ(0, mFakePointerController->getButtonState()); - ASSERT_NO_FATAL_FAILURE( - assertCursorPointerCoords(motionArgs.pointerCoords[0], 100.0f, 200.0f, 0.0f)); - process(mapper, ARBITRARY_TIME, READ_TIME, EV_KEY, BTN_MIDDLE, 0); - process(mapper, ARBITRARY_TIME, READ_TIME, EV_SYN, SYN_REPORT, 0); +void TouchInputMapperTest::prepareLocationCalibration() { + mFakePolicy->setTouchAffineTransformation(AFFINE_TRANSFORM); +} - ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs)); - ASSERT_EQ(0, motionArgs.buttonState); - ASSERT_EQ(0, mFakePointerController->getButtonState()); - ASSERT_EQ(AMOTION_EVENT_ACTION_UP, motionArgs.action); - ASSERT_NO_FATAL_FAILURE( - assertCursorPointerCoords(motionArgs.pointerCoords[0], 100.0f, 200.0f, 0.0f)); +int32_t TouchInputMapperTest::toRawX(float displayX) { + return int32_t(displayX * (RAW_X_MAX - RAW_X_MIN + 1) / DISPLAY_WIDTH + RAW_X_MIN); +} - ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs)); - ASSERT_EQ(0, motionArgs.buttonState); - ASSERT_EQ(0, mFakePointerController->getButtonState()); - ASSERT_EQ(AMOTION_EVENT_ACTION_HOVER_MOVE, motionArgs.action); - ASSERT_NO_FATAL_FAILURE( - assertCursorPointerCoords(motionArgs.pointerCoords[0], 100.0f, 200.0f, 0.0f)); +int32_t TouchInputMapperTest::toRawY(float displayY) { + return int32_t(displayY * (RAW_Y_MAX - RAW_Y_MIN + 1) / DISPLAY_HEIGHT + RAW_Y_MIN); +} - // press BTN_BACK, release BTN_BACK - process(mapper, ARBITRARY_TIME, READ_TIME, EV_KEY, BTN_BACK, 1); - process(mapper, ARBITRARY_TIME, READ_TIME, EV_SYN, SYN_REPORT, 0); - ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyKeyWasCalled(&keyArgs)); - ASSERT_EQ(AKEY_EVENT_ACTION_DOWN, keyArgs.action); - ASSERT_EQ(AKEYCODE_BACK, keyArgs.keyCode); +int32_t TouchInputMapperTest::toRotatedRawX(float displayX) { + return int32_t(displayX * (RAW_X_MAX - RAW_X_MIN + 1) / DISPLAY_HEIGHT + RAW_X_MIN); +} - ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs)); - ASSERT_EQ(AMOTION_EVENT_ACTION_HOVER_MOVE, motionArgs.action); - ASSERT_EQ(AMOTION_EVENT_BUTTON_BACK, motionArgs.buttonState); - ASSERT_EQ(AMOTION_EVENT_BUTTON_BACK, mFakePointerController->getButtonState()); - ASSERT_NO_FATAL_FAILURE( - assertCursorPointerCoords(motionArgs.pointerCoords[0], 100.0f, 200.0f, 0.0f)); +int32_t TouchInputMapperTest::toRotatedRawY(float displayY) { + return int32_t(displayY * (RAW_Y_MAX - RAW_Y_MIN + 1) / DISPLAY_WIDTH + RAW_Y_MIN); +} - ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs)); - ASSERT_EQ(AMOTION_EVENT_ACTION_BUTTON_PRESS, motionArgs.action); - ASSERT_EQ(AMOTION_EVENT_BUTTON_BACK, motionArgs.buttonState); - ASSERT_EQ(AMOTION_EVENT_BUTTON_BACK, mFakePointerController->getButtonState()); - ASSERT_NO_FATAL_FAILURE( - assertCursorPointerCoords(motionArgs.pointerCoords[0], 100.0f, 200.0f, 0.0f)); +float TouchInputMapperTest::toCookedX(float rawX, float rawY) { + AFFINE_TRANSFORM.applyTo(rawX, rawY); + return rawX; +} - process(mapper, ARBITRARY_TIME, READ_TIME, EV_KEY, BTN_BACK, 0); - process(mapper, ARBITRARY_TIME, READ_TIME, EV_SYN, SYN_REPORT, 0); - ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs)); - ASSERT_EQ(AMOTION_EVENT_ACTION_BUTTON_RELEASE, motionArgs.action); - ASSERT_EQ(0, motionArgs.buttonState); - ASSERT_EQ(0, mFakePointerController->getButtonState()); - ASSERT_NO_FATAL_FAILURE( - assertCursorPointerCoords(motionArgs.pointerCoords[0], 100.0f, 200.0f, 0.0f)); +float TouchInputMapperTest::toCookedY(float rawX, float rawY) { + AFFINE_TRANSFORM.applyTo(rawX, rawY); + return rawY; +} - ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs)); - ASSERT_EQ(AMOTION_EVENT_ACTION_HOVER_MOVE, motionArgs.action); - ASSERT_EQ(0, motionArgs.buttonState); - ASSERT_EQ(0, mFakePointerController->getButtonState()); +float TouchInputMapperTest::toDisplayX(int32_t rawX) { + return toDisplayX(rawX, DISPLAY_WIDTH); +} - ASSERT_NO_FATAL_FAILURE( - assertCursorPointerCoords(motionArgs.pointerCoords[0], 100.0f, 200.0f, 0.0f)); - ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyKeyWasCalled(&keyArgs)); - ASSERT_EQ(AKEY_EVENT_ACTION_UP, keyArgs.action); - ASSERT_EQ(AKEYCODE_BACK, keyArgs.keyCode); +float TouchInputMapperTest::toDisplayX(int32_t rawX, int32_t displayWidth) { + return float(rawX - RAW_X_MIN) * displayWidth / (RAW_X_MAX - RAW_X_MIN + 1); +} - // press BTN_SIDE, release BTN_SIDE - process(mapper, ARBITRARY_TIME, READ_TIME, EV_KEY, BTN_SIDE, 1); - process(mapper, ARBITRARY_TIME, READ_TIME, EV_SYN, SYN_REPORT, 0); - ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyKeyWasCalled(&keyArgs)); - ASSERT_EQ(AKEY_EVENT_ACTION_DOWN, keyArgs.action); - ASSERT_EQ(AKEYCODE_BACK, keyArgs.keyCode); +float TouchInputMapperTest::toDisplayY(int32_t rawY) { + return toDisplayY(rawY, DISPLAY_HEIGHT); +} - ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs)); - ASSERT_EQ(AMOTION_EVENT_ACTION_HOVER_MOVE, motionArgs.action); - ASSERT_EQ(AMOTION_EVENT_BUTTON_BACK, motionArgs.buttonState); - ASSERT_EQ(AMOTION_EVENT_BUTTON_BACK, mFakePointerController->getButtonState()); - ASSERT_NO_FATAL_FAILURE( - assertCursorPointerCoords(motionArgs.pointerCoords[0], 100.0f, 200.0f, 0.0f)); +float TouchInputMapperTest::toDisplayY(int32_t rawY, int32_t displayHeight) { + return float(rawY - RAW_Y_MIN) * displayHeight / (RAW_Y_MAX - RAW_Y_MIN + 1); +} - ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs)); - ASSERT_EQ(AMOTION_EVENT_ACTION_BUTTON_PRESS, motionArgs.action); - ASSERT_EQ(AMOTION_EVENT_BUTTON_BACK, motionArgs.buttonState); - ASSERT_EQ(AMOTION_EVENT_BUTTON_BACK, mFakePointerController->getButtonState()); - ASSERT_NO_FATAL_FAILURE( - assertCursorPointerCoords(motionArgs.pointerCoords[0], 100.0f, 200.0f, 0.0f)); - process(mapper, ARBITRARY_TIME, READ_TIME, EV_KEY, BTN_SIDE, 0); - process(mapper, ARBITRARY_TIME, READ_TIME, EV_SYN, SYN_REPORT, 0); - ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs)); - ASSERT_EQ(AMOTION_EVENT_ACTION_BUTTON_RELEASE, motionArgs.action); - ASSERT_EQ(0, motionArgs.buttonState); - ASSERT_EQ(0, mFakePointerController->getButtonState()); - ASSERT_NO_FATAL_FAILURE( - assertCursorPointerCoords(motionArgs.pointerCoords[0], 100.0f, 200.0f, 0.0f)); +// --- SingleTouchInputMapperTest --- - ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs)); - ASSERT_EQ(AMOTION_EVENT_ACTION_HOVER_MOVE, motionArgs.action); - ASSERT_EQ(0, motionArgs.buttonState); - ASSERT_EQ(0, mFakePointerController->getButtonState()); - ASSERT_NO_FATAL_FAILURE( - assertCursorPointerCoords(motionArgs.pointerCoords[0], 100.0f, 200.0f, 0.0f)); +class SingleTouchInputMapperTest : public TouchInputMapperTest { +protected: + void prepareButtons(); + void prepareAxes(int axes); - ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyKeyWasCalled(&keyArgs)); - ASSERT_EQ(AKEY_EVENT_ACTION_UP, keyArgs.action); - ASSERT_EQ(AKEYCODE_BACK, keyArgs.keyCode); + void processDown(SingleTouchInputMapper& mapper, int32_t x, int32_t y); + void processMove(SingleTouchInputMapper& mapper, int32_t x, int32_t y); + void processUp(SingleTouchInputMapper& mappery); + void processPressure(SingleTouchInputMapper& mapper, int32_t pressure); + void processToolMajor(SingleTouchInputMapper& mapper, int32_t toolMajor); + void processDistance(SingleTouchInputMapper& mapper, int32_t distance); + void processTilt(SingleTouchInputMapper& mapper, int32_t tiltX, int32_t tiltY); + void processKey(SingleTouchInputMapper& mapper, int32_t code, int32_t value); + void processSync(SingleTouchInputMapper& mapper); +}; - // press BTN_FORWARD, release BTN_FORWARD - process(mapper, ARBITRARY_TIME, READ_TIME, EV_KEY, BTN_FORWARD, 1); - process(mapper, ARBITRARY_TIME, READ_TIME, EV_SYN, SYN_REPORT, 0); - ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyKeyWasCalled(&keyArgs)); - ASSERT_EQ(AKEY_EVENT_ACTION_DOWN, keyArgs.action); - ASSERT_EQ(AKEYCODE_FORWARD, keyArgs.keyCode); +void SingleTouchInputMapperTest::prepareButtons() { + mFakeEventHub->addKey(EVENTHUB_ID, BTN_TOUCH, 0, AKEYCODE_UNKNOWN, 0); +} - ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs)); - ASSERT_EQ(AMOTION_EVENT_ACTION_HOVER_MOVE, motionArgs.action); - ASSERT_EQ(AMOTION_EVENT_BUTTON_FORWARD, motionArgs.buttonState); - ASSERT_EQ(AMOTION_EVENT_BUTTON_FORWARD, mFakePointerController->getButtonState()); - ASSERT_NO_FATAL_FAILURE( - assertCursorPointerCoords(motionArgs.pointerCoords[0], 100.0f, 200.0f, 0.0f)); +void SingleTouchInputMapperTest::prepareAxes(int axes) { + if (axes & POSITION) { + mFakeEventHub->addAbsoluteAxis(EVENTHUB_ID, ABS_X, RAW_X_MIN, RAW_X_MAX, 0, 0); + mFakeEventHub->addAbsoluteAxis(EVENTHUB_ID, ABS_Y, RAW_Y_MIN, RAW_Y_MAX, 0, 0); + } + if (axes & PRESSURE) { + mFakeEventHub->addAbsoluteAxis(EVENTHUB_ID, ABS_PRESSURE, RAW_PRESSURE_MIN, + RAW_PRESSURE_MAX, 0, 0); + } + if (axes & TOOL) { + mFakeEventHub->addAbsoluteAxis(EVENTHUB_ID, ABS_TOOL_WIDTH, RAW_TOOL_MIN, RAW_TOOL_MAX, 0, + 0); + } + if (axes & DISTANCE) { + mFakeEventHub->addAbsoluteAxis(EVENTHUB_ID, ABS_DISTANCE, RAW_DISTANCE_MIN, + RAW_DISTANCE_MAX, 0, 0); + } + if (axes & TILT) { + mFakeEventHub->addAbsoluteAxis(EVENTHUB_ID, ABS_TILT_X, RAW_TILT_MIN, RAW_TILT_MAX, 0, 0); + mFakeEventHub->addAbsoluteAxis(EVENTHUB_ID, ABS_TILT_Y, RAW_TILT_MIN, RAW_TILT_MAX, 0, 0); + } +} - ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs)); - ASSERT_EQ(AMOTION_EVENT_ACTION_BUTTON_PRESS, motionArgs.action); - ASSERT_EQ(AMOTION_EVENT_BUTTON_FORWARD, motionArgs.buttonState); - ASSERT_EQ(AMOTION_EVENT_BUTTON_FORWARD, mFakePointerController->getButtonState()); - ASSERT_NO_FATAL_FAILURE( - assertCursorPointerCoords(motionArgs.pointerCoords[0], 100.0f, 200.0f, 0.0f)); +void SingleTouchInputMapperTest::processDown(SingleTouchInputMapper& mapper, int32_t x, int32_t y) { + process(mapper, ARBITRARY_TIME, READ_TIME, EV_KEY, BTN_TOUCH, 1); + process(mapper, ARBITRARY_TIME, READ_TIME, EV_ABS, ABS_X, x); + process(mapper, ARBITRARY_TIME, READ_TIME, EV_ABS, ABS_Y, y); +} - process(mapper, ARBITRARY_TIME, READ_TIME, EV_KEY, BTN_FORWARD, 0); - process(mapper, ARBITRARY_TIME, READ_TIME, EV_SYN, SYN_REPORT, 0); - ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs)); - ASSERT_EQ(AMOTION_EVENT_ACTION_BUTTON_RELEASE, motionArgs.action); - ASSERT_EQ(0, motionArgs.buttonState); - ASSERT_EQ(0, mFakePointerController->getButtonState()); - ASSERT_NO_FATAL_FAILURE( - assertCursorPointerCoords(motionArgs.pointerCoords[0], 100.0f, 200.0f, 0.0f)); +void SingleTouchInputMapperTest::processMove(SingleTouchInputMapper& mapper, int32_t x, int32_t y) { + process(mapper, ARBITRARY_TIME, READ_TIME, EV_ABS, ABS_X, x); + process(mapper, ARBITRARY_TIME, READ_TIME, EV_ABS, ABS_Y, y); +} - ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs)); - ASSERT_EQ(AMOTION_EVENT_ACTION_HOVER_MOVE, motionArgs.action); - ASSERT_EQ(0, motionArgs.buttonState); - ASSERT_EQ(0, mFakePointerController->getButtonState()); - ASSERT_NO_FATAL_FAILURE( - assertCursorPointerCoords(motionArgs.pointerCoords[0], 100.0f, 200.0f, 0.0f)); +void SingleTouchInputMapperTest::processUp(SingleTouchInputMapper& mapper) { + process(mapper, ARBITRARY_TIME, READ_TIME, EV_KEY, BTN_TOUCH, 0); +} - ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyKeyWasCalled(&keyArgs)); - ASSERT_EQ(AKEY_EVENT_ACTION_UP, keyArgs.action); - ASSERT_EQ(AKEYCODE_FORWARD, keyArgs.keyCode); +void SingleTouchInputMapperTest::processPressure(SingleTouchInputMapper& mapper, int32_t pressure) { + process(mapper, ARBITRARY_TIME, READ_TIME, EV_ABS, ABS_PRESSURE, pressure); +} - // press BTN_EXTRA, release BTN_EXTRA - process(mapper, ARBITRARY_TIME, READ_TIME, EV_KEY, BTN_EXTRA, 1); - process(mapper, ARBITRARY_TIME, READ_TIME, EV_SYN, SYN_REPORT, 0); - ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyKeyWasCalled(&keyArgs)); - ASSERT_EQ(AKEY_EVENT_ACTION_DOWN, keyArgs.action); - ASSERT_EQ(AKEYCODE_FORWARD, keyArgs.keyCode); +void SingleTouchInputMapperTest::processToolMajor(SingleTouchInputMapper& mapper, + int32_t toolMajor) { + process(mapper, ARBITRARY_TIME, READ_TIME, EV_ABS, ABS_TOOL_WIDTH, toolMajor); +} - ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs)); - ASSERT_EQ(AMOTION_EVENT_ACTION_HOVER_MOVE, motionArgs.action); - ASSERT_EQ(AMOTION_EVENT_BUTTON_FORWARD, motionArgs.buttonState); - ASSERT_EQ(AMOTION_EVENT_BUTTON_FORWARD, mFakePointerController->getButtonState()); - ASSERT_NO_FATAL_FAILURE( - assertCursorPointerCoords(motionArgs.pointerCoords[0], 100.0f, 200.0f, 0.0f)); +void SingleTouchInputMapperTest::processDistance(SingleTouchInputMapper& mapper, int32_t distance) { + process(mapper, ARBITRARY_TIME, READ_TIME, EV_ABS, ABS_DISTANCE, distance); +} - ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs)); - ASSERT_EQ(AMOTION_EVENT_ACTION_BUTTON_PRESS, motionArgs.action); - ASSERT_EQ(AMOTION_EVENT_BUTTON_FORWARD, motionArgs.buttonState); - ASSERT_EQ(AMOTION_EVENT_BUTTON_FORWARD, mFakePointerController->getButtonState()); - ASSERT_NO_FATAL_FAILURE( - assertCursorPointerCoords(motionArgs.pointerCoords[0], 100.0f, 200.0f, 0.0f)); +void SingleTouchInputMapperTest::processTilt(SingleTouchInputMapper& mapper, int32_t tiltX, + int32_t tiltY) { + process(mapper, ARBITRARY_TIME, READ_TIME, EV_ABS, ABS_TILT_X, tiltX); + process(mapper, ARBITRARY_TIME, READ_TIME, EV_ABS, ABS_TILT_Y, tiltY); +} - process(mapper, ARBITRARY_TIME, READ_TIME, EV_KEY, BTN_EXTRA, 0); +void SingleTouchInputMapperTest::processKey(SingleTouchInputMapper& mapper, int32_t code, + int32_t value) { + process(mapper, ARBITRARY_TIME, READ_TIME, EV_KEY, code, value); +} + +void SingleTouchInputMapperTest::processSync(SingleTouchInputMapper& mapper) { process(mapper, ARBITRARY_TIME, READ_TIME, EV_SYN, SYN_REPORT, 0); - ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs)); - ASSERT_EQ(AMOTION_EVENT_ACTION_BUTTON_RELEASE, motionArgs.action); - ASSERT_EQ(0, motionArgs.buttonState); - ASSERT_EQ(0, mFakePointerController->getButtonState()); - ASSERT_NO_FATAL_FAILURE( - assertCursorPointerCoords(motionArgs.pointerCoords[0], 100.0f, 200.0f, 0.0f)); +} - ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs)); - ASSERT_EQ(AMOTION_EVENT_ACTION_HOVER_MOVE, motionArgs.action); - ASSERT_EQ(0, motionArgs.buttonState); - ASSERT_EQ(0, mFakePointerController->getButtonState()); - ASSERT_NO_FATAL_FAILURE( - assertCursorPointerCoords(motionArgs.pointerCoords[0], 100.0f, 200.0f, 0.0f)); +TEST_F(SingleTouchInputMapperTest, GetSources_WhenDeviceTypeIsNotSpecifiedAndNotACursor_ReturnsPointer) { + prepareButtons(); + prepareAxes(POSITION); + SingleTouchInputMapper& mapper = constructAndAddMapper(); - ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyKeyWasCalled(&keyArgs)); - ASSERT_EQ(AKEY_EVENT_ACTION_UP, keyArgs.action); - ASSERT_EQ(AKEYCODE_FORWARD, keyArgs.keyCode); + ASSERT_EQ(AINPUT_SOURCE_MOUSE, mapper.getSources()); } -TEST_F(CursorInputMapperTest, Process_WhenModeIsPointer_ShouldMoveThePointerAround) { - addConfigurationProperty("cursor.mode", "pointer"); - CursorInputMapper& mapper = addMapperAndConfigure(); +TEST_F(SingleTouchInputMapperTest, GetSources_WhenDeviceTypeIsTouchScreen_ReturnsTouchScreen) { + prepareButtons(); + prepareAxes(POSITION); + addConfigurationProperty("touch.deviceType", "touchScreen"); + SingleTouchInputMapper& mapper = constructAndAddMapper(); - mFakePointerController->setBounds(0, 0, 800 - 1, 480 - 1); - mFakePointerController->setPosition(100, 200); - mFakePointerController->setButtonState(0); + ASSERT_EQ(AINPUT_SOURCE_TOUCHSCREEN, mapper.getSources()); +} - NotifyMotionArgs args; +TEST_F(SingleTouchInputMapperTest, GetKeyCodeState) { + addConfigurationProperty("touch.deviceType", "touchScreen"); + prepareDisplay(ui::ROTATION_0); + prepareButtons(); + prepareAxes(POSITION); + prepareVirtualKeys(); + SingleTouchInputMapper& mapper = constructAndAddMapper(); - process(mapper, ARBITRARY_TIME, READ_TIME, EV_REL, REL_X, 10); - process(mapper, ARBITRARY_TIME, READ_TIME, EV_REL, REL_Y, 20); - process(mapper, ARBITRARY_TIME, READ_TIME, EV_SYN, SYN_REPORT, 0); - ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&args)); - ASSERT_EQ(AINPUT_SOURCE_MOUSE, args.source); - ASSERT_EQ(AMOTION_EVENT_ACTION_HOVER_MOVE, args.action); - ASSERT_NO_FATAL_FAILURE(assertPointerCoords(args.pointerCoords[0], - 110.0f, 220.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f)); - ASSERT_NO_FATAL_FAILURE(assertPosition(*mFakePointerController, 110.0f, 220.0f)); -} + // Unknown key. + ASSERT_EQ(AKEY_STATE_UNKNOWN, mapper.getKeyCodeState(AINPUT_SOURCE_ANY, AKEYCODE_A)); -TEST_F(CursorInputMapperTest, Process_PointerCapture) { - addConfigurationProperty("cursor.mode", "pointer"); - mFakePolicy->setPointerCapture(true); - CursorInputMapper& mapper = addMapperAndConfigure(); + // Virtual key is down. + int32_t x = toRawX(VIRTUAL_KEYS[0].centerX); + int32_t y = toRawY(VIRTUAL_KEYS[0].centerY); + processDown(mapper, x, y); + processSync(mapper); + ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyKeyWasCalled()); - NotifyDeviceResetArgs resetArgs; - ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyDeviceResetWasCalled(&resetArgs)); - ASSERT_EQ(ARBITRARY_TIME, resetArgs.eventTime); - ASSERT_EQ(DEVICE_ID, resetArgs.deviceId); + ASSERT_EQ(AKEY_STATE_VIRTUAL, mapper.getKeyCodeState(AINPUT_SOURCE_ANY, AKEYCODE_HOME)); + + // Virtual key is up. + processUp(mapper); + processSync(mapper); + ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyKeyWasCalled()); - mFakePointerController->setBounds(0, 0, 800 - 1, 480 - 1); - mFakePointerController->setPosition(100, 200); - mFakePointerController->setButtonState(0); + ASSERT_EQ(AKEY_STATE_UP, mapper.getKeyCodeState(AINPUT_SOURCE_ANY, AKEYCODE_HOME)); +} - NotifyMotionArgs args; +TEST_F(SingleTouchInputMapperTest, GetScanCodeState) { + addConfigurationProperty("touch.deviceType", "touchScreen"); + prepareDisplay(ui::ROTATION_0); + prepareButtons(); + prepareAxes(POSITION); + prepareVirtualKeys(); + SingleTouchInputMapper& mapper = constructAndAddMapper(); - // Move. - process(mapper, ARBITRARY_TIME, READ_TIME, EV_REL, REL_X, 10); - process(mapper, ARBITRARY_TIME, READ_TIME, EV_REL, REL_Y, 20); - process(mapper, ARBITRARY_TIME, READ_TIME, EV_SYN, SYN_REPORT, 0); - ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&args)); - ASSERT_EQ(AINPUT_SOURCE_MOUSE_RELATIVE, args.source); - ASSERT_EQ(AMOTION_EVENT_ACTION_MOVE, args.action); - ASSERT_NO_FATAL_FAILURE(assertPointerCoords(args.pointerCoords[0], - 10.0f, 20.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f)); - ASSERT_NO_FATAL_FAILURE(assertPosition(*mFakePointerController, 100.0f, 200.0f)); + // Unknown key. + ASSERT_EQ(AKEY_STATE_UNKNOWN, mapper.getScanCodeState(AINPUT_SOURCE_ANY, KEY_A)); - // Button press. - process(mapper, ARBITRARY_TIME, READ_TIME, EV_KEY, BTN_MOUSE, 1); - process(mapper, ARBITRARY_TIME, READ_TIME, EV_SYN, SYN_REPORT, 0); - ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&args)); - ASSERT_EQ(AINPUT_SOURCE_MOUSE_RELATIVE, args.source); - ASSERT_EQ(AMOTION_EVENT_ACTION_DOWN, args.action); - ASSERT_NO_FATAL_FAILURE(assertPointerCoords(args.pointerCoords[0], - 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f)); - ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&args)); - ASSERT_EQ(AINPUT_SOURCE_MOUSE_RELATIVE, args.source); - ASSERT_EQ(AMOTION_EVENT_ACTION_BUTTON_PRESS, args.action); - ASSERT_NO_FATAL_FAILURE(assertPointerCoords(args.pointerCoords[0], - 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f)); + // Virtual key is down. + int32_t x = toRawX(VIRTUAL_KEYS[0].centerX); + int32_t y = toRawY(VIRTUAL_KEYS[0].centerY); + processDown(mapper, x, y); + processSync(mapper); + ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyKeyWasCalled()); - // Button release. - process(mapper, ARBITRARY_TIME + 2, READ_TIME, EV_KEY, BTN_MOUSE, 0); - process(mapper, ARBITRARY_TIME + 2, READ_TIME, EV_SYN, SYN_REPORT, 0); - ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&args)); - ASSERT_EQ(AINPUT_SOURCE_MOUSE_RELATIVE, args.source); - ASSERT_EQ(AMOTION_EVENT_ACTION_BUTTON_RELEASE, args.action); - ASSERT_NO_FATAL_FAILURE(assertPointerCoords(args.pointerCoords[0], - 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f)); - ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&args)); - ASSERT_EQ(AINPUT_SOURCE_MOUSE_RELATIVE, args.source); - ASSERT_EQ(AMOTION_EVENT_ACTION_UP, args.action); - ASSERT_NO_FATAL_FAILURE(assertPointerCoords(args.pointerCoords[0], - 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f)); + ASSERT_EQ(AKEY_STATE_VIRTUAL, mapper.getScanCodeState(AINPUT_SOURCE_ANY, KEY_HOME)); - // Another move. - process(mapper, ARBITRARY_TIME, READ_TIME, EV_REL, REL_X, 30); - process(mapper, ARBITRARY_TIME, READ_TIME, EV_REL, REL_Y, 40); - process(mapper, ARBITRARY_TIME, READ_TIME, EV_SYN, SYN_REPORT, 0); - ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&args)); - ASSERT_EQ(AINPUT_SOURCE_MOUSE_RELATIVE, args.source); - ASSERT_EQ(AMOTION_EVENT_ACTION_MOVE, args.action); - ASSERT_NO_FATAL_FAILURE(assertPointerCoords(args.pointerCoords[0], - 30.0f, 40.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f)); - ASSERT_NO_FATAL_FAILURE(assertPosition(*mFakePointerController, 100.0f, 200.0f)); + // Virtual key is up. + processUp(mapper); + processSync(mapper); + ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyKeyWasCalled()); - // Disable pointer capture and check that the device generation got bumped - // and events are generated the usual way. - const uint32_t generation = mReader->getContext()->getGeneration(); - mFakePolicy->setPointerCapture(false); - configureDevice(InputReaderConfiguration::CHANGE_POINTER_CAPTURE); - ASSERT_TRUE(mReader->getContext()->getGeneration() != generation); + ASSERT_EQ(AKEY_STATE_UP, mapper.getScanCodeState(AINPUT_SOURCE_ANY, KEY_HOME)); +} - ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyDeviceResetWasCalled(&resetArgs)); - ASSERT_EQ(DEVICE_ID, resetArgs.deviceId); +TEST_F(SingleTouchInputMapperTest, MarkSupportedKeyCodes) { + addConfigurationProperty("touch.deviceType", "touchScreen"); + prepareDisplay(ui::ROTATION_0); + prepareButtons(); + prepareAxes(POSITION); + prepareVirtualKeys(); + SingleTouchInputMapper& mapper = constructAndAddMapper(); - process(mapper, ARBITRARY_TIME, READ_TIME, EV_REL, REL_X, 10); - process(mapper, ARBITRARY_TIME, READ_TIME, EV_REL, REL_Y, 20); - process(mapper, ARBITRARY_TIME, READ_TIME, EV_SYN, SYN_REPORT, 0); - ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&args)); - ASSERT_EQ(AINPUT_SOURCE_MOUSE, args.source); - ASSERT_EQ(AMOTION_EVENT_ACTION_HOVER_MOVE, args.action); - ASSERT_NO_FATAL_FAILURE(assertPointerCoords(args.pointerCoords[0], - 110.0f, 220.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f)); - ASSERT_NO_FATAL_FAILURE(assertPosition(*mFakePointerController, 110.0f, 220.0f)); + uint8_t flags[2] = { 0, 0 }; + ASSERT_TRUE( + mapper.markSupportedKeyCodes(AINPUT_SOURCE_ANY, {AKEYCODE_HOME, AKEYCODE_A}, flags)); + ASSERT_TRUE(flags[0]); + ASSERT_FALSE(flags[1]); } -/** - * When Pointer Capture is enabled, we expect to report unprocessed relative movements, so any - * pointer acceleration or speed processing should not be applied. - */ -TEST_F(CursorInputMapperTest, PointerCaptureDisablesVelocityProcessing) { - addConfigurationProperty("cursor.mode", "pointer"); - const VelocityControlParameters testParams(5.f /*scale*/, 0.f /*low threshold*/, - 100.f /*high threshold*/, 10.f /*acceleration*/); - mFakePolicy->setVelocityControlParams(testParams); - CursorInputMapper& mapper = addMapperAndConfigure(); +TEST_F(SingleTouchInputMapperTest, Process_WhenVirtualKeyIsPressedAndReleasedNormally_SendsKeyDownAndKeyUp) { + addConfigurationProperty("touch.deviceType", "touchScreen"); + prepareDisplay(ui::ROTATION_0); + prepareButtons(); + prepareAxes(POSITION); + prepareVirtualKeys(); + SingleTouchInputMapper& mapper = constructAndAddMapper(); - NotifyDeviceResetArgs resetArgs; - ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyDeviceResetWasCalled(&resetArgs)); - ASSERT_EQ(ARBITRARY_TIME, resetArgs.eventTime); - ASSERT_EQ(DEVICE_ID, resetArgs.deviceId); + mReader->getContext()->setGlobalMetaState(AMETA_SHIFT_LEFT_ON | AMETA_SHIFT_ON); - NotifyMotionArgs args; + NotifyKeyArgs args; - // Move and verify scale is applied. - process(mapper, ARBITRARY_TIME, READ_TIME, EV_REL, REL_X, 10); - process(mapper, ARBITRARY_TIME, READ_TIME, EV_REL, REL_Y, 20); - process(mapper, ARBITRARY_TIME, READ_TIME, EV_SYN, SYN_REPORT, 0); - ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&args)); - ASSERT_EQ(AINPUT_SOURCE_MOUSE, args.source); - ASSERT_EQ(AMOTION_EVENT_ACTION_HOVER_MOVE, args.action); - const float relX = args.pointerCoords[0].getAxisValue(AMOTION_EVENT_AXIS_RELATIVE_X); - const float relY = args.pointerCoords[0].getAxisValue(AMOTION_EVENT_AXIS_RELATIVE_Y); - ASSERT_GT(relX, 10); - ASSERT_GT(relY, 20); + // Press virtual key. + int32_t x = toRawX(VIRTUAL_KEYS[0].centerX); + int32_t y = toRawY(VIRTUAL_KEYS[0].centerY); + processDown(mapper, x, y); + processSync(mapper); - // Enable Pointer Capture - mFakePolicy->setPointerCapture(true); - configureDevice(InputReaderConfiguration::CHANGE_POINTER_CAPTURE); - NotifyPointerCaptureChangedArgs captureArgs; - ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyCaptureWasCalled(&captureArgs)); - ASSERT_TRUE(captureArgs.request.enable); + ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyKeyWasCalled(&args)); + ASSERT_EQ(ARBITRARY_TIME, args.eventTime); + ASSERT_EQ(DEVICE_ID, args.deviceId); + ASSERT_EQ(AINPUT_SOURCE_KEYBOARD, args.source); + ASSERT_EQ(POLICY_FLAG_VIRTUAL, args.policyFlags); + ASSERT_EQ(AKEY_EVENT_ACTION_DOWN, args.action); + ASSERT_EQ(AKEY_EVENT_FLAG_FROM_SYSTEM | AKEY_EVENT_FLAG_VIRTUAL_HARD_KEY, args.flags); + ASSERT_EQ(AKEYCODE_HOME, args.keyCode); + ASSERT_EQ(KEY_HOME, args.scanCode); + ASSERT_EQ(AMETA_SHIFT_LEFT_ON | AMETA_SHIFT_ON, args.metaState); + ASSERT_EQ(ARBITRARY_TIME, args.downTime); - // Move and verify scale is not applied. - process(mapper, ARBITRARY_TIME, READ_TIME, EV_REL, REL_X, 10); - process(mapper, ARBITRARY_TIME, READ_TIME, EV_REL, REL_Y, 20); - process(mapper, ARBITRARY_TIME, READ_TIME, EV_SYN, SYN_REPORT, 0); - ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&args)); - ASSERT_EQ(AINPUT_SOURCE_MOUSE_RELATIVE, args.source); - ASSERT_EQ(AMOTION_EVENT_ACTION_MOVE, args.action); - ASSERT_EQ(10, args.pointerCoords[0].getX()); - ASSERT_EQ(20, args.pointerCoords[0].getY()); + // Release virtual key. + processUp(mapper); + processSync(mapper); + + ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyKeyWasCalled(&args)); + ASSERT_EQ(ARBITRARY_TIME, args.eventTime); + ASSERT_EQ(DEVICE_ID, args.deviceId); + ASSERT_EQ(AINPUT_SOURCE_KEYBOARD, args.source); + ASSERT_EQ(POLICY_FLAG_VIRTUAL, args.policyFlags); + ASSERT_EQ(AKEY_EVENT_ACTION_UP, args.action); + ASSERT_EQ(AKEY_EVENT_FLAG_FROM_SYSTEM | AKEY_EVENT_FLAG_VIRTUAL_HARD_KEY, args.flags); + ASSERT_EQ(AKEYCODE_HOME, args.keyCode); + ASSERT_EQ(KEY_HOME, args.scanCode); + ASSERT_EQ(AMETA_SHIFT_LEFT_ON | AMETA_SHIFT_ON, args.metaState); + ASSERT_EQ(ARBITRARY_TIME, args.downTime); + + // Should not have sent any motions. + ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyKeyWasNotCalled()); } -TEST_F(CursorInputMapperTest, PointerCaptureDisablesOrientationChanges) { - addConfigurationProperty("cursor.mode", "pointer"); - CursorInputMapper& mapper = addMapperAndConfigure(); +TEST_F(SingleTouchInputMapperTest, Process_WhenVirtualKeyIsPressedAndMovedOutOfBounds_SendsKeyDownAndKeyCancel) { + addConfigurationProperty("touch.deviceType", "touchScreen"); + prepareDisplay(ui::ROTATION_0); + prepareButtons(); + prepareAxes(POSITION); + prepareVirtualKeys(); + SingleTouchInputMapper& mapper = constructAndAddMapper(); - NotifyDeviceResetArgs resetArgs; - ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyDeviceResetWasCalled(&resetArgs)); - ASSERT_EQ(ARBITRARY_TIME, resetArgs.eventTime); - ASSERT_EQ(DEVICE_ID, resetArgs.deviceId); + mReader->getContext()->setGlobalMetaState(AMETA_SHIFT_LEFT_ON | AMETA_SHIFT_ON); - // Ensure the display is rotated. - prepareDisplay(DISPLAY_ORIENTATION_90); + NotifyKeyArgs keyArgs; - NotifyMotionArgs args; + // Press virtual key. + int32_t x = toRawX(VIRTUAL_KEYS[0].centerX); + int32_t y = toRawY(VIRTUAL_KEYS[0].centerY); + processDown(mapper, x, y); + processSync(mapper); - // Verify that the coordinates are rotated. - process(mapper, ARBITRARY_TIME, READ_TIME, EV_REL, REL_X, 10); - process(mapper, ARBITRARY_TIME, READ_TIME, EV_REL, REL_Y, 20); - process(mapper, ARBITRARY_TIME, READ_TIME, EV_SYN, SYN_REPORT, 0); - ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&args)); - ASSERT_EQ(AINPUT_SOURCE_MOUSE, args.source); - ASSERT_EQ(AMOTION_EVENT_ACTION_HOVER_MOVE, args.action); - ASSERT_EQ(-20, args.pointerCoords[0].getAxisValue(AMOTION_EVENT_AXIS_RELATIVE_X)); - ASSERT_EQ(10, args.pointerCoords[0].getAxisValue(AMOTION_EVENT_AXIS_RELATIVE_Y)); + ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyKeyWasCalled(&keyArgs)); + ASSERT_EQ(ARBITRARY_TIME, keyArgs.eventTime); + ASSERT_EQ(DEVICE_ID, keyArgs.deviceId); + ASSERT_EQ(AINPUT_SOURCE_KEYBOARD, keyArgs.source); + ASSERT_EQ(POLICY_FLAG_VIRTUAL, keyArgs.policyFlags); + ASSERT_EQ(AKEY_EVENT_ACTION_DOWN, keyArgs.action); + ASSERT_EQ(AKEY_EVENT_FLAG_FROM_SYSTEM | AKEY_EVENT_FLAG_VIRTUAL_HARD_KEY, keyArgs.flags); + ASSERT_EQ(AKEYCODE_HOME, keyArgs.keyCode); + ASSERT_EQ(KEY_HOME, keyArgs.scanCode); + ASSERT_EQ(AMETA_SHIFT_LEFT_ON | AMETA_SHIFT_ON, keyArgs.metaState); + ASSERT_EQ(ARBITRARY_TIME, keyArgs.downTime); - // Enable Pointer Capture. - mFakePolicy->setPointerCapture(true); - configureDevice(InputReaderConfiguration::CHANGE_POINTER_CAPTURE); - NotifyPointerCaptureChangedArgs captureArgs; - ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyCaptureWasCalled(&captureArgs)); - ASSERT_TRUE(captureArgs.request.enable); + // Move out of bounds. This should generate a cancel and a pointer down since we moved + // into the display area. + y -= 100; + processMove(mapper, x, y); + processSync(mapper); - // Move and verify rotation is not applied. - process(mapper, ARBITRARY_TIME, READ_TIME, EV_REL, REL_X, 10); - process(mapper, ARBITRARY_TIME, READ_TIME, EV_REL, REL_Y, 20); - process(mapper, ARBITRARY_TIME, READ_TIME, EV_SYN, SYN_REPORT, 0); - ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&args)); - ASSERT_EQ(AINPUT_SOURCE_MOUSE_RELATIVE, args.source); - ASSERT_EQ(AMOTION_EVENT_ACTION_MOVE, args.action); - ASSERT_EQ(10, args.pointerCoords[0].getX()); - ASSERT_EQ(20, args.pointerCoords[0].getY()); -} + ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyKeyWasCalled(&keyArgs)); + ASSERT_EQ(ARBITRARY_TIME, keyArgs.eventTime); + ASSERT_EQ(DEVICE_ID, keyArgs.deviceId); + ASSERT_EQ(AINPUT_SOURCE_KEYBOARD, keyArgs.source); + ASSERT_EQ(POLICY_FLAG_VIRTUAL, keyArgs.policyFlags); + ASSERT_EQ(AKEY_EVENT_ACTION_UP, keyArgs.action); + ASSERT_EQ(AKEY_EVENT_FLAG_FROM_SYSTEM | AKEY_EVENT_FLAG_VIRTUAL_HARD_KEY + | AKEY_EVENT_FLAG_CANCELED, keyArgs.flags); + ASSERT_EQ(AKEYCODE_HOME, keyArgs.keyCode); + ASSERT_EQ(KEY_HOME, keyArgs.scanCode); + ASSERT_EQ(AMETA_SHIFT_LEFT_ON | AMETA_SHIFT_ON, keyArgs.metaState); + ASSERT_EQ(ARBITRARY_TIME, keyArgs.downTime); + + NotifyMotionArgs motionArgs; + ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs)); + ASSERT_EQ(ARBITRARY_TIME, motionArgs.eventTime); + ASSERT_EQ(DEVICE_ID, motionArgs.deviceId); + ASSERT_EQ(AINPUT_SOURCE_TOUCHSCREEN, motionArgs.source); + ASSERT_EQ(uint32_t(0), motionArgs.policyFlags); + ASSERT_EQ(AMOTION_EVENT_ACTION_DOWN, motionArgs.action); + ASSERT_EQ(0, motionArgs.flags); + ASSERT_EQ(AMETA_SHIFT_LEFT_ON | AMETA_SHIFT_ON, motionArgs.metaState); + ASSERT_EQ(0, motionArgs.buttonState); + ASSERT_EQ(0, motionArgs.edgeFlags); + ASSERT_EQ(size_t(1), motionArgs.pointerCount); + ASSERT_EQ(0, motionArgs.pointerProperties[0].id); + ASSERT_EQ(ToolType::FINGER, motionArgs.pointerProperties[0].toolType); + ASSERT_NO_FATAL_FAILURE(assertPointerCoords(motionArgs.pointerCoords[0], + toDisplayX(x), toDisplayY(y), 1, 0, 0, 0, 0, 0, 0, 0)); + ASSERT_NEAR(X_PRECISION, motionArgs.xPrecision, EPSILON); + ASSERT_NEAR(Y_PRECISION, motionArgs.yPrecision, EPSILON); + ASSERT_EQ(ARBITRARY_TIME, motionArgs.downTime); -TEST_F(CursorInputMapperTest, ConfigureDisplayId_NoAssociatedViewport) { - CursorInputMapper& mapper = addMapperAndConfigure(); + // Keep moving out of bounds. Should generate a pointer move. + y -= 50; + processMove(mapper, x, y); + processSync(mapper); - // Set up the default display. - prepareDisplay(DISPLAY_ORIENTATION_90); + ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs)); + ASSERT_EQ(ARBITRARY_TIME, motionArgs.eventTime); + ASSERT_EQ(DEVICE_ID, motionArgs.deviceId); + ASSERT_EQ(AINPUT_SOURCE_TOUCHSCREEN, motionArgs.source); + ASSERT_EQ(uint32_t(0), motionArgs.policyFlags); + ASSERT_EQ(AMOTION_EVENT_ACTION_MOVE, motionArgs.action); + ASSERT_EQ(0, motionArgs.flags); + ASSERT_EQ(AMETA_SHIFT_LEFT_ON | AMETA_SHIFT_ON, motionArgs.metaState); + ASSERT_EQ(0, motionArgs.buttonState); + ASSERT_EQ(0, motionArgs.edgeFlags); + ASSERT_EQ(size_t(1), motionArgs.pointerCount); + ASSERT_EQ(0, motionArgs.pointerProperties[0].id); + ASSERT_EQ(ToolType::FINGER, motionArgs.pointerProperties[0].toolType); + ASSERT_NO_FATAL_FAILURE(assertPointerCoords(motionArgs.pointerCoords[0], + toDisplayX(x), toDisplayY(y), 1, 0, 0, 0, 0, 0, 0, 0)); + ASSERT_NEAR(X_PRECISION, motionArgs.xPrecision, EPSILON); + ASSERT_NEAR(Y_PRECISION, motionArgs.yPrecision, EPSILON); + ASSERT_EQ(ARBITRARY_TIME, motionArgs.downTime); - // Set up the secondary display as the display on which the pointer should be shown. - // The InputDevice is not associated with any display. - prepareSecondaryDisplay(); - mFakePolicy->setDefaultPointerDisplayId(SECONDARY_DISPLAY_ID); - configureDevice(InputReaderConfiguration::CHANGE_DISPLAY_INFO); + // Release out of bounds. Should generate a pointer up. + processUp(mapper); + processSync(mapper); - mFakePointerController->setBounds(0, 0, DISPLAY_WIDTH - 1, DISPLAY_HEIGHT - 1); - mFakePointerController->setPosition(100, 200); - mFakePointerController->setButtonState(0); + ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs)); + ASSERT_EQ(ARBITRARY_TIME, motionArgs.eventTime); + ASSERT_EQ(DEVICE_ID, motionArgs.deviceId); + ASSERT_EQ(AINPUT_SOURCE_TOUCHSCREEN, motionArgs.source); + ASSERT_EQ(uint32_t(0), motionArgs.policyFlags); + ASSERT_EQ(AMOTION_EVENT_ACTION_UP, motionArgs.action); + ASSERT_EQ(0, motionArgs.flags); + ASSERT_EQ(AMETA_SHIFT_LEFT_ON | AMETA_SHIFT_ON, motionArgs.metaState); + ASSERT_EQ(0, motionArgs.buttonState); + ASSERT_EQ(0, motionArgs.edgeFlags); + ASSERT_EQ(size_t(1), motionArgs.pointerCount); + ASSERT_EQ(0, motionArgs.pointerProperties[0].id); + ASSERT_EQ(ToolType::FINGER, motionArgs.pointerProperties[0].toolType); + ASSERT_NO_FATAL_FAILURE(assertPointerCoords(motionArgs.pointerCoords[0], + toDisplayX(x), toDisplayY(y), 1, 0, 0, 0, 0, 0, 0, 0)); + ASSERT_NEAR(X_PRECISION, motionArgs.xPrecision, EPSILON); + ASSERT_NEAR(Y_PRECISION, motionArgs.yPrecision, EPSILON); + ASSERT_EQ(ARBITRARY_TIME, motionArgs.downTime); - // Ensure input events are generated for the secondary display. - process(mapper, ARBITRARY_TIME, READ_TIME, EV_REL, REL_X, 10); - process(mapper, ARBITRARY_TIME, READ_TIME, EV_REL, REL_Y, 20); - process(mapper, ARBITRARY_TIME, READ_TIME, EV_SYN, SYN_REPORT, 0); - ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled( - AllOf(WithAction(AMOTION_EVENT_ACTION_HOVER_MOVE), WithSource(AINPUT_SOURCE_MOUSE), - WithDisplayId(SECONDARY_DISPLAY_ID), WithCoords(110.0f, 220.0f)))); - ASSERT_NO_FATAL_FAILURE(assertPosition(*mFakePointerController, 110.0f, 220.0f)); + // Should not have sent any more keys or motions. + ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyKeyWasNotCalled()); + ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasNotCalled()); } -TEST_F(CursorInputMapperTest, ConfigureDisplayId_WithAssociatedViewport) { - CursorInputMapper& mapper = addMapperAndConfigure(); - - // Set up the default display. - prepareDisplay(DISPLAY_ORIENTATION_90); - - // Set up the secondary display as the display on which the pointer should be shown, - // and associate the InputDevice with the secondary display. - prepareSecondaryDisplay(); - mFakePolicy->setDefaultPointerDisplayId(SECONDARY_DISPLAY_ID); - mFakePolicy->addInputUniqueIdAssociation(DEVICE_LOCATION, SECONDARY_DISPLAY_UNIQUE_ID); - configureDevice(InputReaderConfiguration::CHANGE_DISPLAY_INFO); - - mFakePointerController->setBounds(0, 0, DISPLAY_WIDTH - 1, DISPLAY_HEIGHT - 1); - mFakePointerController->setPosition(100, 200); - mFakePointerController->setButtonState(0); - - process(mapper, ARBITRARY_TIME, READ_TIME, EV_REL, REL_X, 10); - process(mapper, ARBITRARY_TIME, READ_TIME, EV_REL, REL_Y, 20); - process(mapper, ARBITRARY_TIME, READ_TIME, EV_SYN, SYN_REPORT, 0); - ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled( - AllOf(WithAction(AMOTION_EVENT_ACTION_HOVER_MOVE), WithSource(AINPUT_SOURCE_MOUSE), - WithDisplayId(SECONDARY_DISPLAY_ID), WithCoords(110.0f, 220.0f)))); - ASSERT_NO_FATAL_FAILURE(assertPosition(*mFakePointerController, 110.0f, 220.0f)); -} +TEST_F(SingleTouchInputMapperTest, Process_WhenTouchStartsOutsideDisplayAndMovesIn_SendsDownAsTouchEntersDisplay) { + addConfigurationProperty("touch.deviceType", "touchScreen"); + prepareDisplay(ui::ROTATION_0); + prepareButtons(); + prepareAxes(POSITION); + prepareVirtualKeys(); + SingleTouchInputMapper& mapper = constructAndAddMapper(); -TEST_F(CursorInputMapperTest, ConfigureDisplayId_IgnoresEventsForMismatchedPointerDisplay) { - CursorInputMapper& mapper = addMapperAndConfigure(); + mReader->getContext()->setGlobalMetaState(AMETA_SHIFT_LEFT_ON | AMETA_SHIFT_ON); - // Set up the default display as the display on which the pointer should be shown. - prepareDisplay(DISPLAY_ORIENTATION_90); - mFakePolicy->setDefaultPointerDisplayId(DISPLAY_ID); + NotifyMotionArgs motionArgs; - // Associate the InputDevice with the secondary display. - prepareSecondaryDisplay(); - mFakePolicy->addInputUniqueIdAssociation(DEVICE_LOCATION, SECONDARY_DISPLAY_UNIQUE_ID); - configureDevice(InputReaderConfiguration::CHANGE_DISPLAY_INFO); + // Initially go down out of bounds. + int32_t x = -10; + int32_t y = -10; + processDown(mapper, x, y); + processSync(mapper); - // The mapper should not generate any events because it is associated with a display that is - // different from the pointer display. - process(mapper, ARBITRARY_TIME, READ_TIME, EV_REL, REL_X, 10); - process(mapper, ARBITRARY_TIME, READ_TIME, EV_REL, REL_Y, 20); - process(mapper, ARBITRARY_TIME, READ_TIME, EV_SYN, SYN_REPORT, 0); ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasNotCalled()); -} -// --- TouchInputMapperTest --- - -class TouchInputMapperTest : public InputMapperTest { -protected: - static const int32_t RAW_X_MIN; - static const int32_t RAW_X_MAX; - static const int32_t RAW_Y_MIN; - static const int32_t RAW_Y_MAX; - static const int32_t RAW_TOUCH_MIN; - static const int32_t RAW_TOUCH_MAX; - static const int32_t RAW_TOOL_MIN; - static const int32_t RAW_TOOL_MAX; - static const int32_t RAW_PRESSURE_MIN; - static const int32_t RAW_PRESSURE_MAX; - static const int32_t RAW_ORIENTATION_MIN; - static const int32_t RAW_ORIENTATION_MAX; - static const int32_t RAW_DISTANCE_MIN; - static const int32_t RAW_DISTANCE_MAX; - static const int32_t RAW_TILT_MIN; - static const int32_t RAW_TILT_MAX; - static const int32_t RAW_ID_MIN; - static const int32_t RAW_ID_MAX; - static const int32_t RAW_SLOT_MIN; - static const int32_t RAW_SLOT_MAX; - static const float X_PRECISION; - static const float Y_PRECISION; - static const float X_PRECISION_VIRTUAL; - static const float Y_PRECISION_VIRTUAL; + // Move into the display area. Should generate a pointer down. + x = 50; + y = 75; + processMove(mapper, x, y); + processSync(mapper); - static const float GEOMETRIC_SCALE; - static const TouchAffineTransformation AFFINE_TRANSFORM; + ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs)); + ASSERT_EQ(ARBITRARY_TIME, motionArgs.eventTime); + ASSERT_EQ(DEVICE_ID, motionArgs.deviceId); + ASSERT_EQ(AINPUT_SOURCE_TOUCHSCREEN, motionArgs.source); + ASSERT_EQ(uint32_t(0), motionArgs.policyFlags); + ASSERT_EQ(AMOTION_EVENT_ACTION_DOWN, motionArgs.action); + ASSERT_EQ(0, motionArgs.flags); + ASSERT_EQ(AMETA_SHIFT_LEFT_ON | AMETA_SHIFT_ON, motionArgs.metaState); + ASSERT_EQ(0, motionArgs.buttonState); + ASSERT_EQ(0, motionArgs.edgeFlags); + ASSERT_EQ(size_t(1), motionArgs.pointerCount); + ASSERT_EQ(0, motionArgs.pointerProperties[0].id); + ASSERT_EQ(ToolType::FINGER, motionArgs.pointerProperties[0].toolType); + ASSERT_NO_FATAL_FAILURE(assertPointerCoords(motionArgs.pointerCoords[0], + toDisplayX(x), toDisplayY(y), 1, 0, 0, 0, 0, 0, 0, 0)); + ASSERT_NEAR(X_PRECISION, motionArgs.xPrecision, EPSILON); + ASSERT_NEAR(Y_PRECISION, motionArgs.yPrecision, EPSILON); + ASSERT_EQ(ARBITRARY_TIME, motionArgs.downTime); - static const VirtualKeyDefinition VIRTUAL_KEYS[2]; + // Release. Should generate a pointer up. + processUp(mapper); + processSync(mapper); - const std::string UNIQUE_ID = "local:0"; - const std::string SECONDARY_UNIQUE_ID = "local:1"; + ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs)); + ASSERT_EQ(ARBITRARY_TIME, motionArgs.eventTime); + ASSERT_EQ(DEVICE_ID, motionArgs.deviceId); + ASSERT_EQ(AINPUT_SOURCE_TOUCHSCREEN, motionArgs.source); + ASSERT_EQ(uint32_t(0), motionArgs.policyFlags); + ASSERT_EQ(AMOTION_EVENT_ACTION_UP, motionArgs.action); + ASSERT_EQ(0, motionArgs.flags); + ASSERT_EQ(AMETA_SHIFT_LEFT_ON | AMETA_SHIFT_ON, motionArgs.metaState); + ASSERT_EQ(0, motionArgs.buttonState); + ASSERT_EQ(0, motionArgs.edgeFlags); + ASSERT_EQ(size_t(1), motionArgs.pointerCount); + ASSERT_EQ(0, motionArgs.pointerProperties[0].id); + ASSERT_EQ(ToolType::FINGER, motionArgs.pointerProperties[0].toolType); + ASSERT_NO_FATAL_FAILURE(assertPointerCoords(motionArgs.pointerCoords[0], + toDisplayX(x), toDisplayY(y), 1, 0, 0, 0, 0, 0, 0, 0)); + ASSERT_NEAR(X_PRECISION, motionArgs.xPrecision, EPSILON); + ASSERT_NEAR(Y_PRECISION, motionArgs.yPrecision, EPSILON); + ASSERT_EQ(ARBITRARY_TIME, motionArgs.downTime); - enum Axes { - POSITION = 1 << 0, - TOUCH = 1 << 1, - TOOL = 1 << 2, - PRESSURE = 1 << 3, - ORIENTATION = 1 << 4, - MINOR = 1 << 5, - ID = 1 << 6, - DISTANCE = 1 << 7, - TILT = 1 << 8, - SLOT = 1 << 9, - TOOL_TYPE = 1 << 10, - }; + // Should not have sent any more keys or motions. + ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyKeyWasNotCalled()); + ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasNotCalled()); +} - void prepareDisplay(int32_t orientation, std::optional port = NO_PORT); - void prepareSecondaryDisplay(ViewportType type, std::optional port = NO_PORT); - void prepareVirtualDisplay(int32_t orientation); - void prepareVirtualKeys(); - void prepareLocationCalibration(); - int32_t toRawX(float displayX); - int32_t toRawY(float displayY); - int32_t toRotatedRawX(float displayX); - int32_t toRotatedRawY(float displayY); - float toCookedX(float rawX, float rawY); - float toCookedY(float rawX, float rawY); - float toDisplayX(int32_t rawX); - float toDisplayX(int32_t rawX, int32_t displayWidth); - float toDisplayY(int32_t rawY); - float toDisplayY(int32_t rawY, int32_t displayHeight); +TEST_F(SingleTouchInputMapperTest, Process_NormalSingleTouchGesture_VirtualDisplay) { + addConfigurationProperty("touch.deviceType", "touchScreen"); + addConfigurationProperty("touch.displayId", VIRTUAL_DISPLAY_UNIQUE_ID); -}; + prepareVirtualDisplay(ui::ROTATION_0); + prepareButtons(); + prepareAxes(POSITION); + prepareVirtualKeys(); + SingleTouchInputMapper& mapper = constructAndAddMapper(); -const int32_t TouchInputMapperTest::RAW_X_MIN = 25; -const int32_t TouchInputMapperTest::RAW_X_MAX = 1019; -const int32_t TouchInputMapperTest::RAW_Y_MIN = 30; -const int32_t TouchInputMapperTest::RAW_Y_MAX = 1009; -const int32_t TouchInputMapperTest::RAW_TOUCH_MIN = 0; -const int32_t TouchInputMapperTest::RAW_TOUCH_MAX = 31; -const int32_t TouchInputMapperTest::RAW_TOOL_MIN = 0; -const int32_t TouchInputMapperTest::RAW_TOOL_MAX = 15; -const int32_t TouchInputMapperTest::RAW_PRESSURE_MIN = 0; -const int32_t TouchInputMapperTest::RAW_PRESSURE_MAX = 255; -const int32_t TouchInputMapperTest::RAW_ORIENTATION_MIN = -7; -const int32_t TouchInputMapperTest::RAW_ORIENTATION_MAX = 7; -const int32_t TouchInputMapperTest::RAW_DISTANCE_MIN = 0; -const int32_t TouchInputMapperTest::RAW_DISTANCE_MAX = 7; -const int32_t TouchInputMapperTest::RAW_TILT_MIN = 0; -const int32_t TouchInputMapperTest::RAW_TILT_MAX = 150; -const int32_t TouchInputMapperTest::RAW_ID_MIN = 0; -const int32_t TouchInputMapperTest::RAW_ID_MAX = 9; -const int32_t TouchInputMapperTest::RAW_SLOT_MIN = 0; -const int32_t TouchInputMapperTest::RAW_SLOT_MAX = 9; -const float TouchInputMapperTest::X_PRECISION = float(RAW_X_MAX - RAW_X_MIN + 1) / DISPLAY_WIDTH; -const float TouchInputMapperTest::Y_PRECISION = float(RAW_Y_MAX - RAW_Y_MIN + 1) / DISPLAY_HEIGHT; -const float TouchInputMapperTest::X_PRECISION_VIRTUAL = - float(RAW_X_MAX - RAW_X_MIN + 1) / VIRTUAL_DISPLAY_WIDTH; -const float TouchInputMapperTest::Y_PRECISION_VIRTUAL = - float(RAW_Y_MAX - RAW_Y_MIN + 1) / VIRTUAL_DISPLAY_HEIGHT; -const TouchAffineTransformation TouchInputMapperTest::AFFINE_TRANSFORM = - TouchAffineTransformation(1, -2, 3, -4, 5, -6); + mReader->getContext()->setGlobalMetaState(AMETA_SHIFT_LEFT_ON | AMETA_SHIFT_ON); -const float TouchInputMapperTest::GEOMETRIC_SCALE = - avg(float(DISPLAY_WIDTH) / (RAW_X_MAX - RAW_X_MIN + 1), - float(DISPLAY_HEIGHT) / (RAW_Y_MAX - RAW_Y_MIN + 1)); + NotifyMotionArgs motionArgs; -const VirtualKeyDefinition TouchInputMapperTest::VIRTUAL_KEYS[2] = { - { KEY_HOME, 60, DISPLAY_HEIGHT + 15, 20, 20 }, - { KEY_MENU, DISPLAY_HEIGHT - 60, DISPLAY_WIDTH + 15, 20, 20 }, -}; + // Down. + int32_t x = 100; + int32_t y = 125; + processDown(mapper, x, y); + processSync(mapper); -void TouchInputMapperTest::prepareDisplay(int32_t orientation, std::optional port) { - setDisplayInfoAndReconfigure(DISPLAY_ID, DISPLAY_WIDTH, DISPLAY_HEIGHT, orientation, UNIQUE_ID, - port, ViewportType::INTERNAL); -} + ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs)); + ASSERT_EQ(ARBITRARY_TIME, motionArgs.eventTime); + ASSERT_EQ(DEVICE_ID, motionArgs.deviceId); + ASSERT_EQ(VIRTUAL_DISPLAY_ID, motionArgs.displayId); + ASSERT_EQ(AINPUT_SOURCE_TOUCHSCREEN, motionArgs.source); + ASSERT_EQ(uint32_t(0), motionArgs.policyFlags); + ASSERT_EQ(AMOTION_EVENT_ACTION_DOWN, motionArgs.action); + ASSERT_EQ(0, motionArgs.flags); + ASSERT_EQ(AMETA_SHIFT_LEFT_ON | AMETA_SHIFT_ON, motionArgs.metaState); + ASSERT_EQ(0, motionArgs.buttonState); + ASSERT_EQ(0, motionArgs.edgeFlags); + ASSERT_EQ(size_t(1), motionArgs.pointerCount); + ASSERT_EQ(0, motionArgs.pointerProperties[0].id); + ASSERT_EQ(ToolType::FINGER, motionArgs.pointerProperties[0].toolType); + ASSERT_NO_FATAL_FAILURE(assertPointerCoords(motionArgs.pointerCoords[0], + toDisplayX(x, VIRTUAL_DISPLAY_WIDTH), toDisplayY(y, VIRTUAL_DISPLAY_HEIGHT), + 1, 0, 0, 0, 0, 0, 0, 0)); + ASSERT_NEAR(X_PRECISION_VIRTUAL, motionArgs.xPrecision, EPSILON); + ASSERT_NEAR(Y_PRECISION_VIRTUAL, motionArgs.yPrecision, EPSILON); + ASSERT_EQ(ARBITRARY_TIME, motionArgs.downTime); -void TouchInputMapperTest::prepareSecondaryDisplay(ViewportType type, std::optional port) { - setDisplayInfoAndReconfigure(SECONDARY_DISPLAY_ID, DISPLAY_WIDTH, DISPLAY_HEIGHT, - DISPLAY_ORIENTATION_0, SECONDARY_UNIQUE_ID, port, type); -} + // Move. + x += 50; + y += 75; + processMove(mapper, x, y); + processSync(mapper); -void TouchInputMapperTest::prepareVirtualDisplay(int32_t orientation) { - setDisplayInfoAndReconfigure(VIRTUAL_DISPLAY_ID, VIRTUAL_DISPLAY_WIDTH, VIRTUAL_DISPLAY_HEIGHT, - orientation, VIRTUAL_DISPLAY_UNIQUE_ID, NO_PORT, - ViewportType::VIRTUAL); -} + ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs)); + ASSERT_EQ(ARBITRARY_TIME, motionArgs.eventTime); + ASSERT_EQ(DEVICE_ID, motionArgs.deviceId); + ASSERT_EQ(VIRTUAL_DISPLAY_ID, motionArgs.displayId); + ASSERT_EQ(AINPUT_SOURCE_TOUCHSCREEN, motionArgs.source); + ASSERT_EQ(uint32_t(0), motionArgs.policyFlags); + ASSERT_EQ(AMOTION_EVENT_ACTION_MOVE, motionArgs.action); + ASSERT_EQ(0, motionArgs.flags); + ASSERT_EQ(AMETA_SHIFT_LEFT_ON | AMETA_SHIFT_ON, motionArgs.metaState); + ASSERT_EQ(0, motionArgs.buttonState); + ASSERT_EQ(0, motionArgs.edgeFlags); + ASSERT_EQ(size_t(1), motionArgs.pointerCount); + ASSERT_EQ(0, motionArgs.pointerProperties[0].id); + ASSERT_EQ(ToolType::FINGER, motionArgs.pointerProperties[0].toolType); + ASSERT_NO_FATAL_FAILURE(assertPointerCoords(motionArgs.pointerCoords[0], + toDisplayX(x, VIRTUAL_DISPLAY_WIDTH), toDisplayY(y, VIRTUAL_DISPLAY_HEIGHT), + 1, 0, 0, 0, 0, 0, 0, 0)); + ASSERT_NEAR(X_PRECISION_VIRTUAL, motionArgs.xPrecision, EPSILON); + ASSERT_NEAR(Y_PRECISION_VIRTUAL, motionArgs.yPrecision, EPSILON); + ASSERT_EQ(ARBITRARY_TIME, motionArgs.downTime); -void TouchInputMapperTest::prepareVirtualKeys() { - mFakeEventHub->addVirtualKeyDefinition(EVENTHUB_ID, VIRTUAL_KEYS[0]); - mFakeEventHub->addVirtualKeyDefinition(EVENTHUB_ID, VIRTUAL_KEYS[1]); - mFakeEventHub->addKey(EVENTHUB_ID, KEY_HOME, 0, AKEYCODE_HOME, POLICY_FLAG_WAKE); - mFakeEventHub->addKey(EVENTHUB_ID, KEY_MENU, 0, AKEYCODE_MENU, POLICY_FLAG_WAKE); -} + // Up. + processUp(mapper); + processSync(mapper); -void TouchInputMapperTest::prepareLocationCalibration() { - mFakePolicy->setTouchAffineTransformation(AFFINE_TRANSFORM); -} + ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs)); + ASSERT_EQ(ARBITRARY_TIME, motionArgs.eventTime); + ASSERT_EQ(DEVICE_ID, motionArgs.deviceId); + ASSERT_EQ(VIRTUAL_DISPLAY_ID, motionArgs.displayId); + ASSERT_EQ(AINPUT_SOURCE_TOUCHSCREEN, motionArgs.source); + ASSERT_EQ(uint32_t(0), motionArgs.policyFlags); + ASSERT_EQ(AMOTION_EVENT_ACTION_UP, motionArgs.action); + ASSERT_EQ(0, motionArgs.flags); + ASSERT_EQ(AMETA_SHIFT_LEFT_ON | AMETA_SHIFT_ON, motionArgs.metaState); + ASSERT_EQ(0, motionArgs.buttonState); + ASSERT_EQ(0, motionArgs.edgeFlags); + ASSERT_EQ(size_t(1), motionArgs.pointerCount); + ASSERT_EQ(0, motionArgs.pointerProperties[0].id); + ASSERT_EQ(ToolType::FINGER, motionArgs.pointerProperties[0].toolType); + ASSERT_NO_FATAL_FAILURE(assertPointerCoords(motionArgs.pointerCoords[0], + toDisplayX(x, VIRTUAL_DISPLAY_WIDTH), toDisplayY(y, VIRTUAL_DISPLAY_HEIGHT), + 1, 0, 0, 0, 0, 0, 0, 0)); + ASSERT_NEAR(X_PRECISION_VIRTUAL, motionArgs.xPrecision, EPSILON); + ASSERT_NEAR(Y_PRECISION_VIRTUAL, motionArgs.yPrecision, EPSILON); + ASSERT_EQ(ARBITRARY_TIME, motionArgs.downTime); -int32_t TouchInputMapperTest::toRawX(float displayX) { - return int32_t(displayX * (RAW_X_MAX - RAW_X_MIN + 1) / DISPLAY_WIDTH + RAW_X_MIN); + // Should not have sent any more keys or motions. + ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyKeyWasNotCalled()); + ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasNotCalled()); } -int32_t TouchInputMapperTest::toRawY(float displayY) { - return int32_t(displayY * (RAW_Y_MAX - RAW_Y_MIN + 1) / DISPLAY_HEIGHT + RAW_Y_MIN); -} +TEST_F(SingleTouchInputMapperTest, Process_NormalSingleTouchGesture) { + addConfigurationProperty("touch.deviceType", "touchScreen"); + prepareDisplay(ui::ROTATION_0); + prepareButtons(); + prepareAxes(POSITION); + prepareVirtualKeys(); + SingleTouchInputMapper& mapper = constructAndAddMapper(); -int32_t TouchInputMapperTest::toRotatedRawX(float displayX) { - return int32_t(displayX * (RAW_X_MAX - RAW_X_MIN + 1) / DISPLAY_HEIGHT + RAW_X_MIN); -} + mReader->getContext()->setGlobalMetaState(AMETA_SHIFT_LEFT_ON | AMETA_SHIFT_ON); -int32_t TouchInputMapperTest::toRotatedRawY(float displayY) { - return int32_t(displayY * (RAW_Y_MAX - RAW_Y_MIN + 1) / DISPLAY_WIDTH + RAW_Y_MIN); -} + NotifyMotionArgs motionArgs; -float TouchInputMapperTest::toCookedX(float rawX, float rawY) { - AFFINE_TRANSFORM.applyTo(rawX, rawY); - return rawX; -} + // Down. + int32_t x = 100; + int32_t y = 125; + processDown(mapper, x, y); + processSync(mapper); -float TouchInputMapperTest::toCookedY(float rawX, float rawY) { - AFFINE_TRANSFORM.applyTo(rawX, rawY); - return rawY; -} + ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs)); + ASSERT_EQ(ARBITRARY_TIME, motionArgs.eventTime); + ASSERT_EQ(DEVICE_ID, motionArgs.deviceId); + ASSERT_EQ(AINPUT_SOURCE_TOUCHSCREEN, motionArgs.source); + ASSERT_EQ(uint32_t(0), motionArgs.policyFlags); + ASSERT_EQ(AMOTION_EVENT_ACTION_DOWN, motionArgs.action); + ASSERT_EQ(0, motionArgs.flags); + ASSERT_EQ(AMETA_SHIFT_LEFT_ON | AMETA_SHIFT_ON, motionArgs.metaState); + ASSERT_EQ(0, motionArgs.buttonState); + ASSERT_EQ(0, motionArgs.edgeFlags); + ASSERT_EQ(size_t(1), motionArgs.pointerCount); + ASSERT_EQ(0, motionArgs.pointerProperties[0].id); + ASSERT_EQ(ToolType::FINGER, motionArgs.pointerProperties[0].toolType); + ASSERT_NO_FATAL_FAILURE(assertPointerCoords(motionArgs.pointerCoords[0], + toDisplayX(x), toDisplayY(y), 1, 0, 0, 0, 0, 0, 0, 0)); + ASSERT_NEAR(X_PRECISION, motionArgs.xPrecision, EPSILON); + ASSERT_NEAR(Y_PRECISION, motionArgs.yPrecision, EPSILON); + ASSERT_EQ(ARBITRARY_TIME, motionArgs.downTime); -float TouchInputMapperTest::toDisplayX(int32_t rawX) { - return toDisplayX(rawX, DISPLAY_WIDTH); -} + // Move. + x += 50; + y += 75; + processMove(mapper, x, y); + processSync(mapper); -float TouchInputMapperTest::toDisplayX(int32_t rawX, int32_t displayWidth) { - return float(rawX - RAW_X_MIN) * displayWidth / (RAW_X_MAX - RAW_X_MIN + 1); -} + ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs)); + ASSERT_EQ(ARBITRARY_TIME, motionArgs.eventTime); + ASSERT_EQ(DEVICE_ID, motionArgs.deviceId); + ASSERT_EQ(AINPUT_SOURCE_TOUCHSCREEN, motionArgs.source); + ASSERT_EQ(uint32_t(0), motionArgs.policyFlags); + ASSERT_EQ(AMOTION_EVENT_ACTION_MOVE, motionArgs.action); + ASSERT_EQ(0, motionArgs.flags); + ASSERT_EQ(AMETA_SHIFT_LEFT_ON | AMETA_SHIFT_ON, motionArgs.metaState); + ASSERT_EQ(0, motionArgs.buttonState); + ASSERT_EQ(0, motionArgs.edgeFlags); + ASSERT_EQ(size_t(1), motionArgs.pointerCount); + ASSERT_EQ(0, motionArgs.pointerProperties[0].id); + ASSERT_EQ(ToolType::FINGER, motionArgs.pointerProperties[0].toolType); + ASSERT_NO_FATAL_FAILURE(assertPointerCoords(motionArgs.pointerCoords[0], + toDisplayX(x), toDisplayY(y), 1, 0, 0, 0, 0, 0, 0, 0)); + ASSERT_NEAR(X_PRECISION, motionArgs.xPrecision, EPSILON); + ASSERT_NEAR(Y_PRECISION, motionArgs.yPrecision, EPSILON); + ASSERT_EQ(ARBITRARY_TIME, motionArgs.downTime); -float TouchInputMapperTest::toDisplayY(int32_t rawY) { - return toDisplayY(rawY, DISPLAY_HEIGHT); -} + // Up. + processUp(mapper); + processSync(mapper); -float TouchInputMapperTest::toDisplayY(int32_t rawY, int32_t displayHeight) { - return float(rawY - RAW_Y_MIN) * displayHeight / (RAW_Y_MAX - RAW_Y_MIN + 1); + ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs)); + ASSERT_EQ(ARBITRARY_TIME, motionArgs.eventTime); + ASSERT_EQ(DEVICE_ID, motionArgs.deviceId); + ASSERT_EQ(AINPUT_SOURCE_TOUCHSCREEN, motionArgs.source); + ASSERT_EQ(uint32_t(0), motionArgs.policyFlags); + ASSERT_EQ(AMOTION_EVENT_ACTION_UP, motionArgs.action); + ASSERT_EQ(0, motionArgs.flags); + ASSERT_EQ(AMETA_SHIFT_LEFT_ON | AMETA_SHIFT_ON, motionArgs.metaState); + ASSERT_EQ(0, motionArgs.buttonState); + ASSERT_EQ(0, motionArgs.edgeFlags); + ASSERT_EQ(size_t(1), motionArgs.pointerCount); + ASSERT_EQ(0, motionArgs.pointerProperties[0].id); + ASSERT_EQ(ToolType::FINGER, motionArgs.pointerProperties[0].toolType); + ASSERT_NO_FATAL_FAILURE(assertPointerCoords(motionArgs.pointerCoords[0], + toDisplayX(x), toDisplayY(y), 1, 0, 0, 0, 0, 0, 0, 0)); + ASSERT_NEAR(X_PRECISION, motionArgs.xPrecision, EPSILON); + ASSERT_NEAR(Y_PRECISION, motionArgs.yPrecision, EPSILON); + ASSERT_EQ(ARBITRARY_TIME, motionArgs.downTime); + + // Should not have sent any more keys or motions. + ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyKeyWasNotCalled()); + ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasNotCalled()); } +TEST_F(SingleTouchInputMapperTest, Process_WhenOrientationAware_DoesNotRotateMotions) { + addConfigurationProperty("touch.deviceType", "touchScreen"); + prepareButtons(); + prepareAxes(POSITION); + // InputReader works in the un-rotated coordinate space, so orientation-aware devices do not + // need to be rotated. Touchscreens are orientation-aware by default. + SingleTouchInputMapper& mapper = constructAndAddMapper(); -// --- SingleTouchInputMapperTest --- + NotifyMotionArgs args; -class SingleTouchInputMapperTest : public TouchInputMapperTest { -protected: - void prepareButtons(); - void prepareAxes(int axes); + // Rotation 90. + prepareDisplay(ui::ROTATION_90); + processDown(mapper, toRawX(50), toRawY(75)); + processSync(mapper); - void processDown(SingleTouchInputMapper& mapper, int32_t x, int32_t y); - void processMove(SingleTouchInputMapper& mapper, int32_t x, int32_t y); - void processUp(SingleTouchInputMapper& mappery); - void processPressure(SingleTouchInputMapper& mapper, int32_t pressure); - void processToolMajor(SingleTouchInputMapper& mapper, int32_t toolMajor); - void processDistance(SingleTouchInputMapper& mapper, int32_t distance); - void processTilt(SingleTouchInputMapper& mapper, int32_t tiltX, int32_t tiltY); - void processKey(SingleTouchInputMapper& mapper, int32_t code, int32_t value); - void processSync(SingleTouchInputMapper& mapper); -}; + ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&args)); + ASSERT_NEAR(50, args.pointerCoords[0].getAxisValue(AMOTION_EVENT_AXIS_X), 1); + ASSERT_NEAR(75, args.pointerCoords[0].getAxisValue(AMOTION_EVENT_AXIS_Y), 1); -void SingleTouchInputMapperTest::prepareButtons() { - mFakeEventHub->addKey(EVENTHUB_ID, BTN_TOUCH, 0, AKEYCODE_UNKNOWN, 0); + processUp(mapper); + processSync(mapper); + ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled()); } -void SingleTouchInputMapperTest::prepareAxes(int axes) { - if (axes & POSITION) { - mFakeEventHub->addAbsoluteAxis(EVENTHUB_ID, ABS_X, RAW_X_MIN, RAW_X_MAX, 0, 0); - mFakeEventHub->addAbsoluteAxis(EVENTHUB_ID, ABS_Y, RAW_Y_MIN, RAW_Y_MAX, 0, 0); - } - if (axes & PRESSURE) { - mFakeEventHub->addAbsoluteAxis(EVENTHUB_ID, ABS_PRESSURE, RAW_PRESSURE_MIN, - RAW_PRESSURE_MAX, 0, 0); - } - if (axes & TOOL) { - mFakeEventHub->addAbsoluteAxis(EVENTHUB_ID, ABS_TOOL_WIDTH, RAW_TOOL_MIN, RAW_TOOL_MAX, 0, - 0); - } - if (axes & DISTANCE) { - mFakeEventHub->addAbsoluteAxis(EVENTHUB_ID, ABS_DISTANCE, RAW_DISTANCE_MIN, - RAW_DISTANCE_MAX, 0, 0); - } - if (axes & TILT) { - mFakeEventHub->addAbsoluteAxis(EVENTHUB_ID, ABS_TILT_X, RAW_TILT_MIN, RAW_TILT_MAX, 0, 0); - mFakeEventHub->addAbsoluteAxis(EVENTHUB_ID, ABS_TILT_Y, RAW_TILT_MIN, RAW_TILT_MAX, 0, 0); - } -} +TEST_F(SingleTouchInputMapperTest, Process_WhenNotOrientationAware_RotatesMotions) { + addConfigurationProperty("touch.deviceType", "touchScreen"); + prepareButtons(); + prepareAxes(POSITION); + // Since InputReader works in the un-rotated coordinate space, only devices that are not + // orientation-aware are affected by display rotation. + addConfigurationProperty("touch.orientationAware", "0"); + SingleTouchInputMapper& mapper = constructAndAddMapper(); -void SingleTouchInputMapperTest::processDown(SingleTouchInputMapper& mapper, int32_t x, int32_t y) { - process(mapper, ARBITRARY_TIME, READ_TIME, EV_KEY, BTN_TOUCH, 1); - process(mapper, ARBITRARY_TIME, READ_TIME, EV_ABS, ABS_X, x); - process(mapper, ARBITRARY_TIME, READ_TIME, EV_ABS, ABS_Y, y); -} + NotifyMotionArgs args; -void SingleTouchInputMapperTest::processMove(SingleTouchInputMapper& mapper, int32_t x, int32_t y) { - process(mapper, ARBITRARY_TIME, READ_TIME, EV_ABS, ABS_X, x); - process(mapper, ARBITRARY_TIME, READ_TIME, EV_ABS, ABS_Y, y); -} + // Rotation 0. + clearViewports(); + prepareDisplay(ui::ROTATION_0); + processDown(mapper, toRawX(50), toRawY(75)); + processSync(mapper); -void SingleTouchInputMapperTest::processUp(SingleTouchInputMapper& mapper) { - process(mapper, ARBITRARY_TIME, READ_TIME, EV_KEY, BTN_TOUCH, 0); -} + ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&args)); + ASSERT_NEAR(50, args.pointerCoords[0].getAxisValue(AMOTION_EVENT_AXIS_X), 1); + ASSERT_NEAR(75, args.pointerCoords[0].getAxisValue(AMOTION_EVENT_AXIS_Y), 1); -void SingleTouchInputMapperTest::processPressure(SingleTouchInputMapper& mapper, int32_t pressure) { - process(mapper, ARBITRARY_TIME, READ_TIME, EV_ABS, ABS_PRESSURE, pressure); -} + processUp(mapper); + processSync(mapper); + ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled()); -void SingleTouchInputMapperTest::processToolMajor(SingleTouchInputMapper& mapper, - int32_t toolMajor) { - process(mapper, ARBITRARY_TIME, READ_TIME, EV_ABS, ABS_TOOL_WIDTH, toolMajor); -} + // Rotation 90. + clearViewports(); + prepareDisplay(ui::ROTATION_90); + processDown(mapper, toRotatedRawX(75), RAW_Y_MAX - toRotatedRawY(50) + RAW_Y_MIN); + processSync(mapper); -void SingleTouchInputMapperTest::processDistance(SingleTouchInputMapper& mapper, int32_t distance) { - process(mapper, ARBITRARY_TIME, READ_TIME, EV_ABS, ABS_DISTANCE, distance); -} + ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&args)); + ASSERT_NEAR(50, args.pointerCoords[0].getAxisValue(AMOTION_EVENT_AXIS_X), 1); + ASSERT_NEAR(75, args.pointerCoords[0].getAxisValue(AMOTION_EVENT_AXIS_Y), 1); -void SingleTouchInputMapperTest::processTilt(SingleTouchInputMapper& mapper, int32_t tiltX, - int32_t tiltY) { - process(mapper, ARBITRARY_TIME, READ_TIME, EV_ABS, ABS_TILT_X, tiltX); - process(mapper, ARBITRARY_TIME, READ_TIME, EV_ABS, ABS_TILT_Y, tiltY); -} + processUp(mapper); + processSync(mapper); + ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled()); -void SingleTouchInputMapperTest::processKey(SingleTouchInputMapper& mapper, int32_t code, - int32_t value) { - process(mapper, ARBITRARY_TIME, READ_TIME, EV_KEY, code, value); -} + // Rotation 180. + clearViewports(); + prepareDisplay(ui::ROTATION_180); + processDown(mapper, RAW_X_MAX - toRawX(50) + RAW_X_MIN, RAW_Y_MAX - toRawY(75) + RAW_Y_MIN); + processSync(mapper); -void SingleTouchInputMapperTest::processSync(SingleTouchInputMapper& mapper) { - process(mapper, ARBITRARY_TIME, READ_TIME, EV_SYN, SYN_REPORT, 0); + ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&args)); + ASSERT_NEAR(50, args.pointerCoords[0].getAxisValue(AMOTION_EVENT_AXIS_X), 1); + ASSERT_NEAR(75, args.pointerCoords[0].getAxisValue(AMOTION_EVENT_AXIS_Y), 1); + + processUp(mapper); + processSync(mapper); + ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled()); + + // Rotation 270. + clearViewports(); + prepareDisplay(ui::ROTATION_270); + processDown(mapper, RAW_X_MAX - toRotatedRawX(75) + RAW_X_MIN, toRotatedRawY(50)); + processSync(mapper); + + ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&args)); + ASSERT_NEAR(50, args.pointerCoords[0].getAxisValue(AMOTION_EVENT_AXIS_X), 1); + ASSERT_NEAR(75, args.pointerCoords[0].getAxisValue(AMOTION_EVENT_AXIS_Y), 1); + + processUp(mapper); + processSync(mapper); + ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled()); } -TEST_F(SingleTouchInputMapperTest, GetSources_WhenDeviceTypeIsNotSpecifiedAndNotACursor_ReturnsPointer) { +TEST_F(SingleTouchInputMapperTest, Process_WhenOrientation0_RotatesMotions) { + addConfigurationProperty("touch.deviceType", "touchScreen"); prepareButtons(); prepareAxes(POSITION); - SingleTouchInputMapper& mapper = addMapperAndConfigure(); + addConfigurationProperty("touch.orientationAware", "1"); + addConfigurationProperty("touch.orientation", "ORIENTATION_0"); + clearViewports(); + prepareDisplay(ui::ROTATION_0); + auto& mapper = constructAndAddMapper(); + NotifyMotionArgs args; - ASSERT_EQ(AINPUT_SOURCE_MOUSE, mapper.getSources()); + // Orientation 0. + processDown(mapper, toRawX(50), toRawY(75)); + processSync(mapper); + + EXPECT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&args)); + EXPECT_NEAR(50, args.pointerCoords[0].getAxisValue(AMOTION_EVENT_AXIS_X), 1); + EXPECT_NEAR(75, args.pointerCoords[0].getAxisValue(AMOTION_EVENT_AXIS_Y), 1); + + processUp(mapper); + processSync(mapper); + EXPECT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled()); } -TEST_F(SingleTouchInputMapperTest, GetSources_WhenDeviceTypeIsNotSpecifiedAndIsACursor_ReturnsTouchPad) { - mFakeEventHub->addRelativeAxis(EVENTHUB_ID, REL_X); - mFakeEventHub->addRelativeAxis(EVENTHUB_ID, REL_Y); +TEST_F(SingleTouchInputMapperTest, Process_WhenOrientation90_RotatesMotions) { + addConfigurationProperty("touch.deviceType", "touchScreen"); prepareButtons(); prepareAxes(POSITION); - SingleTouchInputMapper& mapper = addMapperAndConfigure(); + addConfigurationProperty("touch.orientationAware", "1"); + addConfigurationProperty("touch.orientation", "ORIENTATION_90"); + clearViewports(); + prepareDisplay(ui::ROTATION_0); + auto& mapper = constructAndAddMapper(); + NotifyMotionArgs args; - ASSERT_EQ(AINPUT_SOURCE_TOUCHPAD, mapper.getSources()); + // Orientation 90. + processDown(mapper, RAW_X_MAX - toRotatedRawX(75) + RAW_X_MIN, toRotatedRawY(50)); + processSync(mapper); + + EXPECT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&args)); + EXPECT_NEAR(50, args.pointerCoords[0].getAxisValue(AMOTION_EVENT_AXIS_X), 1); + EXPECT_NEAR(75, args.pointerCoords[0].getAxisValue(AMOTION_EVENT_AXIS_Y), 1); + + processUp(mapper); + processSync(mapper); + EXPECT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled()); } -TEST_F(SingleTouchInputMapperTest, GetSources_WhenDeviceTypeIsTouchPad_ReturnsTouchPad) { +TEST_F(SingleTouchInputMapperTest, Process_WhenOrientation180_RotatesMotions) { + addConfigurationProperty("touch.deviceType", "touchScreen"); prepareButtons(); prepareAxes(POSITION); - addConfigurationProperty("touch.deviceType", "touchPad"); - SingleTouchInputMapper& mapper = addMapperAndConfigure(); + addConfigurationProperty("touch.orientationAware", "1"); + addConfigurationProperty("touch.orientation", "ORIENTATION_180"); + clearViewports(); + prepareDisplay(ui::ROTATION_0); + auto& mapper = constructAndAddMapper(); + NotifyMotionArgs args; - ASSERT_EQ(AINPUT_SOURCE_TOUCHPAD, mapper.getSources()); + // Orientation 180. + processDown(mapper, RAW_X_MAX - toRawX(50) + RAW_X_MIN, RAW_Y_MAX - toRawY(75) + RAW_Y_MIN); + processSync(mapper); + + EXPECT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&args)); + EXPECT_NEAR(50, args.pointerCoords[0].getAxisValue(AMOTION_EVENT_AXIS_X), 1); + EXPECT_NEAR(75, args.pointerCoords[0].getAxisValue(AMOTION_EVENT_AXIS_Y), 1); + + processUp(mapper); + processSync(mapper); + EXPECT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled()); } -TEST_F(SingleTouchInputMapperTest, GetSources_WhenDeviceTypeIsTouchScreen_ReturnsTouchScreen) { +TEST_F(SingleTouchInputMapperTest, Process_WhenOrientation270_RotatesMotions) { + addConfigurationProperty("touch.deviceType", "touchScreen"); prepareButtons(); prepareAxes(POSITION); - addConfigurationProperty("touch.deviceType", "touchScreen"); - SingleTouchInputMapper& mapper = addMapperAndConfigure(); + addConfigurationProperty("touch.orientationAware", "1"); + addConfigurationProperty("touch.orientation", "ORIENTATION_270"); + clearViewports(); + prepareDisplay(ui::ROTATION_0); + auto& mapper = constructAndAddMapper(); + NotifyMotionArgs args; - ASSERT_EQ(AINPUT_SOURCE_TOUCHSCREEN, mapper.getSources()); + // Orientation 270. + processDown(mapper, toRotatedRawX(75), RAW_Y_MAX - toRotatedRawY(50) + RAW_Y_MIN); + processSync(mapper); + + EXPECT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&args)); + EXPECT_NEAR(50, args.pointerCoords[0].getAxisValue(AMOTION_EVENT_AXIS_X), 1); + EXPECT_NEAR(75, args.pointerCoords[0].getAxisValue(AMOTION_EVENT_AXIS_Y), 1); + + processUp(mapper); + processSync(mapper); + EXPECT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled()); } -TEST_F(SingleTouchInputMapperTest, GetKeyCodeState) { +TEST_F(SingleTouchInputMapperTest, Process_WhenOrientationSpecified_RotatesMotionWithDisplay) { addConfigurationProperty("touch.deviceType", "touchScreen"); - prepareDisplay(DISPLAY_ORIENTATION_0); prepareButtons(); prepareAxes(POSITION); - prepareVirtualKeys(); - SingleTouchInputMapper& mapper = addMapperAndConfigure(); + // Since InputReader works in the un-rotated coordinate space, only devices that are not + // orientation-aware are affected by display rotation. + addConfigurationProperty("touch.orientationAware", "0"); + addConfigurationProperty("touch.orientation", "ORIENTATION_90"); + auto& mapper = constructAndAddMapper(); - // Unknown key. - ASSERT_EQ(AKEY_STATE_UNKNOWN, mapper.getKeyCodeState(AINPUT_SOURCE_ANY, AKEYCODE_A)); + NotifyMotionArgs args; - // Virtual key is down. - int32_t x = toRawX(VIRTUAL_KEYS[0].centerX); - int32_t y = toRawY(VIRTUAL_KEYS[0].centerY); - processDown(mapper, x, y); + // Orientation 90, Rotation 0. + clearViewports(); + prepareDisplay(ui::ROTATION_0); + processDown(mapper, RAW_X_MAX - toRotatedRawX(75) + RAW_X_MIN, toRotatedRawY(50)); processSync(mapper); - ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyKeyWasCalled()); - ASSERT_EQ(AKEY_STATE_VIRTUAL, mapper.getKeyCodeState(AINPUT_SOURCE_ANY, AKEYCODE_HOME)); + EXPECT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&args)); + EXPECT_NEAR(50, args.pointerCoords[0].getAxisValue(AMOTION_EVENT_AXIS_X), 1); + EXPECT_NEAR(75, args.pointerCoords[0].getAxisValue(AMOTION_EVENT_AXIS_Y), 1); - // Virtual key is up. processUp(mapper); processSync(mapper); - ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyKeyWasCalled()); + EXPECT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled()); - ASSERT_EQ(AKEY_STATE_UP, mapper.getKeyCodeState(AINPUT_SOURCE_ANY, AKEYCODE_HOME)); + // Orientation 90, Rotation 90. + clearViewports(); + prepareDisplay(ui::ROTATION_90); + processDown(mapper, toRawX(50), toRawY(75)); + processSync(mapper); + + EXPECT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&args)); + EXPECT_NEAR(50, args.pointerCoords[0].getAxisValue(AMOTION_EVENT_AXIS_X), 1); + EXPECT_NEAR(75, args.pointerCoords[0].getAxisValue(AMOTION_EVENT_AXIS_Y), 1); + + processUp(mapper); + processSync(mapper); + EXPECT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled()); + + // Orientation 90, Rotation 180. + clearViewports(); + prepareDisplay(ui::ROTATION_180); + processDown(mapper, toRotatedRawX(75), RAW_Y_MAX - toRotatedRawY(50) + RAW_Y_MIN); + processSync(mapper); + + EXPECT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&args)); + EXPECT_NEAR(50, args.pointerCoords[0].getAxisValue(AMOTION_EVENT_AXIS_X), 1); + EXPECT_NEAR(75, args.pointerCoords[0].getAxisValue(AMOTION_EVENT_AXIS_Y), 1); + + processUp(mapper); + processSync(mapper); + EXPECT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled()); + + // Orientation 90, Rotation 270. + clearViewports(); + prepareDisplay(ui::ROTATION_270); + processDown(mapper, RAW_X_MAX - toRawX(50) + RAW_X_MIN, RAW_Y_MAX - toRawY(75) + RAW_Y_MIN); + processSync(mapper); + + EXPECT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&args)); + EXPECT_NEAR(50, args.pointerCoords[0].getAxisValue(AMOTION_EVENT_AXIS_X), 1); + EXPECT_NEAR(75, args.pointerCoords[0].getAxisValue(AMOTION_EVENT_AXIS_Y), 1); + + processUp(mapper); + processSync(mapper); + EXPECT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled()); } -TEST_F(SingleTouchInputMapperTest, GetScanCodeState) { +TEST_F(SingleTouchInputMapperTest, Process_IgnoresTouchesOutsidePhysicalFrame) { addConfigurationProperty("touch.deviceType", "touchScreen"); - prepareDisplay(DISPLAY_ORIENTATION_0); prepareButtons(); prepareAxes(POSITION); - prepareVirtualKeys(); - SingleTouchInputMapper& mapper = addMapperAndConfigure(); + addConfigurationProperty("touch.orientationAware", "1"); + prepareDisplay(ui::ROTATION_0); + auto& mapper = constructAndAddMapper(); - // Unknown key. - ASSERT_EQ(AKEY_STATE_UNKNOWN, mapper.getScanCodeState(AINPUT_SOURCE_ANY, KEY_A)); + // Set a physical frame in the display viewport. + auto viewport = mFakePolicy->getDisplayViewportByType(ViewportType::INTERNAL); + viewport->physicalLeft = 20; + viewport->physicalTop = 600; + viewport->physicalRight = 30; + viewport->physicalBottom = 610; + mFakePolicy->updateViewport(*viewport); + configureDevice(InputReaderConfiguration::Change::DISPLAY_INFO); - // Virtual key is down. - int32_t x = toRawX(VIRTUAL_KEYS[0].centerX); - int32_t y = toRawY(VIRTUAL_KEYS[0].centerY); - processDown(mapper, x, y); + // Start the touch. + process(mapper, ARBITRARY_TIME, READ_TIME, EV_KEY, BTN_TOUCH, 1); processSync(mapper); - ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyKeyWasCalled()); - ASSERT_EQ(AKEY_STATE_VIRTUAL, mapper.getScanCodeState(AINPUT_SOURCE_ANY, KEY_HOME)); + // Expect all input starting outside the physical frame to be ignored. + const std::array outsidePoints = { + {{0, 0}, {19, 605}, {31, 605}, {25, 599}, {25, 611}, {DISPLAY_WIDTH, DISPLAY_HEIGHT}}}; + for (const auto& p : outsidePoints) { + processMove(mapper, toRawX(p.x), toRawY(p.y)); + processSync(mapper); + EXPECT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasNotCalled()); + } - // Virtual key is up. - processUp(mapper); + // Move the touch into the physical frame. + processMove(mapper, toRawX(25), toRawY(605)); processSync(mapper); - ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyKeyWasCalled()); + NotifyMotionArgs args; + EXPECT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&args)); + EXPECT_EQ(AMOTION_EVENT_ACTION_DOWN, args.action); + EXPECT_NEAR(25, args.pointerCoords[0].getAxisValue(AMOTION_EVENT_AXIS_X), 1); + EXPECT_NEAR(605, args.pointerCoords[0].getAxisValue(AMOTION_EVENT_AXIS_Y), 1); - ASSERT_EQ(AKEY_STATE_UP, mapper.getScanCodeState(AINPUT_SOURCE_ANY, KEY_HOME)); + // Once the touch down is reported, continue reporting input, even if it is outside the frame. + for (const auto& p : outsidePoints) { + processMove(mapper, toRawX(p.x), toRawY(p.y)); + processSync(mapper); + EXPECT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&args)); + EXPECT_EQ(AMOTION_EVENT_ACTION_MOVE, args.action); + EXPECT_NEAR(p.x, args.pointerCoords[0].getAxisValue(AMOTION_EVENT_AXIS_X), 1); + EXPECT_NEAR(p.y, args.pointerCoords[0].getAxisValue(AMOTION_EVENT_AXIS_Y), 1); + } + + processUp(mapper); + processSync(mapper); + EXPECT_NO_FATAL_FAILURE( + mFakeListener->assertNotifyMotionWasCalled(WithMotionAction(AMOTION_EVENT_ACTION_UP))); } -TEST_F(SingleTouchInputMapperTest, MarkSupportedKeyCodes) { - addConfigurationProperty("touch.deviceType", "touchScreen"); - prepareDisplay(DISPLAY_ORIENTATION_0); - prepareButtons(); +TEST_F(SingleTouchInputMapperTest, Process_DoesntCheckPhysicalFrameForTouchpads) { + std::shared_ptr fakePointerController = + std::make_shared(); + mFakePolicy->setPointerController(fakePointerController); + + addConfigurationProperty("touch.deviceType", "pointer"); prepareAxes(POSITION); - prepareVirtualKeys(); - SingleTouchInputMapper& mapper = addMapperAndConfigure(); + prepareDisplay(ui::ROTATION_0); + auto& mapper = constructAndAddMapper(); + + // Set a physical frame in the display viewport. + auto viewport = mFakePolicy->getDisplayViewportByType(ViewportType::INTERNAL); + viewport->physicalLeft = 20; + viewport->physicalTop = 600; + viewport->physicalRight = 30; + viewport->physicalBottom = 610; + mFakePolicy->updateViewport(*viewport); + configureDevice(InputReaderConfiguration::Change::DISPLAY_INFO); - const int32_t keys[2] = { AKEYCODE_HOME, AKEYCODE_A }; - uint8_t flags[2] = { 0, 0 }; - ASSERT_TRUE(mapper.markSupportedKeyCodes(AINPUT_SOURCE_ANY, 2, keys, flags)); - ASSERT_TRUE(flags[0]); - ASSERT_FALSE(flags[1]); + // Start the touch. + process(mapper, ARBITRARY_TIME, READ_TIME, EV_KEY, BTN_TOUCH, 1); + processSync(mapper); + + // Expect all input starting outside the physical frame to result in NotifyMotionArgs being + // produced. + const std::array outsidePoints = { + {{0, 0}, {19, 605}, {31, 605}, {25, 599}, {25, 611}, {DISPLAY_WIDTH, DISPLAY_HEIGHT}}}; + for (const auto& p : outsidePoints) { + processMove(mapper, toRawX(p.x), toRawY(p.y)); + processSync(mapper); + EXPECT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled()); + } } -TEST_F(SingleTouchInputMapperTest, Process_WhenVirtualKeyIsPressedAndReleasedNormally_SendsKeyDownAndKeyUp) { +TEST_F(SingleTouchInputMapperTest, Process_AllAxes_DefaultCalibration) { addConfigurationProperty("touch.deviceType", "touchScreen"); - prepareDisplay(DISPLAY_ORIENTATION_0); + prepareDisplay(ui::ROTATION_0); prepareButtons(); - prepareAxes(POSITION); - prepareVirtualKeys(); - SingleTouchInputMapper& mapper = addMapperAndConfigure(); - - mReader->getContext()->setGlobalMetaState(AMETA_SHIFT_LEFT_ON | AMETA_SHIFT_ON); + prepareAxes(POSITION | PRESSURE | TOOL | DISTANCE | TILT); + SingleTouchInputMapper& mapper = constructAndAddMapper(); - NotifyKeyArgs args; + // These calculations are based on the input device calibration documentation. + int32_t rawX = 100; + int32_t rawY = 200; + int32_t rawPressure = 10; + int32_t rawToolMajor = 12; + int32_t rawDistance = 2; + int32_t rawTiltX = 30; + int32_t rawTiltY = 110; - // Press virtual key. - int32_t x = toRawX(VIRTUAL_KEYS[0].centerX); - int32_t y = toRawY(VIRTUAL_KEYS[0].centerY); - processDown(mapper, x, y); - processSync(mapper); + float x = toDisplayX(rawX); + float y = toDisplayY(rawY); + float pressure = float(rawPressure) / RAW_PRESSURE_MAX; + float size = float(rawToolMajor) / RAW_TOOL_MAX; + float tool = float(rawToolMajor) * GEOMETRIC_SCALE; + float distance = float(rawDistance); - ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyKeyWasCalled(&args)); - ASSERT_EQ(ARBITRARY_TIME, args.eventTime); - ASSERT_EQ(DEVICE_ID, args.deviceId); - ASSERT_EQ(AINPUT_SOURCE_KEYBOARD, args.source); - ASSERT_EQ(POLICY_FLAG_VIRTUAL, args.policyFlags); - ASSERT_EQ(AKEY_EVENT_ACTION_DOWN, args.action); - ASSERT_EQ(AKEY_EVENT_FLAG_FROM_SYSTEM | AKEY_EVENT_FLAG_VIRTUAL_HARD_KEY, args.flags); - ASSERT_EQ(AKEYCODE_HOME, args.keyCode); - ASSERT_EQ(KEY_HOME, args.scanCode); - ASSERT_EQ(AMETA_SHIFT_LEFT_ON | AMETA_SHIFT_ON, args.metaState); - ASSERT_EQ(ARBITRARY_TIME, args.downTime); + float tiltCenter = (RAW_TILT_MAX + RAW_TILT_MIN) * 0.5f; + float tiltScale = M_PI / 180; + float tiltXAngle = (rawTiltX - tiltCenter) * tiltScale; + float tiltYAngle = (rawTiltY - tiltCenter) * tiltScale; + float orientation = atan2f(-sinf(tiltXAngle), sinf(tiltYAngle)); + float tilt = acosf(cosf(tiltXAngle) * cosf(tiltYAngle)); - // Release virtual key. - processUp(mapper); + processDown(mapper, rawX, rawY); + processPressure(mapper, rawPressure); + processToolMajor(mapper, rawToolMajor); + processDistance(mapper, rawDistance); + processTilt(mapper, rawTiltX, rawTiltY); processSync(mapper); - ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyKeyWasCalled(&args)); - ASSERT_EQ(ARBITRARY_TIME, args.eventTime); - ASSERT_EQ(DEVICE_ID, args.deviceId); - ASSERT_EQ(AINPUT_SOURCE_KEYBOARD, args.source); - ASSERT_EQ(POLICY_FLAG_VIRTUAL, args.policyFlags); - ASSERT_EQ(AKEY_EVENT_ACTION_UP, args.action); - ASSERT_EQ(AKEY_EVENT_FLAG_FROM_SYSTEM | AKEY_EVENT_FLAG_VIRTUAL_HARD_KEY, args.flags); - ASSERT_EQ(AKEYCODE_HOME, args.keyCode); - ASSERT_EQ(KEY_HOME, args.scanCode); - ASSERT_EQ(AMETA_SHIFT_LEFT_ON | AMETA_SHIFT_ON, args.metaState); - ASSERT_EQ(ARBITRARY_TIME, args.downTime); - - // Should not have sent any motions. - ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyKeyWasNotCalled()); + NotifyMotionArgs args; + ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&args)); + 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)); } -TEST_F(SingleTouchInputMapperTest, Process_WhenVirtualKeyIsPressedAndMovedOutOfBounds_SendsKeyDownAndKeyCancel) { +TEST_F(SingleTouchInputMapperTest, Process_XYAxes_AffineCalibration) { addConfigurationProperty("touch.deviceType", "touchScreen"); - prepareDisplay(DISPLAY_ORIENTATION_0); + prepareDisplay(ui::ROTATION_0); + prepareLocationCalibration(); prepareButtons(); prepareAxes(POSITION); - prepareVirtualKeys(); - SingleTouchInputMapper& mapper = addMapperAndConfigure(); + SingleTouchInputMapper& mapper = constructAndAddMapper(); - mReader->getContext()->setGlobalMetaState(AMETA_SHIFT_LEFT_ON | AMETA_SHIFT_ON); + int32_t rawX = 100; + int32_t rawY = 200; - NotifyKeyArgs keyArgs; + float x = toDisplayX(toCookedX(rawX, rawY)); + float y = toDisplayY(toCookedY(rawX, rawY)); - // Press virtual key. - int32_t x = toRawX(VIRTUAL_KEYS[0].centerX); - int32_t y = toRawY(VIRTUAL_KEYS[0].centerY); - processDown(mapper, x, y); + processDown(mapper, rawX, rawY); processSync(mapper); - ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyKeyWasCalled(&keyArgs)); - ASSERT_EQ(ARBITRARY_TIME, keyArgs.eventTime); - ASSERT_EQ(DEVICE_ID, keyArgs.deviceId); - ASSERT_EQ(AINPUT_SOURCE_KEYBOARD, keyArgs.source); - ASSERT_EQ(POLICY_FLAG_VIRTUAL, keyArgs.policyFlags); - ASSERT_EQ(AKEY_EVENT_ACTION_DOWN, keyArgs.action); - ASSERT_EQ(AKEY_EVENT_FLAG_FROM_SYSTEM | AKEY_EVENT_FLAG_VIRTUAL_HARD_KEY, keyArgs.flags); - ASSERT_EQ(AKEYCODE_HOME, keyArgs.keyCode); - ASSERT_EQ(KEY_HOME, keyArgs.scanCode); - ASSERT_EQ(AMETA_SHIFT_LEFT_ON | AMETA_SHIFT_ON, keyArgs.metaState); - ASSERT_EQ(ARBITRARY_TIME, keyArgs.downTime); - - // Move out of bounds. This should generate a cancel and a pointer down since we moved - // into the display area. - y -= 100; - processMove(mapper, x, y); - processSync(mapper); + NotifyMotionArgs args; + ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&args)); + ASSERT_NO_FATAL_FAILURE(assertPointerCoords(args.pointerCoords[0], + x, y, 1, 0, 0, 0, 0, 0, 0, 0)); +} - ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyKeyWasCalled(&keyArgs)); - ASSERT_EQ(ARBITRARY_TIME, keyArgs.eventTime); - ASSERT_EQ(DEVICE_ID, keyArgs.deviceId); - ASSERT_EQ(AINPUT_SOURCE_KEYBOARD, keyArgs.source); - ASSERT_EQ(POLICY_FLAG_VIRTUAL, keyArgs.policyFlags); - ASSERT_EQ(AKEY_EVENT_ACTION_UP, keyArgs.action); - ASSERT_EQ(AKEY_EVENT_FLAG_FROM_SYSTEM | AKEY_EVENT_FLAG_VIRTUAL_HARD_KEY - | AKEY_EVENT_FLAG_CANCELED, keyArgs.flags); - ASSERT_EQ(AKEYCODE_HOME, keyArgs.keyCode); - ASSERT_EQ(KEY_HOME, keyArgs.scanCode); - ASSERT_EQ(AMETA_SHIFT_LEFT_ON | AMETA_SHIFT_ON, keyArgs.metaState); - ASSERT_EQ(ARBITRARY_TIME, keyArgs.downTime); +TEST_F(SingleTouchInputMapperTest, Process_ShouldHandleAllButtons) { + addConfigurationProperty("touch.deviceType", "touchScreen"); + prepareDisplay(ui::ROTATION_0); + prepareButtons(); + prepareAxes(POSITION); + SingleTouchInputMapper& mapper = constructAndAddMapper(); NotifyMotionArgs motionArgs; + NotifyKeyArgs keyArgs; + + processDown(mapper, 100, 200); + processSync(mapper); ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs)); - ASSERT_EQ(ARBITRARY_TIME, motionArgs.eventTime); - ASSERT_EQ(DEVICE_ID, motionArgs.deviceId); - ASSERT_EQ(AINPUT_SOURCE_TOUCHSCREEN, motionArgs.source); - ASSERT_EQ(uint32_t(0), motionArgs.policyFlags); ASSERT_EQ(AMOTION_EVENT_ACTION_DOWN, motionArgs.action); - ASSERT_EQ(0, motionArgs.flags); - ASSERT_EQ(AMETA_SHIFT_LEFT_ON | AMETA_SHIFT_ON, motionArgs.metaState); ASSERT_EQ(0, motionArgs.buttonState); - ASSERT_EQ(0, motionArgs.edgeFlags); - ASSERT_EQ(size_t(1), motionArgs.pointerCount); - ASSERT_EQ(0, motionArgs.pointerProperties[0].id); - ASSERT_EQ(AMOTION_EVENT_TOOL_TYPE_FINGER, motionArgs.pointerProperties[0].toolType); - ASSERT_NO_FATAL_FAILURE(assertPointerCoords(motionArgs.pointerCoords[0], - toDisplayX(x), toDisplayY(y), 1, 0, 0, 0, 0, 0, 0, 0)); - ASSERT_NEAR(X_PRECISION, motionArgs.xPrecision, EPSILON); - ASSERT_NEAR(Y_PRECISION, motionArgs.yPrecision, EPSILON); - ASSERT_EQ(ARBITRARY_TIME, motionArgs.downTime); - // Keep moving out of bounds. Should generate a pointer move. - y -= 50; - processMove(mapper, x, y); + // press BTN_LEFT, release BTN_LEFT + processKey(mapper, BTN_LEFT, 1); processSync(mapper); - ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs)); - ASSERT_EQ(ARBITRARY_TIME, motionArgs.eventTime); - ASSERT_EQ(DEVICE_ID, motionArgs.deviceId); - ASSERT_EQ(AINPUT_SOURCE_TOUCHSCREEN, motionArgs.source); - ASSERT_EQ(uint32_t(0), motionArgs.policyFlags); ASSERT_EQ(AMOTION_EVENT_ACTION_MOVE, motionArgs.action); - ASSERT_EQ(0, motionArgs.flags); - ASSERT_EQ(AMETA_SHIFT_LEFT_ON | AMETA_SHIFT_ON, motionArgs.metaState); - ASSERT_EQ(0, motionArgs.buttonState); - ASSERT_EQ(0, motionArgs.edgeFlags); - ASSERT_EQ(size_t(1), motionArgs.pointerCount); - ASSERT_EQ(0, motionArgs.pointerProperties[0].id); - ASSERT_EQ(AMOTION_EVENT_TOOL_TYPE_FINGER, motionArgs.pointerProperties[0].toolType); - ASSERT_NO_FATAL_FAILURE(assertPointerCoords(motionArgs.pointerCoords[0], - toDisplayX(x), toDisplayY(y), 1, 0, 0, 0, 0, 0, 0, 0)); - ASSERT_NEAR(X_PRECISION, motionArgs.xPrecision, EPSILON); - ASSERT_NEAR(Y_PRECISION, motionArgs.yPrecision, EPSILON); - ASSERT_EQ(ARBITRARY_TIME, motionArgs.downTime); + ASSERT_EQ(AMOTION_EVENT_BUTTON_PRIMARY, motionArgs.buttonState); - // Release out of bounds. Should generate a pointer up. - processUp(mapper); - processSync(mapper); + ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs)); + ASSERT_EQ(AMOTION_EVENT_ACTION_BUTTON_PRESS, motionArgs.action); + ASSERT_EQ(AMOTION_EVENT_BUTTON_PRIMARY, motionArgs.buttonState); + processKey(mapper, BTN_LEFT, 0); + processSync(mapper); ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs)); - ASSERT_EQ(ARBITRARY_TIME, motionArgs.eventTime); - ASSERT_EQ(DEVICE_ID, motionArgs.deviceId); - ASSERT_EQ(AINPUT_SOURCE_TOUCHSCREEN, motionArgs.source); - ASSERT_EQ(uint32_t(0), motionArgs.policyFlags); - ASSERT_EQ(AMOTION_EVENT_ACTION_UP, motionArgs.action); - ASSERT_EQ(0, motionArgs.flags); - ASSERT_EQ(AMETA_SHIFT_LEFT_ON | AMETA_SHIFT_ON, motionArgs.metaState); + ASSERT_EQ(AMOTION_EVENT_ACTION_BUTTON_RELEASE, motionArgs.action); ASSERT_EQ(0, motionArgs.buttonState); - ASSERT_EQ(0, motionArgs.edgeFlags); - ASSERT_EQ(size_t(1), motionArgs.pointerCount); - ASSERT_EQ(0, motionArgs.pointerProperties[0].id); - ASSERT_EQ(AMOTION_EVENT_TOOL_TYPE_FINGER, motionArgs.pointerProperties[0].toolType); - ASSERT_NO_FATAL_FAILURE(assertPointerCoords(motionArgs.pointerCoords[0], - toDisplayX(x), toDisplayY(y), 1, 0, 0, 0, 0, 0, 0, 0)); - ASSERT_NEAR(X_PRECISION, motionArgs.xPrecision, EPSILON); - ASSERT_NEAR(Y_PRECISION, motionArgs.yPrecision, EPSILON); - ASSERT_EQ(ARBITRARY_TIME, motionArgs.downTime); - - // Should not have sent any more keys or motions. - ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyKeyWasNotCalled()); - ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasNotCalled()); -} - -TEST_F(SingleTouchInputMapperTest, Process_WhenTouchStartsOutsideDisplayAndMovesIn_SendsDownAsTouchEntersDisplay) { - addConfigurationProperty("touch.deviceType", "touchScreen"); - prepareDisplay(DISPLAY_ORIENTATION_0); - prepareButtons(); - prepareAxes(POSITION); - prepareVirtualKeys(); - SingleTouchInputMapper& mapper = addMapperAndConfigure(); - - mReader->getContext()->setGlobalMetaState(AMETA_SHIFT_LEFT_ON | AMETA_SHIFT_ON); - NotifyMotionArgs motionArgs; + ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs)); + ASSERT_EQ(AMOTION_EVENT_ACTION_MOVE, motionArgs.action); + ASSERT_EQ(0, motionArgs.buttonState); - // Initially go down out of bounds. - int32_t x = -10; - int32_t y = -10; - processDown(mapper, x, y); + // press BTN_RIGHT + BTN_MIDDLE, release BTN_RIGHT, release BTN_MIDDLE + processKey(mapper, BTN_RIGHT, 1); + processKey(mapper, BTN_MIDDLE, 1); processSync(mapper); + ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs)); + ASSERT_EQ(AMOTION_EVENT_ACTION_MOVE, motionArgs.action); + ASSERT_EQ(AMOTION_EVENT_BUTTON_SECONDARY | AMOTION_EVENT_BUTTON_TERTIARY, + motionArgs.buttonState); - ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasNotCalled()); + ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs)); + ASSERT_EQ(AMOTION_EVENT_ACTION_BUTTON_PRESS, motionArgs.action); + ASSERT_EQ(AMOTION_EVENT_BUTTON_TERTIARY, motionArgs.buttonState); - // Move into the display area. Should generate a pointer down. - x = 50; - y = 75; - processMove(mapper, x, y); + ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs)); + ASSERT_EQ(AMOTION_EVENT_ACTION_BUTTON_PRESS, motionArgs.action); + ASSERT_EQ(AMOTION_EVENT_BUTTON_SECONDARY | AMOTION_EVENT_BUTTON_TERTIARY, + motionArgs.buttonState); + + processKey(mapper, BTN_RIGHT, 0); processSync(mapper); + ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs)); + ASSERT_EQ(AMOTION_EVENT_ACTION_BUTTON_RELEASE, motionArgs.action); + ASSERT_EQ(AMOTION_EVENT_BUTTON_TERTIARY, motionArgs.buttonState); ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs)); - ASSERT_EQ(ARBITRARY_TIME, motionArgs.eventTime); - ASSERT_EQ(DEVICE_ID, motionArgs.deviceId); - ASSERT_EQ(AINPUT_SOURCE_TOUCHSCREEN, motionArgs.source); - ASSERT_EQ(uint32_t(0), motionArgs.policyFlags); - ASSERT_EQ(AMOTION_EVENT_ACTION_DOWN, motionArgs.action); - ASSERT_EQ(0, motionArgs.flags); - ASSERT_EQ(AMETA_SHIFT_LEFT_ON | AMETA_SHIFT_ON, motionArgs.metaState); - ASSERT_EQ(0, motionArgs.buttonState); - ASSERT_EQ(0, motionArgs.edgeFlags); - ASSERT_EQ(size_t(1), motionArgs.pointerCount); - ASSERT_EQ(0, motionArgs.pointerProperties[0].id); - ASSERT_EQ(AMOTION_EVENT_TOOL_TYPE_FINGER, motionArgs.pointerProperties[0].toolType); - ASSERT_NO_FATAL_FAILURE(assertPointerCoords(motionArgs.pointerCoords[0], - toDisplayX(x), toDisplayY(y), 1, 0, 0, 0, 0, 0, 0, 0)); - ASSERT_NEAR(X_PRECISION, motionArgs.xPrecision, EPSILON); - ASSERT_NEAR(Y_PRECISION, motionArgs.yPrecision, EPSILON); - ASSERT_EQ(ARBITRARY_TIME, motionArgs.downTime); + ASSERT_EQ(AMOTION_EVENT_ACTION_MOVE, motionArgs.action); + ASSERT_EQ(AMOTION_EVENT_BUTTON_TERTIARY, motionArgs.buttonState); - // Release. Should generate a pointer up. - processUp(mapper); + processKey(mapper, BTN_MIDDLE, 0); processSync(mapper); - ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs)); - ASSERT_EQ(ARBITRARY_TIME, motionArgs.eventTime); - ASSERT_EQ(DEVICE_ID, motionArgs.deviceId); - ASSERT_EQ(AINPUT_SOURCE_TOUCHSCREEN, motionArgs.source); - ASSERT_EQ(uint32_t(0), motionArgs.policyFlags); - ASSERT_EQ(AMOTION_EVENT_ACTION_UP, motionArgs.action); - ASSERT_EQ(0, motionArgs.flags); - ASSERT_EQ(AMETA_SHIFT_LEFT_ON | AMETA_SHIFT_ON, motionArgs.metaState); + ASSERT_EQ(AMOTION_EVENT_ACTION_BUTTON_RELEASE, motionArgs.action); ASSERT_EQ(0, motionArgs.buttonState); - ASSERT_EQ(0, motionArgs.edgeFlags); - ASSERT_EQ(size_t(1), motionArgs.pointerCount); - ASSERT_EQ(0, motionArgs.pointerProperties[0].id); - ASSERT_EQ(AMOTION_EVENT_TOOL_TYPE_FINGER, motionArgs.pointerProperties[0].toolType); - ASSERT_NO_FATAL_FAILURE(assertPointerCoords(motionArgs.pointerCoords[0], - toDisplayX(x), toDisplayY(y), 1, 0, 0, 0, 0, 0, 0, 0)); - ASSERT_NEAR(X_PRECISION, motionArgs.xPrecision, EPSILON); - ASSERT_NEAR(Y_PRECISION, motionArgs.yPrecision, EPSILON); - ASSERT_EQ(ARBITRARY_TIME, motionArgs.downTime); - - // Should not have sent any more keys or motions. - ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyKeyWasNotCalled()); - ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasNotCalled()); -} -TEST_F(SingleTouchInputMapperTest, Process_NormalSingleTouchGesture_VirtualDisplay) { - addConfigurationProperty("touch.deviceType", "touchScreen"); - addConfigurationProperty("touch.displayId", VIRTUAL_DISPLAY_UNIQUE_ID); + ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs)); + ASSERT_EQ(AMOTION_EVENT_ACTION_MOVE, motionArgs.action); + ASSERT_EQ(0, motionArgs.buttonState); - prepareVirtualDisplay(DISPLAY_ORIENTATION_0); - prepareButtons(); - prepareAxes(POSITION); - prepareVirtualKeys(); - SingleTouchInputMapper& mapper = addMapperAndConfigure(); + // press BTN_BACK, release BTN_BACK + processKey(mapper, BTN_BACK, 1); + processSync(mapper); + ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyKeyWasCalled(&keyArgs)); + ASSERT_EQ(AKEY_EVENT_ACTION_DOWN, keyArgs.action); + ASSERT_EQ(AKEYCODE_BACK, keyArgs.keyCode); - mReader->getContext()->setGlobalMetaState(AMETA_SHIFT_LEFT_ON | AMETA_SHIFT_ON); + ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs)); + ASSERT_EQ(AMOTION_EVENT_ACTION_MOVE, motionArgs.action); + ASSERT_EQ(AMOTION_EVENT_BUTTON_BACK, motionArgs.buttonState); - NotifyMotionArgs motionArgs; + ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs)); + ASSERT_EQ(AMOTION_EVENT_ACTION_BUTTON_PRESS, motionArgs.action); + ASSERT_EQ(AMOTION_EVENT_BUTTON_BACK, motionArgs.buttonState); - // Down. - int32_t x = 100; - int32_t y = 125; - processDown(mapper, x, y); + processKey(mapper, BTN_BACK, 0); processSync(mapper); + ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs)); + ASSERT_EQ(AMOTION_EVENT_ACTION_BUTTON_RELEASE, motionArgs.action); + ASSERT_EQ(0, motionArgs.buttonState); ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs)); - ASSERT_EQ(ARBITRARY_TIME, motionArgs.eventTime); - ASSERT_EQ(DEVICE_ID, motionArgs.deviceId); - ASSERT_EQ(VIRTUAL_DISPLAY_ID, motionArgs.displayId); - ASSERT_EQ(AINPUT_SOURCE_TOUCHSCREEN, motionArgs.source); - ASSERT_EQ(uint32_t(0), motionArgs.policyFlags); - ASSERT_EQ(AMOTION_EVENT_ACTION_DOWN, motionArgs.action); - ASSERT_EQ(0, motionArgs.flags); - ASSERT_EQ(AMETA_SHIFT_LEFT_ON | AMETA_SHIFT_ON, motionArgs.metaState); + ASSERT_EQ(AMOTION_EVENT_ACTION_MOVE, motionArgs.action); ASSERT_EQ(0, motionArgs.buttonState); - ASSERT_EQ(0, motionArgs.edgeFlags); - ASSERT_EQ(size_t(1), motionArgs.pointerCount); - ASSERT_EQ(0, motionArgs.pointerProperties[0].id); - ASSERT_EQ(AMOTION_EVENT_TOOL_TYPE_FINGER, motionArgs.pointerProperties[0].toolType); - ASSERT_NO_FATAL_FAILURE(assertPointerCoords(motionArgs.pointerCoords[0], - toDisplayX(x, VIRTUAL_DISPLAY_WIDTH), toDisplayY(y, VIRTUAL_DISPLAY_HEIGHT), - 1, 0, 0, 0, 0, 0, 0, 0)); - ASSERT_NEAR(X_PRECISION_VIRTUAL, motionArgs.xPrecision, EPSILON); - ASSERT_NEAR(Y_PRECISION_VIRTUAL, motionArgs.yPrecision, EPSILON); - ASSERT_EQ(ARBITRARY_TIME, motionArgs.downTime); - // Move. - x += 50; - y += 75; - processMove(mapper, x, y); + ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyKeyWasCalled(&keyArgs)); + ASSERT_EQ(AKEY_EVENT_ACTION_UP, keyArgs.action); + ASSERT_EQ(AKEYCODE_BACK, keyArgs.keyCode); + + // press BTN_SIDE, release BTN_SIDE + processKey(mapper, BTN_SIDE, 1); processSync(mapper); + ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyKeyWasCalled(&keyArgs)); + ASSERT_EQ(AKEY_EVENT_ACTION_DOWN, keyArgs.action); + ASSERT_EQ(AKEYCODE_BACK, keyArgs.keyCode); ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs)); - ASSERT_EQ(ARBITRARY_TIME, motionArgs.eventTime); - ASSERT_EQ(DEVICE_ID, motionArgs.deviceId); - ASSERT_EQ(VIRTUAL_DISPLAY_ID, motionArgs.displayId); - ASSERT_EQ(AINPUT_SOURCE_TOUCHSCREEN, motionArgs.source); - ASSERT_EQ(uint32_t(0), motionArgs.policyFlags); ASSERT_EQ(AMOTION_EVENT_ACTION_MOVE, motionArgs.action); - ASSERT_EQ(0, motionArgs.flags); - ASSERT_EQ(AMETA_SHIFT_LEFT_ON | AMETA_SHIFT_ON, motionArgs.metaState); - ASSERT_EQ(0, motionArgs.buttonState); - ASSERT_EQ(0, motionArgs.edgeFlags); - ASSERT_EQ(size_t(1), motionArgs.pointerCount); - ASSERT_EQ(0, motionArgs.pointerProperties[0].id); - ASSERT_EQ(AMOTION_EVENT_TOOL_TYPE_FINGER, motionArgs.pointerProperties[0].toolType); - ASSERT_NO_FATAL_FAILURE(assertPointerCoords(motionArgs.pointerCoords[0], - toDisplayX(x, VIRTUAL_DISPLAY_WIDTH), toDisplayY(y, VIRTUAL_DISPLAY_HEIGHT), - 1, 0, 0, 0, 0, 0, 0, 0)); - ASSERT_NEAR(X_PRECISION_VIRTUAL, motionArgs.xPrecision, EPSILON); - ASSERT_NEAR(Y_PRECISION_VIRTUAL, motionArgs.yPrecision, EPSILON); - ASSERT_EQ(ARBITRARY_TIME, motionArgs.downTime); + ASSERT_EQ(AMOTION_EVENT_BUTTON_BACK, motionArgs.buttonState); - // Up. - processUp(mapper); + ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs)); + ASSERT_EQ(AMOTION_EVENT_ACTION_BUTTON_PRESS, motionArgs.action); + ASSERT_EQ(AMOTION_EVENT_BUTTON_BACK, motionArgs.buttonState); + + processKey(mapper, BTN_SIDE, 0); processSync(mapper); + ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs)); + ASSERT_EQ(AMOTION_EVENT_ACTION_BUTTON_RELEASE, motionArgs.action); + ASSERT_EQ(0, motionArgs.buttonState); ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs)); - ASSERT_EQ(ARBITRARY_TIME, motionArgs.eventTime); - ASSERT_EQ(DEVICE_ID, motionArgs.deviceId); - ASSERT_EQ(VIRTUAL_DISPLAY_ID, motionArgs.displayId); - ASSERT_EQ(AINPUT_SOURCE_TOUCHSCREEN, motionArgs.source); - ASSERT_EQ(uint32_t(0), motionArgs.policyFlags); - ASSERT_EQ(AMOTION_EVENT_ACTION_UP, motionArgs.action); - ASSERT_EQ(0, motionArgs.flags); - ASSERT_EQ(AMETA_SHIFT_LEFT_ON | AMETA_SHIFT_ON, motionArgs.metaState); + ASSERT_EQ(AMOTION_EVENT_ACTION_MOVE, motionArgs.action); ASSERT_EQ(0, motionArgs.buttonState); - ASSERT_EQ(0, motionArgs.edgeFlags); - ASSERT_EQ(size_t(1), motionArgs.pointerCount); - ASSERT_EQ(0, motionArgs.pointerProperties[0].id); - ASSERT_EQ(AMOTION_EVENT_TOOL_TYPE_FINGER, motionArgs.pointerProperties[0].toolType); - ASSERT_NO_FATAL_FAILURE(assertPointerCoords(motionArgs.pointerCoords[0], - toDisplayX(x, VIRTUAL_DISPLAY_WIDTH), toDisplayY(y, VIRTUAL_DISPLAY_HEIGHT), - 1, 0, 0, 0, 0, 0, 0, 0)); - ASSERT_NEAR(X_PRECISION_VIRTUAL, motionArgs.xPrecision, EPSILON); - ASSERT_NEAR(Y_PRECISION_VIRTUAL, motionArgs.yPrecision, EPSILON); - ASSERT_EQ(ARBITRARY_TIME, motionArgs.downTime); - // Should not have sent any more keys or motions. - ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyKeyWasNotCalled()); - ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasNotCalled()); -} + ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyKeyWasCalled(&keyArgs)); + ASSERT_EQ(AKEY_EVENT_ACTION_UP, keyArgs.action); + ASSERT_EQ(AKEYCODE_BACK, keyArgs.keyCode); -TEST_F(SingleTouchInputMapperTest, Process_NormalSingleTouchGesture) { - addConfigurationProperty("touch.deviceType", "touchScreen"); - prepareDisplay(DISPLAY_ORIENTATION_0); - prepareButtons(); - prepareAxes(POSITION); - prepareVirtualKeys(); - SingleTouchInputMapper& mapper = addMapperAndConfigure(); + // press BTN_FORWARD, release BTN_FORWARD + processKey(mapper, BTN_FORWARD, 1); + processSync(mapper); + ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyKeyWasCalled(&keyArgs)); + ASSERT_EQ(AKEY_EVENT_ACTION_DOWN, keyArgs.action); + ASSERT_EQ(AKEYCODE_FORWARD, keyArgs.keyCode); - mReader->getContext()->setGlobalMetaState(AMETA_SHIFT_LEFT_ON | AMETA_SHIFT_ON); + ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs)); + ASSERT_EQ(AMOTION_EVENT_ACTION_MOVE, motionArgs.action); + ASSERT_EQ(AMOTION_EVENT_BUTTON_FORWARD, motionArgs.buttonState); - NotifyMotionArgs motionArgs; + ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs)); + ASSERT_EQ(AMOTION_EVENT_ACTION_BUTTON_PRESS, motionArgs.action); + ASSERT_EQ(AMOTION_EVENT_BUTTON_FORWARD, motionArgs.buttonState); - // Down. - int32_t x = 100; - int32_t y = 125; - processDown(mapper, x, y); + processKey(mapper, BTN_FORWARD, 0); processSync(mapper); + ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs)); + ASSERT_EQ(AMOTION_EVENT_ACTION_BUTTON_RELEASE, motionArgs.action); + ASSERT_EQ(0, motionArgs.buttonState); ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs)); - ASSERT_EQ(ARBITRARY_TIME, motionArgs.eventTime); - ASSERT_EQ(DEVICE_ID, motionArgs.deviceId); - ASSERT_EQ(AINPUT_SOURCE_TOUCHSCREEN, motionArgs.source); - ASSERT_EQ(uint32_t(0), motionArgs.policyFlags); - ASSERT_EQ(AMOTION_EVENT_ACTION_DOWN, motionArgs.action); - ASSERT_EQ(0, motionArgs.flags); - ASSERT_EQ(AMETA_SHIFT_LEFT_ON | AMETA_SHIFT_ON, motionArgs.metaState); + ASSERT_EQ(AMOTION_EVENT_ACTION_MOVE, motionArgs.action); ASSERT_EQ(0, motionArgs.buttonState); - ASSERT_EQ(0, motionArgs.edgeFlags); - ASSERT_EQ(size_t(1), motionArgs.pointerCount); - ASSERT_EQ(0, motionArgs.pointerProperties[0].id); - ASSERT_EQ(AMOTION_EVENT_TOOL_TYPE_FINGER, motionArgs.pointerProperties[0].toolType); - ASSERT_NO_FATAL_FAILURE(assertPointerCoords(motionArgs.pointerCoords[0], - toDisplayX(x), toDisplayY(y), 1, 0, 0, 0, 0, 0, 0, 0)); - ASSERT_NEAR(X_PRECISION, motionArgs.xPrecision, EPSILON); - ASSERT_NEAR(Y_PRECISION, motionArgs.yPrecision, EPSILON); - ASSERT_EQ(ARBITRARY_TIME, motionArgs.downTime); - // Move. - x += 50; - y += 75; - processMove(mapper, x, y); + ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyKeyWasCalled(&keyArgs)); + ASSERT_EQ(AKEY_EVENT_ACTION_UP, keyArgs.action); + ASSERT_EQ(AKEYCODE_FORWARD, keyArgs.keyCode); + + // press BTN_EXTRA, release BTN_EXTRA + processKey(mapper, BTN_EXTRA, 1); processSync(mapper); + ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyKeyWasCalled(&keyArgs)); + ASSERT_EQ(AKEY_EVENT_ACTION_DOWN, keyArgs.action); + ASSERT_EQ(AKEYCODE_FORWARD, keyArgs.keyCode); ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs)); - ASSERT_EQ(ARBITRARY_TIME, motionArgs.eventTime); - ASSERT_EQ(DEVICE_ID, motionArgs.deviceId); - ASSERT_EQ(AINPUT_SOURCE_TOUCHSCREEN, motionArgs.source); - ASSERT_EQ(uint32_t(0), motionArgs.policyFlags); ASSERT_EQ(AMOTION_EVENT_ACTION_MOVE, motionArgs.action); - ASSERT_EQ(0, motionArgs.flags); - ASSERT_EQ(AMETA_SHIFT_LEFT_ON | AMETA_SHIFT_ON, motionArgs.metaState); - ASSERT_EQ(0, motionArgs.buttonState); - ASSERT_EQ(0, motionArgs.edgeFlags); - ASSERT_EQ(size_t(1), motionArgs.pointerCount); - ASSERT_EQ(0, motionArgs.pointerProperties[0].id); - ASSERT_EQ(AMOTION_EVENT_TOOL_TYPE_FINGER, motionArgs.pointerProperties[0].toolType); - ASSERT_NO_FATAL_FAILURE(assertPointerCoords(motionArgs.pointerCoords[0], - toDisplayX(x), toDisplayY(y), 1, 0, 0, 0, 0, 0, 0, 0)); - ASSERT_NEAR(X_PRECISION, motionArgs.xPrecision, EPSILON); - ASSERT_NEAR(Y_PRECISION, motionArgs.yPrecision, EPSILON); - ASSERT_EQ(ARBITRARY_TIME, motionArgs.downTime); + ASSERT_EQ(AMOTION_EVENT_BUTTON_FORWARD, motionArgs.buttonState); - // Up. - processUp(mapper); + ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs)); + ASSERT_EQ(AMOTION_EVENT_ACTION_BUTTON_PRESS, motionArgs.action); + ASSERT_EQ(AMOTION_EVENT_BUTTON_FORWARD, motionArgs.buttonState); + + processKey(mapper, BTN_EXTRA, 0); processSync(mapper); + ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs)); + ASSERT_EQ(AMOTION_EVENT_ACTION_BUTTON_RELEASE, motionArgs.action); + ASSERT_EQ(0, motionArgs.buttonState); ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs)); - ASSERT_EQ(ARBITRARY_TIME, motionArgs.eventTime); - ASSERT_EQ(DEVICE_ID, motionArgs.deviceId); - ASSERT_EQ(AINPUT_SOURCE_TOUCHSCREEN, motionArgs.source); - ASSERT_EQ(uint32_t(0), motionArgs.policyFlags); - ASSERT_EQ(AMOTION_EVENT_ACTION_UP, motionArgs.action); - ASSERT_EQ(0, motionArgs.flags); - ASSERT_EQ(AMETA_SHIFT_LEFT_ON | AMETA_SHIFT_ON, motionArgs.metaState); + ASSERT_EQ(AMOTION_EVENT_ACTION_MOVE, motionArgs.action); ASSERT_EQ(0, motionArgs.buttonState); - ASSERT_EQ(0, motionArgs.edgeFlags); - ASSERT_EQ(size_t(1), motionArgs.pointerCount); - ASSERT_EQ(0, motionArgs.pointerProperties[0].id); - ASSERT_EQ(AMOTION_EVENT_TOOL_TYPE_FINGER, motionArgs.pointerProperties[0].toolType); - ASSERT_NO_FATAL_FAILURE(assertPointerCoords(motionArgs.pointerCoords[0], - toDisplayX(x), toDisplayY(y), 1, 0, 0, 0, 0, 0, 0, 0)); - ASSERT_NEAR(X_PRECISION, motionArgs.xPrecision, EPSILON); - ASSERT_NEAR(Y_PRECISION, motionArgs.yPrecision, EPSILON); - ASSERT_EQ(ARBITRARY_TIME, motionArgs.downTime); - // Should not have sent any more keys or motions. - ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyKeyWasNotCalled()); - ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasNotCalled()); -} + ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyKeyWasCalled(&keyArgs)); + ASSERT_EQ(AKEY_EVENT_ACTION_UP, keyArgs.action); + ASSERT_EQ(AKEYCODE_FORWARD, keyArgs.keyCode); + + ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyKeyWasNotCalled()); + + // press BTN_STYLUS, release BTN_STYLUS + processKey(mapper, BTN_STYLUS, 1); + processSync(mapper); + ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs)); + ASSERT_EQ(AMOTION_EVENT_ACTION_MOVE, motionArgs.action); + ASSERT_EQ(AMOTION_EVENT_BUTTON_STYLUS_PRIMARY, motionArgs.buttonState); + + ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs)); + ASSERT_EQ(AMOTION_EVENT_ACTION_BUTTON_PRESS, motionArgs.action); + ASSERT_EQ(AMOTION_EVENT_BUTTON_STYLUS_PRIMARY, motionArgs.buttonState); + + processKey(mapper, BTN_STYLUS, 0); + processSync(mapper); + ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs)); + ASSERT_EQ(AMOTION_EVENT_ACTION_BUTTON_RELEASE, motionArgs.action); + ASSERT_EQ(0, motionArgs.buttonState); -TEST_F(SingleTouchInputMapperTest, Process_WhenOrientationAware_DoesNotRotateMotions) { - addConfigurationProperty("touch.deviceType", "touchScreen"); - prepareButtons(); - prepareAxes(POSITION); - // InputReader works in the un-rotated coordinate space, so orientation-aware devices do not - // need to be rotated. Touchscreens are orientation-aware by default. - SingleTouchInputMapper& mapper = addMapperAndConfigure(); + ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs)); + ASSERT_EQ(AMOTION_EVENT_ACTION_MOVE, motionArgs.action); + ASSERT_EQ(0, motionArgs.buttonState); - NotifyMotionArgs args; + // press BTN_STYLUS2, release BTN_STYLUS2 + processKey(mapper, BTN_STYLUS2, 1); + processSync(mapper); + ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs)); + ASSERT_EQ(AMOTION_EVENT_ACTION_MOVE, motionArgs.action); + ASSERT_EQ(AMOTION_EVENT_BUTTON_STYLUS_SECONDARY, motionArgs.buttonState); - // Rotation 90. - prepareDisplay(DISPLAY_ORIENTATION_90); - processDown(mapper, toRawX(50), toRawY(75)); + ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs)); + ASSERT_EQ(AMOTION_EVENT_ACTION_BUTTON_PRESS, motionArgs.action); + ASSERT_EQ(AMOTION_EVENT_BUTTON_STYLUS_SECONDARY, motionArgs.buttonState); + + processKey(mapper, BTN_STYLUS2, 0); processSync(mapper); + ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs)); + ASSERT_EQ(AMOTION_EVENT_ACTION_BUTTON_RELEASE, motionArgs.action); + ASSERT_EQ(0, motionArgs.buttonState); - ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&args)); - ASSERT_NEAR(50, args.pointerCoords[0].getAxisValue(AMOTION_EVENT_AXIS_X), 1); - ASSERT_NEAR(75, args.pointerCoords[0].getAxisValue(AMOTION_EVENT_AXIS_Y), 1); + ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs)); + ASSERT_EQ(AMOTION_EVENT_ACTION_MOVE, motionArgs.action); + ASSERT_EQ(0, motionArgs.buttonState); + // release touch processUp(mapper); processSync(mapper); - ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled()); + ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs)); + ASSERT_EQ(AMOTION_EVENT_ACTION_UP, motionArgs.action); + ASSERT_EQ(0, motionArgs.buttonState); } -TEST_F(SingleTouchInputMapperTest, Process_WhenNotOrientationAware_RotatesMotions) { +TEST_F(SingleTouchInputMapperTest, Process_ShouldHandleAllToolTypes) { addConfigurationProperty("touch.deviceType", "touchScreen"); + prepareDisplay(ui::ROTATION_0); prepareButtons(); prepareAxes(POSITION); - // Since InputReader works in the un-rotated coordinate space, only devices that are not - // orientation-aware are affected by display rotation. - addConfigurationProperty("touch.orientationAware", "0"); - SingleTouchInputMapper& mapper = addMapperAndConfigure(); + SingleTouchInputMapper& mapper = constructAndAddMapper(); - NotifyMotionArgs args; + NotifyMotionArgs motionArgs; - // Rotation 0. - clearViewports(); - prepareDisplay(DISPLAY_ORIENTATION_0); - processDown(mapper, toRawX(50), toRawY(75)); + // default tool type is finger + processDown(mapper, 100, 200); processSync(mapper); + ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs)); + ASSERT_EQ(AMOTION_EVENT_ACTION_DOWN, motionArgs.action); + ASSERT_EQ(ToolType::FINGER, motionArgs.pointerProperties[0].toolType); - ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&args)); - ASSERT_NEAR(50, args.pointerCoords[0].getAxisValue(AMOTION_EVENT_AXIS_X), 1); - ASSERT_NEAR(75, args.pointerCoords[0].getAxisValue(AMOTION_EVENT_AXIS_Y), 1); - - processUp(mapper); + // eraser + processKey(mapper, BTN_TOOL_RUBBER, 1); processSync(mapper); - ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled()); + ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs)); + ASSERT_EQ(AMOTION_EVENT_ACTION_MOVE, motionArgs.action); + ASSERT_EQ(ToolType::ERASER, motionArgs.pointerProperties[0].toolType); - // Rotation 90. - clearViewports(); - prepareDisplay(DISPLAY_ORIENTATION_90); - processDown(mapper, toRawX(75), RAW_Y_MAX - toRawY(50) + RAW_Y_MIN); + // stylus + processKey(mapper, BTN_TOOL_RUBBER, 0); + processKey(mapper, BTN_TOOL_PEN, 1); processSync(mapper); + ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs)); + ASSERT_EQ(AMOTION_EVENT_ACTION_MOVE, motionArgs.action); + ASSERT_EQ(ToolType::STYLUS, motionArgs.pointerProperties[0].toolType); - ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&args)); - ASSERT_NEAR(50, args.pointerCoords[0].getAxisValue(AMOTION_EVENT_AXIS_X), 1); - ASSERT_NEAR(75, args.pointerCoords[0].getAxisValue(AMOTION_EVENT_AXIS_Y), 1); - - processUp(mapper); + // brush + processKey(mapper, BTN_TOOL_PEN, 0); + processKey(mapper, BTN_TOOL_BRUSH, 1); processSync(mapper); - ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled()); + ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs)); + ASSERT_EQ(AMOTION_EVENT_ACTION_MOVE, motionArgs.action); + ASSERT_EQ(ToolType::STYLUS, motionArgs.pointerProperties[0].toolType); - // Rotation 180. - clearViewports(); - prepareDisplay(DISPLAY_ORIENTATION_180); - processDown(mapper, RAW_X_MAX - toRawX(50) + RAW_X_MIN, RAW_Y_MAX - toRawY(75) + RAW_Y_MIN); + // pencil + processKey(mapper, BTN_TOOL_BRUSH, 0); + processKey(mapper, BTN_TOOL_PENCIL, 1); processSync(mapper); + ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs)); + ASSERT_EQ(AMOTION_EVENT_ACTION_MOVE, motionArgs.action); + ASSERT_EQ(ToolType::STYLUS, motionArgs.pointerProperties[0].toolType); - ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&args)); - ASSERT_NEAR(50, args.pointerCoords[0].getAxisValue(AMOTION_EVENT_AXIS_X), 1); - ASSERT_NEAR(75, args.pointerCoords[0].getAxisValue(AMOTION_EVENT_AXIS_Y), 1); - - processUp(mapper); + // air-brush + processKey(mapper, BTN_TOOL_PENCIL, 0); + processKey(mapper, BTN_TOOL_AIRBRUSH, 1); processSync(mapper); - ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled()); + ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs)); + ASSERT_EQ(AMOTION_EVENT_ACTION_MOVE, motionArgs.action); + ASSERT_EQ(ToolType::STYLUS, motionArgs.pointerProperties[0].toolType); - // Rotation 270. - clearViewports(); - prepareDisplay(DISPLAY_ORIENTATION_270); - processDown(mapper, RAW_X_MAX - toRawX(75) + RAW_X_MIN, toRawY(50)); + // mouse + processKey(mapper, BTN_TOOL_AIRBRUSH, 0); + processKey(mapper, BTN_TOOL_MOUSE, 1); processSync(mapper); + ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs)); + ASSERT_EQ(AMOTION_EVENT_ACTION_MOVE, motionArgs.action); + ASSERT_EQ(ToolType::MOUSE, motionArgs.pointerProperties[0].toolType); - ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&args)); - ASSERT_NEAR(50, args.pointerCoords[0].getAxisValue(AMOTION_EVENT_AXIS_X), 1); - ASSERT_NEAR(75, args.pointerCoords[0].getAxisValue(AMOTION_EVENT_AXIS_Y), 1); - - processUp(mapper); + // lens + processKey(mapper, BTN_TOOL_MOUSE, 0); + processKey(mapper, BTN_TOOL_LENS, 1); processSync(mapper); - ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled()); -} + ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs)); + ASSERT_EQ(AMOTION_EVENT_ACTION_MOVE, motionArgs.action); + ASSERT_EQ(ToolType::MOUSE, motionArgs.pointerProperties[0].toolType); -TEST_F(SingleTouchInputMapperTest, Process_WhenOrientation0_RotatesMotions) { - addConfigurationProperty("touch.deviceType", "touchScreen"); - prepareButtons(); - prepareAxes(POSITION); - addConfigurationProperty("touch.orientationAware", "1"); - addConfigurationProperty("touch.orientation", "ORIENTATION_0"); - clearViewports(); - prepareDisplay(DISPLAY_ORIENTATION_0); - auto& mapper = addMapperAndConfigure(); - NotifyMotionArgs args; + // double-tap + processKey(mapper, BTN_TOOL_LENS, 0); + processKey(mapper, BTN_TOOL_DOUBLETAP, 1); + processSync(mapper); + ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs)); + ASSERT_EQ(AMOTION_EVENT_ACTION_MOVE, motionArgs.action); + ASSERT_EQ(ToolType::FINGER, motionArgs.pointerProperties[0].toolType); - // Orientation 0. - processDown(mapper, toRawX(50), toRawY(75)); + // triple-tap + processKey(mapper, BTN_TOOL_DOUBLETAP, 0); + processKey(mapper, BTN_TOOL_TRIPLETAP, 1); processSync(mapper); + ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs)); + ASSERT_EQ(AMOTION_EVENT_ACTION_MOVE, motionArgs.action); + ASSERT_EQ(ToolType::FINGER, motionArgs.pointerProperties[0].toolType); - EXPECT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&args)); - EXPECT_NEAR(50, args.pointerCoords[0].getAxisValue(AMOTION_EVENT_AXIS_X), 1); - EXPECT_NEAR(75, args.pointerCoords[0].getAxisValue(AMOTION_EVENT_AXIS_Y), 1); + // quad-tap + processKey(mapper, BTN_TOOL_TRIPLETAP, 0); + processKey(mapper, BTN_TOOL_QUADTAP, 1); + processSync(mapper); + ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs)); + ASSERT_EQ(AMOTION_EVENT_ACTION_MOVE, motionArgs.action); + ASSERT_EQ(ToolType::FINGER, motionArgs.pointerProperties[0].toolType); - processUp(mapper); + // finger + processKey(mapper, BTN_TOOL_QUADTAP, 0); + processKey(mapper, BTN_TOOL_FINGER, 1); processSync(mapper); - EXPECT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled()); -} + ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs)); + ASSERT_EQ(AMOTION_EVENT_ACTION_MOVE, motionArgs.action); + ASSERT_EQ(ToolType::FINGER, motionArgs.pointerProperties[0].toolType); -TEST_F(SingleTouchInputMapperTest, Process_WhenOrientation90_RotatesMotions) { - addConfigurationProperty("touch.deviceType", "touchScreen"); - prepareButtons(); - prepareAxes(POSITION); - addConfigurationProperty("touch.orientationAware", "1"); - addConfigurationProperty("touch.orientation", "ORIENTATION_90"); - clearViewports(); - prepareDisplay(DISPLAY_ORIENTATION_0); - auto& mapper = addMapperAndConfigure(); - NotifyMotionArgs args; + // stylus trumps finger + processKey(mapper, BTN_TOOL_PEN, 1); + processSync(mapper); + ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs)); + ASSERT_EQ(AMOTION_EVENT_ACTION_MOVE, motionArgs.action); + ASSERT_EQ(ToolType::STYLUS, motionArgs.pointerProperties[0].toolType); - // Orientation 90. - processDown(mapper, RAW_X_MAX - toRotatedRawX(75) + RAW_X_MIN, toRotatedRawY(50)); + // eraser trumps stylus + processKey(mapper, BTN_TOOL_RUBBER, 1); processSync(mapper); + ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs)); + ASSERT_EQ(AMOTION_EVENT_ACTION_MOVE, motionArgs.action); + ASSERT_EQ(ToolType::ERASER, motionArgs.pointerProperties[0].toolType); - EXPECT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&args)); - EXPECT_NEAR(50, args.pointerCoords[0].getAxisValue(AMOTION_EVENT_AXIS_X), 1); - EXPECT_NEAR(75, args.pointerCoords[0].getAxisValue(AMOTION_EVENT_AXIS_Y), 1); + // mouse trumps eraser + processKey(mapper, BTN_TOOL_MOUSE, 1); + processSync(mapper); + ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs)); + ASSERT_EQ(AMOTION_EVENT_ACTION_MOVE, motionArgs.action); + ASSERT_EQ(ToolType::MOUSE, motionArgs.pointerProperties[0].toolType); - processUp(mapper); + // back to default tool type + processKey(mapper, BTN_TOOL_MOUSE, 0); + processKey(mapper, BTN_TOOL_RUBBER, 0); + processKey(mapper, BTN_TOOL_PEN, 0); + processKey(mapper, BTN_TOOL_FINGER, 0); processSync(mapper); - EXPECT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled()); + ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs)); + ASSERT_EQ(AMOTION_EVENT_ACTION_MOVE, motionArgs.action); + ASSERT_EQ(ToolType::FINGER, motionArgs.pointerProperties[0].toolType); } -TEST_F(SingleTouchInputMapperTest, Process_WhenOrientation180_RotatesMotions) { +TEST_F(SingleTouchInputMapperTest, Process_WhenBtnTouchPresent_HoversIfItsValueIsZero) { addConfigurationProperty("touch.deviceType", "touchScreen"); + prepareDisplay(ui::ROTATION_0); prepareButtons(); prepareAxes(POSITION); - addConfigurationProperty("touch.orientationAware", "1"); - addConfigurationProperty("touch.orientation", "ORIENTATION_180"); - clearViewports(); - prepareDisplay(DISPLAY_ORIENTATION_0); - auto& mapper = addMapperAndConfigure(); - NotifyMotionArgs args; + mFakeEventHub->addKey(EVENTHUB_ID, BTN_TOOL_FINGER, 0, AKEYCODE_UNKNOWN, 0); + SingleTouchInputMapper& mapper = constructAndAddMapper(); - // Orientation 180. - processDown(mapper, RAW_X_MAX - toRawX(50) + RAW_X_MIN, RAW_Y_MAX - toRawY(75) + RAW_Y_MIN); + NotifyMotionArgs motionArgs; + + // initially hovering because BTN_TOUCH not sent yet, pressure defaults to 0 + processKey(mapper, BTN_TOOL_FINGER, 1); + processMove(mapper, 100, 200); processSync(mapper); + ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs)); + ASSERT_EQ(AMOTION_EVENT_ACTION_HOVER_ENTER, motionArgs.action); + ASSERT_NO_FATAL_FAILURE(assertPointerCoords(motionArgs.pointerCoords[0], + toDisplayX(100), toDisplayY(200), 0, 0, 0, 0, 0, 0, 0, 0)); - EXPECT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&args)); - EXPECT_NEAR(50, args.pointerCoords[0].getAxisValue(AMOTION_EVENT_AXIS_X), 1); - EXPECT_NEAR(75, args.pointerCoords[0].getAxisValue(AMOTION_EVENT_AXIS_Y), 1); + ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs)); + ASSERT_EQ(AMOTION_EVENT_ACTION_HOVER_MOVE, motionArgs.action); + ASSERT_NO_FATAL_FAILURE(assertPointerCoords(motionArgs.pointerCoords[0], + toDisplayX(100), toDisplayY(200), 0, 0, 0, 0, 0, 0, 0, 0)); - processUp(mapper); + // move a little + processMove(mapper, 150, 250); processSync(mapper); - EXPECT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled()); -} + ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs)); + ASSERT_EQ(AMOTION_EVENT_ACTION_HOVER_MOVE, motionArgs.action); + ASSERT_NO_FATAL_FAILURE(assertPointerCoords(motionArgs.pointerCoords[0], + toDisplayX(150), toDisplayY(250), 0, 0, 0, 0, 0, 0, 0, 0)); -TEST_F(SingleTouchInputMapperTest, Process_WhenOrientation270_RotatesMotions) { - addConfigurationProperty("touch.deviceType", "touchScreen"); - prepareButtons(); - prepareAxes(POSITION); - addConfigurationProperty("touch.orientationAware", "1"); - addConfigurationProperty("touch.orientation", "ORIENTATION_270"); - clearViewports(); - prepareDisplay(DISPLAY_ORIENTATION_0); - auto& mapper = addMapperAndConfigure(); - NotifyMotionArgs args; + // down when BTN_TOUCH is pressed, pressure defaults to 1 + processKey(mapper, BTN_TOUCH, 1); + processSync(mapper); + ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs)); + ASSERT_EQ(AMOTION_EVENT_ACTION_HOVER_EXIT, motionArgs.action); + ASSERT_NO_FATAL_FAILURE(assertPointerCoords(motionArgs.pointerCoords[0], + toDisplayX(150), toDisplayY(250), 0, 0, 0, 0, 0, 0, 0, 0)); - // Orientation 270. - processDown(mapper, toRotatedRawX(75), RAW_Y_MAX - toRotatedRawY(50) + RAW_Y_MIN); + ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs)); + ASSERT_EQ(AMOTION_EVENT_ACTION_DOWN, motionArgs.action); + ASSERT_NO_FATAL_FAILURE(assertPointerCoords(motionArgs.pointerCoords[0], + toDisplayX(150), toDisplayY(250), 1, 0, 0, 0, 0, 0, 0, 0)); + + // up when BTN_TOUCH is released, hover restored + processKey(mapper, BTN_TOUCH, 0); processSync(mapper); + ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs)); + ASSERT_EQ(AMOTION_EVENT_ACTION_UP, motionArgs.action); + ASSERT_NO_FATAL_FAILURE(assertPointerCoords(motionArgs.pointerCoords[0], + toDisplayX(150), toDisplayY(250), 1, 0, 0, 0, 0, 0, 0, 0)); - EXPECT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&args)); - EXPECT_NEAR(50, args.pointerCoords[0].getAxisValue(AMOTION_EVENT_AXIS_X), 1); - EXPECT_NEAR(75, args.pointerCoords[0].getAxisValue(AMOTION_EVENT_AXIS_Y), 1); + ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs)); + ASSERT_EQ(AMOTION_EVENT_ACTION_HOVER_ENTER, motionArgs.action); + ASSERT_NO_FATAL_FAILURE(assertPointerCoords(motionArgs.pointerCoords[0], + toDisplayX(150), toDisplayY(250), 0, 0, 0, 0, 0, 0, 0, 0)); - processUp(mapper); + ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs)); + ASSERT_EQ(AMOTION_EVENT_ACTION_HOVER_MOVE, motionArgs.action); + ASSERT_NO_FATAL_FAILURE(assertPointerCoords(motionArgs.pointerCoords[0], + toDisplayX(150), toDisplayY(250), 0, 0, 0, 0, 0, 0, 0, 0)); + + // exit hover when pointer goes away + processKey(mapper, BTN_TOOL_FINGER, 0); processSync(mapper); - EXPECT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled()); + ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs)); + ASSERT_EQ(AMOTION_EVENT_ACTION_HOVER_EXIT, motionArgs.action); + ASSERT_NO_FATAL_FAILURE(assertPointerCoords(motionArgs.pointerCoords[0], + toDisplayX(150), toDisplayY(250), 0, 0, 0, 0, 0, 0, 0, 0)); } -TEST_F(SingleTouchInputMapperTest, Process_WhenOrientationSpecified_RotatesMotionWithDisplay) { +TEST_F(SingleTouchInputMapperTest, Process_WhenAbsPressureIsPresent_HoversIfItsValueIsZero) { addConfigurationProperty("touch.deviceType", "touchScreen"); + prepareDisplay(ui::ROTATION_0); prepareButtons(); - prepareAxes(POSITION); - // Since InputReader works in the un-rotated coordinate space, only devices that are not - // orientation-aware are affected by display rotation. - addConfigurationProperty("touch.orientationAware", "0"); - addConfigurationProperty("touch.orientation", "ORIENTATION_90"); - auto& mapper = addMapperAndConfigure(); + prepareAxes(POSITION | PRESSURE); + SingleTouchInputMapper& mapper = constructAndAddMapper(); - NotifyMotionArgs args; + NotifyMotionArgs motionArgs; - // Orientation 90, Rotation 0. - clearViewports(); - prepareDisplay(DISPLAY_ORIENTATION_0); - processDown(mapper, RAW_X_MAX - toRotatedRawX(75) + RAW_X_MIN, toRotatedRawY(50)); + // initially hovering because pressure is 0 + processDown(mapper, 100, 200); + processPressure(mapper, 0); processSync(mapper); + ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs)); + ASSERT_EQ(AMOTION_EVENT_ACTION_HOVER_ENTER, motionArgs.action); + ASSERT_NO_FATAL_FAILURE(assertPointerCoords(motionArgs.pointerCoords[0], + toDisplayX(100), toDisplayY(200), 0, 0, 0, 0, 0, 0, 0, 0)); - EXPECT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&args)); - EXPECT_NEAR(50, args.pointerCoords[0].getAxisValue(AMOTION_EVENT_AXIS_X), 1); - EXPECT_NEAR(75, args.pointerCoords[0].getAxisValue(AMOTION_EVENT_AXIS_Y), 1); + ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs)); + ASSERT_EQ(AMOTION_EVENT_ACTION_HOVER_MOVE, motionArgs.action); + ASSERT_NO_FATAL_FAILURE(assertPointerCoords(motionArgs.pointerCoords[0], + toDisplayX(100), toDisplayY(200), 0, 0, 0, 0, 0, 0, 0, 0)); - processUp(mapper); + // move a little + processMove(mapper, 150, 250); processSync(mapper); - EXPECT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled()); + ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs)); + ASSERT_EQ(AMOTION_EVENT_ACTION_HOVER_MOVE, motionArgs.action); + ASSERT_NO_FATAL_FAILURE(assertPointerCoords(motionArgs.pointerCoords[0], + toDisplayX(150), toDisplayY(250), 0, 0, 0, 0, 0, 0, 0, 0)); - // Orientation 90, Rotation 90. - clearViewports(); - prepareDisplay(DISPLAY_ORIENTATION_90); - processDown(mapper, toRotatedRawX(50), toRotatedRawY(75)); + // down when pressure is non-zero + processPressure(mapper, RAW_PRESSURE_MAX); processSync(mapper); + ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs)); + ASSERT_EQ(AMOTION_EVENT_ACTION_HOVER_EXIT, motionArgs.action); + ASSERT_NO_FATAL_FAILURE(assertPointerCoords(motionArgs.pointerCoords[0], + toDisplayX(150), toDisplayY(250), 0, 0, 0, 0, 0, 0, 0, 0)); - EXPECT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&args)); - EXPECT_NEAR(50, args.pointerCoords[0].getAxisValue(AMOTION_EVENT_AXIS_X), 1); - EXPECT_NEAR(75, args.pointerCoords[0].getAxisValue(AMOTION_EVENT_AXIS_Y), 1); + ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs)); + ASSERT_EQ(AMOTION_EVENT_ACTION_DOWN, motionArgs.action); + ASSERT_NO_FATAL_FAILURE(assertPointerCoords(motionArgs.pointerCoords[0], + toDisplayX(150), toDisplayY(250), 1, 0, 0, 0, 0, 0, 0, 0)); - processUp(mapper); + // up when pressure becomes 0, hover restored + processPressure(mapper, 0); processSync(mapper); - EXPECT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled()); + ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs)); + ASSERT_EQ(AMOTION_EVENT_ACTION_UP, motionArgs.action); + ASSERT_NO_FATAL_FAILURE(assertPointerCoords(motionArgs.pointerCoords[0], + toDisplayX(150), toDisplayY(250), 1, 0, 0, 0, 0, 0, 0, 0)); - // Orientation 90, Rotation 180. - clearViewports(); - prepareDisplay(DISPLAY_ORIENTATION_180); - processDown(mapper, toRotatedRawX(75), RAW_Y_MAX - toRotatedRawY(50) + RAW_Y_MIN); - processSync(mapper); + ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs)); + ASSERT_EQ(AMOTION_EVENT_ACTION_HOVER_ENTER, motionArgs.action); + ASSERT_NO_FATAL_FAILURE(assertPointerCoords(motionArgs.pointerCoords[0], + toDisplayX(150), toDisplayY(250), 0, 0, 0, 0, 0, 0, 0, 0)); - EXPECT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&args)); - EXPECT_NEAR(50, args.pointerCoords[0].getAxisValue(AMOTION_EVENT_AXIS_X), 1); - EXPECT_NEAR(75, args.pointerCoords[0].getAxisValue(AMOTION_EVENT_AXIS_Y), 1); + ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs)); + ASSERT_EQ(AMOTION_EVENT_ACTION_HOVER_MOVE, motionArgs.action); + ASSERT_NO_FATAL_FAILURE(assertPointerCoords(motionArgs.pointerCoords[0], + toDisplayX(150), toDisplayY(250), 0, 0, 0, 0, 0, 0, 0, 0)); + // exit hover when pointer goes away processUp(mapper); processSync(mapper); - EXPECT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled()); + ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs)); + ASSERT_EQ(AMOTION_EVENT_ACTION_HOVER_EXIT, motionArgs.action); + ASSERT_NO_FATAL_FAILURE(assertPointerCoords(motionArgs.pointerCoords[0], + toDisplayX(150), toDisplayY(250), 0, 0, 0, 0, 0, 0, 0, 0)); +} - // Orientation 90, Rotation 270. - clearViewports(); - prepareDisplay(DISPLAY_ORIENTATION_270); - processDown(mapper, RAW_X_MAX - toRotatedRawX(50) + RAW_X_MIN, - RAW_Y_MAX - toRotatedRawY(75) + RAW_Y_MIN); +TEST_F(SingleTouchInputMapperTest, Reset_CancelsOngoingGesture) { + addConfigurationProperty("touch.deviceType", "touchScreen"); + prepareDisplay(ui::ROTATION_0); + prepareButtons(); + prepareAxes(POSITION | PRESSURE); + SingleTouchInputMapper& mapper = constructAndAddMapper(); + + // Touch down. + processDown(mapper, 100, 200); + processPressure(mapper, 1); processSync(mapper); + ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled( + WithMotionAction(AMOTION_EVENT_ACTION_DOWN))); - EXPECT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&args)); - EXPECT_NEAR(50, args.pointerCoords[0].getAxisValue(AMOTION_EVENT_AXIS_X), 1); - EXPECT_NEAR(75, args.pointerCoords[0].getAxisValue(AMOTION_EVENT_AXIS_Y), 1); + // Reset the mapper. This should cancel the ongoing gesture. + resetMapper(mapper, ARBITRARY_TIME); + ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled( + WithMotionAction(AMOTION_EVENT_ACTION_CANCEL))); - processUp(mapper); - processSync(mapper); - EXPECT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled()); + ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasNotCalled()); } -TEST_F(SingleTouchInputMapperTest, Process_AllAxes_DefaultCalibration) { +TEST_F(SingleTouchInputMapperTest, Reset_RecreatesTouchState) { addConfigurationProperty("touch.deviceType", "touchScreen"); - prepareDisplay(DISPLAY_ORIENTATION_0); + prepareDisplay(ui::ROTATION_0); prepareButtons(); - prepareAxes(POSITION | PRESSURE | TOOL | DISTANCE | TILT); - SingleTouchInputMapper& mapper = addMapperAndConfigure(); - - // These calculations are based on the input device calibration documentation. - int32_t rawX = 100; - int32_t rawY = 200; - int32_t rawPressure = 10; - int32_t rawToolMajor = 12; - int32_t rawDistance = 2; - int32_t rawTiltX = 30; - int32_t rawTiltY = 110; + prepareAxes(POSITION | PRESSURE); + SingleTouchInputMapper& mapper = constructAndAddMapper(); - float x = toDisplayX(rawX); - float y = toDisplayY(rawY); - float pressure = float(rawPressure) / RAW_PRESSURE_MAX; - float size = float(rawToolMajor) / RAW_TOOL_MAX; - float tool = float(rawToolMajor) * GEOMETRIC_SCALE; - float distance = float(rawDistance); + // Set the initial state for the touch pointer. + mFakeEventHub->setAbsoluteAxisValue(EVENTHUB_ID, ABS_X, 100); + mFakeEventHub->setAbsoluteAxisValue(EVENTHUB_ID, ABS_Y, 200); + mFakeEventHub->setAbsoluteAxisValue(EVENTHUB_ID, ABS_PRESSURE, RAW_PRESSURE_MAX); + mFakeEventHub->setScanCodeState(EVENTHUB_ID, BTN_TOUCH, 1); - float tiltCenter = (RAW_TILT_MAX + RAW_TILT_MIN) * 0.5f; - float tiltScale = M_PI / 180; - float tiltXAngle = (rawTiltX - tiltCenter) * tiltScale; - float tiltYAngle = (rawTiltY - tiltCenter) * tiltScale; - float orientation = atan2f(-sinf(tiltXAngle), sinf(tiltYAngle)); - float tilt = acosf(cosf(tiltXAngle) * cosf(tiltYAngle)); + // Reset the mapper. When the mapper is reset, we expect it to attempt to recreate the touch + // state by reading the current axis values. Since there was no ongoing gesture, calling reset + // does not generate any events. + resetMapper(mapper, ARBITRARY_TIME); - processDown(mapper, rawX, rawY); - processPressure(mapper, rawPressure); - processToolMajor(mapper, rawToolMajor); - processDistance(mapper, rawDistance); - processTilt(mapper, rawTiltX, rawTiltY); + // Send a sync to simulate an empty touch frame where nothing changes. The mapper should use + // the recreated touch state to generate a down event. processSync(mapper); + ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled( + AllOf(WithMotionAction(AMOTION_EVENT_ACTION_DOWN), WithPressure(1.f)))); - NotifyMotionArgs args; - ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&args)); - 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_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasNotCalled()); } -TEST_F(SingleTouchInputMapperTest, Process_XYAxes_AffineCalibration) { +TEST_F(SingleTouchInputMapperTest, + Process_WhenViewportDisplayIdChanged_TouchIsCanceledAndDeviceIsReset) { addConfigurationProperty("touch.deviceType", "touchScreen"); - prepareDisplay(DISPLAY_ORIENTATION_0); - prepareLocationCalibration(); + prepareDisplay(ui::ROTATION_0); prepareButtons(); prepareAxes(POSITION); - SingleTouchInputMapper& mapper = addMapperAndConfigure(); + SingleTouchInputMapper& mapper = constructAndAddMapper(); + NotifyMotionArgs motionArgs; - int32_t rawX = 100; - int32_t rawY = 200; + // Down. + processDown(mapper, 100, 200); + processSync(mapper); - float x = toDisplayX(toCookedX(rawX, rawY)); - float y = toDisplayY(toCookedY(rawX, rawY)); + // We should receive a down event + ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs)); + ASSERT_EQ(AMOTION_EVENT_ACTION_DOWN, motionArgs.action); - processDown(mapper, rawX, rawY); - processSync(mapper); + // Change display id + clearViewports(); + prepareSecondaryDisplay(ViewportType::INTERNAL); - NotifyMotionArgs args; - ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&args)); - ASSERT_NO_FATAL_FAILURE(assertPointerCoords(args.pointerCoords[0], - x, y, 1, 0, 0, 0, 0, 0, 0, 0)); + // We should receive a cancel event + ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs)); + ASSERT_EQ(AMOTION_EVENT_ACTION_CANCEL, motionArgs.action); + // Then receive reset called + ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyDeviceResetWasCalled()); } -TEST_F(SingleTouchInputMapperTest, Process_ShouldHandleAllButtons) { +TEST_F(SingleTouchInputMapperTest, + Process_WhenViewportActiveStatusChanged_TouchIsCanceledAndDeviceIsReset) { addConfigurationProperty("touch.deviceType", "touchScreen"); - prepareDisplay(DISPLAY_ORIENTATION_0); + prepareDisplay(ui::ROTATION_0); prepareButtons(); prepareAxes(POSITION); - SingleTouchInputMapper& mapper = addMapperAndConfigure(); - + SingleTouchInputMapper& mapper = constructAndAddMapper(); + ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyDeviceResetWasCalled()); NotifyMotionArgs motionArgs; - NotifyKeyArgs keyArgs; + // Start a new gesture. processDown(mapper, 100, 200); processSync(mapper); ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs)); ASSERT_EQ(AMOTION_EVENT_ACTION_DOWN, motionArgs.action); - ASSERT_EQ(0, motionArgs.buttonState); - // press BTN_LEFT, release BTN_LEFT - processKey(mapper, BTN_LEFT, 1); - processSync(mapper); - ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs)); - ASSERT_EQ(AMOTION_EVENT_ACTION_MOVE, motionArgs.action); - ASSERT_EQ(AMOTION_EVENT_BUTTON_PRIMARY, motionArgs.buttonState); + // Make the viewport inactive. This will put the device in disabled mode. + auto viewport = mFakePolicy->getDisplayViewportByType(ViewportType::INTERNAL); + viewport->isActive = false; + mFakePolicy->updateViewport(*viewport); + configureDevice(InputReaderConfiguration::Change::DISPLAY_INFO); + // We should receive a cancel event for the ongoing gesture. ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs)); - ASSERT_EQ(AMOTION_EVENT_ACTION_BUTTON_PRESS, motionArgs.action); - ASSERT_EQ(AMOTION_EVENT_BUTTON_PRIMARY, motionArgs.buttonState); + ASSERT_EQ(AMOTION_EVENT_ACTION_CANCEL, motionArgs.action); + // Then we should be notified that the device was reset. + ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyDeviceResetWasCalled()); - processKey(mapper, BTN_LEFT, 0); + // No events are generated while the viewport is inactive. + processMove(mapper, 101, 201); processSync(mapper); - ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs)); - ASSERT_EQ(AMOTION_EVENT_ACTION_BUTTON_RELEASE, motionArgs.action); - ASSERT_EQ(0, motionArgs.buttonState); - - ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs)); - ASSERT_EQ(AMOTION_EVENT_ACTION_MOVE, motionArgs.action); - ASSERT_EQ(0, motionArgs.buttonState); + processUp(mapper); + processSync(mapper); + ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasNotCalled()); - // press BTN_RIGHT + BTN_MIDDLE, release BTN_RIGHT, release BTN_MIDDLE - processKey(mapper, BTN_RIGHT, 1); - processKey(mapper, BTN_MIDDLE, 1); + // Start a new gesture while the viewport is still inactive. + processDown(mapper, 300, 400); + mFakeEventHub->setAbsoluteAxisValue(EVENTHUB_ID, ABS_X, 300); + mFakeEventHub->setAbsoluteAxisValue(EVENTHUB_ID, ABS_Y, 400); + mFakeEventHub->setScanCodeState(EVENTHUB_ID, BTN_TOUCH, 1); processSync(mapper); - ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs)); - ASSERT_EQ(AMOTION_EVENT_ACTION_MOVE, motionArgs.action); - ASSERT_EQ(AMOTION_EVENT_BUTTON_SECONDARY | AMOTION_EVENT_BUTTON_TERTIARY, - motionArgs.buttonState); - ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs)); - ASSERT_EQ(AMOTION_EVENT_ACTION_BUTTON_PRESS, motionArgs.action); - ASSERT_EQ(AMOTION_EVENT_BUTTON_TERTIARY, motionArgs.buttonState); + // Make the viewport active again. The device should resume processing events. + viewport->isActive = true; + mFakePolicy->updateViewport(*viewport); + configureDevice(InputReaderConfiguration::Change::DISPLAY_INFO); - ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs)); - ASSERT_EQ(AMOTION_EVENT_ACTION_BUTTON_PRESS, motionArgs.action); - ASSERT_EQ(AMOTION_EVENT_BUTTON_SECONDARY | AMOTION_EVENT_BUTTON_TERTIARY, - motionArgs.buttonState); + // The device is reset because it changes back to direct mode, without generating any events. + ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyDeviceResetWasCalled()); + ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasNotCalled()); - processKey(mapper, BTN_RIGHT, 0); + // In the next sync, the touch state that was recreated when the device was reset is reported. processSync(mapper); - ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs)); - ASSERT_EQ(AMOTION_EVENT_ACTION_BUTTON_RELEASE, motionArgs.action); - ASSERT_EQ(AMOTION_EVENT_BUTTON_TERTIARY, motionArgs.buttonState); + ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled( + WithMotionAction(AMOTION_EVENT_ACTION_DOWN))); - ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs)); - ASSERT_EQ(AMOTION_EVENT_ACTION_MOVE, motionArgs.action); - ASSERT_EQ(AMOTION_EVENT_BUTTON_TERTIARY, motionArgs.buttonState); + // No more events. + ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasNotCalled()); + ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyDeviceResetWasNotCalled()); +} - processKey(mapper, BTN_MIDDLE, 0); - processSync(mapper); - ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs)); - ASSERT_EQ(AMOTION_EVENT_ACTION_BUTTON_RELEASE, motionArgs.action); - ASSERT_EQ(0, motionArgs.buttonState); +TEST_F(SingleTouchInputMapperTest, ButtonIsReleasedOnTouchUp) { + addConfigurationProperty("touch.deviceType", "touchScreen"); + prepareDisplay(ui::ROTATION_0); + prepareButtons(); + prepareAxes(POSITION); + SingleTouchInputMapper& mapper = constructAndAddMapper(); + ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyDeviceResetWasCalled()); - ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs)); - ASSERT_EQ(AMOTION_EVENT_ACTION_MOVE, motionArgs.action); - ASSERT_EQ(0, motionArgs.buttonState); + // Press a stylus button. + processKey(mapper, BTN_STYLUS, 1); + processSync(mapper); - // press BTN_BACK, release BTN_BACK - processKey(mapper, BTN_BACK, 1); + // Start a touch gesture and ensure the BUTTON_PRESS event is generated. + processDown(mapper, 100, 200); processSync(mapper); - ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyKeyWasCalled(&keyArgs)); - ASSERT_EQ(AKEY_EVENT_ACTION_DOWN, keyArgs.action); - ASSERT_EQ(AKEYCODE_BACK, keyArgs.keyCode); + ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled( + AllOf(WithMotionAction(AMOTION_EVENT_ACTION_DOWN), + WithCoords(toDisplayX(100), toDisplayY(200)), + WithButtonState(AMOTION_EVENT_BUTTON_STYLUS_PRIMARY)))); + ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled( + AllOf(WithMotionAction(AMOTION_EVENT_ACTION_BUTTON_PRESS), + WithCoords(toDisplayX(100), toDisplayY(200)), + WithButtonState(AMOTION_EVENT_BUTTON_STYLUS_PRIMARY)))); + + // Release the touch gesture. Ensure that the BUTTON_RELEASE event is generated even though + // the button has not actually been released, since there will be no pointers through which the + // button state can be reported. The event is generated at the location of the pointer before + // it went up. + processUp(mapper); + processSync(mapper); + ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled( + AllOf(WithMotionAction(AMOTION_EVENT_ACTION_BUTTON_RELEASE), + WithCoords(toDisplayX(100), toDisplayY(200)), WithButtonState(0)))); + ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled( + AllOf(WithMotionAction(AMOTION_EVENT_ACTION_UP), + WithCoords(toDisplayX(100), toDisplayY(200)), WithButtonState(0)))); +} - ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs)); - ASSERT_EQ(AMOTION_EVENT_ACTION_MOVE, motionArgs.action); - ASSERT_EQ(AMOTION_EVENT_BUTTON_BACK, motionArgs.buttonState); +TEST_F(SingleTouchInputMapperTest, StylusButtonMotionEventsDisabled) { + addConfigurationProperty("touch.deviceType", "touchScreen"); + prepareDisplay(ui::ROTATION_0); + prepareButtons(); + prepareAxes(POSITION); - ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs)); - ASSERT_EQ(AMOTION_EVENT_ACTION_BUTTON_PRESS, motionArgs.action); - ASSERT_EQ(AMOTION_EVENT_BUTTON_BACK, motionArgs.buttonState); + mFakePolicy->setStylusButtonMotionEventsEnabled(false); - processKey(mapper, BTN_BACK, 0); - processSync(mapper); - ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs)); - ASSERT_EQ(AMOTION_EVENT_ACTION_BUTTON_RELEASE, motionArgs.action); - ASSERT_EQ(0, motionArgs.buttonState); + SingleTouchInputMapper& mapper = constructAndAddMapper(); + ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyDeviceResetWasCalled()); - ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs)); - ASSERT_EQ(AMOTION_EVENT_ACTION_MOVE, motionArgs.action); - ASSERT_EQ(0, motionArgs.buttonState); + // Press a stylus button. + processKey(mapper, BTN_STYLUS, 1); + processSync(mapper); - ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyKeyWasCalled(&keyArgs)); - ASSERT_EQ(AKEY_EVENT_ACTION_UP, keyArgs.action); - ASSERT_EQ(AKEYCODE_BACK, keyArgs.keyCode); + // Start a touch gesture and ensure that the stylus button is not reported. + processDown(mapper, 100, 200); + processSync(mapper); + ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled( + AllOf(WithMotionAction(AMOTION_EVENT_ACTION_DOWN), WithButtonState(0)))); - // press BTN_SIDE, release BTN_SIDE - processKey(mapper, BTN_SIDE, 1); + // Release and press the stylus button again. + processKey(mapper, BTN_STYLUS, 0); processSync(mapper); - ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyKeyWasCalled(&keyArgs)); - ASSERT_EQ(AKEY_EVENT_ACTION_DOWN, keyArgs.action); - ASSERT_EQ(AKEYCODE_BACK, keyArgs.keyCode); + ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled( + AllOf(WithMotionAction(AMOTION_EVENT_ACTION_MOVE), WithButtonState(0)))); + processKey(mapper, BTN_STYLUS, 1); + processSync(mapper); + ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled( + AllOf(WithMotionAction(AMOTION_EVENT_ACTION_MOVE), WithButtonState(0)))); - ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs)); - ASSERT_EQ(AMOTION_EVENT_ACTION_MOVE, motionArgs.action); - ASSERT_EQ(AMOTION_EVENT_BUTTON_BACK, motionArgs.buttonState); + // Release the touch gesture. + processUp(mapper); + processSync(mapper); + ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled( + AllOf(WithMotionAction(AMOTION_EVENT_ACTION_UP), WithButtonState(0)))); - ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs)); - ASSERT_EQ(AMOTION_EVENT_ACTION_BUTTON_PRESS, motionArgs.action); - ASSERT_EQ(AMOTION_EVENT_BUTTON_BACK, motionArgs.buttonState); + ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasNotCalled()); +} - processKey(mapper, BTN_SIDE, 0); - processSync(mapper); - ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs)); - ASSERT_EQ(AMOTION_EVENT_ACTION_BUTTON_RELEASE, motionArgs.action); - ASSERT_EQ(0, motionArgs.buttonState); +TEST_F(SingleTouchInputMapperTest, WhenDeviceTypeIsSetToTouchNavigation_setsCorrectType) { + mFakePolicy->addDeviceTypeAssociation(DEVICE_LOCATION, "touchNavigation"); + prepareDisplay(ui::ROTATION_0); + prepareButtons(); + prepareAxes(POSITION); + SingleTouchInputMapper& mapper = constructAndAddMapper(); + ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyDeviceResetWasCalled()); - ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs)); - ASSERT_EQ(AMOTION_EVENT_ACTION_MOVE, motionArgs.action); - ASSERT_EQ(0, motionArgs.buttonState); + ASSERT_EQ(AINPUT_SOURCE_TOUCH_NAVIGATION, mapper.getSources()); +} - ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyKeyWasCalled(&keyArgs)); - ASSERT_EQ(AKEY_EVENT_ACTION_UP, keyArgs.action); - ASSERT_EQ(AKEYCODE_BACK, keyArgs.keyCode); +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(); - // press BTN_FORWARD, release BTN_FORWARD - processKey(mapper, BTN_FORWARD, 1); + processKey(mapper, BTN_TOOL_PEN, 1); + processMove(mapper, 100, 200); processSync(mapper); - ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyKeyWasCalled(&keyArgs)); - ASSERT_EQ(AKEY_EVENT_ACTION_DOWN, keyArgs.action); - ASSERT_EQ(AKEYCODE_FORWARD, keyArgs.keyCode); - - ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs)); - ASSERT_EQ(AMOTION_EVENT_ACTION_MOVE, motionArgs.action); - ASSERT_EQ(AMOTION_EVENT_BUTTON_FORWARD, motionArgs.buttonState); + 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))); +} - ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs)); - ASSERT_EQ(AMOTION_EVENT_ACTION_BUTTON_PRESS, motionArgs.action); - ASSERT_EQ(AMOTION_EVENT_BUTTON_FORWARD, motionArgs.buttonState); +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_FORWARD, 0); + processKey(mapper, BTN_TOOL_PEN, 1); + processMove(mapper, 100, 200); processSync(mapper); - ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs)); - ASSERT_EQ(AMOTION_EVENT_ACTION_BUTTON_RELEASE, motionArgs.action); - ASSERT_EQ(0, motionArgs.buttonState); + 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()); +} - ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs)); - ASSERT_EQ(AMOTION_EVENT_ACTION_MOVE, motionArgs.action); - ASSERT_EQ(0, motionArgs.buttonState); +TEST_F(SingleTouchInputMapperTest, WhenDeviceTypeIsChangedToTouchNavigation_updatesDeviceType) { + // Initialize the device without setting device source to touch navigation. + addConfigurationProperty("touch.deviceType", "touchScreen"); + prepareDisplay(ui::ROTATION_0); + prepareButtons(); + prepareAxes(POSITION); + SingleTouchInputMapper& mapper = constructAndAddMapper(); - ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyKeyWasCalled(&keyArgs)); - ASSERT_EQ(AKEY_EVENT_ACTION_UP, keyArgs.action); - ASSERT_EQ(AKEYCODE_FORWARD, keyArgs.keyCode); + // Ensure that the device is created as a touchscreen, not touch navigation. + ASSERT_EQ(AINPUT_SOURCE_TOUCHSCREEN, mapper.getSources()); - // press BTN_EXTRA, release BTN_EXTRA - processKey(mapper, BTN_EXTRA, 1); - processSync(mapper); - ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyKeyWasCalled(&keyArgs)); - ASSERT_EQ(AKEY_EVENT_ACTION_DOWN, keyArgs.action); - ASSERT_EQ(AKEYCODE_FORWARD, keyArgs.keyCode); + // Add device type association after the device was created. + mFakePolicy->addDeviceTypeAssociation(DEVICE_LOCATION, "touchNavigation"); - ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs)); - ASSERT_EQ(AMOTION_EVENT_ACTION_MOVE, motionArgs.action); - ASSERT_EQ(AMOTION_EVENT_BUTTON_FORWARD, motionArgs.buttonState); + // Send update to the mapper. + std::list unused2 = + mDevice->configure(ARBITRARY_TIME, mFakePolicy->getReaderConfiguration(), + InputReaderConfiguration::Change::DEVICE_TYPE /*changes*/); - ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs)); - ASSERT_EQ(AMOTION_EVENT_ACTION_BUTTON_PRESS, motionArgs.action); - ASSERT_EQ(AMOTION_EVENT_BUTTON_FORWARD, motionArgs.buttonState); + // Check whether device type update was successful. + ASSERT_EQ(AINPUT_SOURCE_TOUCH_NAVIGATION, mDevice->getSources()); +} - processKey(mapper, BTN_EXTRA, 0); - processSync(mapper); - ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs)); - ASSERT_EQ(AMOTION_EVENT_ACTION_BUTTON_RELEASE, motionArgs.action); - ASSERT_EQ(0, motionArgs.buttonState); +TEST_F(SingleTouchInputMapperTest, HoverEventsOutsidePhysicalFrameAreIgnored) { + // Initialize the device without setting device source to touch navigation. + addConfigurationProperty("touch.deviceType", "touchScreen"); + prepareDisplay(ui::ROTATION_0); + prepareButtons(); + prepareAxes(POSITION); + mFakeEventHub->addKey(EVENTHUB_ID, BTN_TOOL_PEN, 0, AKEYCODE_UNKNOWN, 0); - ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs)); - ASSERT_EQ(AMOTION_EVENT_ACTION_MOVE, motionArgs.action); - ASSERT_EQ(0, motionArgs.buttonState); + // Set a physical frame in the display viewport. + auto viewport = mFakePolicy->getDisplayViewportByType(ViewportType::INTERNAL); + viewport->physicalLeft = 0; + viewport->physicalTop = 0; + viewport->physicalRight = DISPLAY_WIDTH / 2; + viewport->physicalBottom = DISPLAY_HEIGHT / 2; + mFakePolicy->updateViewport(*viewport); + configureDevice(InputReaderConfiguration::Change::DISPLAY_INFO); - ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyKeyWasCalled(&keyArgs)); - ASSERT_EQ(AKEY_EVENT_ACTION_UP, keyArgs.action); - ASSERT_EQ(AKEYCODE_FORWARD, keyArgs.keyCode); + SingleTouchInputMapper& mapper = constructAndAddMapper(); - ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyKeyWasNotCalled()); + // Hovering inside the physical frame produces events. + processKey(mapper, BTN_TOOL_PEN, 1); + processMove(mapper, RAW_X_MIN + 1, RAW_Y_MIN + 1); + processSync(mapper); + ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled( + WithMotionAction(AMOTION_EVENT_ACTION_HOVER_ENTER))); + ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled( + WithMotionAction(AMOTION_EVENT_ACTION_HOVER_MOVE))); - // press BTN_STYLUS, release BTN_STYLUS - processKey(mapper, BTN_STYLUS, 1); + // Leaving the physical frame ends the hovering gesture. + processMove(mapper, RAW_X_MAX - 1, RAW_Y_MAX - 1); processSync(mapper); - ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs)); - ASSERT_EQ(AMOTION_EVENT_ACTION_MOVE, motionArgs.action); - ASSERT_EQ(AMOTION_EVENT_BUTTON_STYLUS_PRIMARY, motionArgs.buttonState); + ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled( + WithMotionAction(AMOTION_EVENT_ACTION_HOVER_EXIT))); - ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs)); - ASSERT_EQ(AMOTION_EVENT_ACTION_BUTTON_PRESS, motionArgs.action); - ASSERT_EQ(AMOTION_EVENT_BUTTON_STYLUS_PRIMARY, motionArgs.buttonState); + // Moving outside the physical frame does not produce events. + processMove(mapper, RAW_X_MAX - 2, RAW_Y_MAX - 2); + processSync(mapper); + ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasNotCalled()); - processKey(mapper, BTN_STYLUS, 0); + // Re-entering the physical frame produces events. + processMove(mapper, RAW_X_MIN, RAW_Y_MIN); processSync(mapper); - ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs)); - ASSERT_EQ(AMOTION_EVENT_ACTION_BUTTON_RELEASE, motionArgs.action); - ASSERT_EQ(0, motionArgs.buttonState); + ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled( + WithMotionAction(AMOTION_EVENT_ACTION_HOVER_ENTER))); + ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled( + WithMotionAction(AMOTION_EVENT_ACTION_HOVER_MOVE))); +} - ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs)); - ASSERT_EQ(AMOTION_EVENT_ACTION_MOVE, motionArgs.action); - ASSERT_EQ(0, motionArgs.buttonState); +// --- TouchDisplayProjectionTest --- - // press BTN_STYLUS2, release BTN_STYLUS2 - processKey(mapper, BTN_STYLUS2, 1); - processSync(mapper); - ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs)); - ASSERT_EQ(AMOTION_EVENT_ACTION_MOVE, motionArgs.action); - ASSERT_EQ(AMOTION_EVENT_BUTTON_STYLUS_SECONDARY, motionArgs.buttonState); +class TouchDisplayProjectionTest : public SingleTouchInputMapperTest { +public: + // The values inside DisplayViewport are expected to be pre-rotated. This updates the current + // DisplayViewport to pre-rotate the values. The viewport's physical display will be set to the + // rotated equivalent of the given un-rotated physical display bounds. + void configurePhysicalDisplay(ui::Rotation orientation, Rect naturalPhysicalDisplay, + int32_t naturalDisplayWidth = DISPLAY_WIDTH, + int32_t naturalDisplayHeight = DISPLAY_HEIGHT) { + uint32_t inverseRotationFlags; + auto rotatedWidth = naturalDisplayWidth; + auto rotatedHeight = naturalDisplayHeight; + switch (orientation) { + case ui::ROTATION_90: + inverseRotationFlags = ui::Transform::ROT_270; + std::swap(rotatedWidth, rotatedHeight); + break; + case ui::ROTATION_180: + inverseRotationFlags = ui::Transform::ROT_180; + break; + case ui::ROTATION_270: + inverseRotationFlags = ui::Transform::ROT_90; + std::swap(rotatedWidth, rotatedHeight); + break; + case ui::ROTATION_0: + inverseRotationFlags = ui::Transform::ROT_0; + break; + } - ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs)); - ASSERT_EQ(AMOTION_EVENT_ACTION_BUTTON_PRESS, motionArgs.action); - ASSERT_EQ(AMOTION_EVENT_BUTTON_STYLUS_SECONDARY, motionArgs.buttonState); + const ui::Transform rotation(inverseRotationFlags, rotatedWidth, rotatedHeight); + const Rect rotatedPhysicalDisplay = rotation.transform(naturalPhysicalDisplay); - processKey(mapper, BTN_STYLUS2, 0); - processSync(mapper); - ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs)); - ASSERT_EQ(AMOTION_EVENT_ACTION_BUTTON_RELEASE, motionArgs.action); - ASSERT_EQ(0, motionArgs.buttonState); + std::optional internalViewport = + *mFakePolicy->getDisplayViewportByType(ViewportType::INTERNAL); + DisplayViewport& v = *internalViewport; + v.displayId = DISPLAY_ID; + v.orientation = orientation; + + v.logicalLeft = 0; + v.logicalTop = 0; + v.logicalRight = 100; + v.logicalBottom = 100; + + v.physicalLeft = rotatedPhysicalDisplay.left; + v.physicalTop = rotatedPhysicalDisplay.top; + v.physicalRight = rotatedPhysicalDisplay.right; + v.physicalBottom = rotatedPhysicalDisplay.bottom; - ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs)); - ASSERT_EQ(AMOTION_EVENT_ACTION_MOVE, motionArgs.action); - ASSERT_EQ(0, motionArgs.buttonState); + v.deviceWidth = rotatedWidth; + v.deviceHeight = rotatedHeight; - // release touch - processUp(mapper); - processSync(mapper); - ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs)); - ASSERT_EQ(AMOTION_EVENT_ACTION_UP, motionArgs.action); - ASSERT_EQ(0, motionArgs.buttonState); -} + v.isActive = true; + v.uniqueId = UNIQUE_ID; + v.type = ViewportType::INTERNAL; + mFakePolicy->updateViewport(v); + configureDevice(InputReaderConfiguration::Change::DISPLAY_INFO); + } -TEST_F(SingleTouchInputMapperTest, Process_ShouldHandleAllToolTypes) { + void assertReceivedMove(const Point& point) { + NotifyMotionArgs motionArgs; + ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs)); + ASSERT_EQ(AMOTION_EVENT_ACTION_MOVE, motionArgs.action); + ASSERT_EQ(size_t(1), motionArgs.pointerCount); + ASSERT_NO_FATAL_FAILURE(assertPointerCoords(motionArgs.pointerCoords[0], point.x, point.y, + 1, 0, 0, 0, 0, 0, 0, 0)); + } +}; + +TEST_F(TouchDisplayProjectionTest, IgnoresTouchesOutsidePhysicalDisplay) { addConfigurationProperty("touch.deviceType", "touchScreen"); - prepareDisplay(DISPLAY_ORIENTATION_0); + prepareDisplay(ui::ROTATION_0); + prepareButtons(); prepareAxes(POSITION); - SingleTouchInputMapper& mapper = addMapperAndConfigure(); + SingleTouchInputMapper& mapper = constructAndAddMapper(); NotifyMotionArgs motionArgs; - // default tool type is finger - processDown(mapper, 100, 200); - processSync(mapper); - ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs)); - ASSERT_EQ(AMOTION_EVENT_ACTION_DOWN, motionArgs.action); - ASSERT_EQ(AMOTION_EVENT_TOOL_TYPE_FINGER, motionArgs.pointerProperties[0].toolType); - - // eraser - processKey(mapper, BTN_TOOL_RUBBER, 1); - processSync(mapper); - ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs)); - ASSERT_EQ(AMOTION_EVENT_ACTION_MOVE, motionArgs.action); - ASSERT_EQ(AMOTION_EVENT_TOOL_TYPE_ERASER, motionArgs.pointerProperties[0].toolType); - - // stylus - processKey(mapper, BTN_TOOL_RUBBER, 0); - processKey(mapper, BTN_TOOL_PEN, 1); - processSync(mapper); - ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs)); - ASSERT_EQ(AMOTION_EVENT_ACTION_MOVE, motionArgs.action); - ASSERT_EQ(AMOTION_EVENT_TOOL_TYPE_STYLUS, motionArgs.pointerProperties[0].toolType); + // Configure the DisplayViewport such that the logical display maps to a subsection of + // the display panel called the physical display. Here, the physical display is bounded by the + // points (10, 20) and (70, 160) inside the display space, which is of the size 400 x 800. + static const Rect kPhysicalDisplay{10, 20, 70, 160}; + static const std::array kPointsOutsidePhysicalDisplay{ + {{-10, -10}, {0, 0}, {5, 100}, {50, 15}, {75, 100}, {50, 165}}}; - // brush - processKey(mapper, BTN_TOOL_PEN, 0); - processKey(mapper, BTN_TOOL_BRUSH, 1); - processSync(mapper); - ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs)); - ASSERT_EQ(AMOTION_EVENT_ACTION_MOVE, motionArgs.action); - ASSERT_EQ(AMOTION_EVENT_TOOL_TYPE_STYLUS, motionArgs.pointerProperties[0].toolType); + for (auto orientation : {ui::ROTATION_0, ui::ROTATION_90, ui::ROTATION_180, ui::ROTATION_270}) { + configurePhysicalDisplay(orientation, kPhysicalDisplay); - // pencil - processKey(mapper, BTN_TOOL_BRUSH, 0); - processKey(mapper, BTN_TOOL_PENCIL, 1); - processSync(mapper); - ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs)); - ASSERT_EQ(AMOTION_EVENT_ACTION_MOVE, motionArgs.action); - ASSERT_EQ(AMOTION_EVENT_TOOL_TYPE_STYLUS, motionArgs.pointerProperties[0].toolType); + // Touches outside the physical display should be ignored, and should not generate any + // events. Ensure touches at the following points that lie outside of the physical display + // area do not generate any events. + for (const auto& point : kPointsOutsidePhysicalDisplay) { + processDown(mapper, toRawX(point.x), toRawY(point.y)); + processSync(mapper); + processUp(mapper); + processSync(mapper); + ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasNotCalled()) + << "Unexpected event generated for touch outside physical display at point: " + << point.x << ", " << point.y; + } + } +} - // air-brush - processKey(mapper, BTN_TOOL_PENCIL, 0); - processKey(mapper, BTN_TOOL_AIRBRUSH, 1); - processSync(mapper); - ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs)); - ASSERT_EQ(AMOTION_EVENT_ACTION_MOVE, motionArgs.action); - ASSERT_EQ(AMOTION_EVENT_TOOL_TYPE_STYLUS, motionArgs.pointerProperties[0].toolType); +TEST_F(TouchDisplayProjectionTest, EmitsTouchDownAfterEnteringPhysicalDisplay) { + addConfigurationProperty("touch.deviceType", "touchScreen"); + prepareDisplay(ui::ROTATION_0); - // mouse - processKey(mapper, BTN_TOOL_AIRBRUSH, 0); - processKey(mapper, BTN_TOOL_MOUSE, 1); - processSync(mapper); - ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs)); - ASSERT_EQ(AMOTION_EVENT_ACTION_MOVE, motionArgs.action); - ASSERT_EQ(AMOTION_EVENT_TOOL_TYPE_MOUSE, motionArgs.pointerProperties[0].toolType); + prepareButtons(); + prepareAxes(POSITION); + SingleTouchInputMapper& mapper = constructAndAddMapper(); - // lens - processKey(mapper, BTN_TOOL_MOUSE, 0); - processKey(mapper, BTN_TOOL_LENS, 1); - processSync(mapper); - ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs)); - ASSERT_EQ(AMOTION_EVENT_ACTION_MOVE, motionArgs.action); - ASSERT_EQ(AMOTION_EVENT_TOOL_TYPE_MOUSE, motionArgs.pointerProperties[0].toolType); + NotifyMotionArgs motionArgs; - // double-tap - processKey(mapper, BTN_TOOL_LENS, 0); - processKey(mapper, BTN_TOOL_DOUBLETAP, 1); - processSync(mapper); - ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs)); - ASSERT_EQ(AMOTION_EVENT_ACTION_MOVE, motionArgs.action); - ASSERT_EQ(AMOTION_EVENT_TOOL_TYPE_FINGER, motionArgs.pointerProperties[0].toolType); + // Configure the DisplayViewport such that the logical display maps to a subsection of + // the display panel called the physical display. Here, the physical display is bounded by the + // points (10, 20) and (70, 160) inside the display space, which is of the size 400 x 800. + static const Rect kPhysicalDisplay{10, 20, 70, 160}; - // triple-tap - processKey(mapper, BTN_TOOL_DOUBLETAP, 0); - processKey(mapper, BTN_TOOL_TRIPLETAP, 1); - processSync(mapper); - ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs)); - ASSERT_EQ(AMOTION_EVENT_ACTION_MOVE, motionArgs.action); - ASSERT_EQ(AMOTION_EVENT_TOOL_TYPE_FINGER, motionArgs.pointerProperties[0].toolType); + for (auto orientation : {ui::ROTATION_0, ui::ROTATION_90, ui::ROTATION_180, ui::ROTATION_270}) { + configurePhysicalDisplay(orientation, kPhysicalDisplay); - // quad-tap - processKey(mapper, BTN_TOOL_TRIPLETAP, 0); - processKey(mapper, BTN_TOOL_QUADTAP, 1); - processSync(mapper); - ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs)); - ASSERT_EQ(AMOTION_EVENT_ACTION_MOVE, motionArgs.action); - ASSERT_EQ(AMOTION_EVENT_TOOL_TYPE_FINGER, motionArgs.pointerProperties[0].toolType); + // Touches that start outside the physical display should be ignored until it enters the + // physical display bounds, at which point it should generate a down event. Start a touch at + // the point (5, 100), which is outside the physical display bounds. + static const Point kOutsidePoint{5, 100}; + processDown(mapper, toRawX(kOutsidePoint.x), toRawY(kOutsidePoint.y)); + processSync(mapper); + ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasNotCalled()); - // finger - processKey(mapper, BTN_TOOL_QUADTAP, 0); - processKey(mapper, BTN_TOOL_FINGER, 1); - processSync(mapper); - ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs)); - ASSERT_EQ(AMOTION_EVENT_ACTION_MOVE, motionArgs.action); - ASSERT_EQ(AMOTION_EVENT_TOOL_TYPE_FINGER, motionArgs.pointerProperties[0].toolType); + // Move the touch into the physical display area. This should generate a pointer down. + processMove(mapper, toRawX(11), toRawY(21)); + processSync(mapper); + ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs)); + ASSERT_EQ(AMOTION_EVENT_ACTION_DOWN, motionArgs.action); + ASSERT_EQ(size_t(1), motionArgs.pointerCount); + ASSERT_NO_FATAL_FAILURE( + assertPointerCoords(motionArgs.pointerCoords[0], 11, 21, 1, 0, 0, 0, 0, 0, 0, 0)); - // stylus trumps finger - processKey(mapper, BTN_TOOL_PEN, 1); - processSync(mapper); - ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs)); - ASSERT_EQ(AMOTION_EVENT_ACTION_MOVE, motionArgs.action); - ASSERT_EQ(AMOTION_EVENT_TOOL_TYPE_STYLUS, motionArgs.pointerProperties[0].toolType); + // Move the touch inside the physical display area. This should generate a pointer move. + processMove(mapper, toRawX(69), toRawY(159)); + processSync(mapper); + assertReceivedMove({69, 159}); - // eraser trumps stylus - processKey(mapper, BTN_TOOL_RUBBER, 1); - processSync(mapper); - ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs)); - ASSERT_EQ(AMOTION_EVENT_ACTION_MOVE, motionArgs.action); - ASSERT_EQ(AMOTION_EVENT_TOOL_TYPE_ERASER, motionArgs.pointerProperties[0].toolType); + // Move outside the physical display area. Since the pointer is already down, this should + // now continue generating events. + processMove(mapper, toRawX(kOutsidePoint.x), toRawY(kOutsidePoint.y)); + processSync(mapper); + assertReceivedMove(kOutsidePoint); - // mouse trumps eraser - processKey(mapper, BTN_TOOL_MOUSE, 1); - processSync(mapper); - ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs)); - ASSERT_EQ(AMOTION_EVENT_ACTION_MOVE, motionArgs.action); - ASSERT_EQ(AMOTION_EVENT_TOOL_TYPE_MOUSE, motionArgs.pointerProperties[0].toolType); + // Release. This should generate a pointer up. + processUp(mapper); + processSync(mapper); + ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs)); + ASSERT_EQ(AMOTION_EVENT_ACTION_UP, motionArgs.action); + ASSERT_NO_FATAL_FAILURE(assertPointerCoords(motionArgs.pointerCoords[0], kOutsidePoint.x, + kOutsidePoint.y, 1, 0, 0, 0, 0, 0, 0, 0)); - // back to default tool type - processKey(mapper, BTN_TOOL_MOUSE, 0); - processKey(mapper, BTN_TOOL_RUBBER, 0); - processKey(mapper, BTN_TOOL_PEN, 0); - processKey(mapper, BTN_TOOL_FINGER, 0); - processSync(mapper); - ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs)); - ASSERT_EQ(AMOTION_EVENT_ACTION_MOVE, motionArgs.action); - ASSERT_EQ(AMOTION_EVENT_TOOL_TYPE_FINGER, motionArgs.pointerProperties[0].toolType); + // Ensure no more events were generated. + ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyKeyWasNotCalled()); + ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasNotCalled()); + } } -TEST_F(SingleTouchInputMapperTest, Process_WhenBtnTouchPresent_HoversIfItsValueIsZero) { - addConfigurationProperty("touch.deviceType", "touchScreen"); - prepareDisplay(DISPLAY_ORIENTATION_0); - prepareButtons(); - prepareAxes(POSITION); - mFakeEventHub->addKey(EVENTHUB_ID, BTN_TOOL_FINGER, 0, AKEYCODE_UNKNOWN, 0); - SingleTouchInputMapper& mapper = addMapperAndConfigure(); - - NotifyMotionArgs motionArgs; - - // initially hovering because BTN_TOUCH not sent yet, pressure defaults to 0 - processKey(mapper, BTN_TOOL_FINGER, 1); - processMove(mapper, 100, 200); - processSync(mapper); - ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs)); - ASSERT_EQ(AMOTION_EVENT_ACTION_HOVER_ENTER, motionArgs.action); - ASSERT_NO_FATAL_FAILURE(assertPointerCoords(motionArgs.pointerCoords[0], - toDisplayX(100), toDisplayY(200), 0, 0, 0, 0, 0, 0, 0, 0)); +// --- TouchscreenPrecisionTests --- - ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs)); - ASSERT_EQ(AMOTION_EVENT_ACTION_HOVER_MOVE, motionArgs.action); - ASSERT_NO_FATAL_FAILURE(assertPointerCoords(motionArgs.pointerCoords[0], - toDisplayX(100), toDisplayY(200), 0, 0, 0, 0, 0, 0, 0, 0)); +// This test suite is used to ensure that touchscreen devices are scaled and configured correctly +// in various orientations and with different display rotations. We configure the touchscreen to +// have a higher resolution than that of the display by an integer scale factor in each axis so that +// we can enforce that coordinates match precisely as expected. +class TouchscreenPrecisionTestsFixture : public TouchDisplayProjectionTest, + public ::testing::WithParamInterface { +public: + void SetUp() override { + SingleTouchInputMapperTest::SetUp(); - // move a little - processMove(mapper, 150, 250); - processSync(mapper); - ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs)); - ASSERT_EQ(AMOTION_EVENT_ACTION_HOVER_MOVE, motionArgs.action); - ASSERT_NO_FATAL_FAILURE(assertPointerCoords(motionArgs.pointerCoords[0], - toDisplayX(150), toDisplayY(250), 0, 0, 0, 0, 0, 0, 0, 0)); + // Prepare the raw axes to have twice the resolution of the display in the X axis and + // four times the resolution of the display in the Y axis. + prepareButtons(); + mFakeEventHub->addAbsoluteAxis(EVENTHUB_ID, ABS_X, PRECISION_RAW_X_MIN, PRECISION_RAW_X_MAX, + PRECISION_RAW_X_FLAT, PRECISION_RAW_X_FUZZ, + PRECISION_RAW_X_RES); + mFakeEventHub->addAbsoluteAxis(EVENTHUB_ID, ABS_Y, PRECISION_RAW_Y_MIN, PRECISION_RAW_Y_MAX, + PRECISION_RAW_Y_FLAT, PRECISION_RAW_Y_FUZZ, + PRECISION_RAW_Y_RES); + } - // down when BTN_TOUCH is pressed, pressure defaults to 1 - processKey(mapper, BTN_TOUCH, 1); - processSync(mapper); - ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs)); - ASSERT_EQ(AMOTION_EVENT_ACTION_HOVER_EXIT, motionArgs.action); - ASSERT_NO_FATAL_FAILURE(assertPointerCoords(motionArgs.pointerCoords[0], - toDisplayX(150), toDisplayY(250), 0, 0, 0, 0, 0, 0, 0, 0)); + static const int32_t PRECISION_RAW_X_MIN = TouchInputMapperTest::RAW_X_MIN; + static const int32_t PRECISION_RAW_X_MAX = PRECISION_RAW_X_MIN + DISPLAY_WIDTH * 2 - 1; + static const int32_t PRECISION_RAW_Y_MIN = TouchInputMapperTest::RAW_Y_MIN; + static const int32_t PRECISION_RAW_Y_MAX = PRECISION_RAW_Y_MIN + DISPLAY_HEIGHT * 4 - 1; - ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs)); - ASSERT_EQ(AMOTION_EVENT_ACTION_DOWN, motionArgs.action); - ASSERT_NO_FATAL_FAILURE(assertPointerCoords(motionArgs.pointerCoords[0], - toDisplayX(150), toDisplayY(250), 1, 0, 0, 0, 0, 0, 0, 0)); + static const int32_t PRECISION_RAW_X_RES = 50; // units per millimeter + static const int32_t PRECISION_RAW_Y_RES = 100; // units per millimeter - // up when BTN_TOUCH is released, hover restored - processKey(mapper, BTN_TOUCH, 0); - processSync(mapper); - ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs)); - ASSERT_EQ(AMOTION_EVENT_ACTION_UP, motionArgs.action); - ASSERT_NO_FATAL_FAILURE(assertPointerCoords(motionArgs.pointerCoords[0], - toDisplayX(150), toDisplayY(250), 1, 0, 0, 0, 0, 0, 0, 0)); + static const int32_t PRECISION_RAW_X_FLAT = 16; + static const int32_t PRECISION_RAW_Y_FLAT = 32; - ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs)); - ASSERT_EQ(AMOTION_EVENT_ACTION_HOVER_ENTER, motionArgs.action); - ASSERT_NO_FATAL_FAILURE(assertPointerCoords(motionArgs.pointerCoords[0], - toDisplayX(150), toDisplayY(250), 0, 0, 0, 0, 0, 0, 0, 0)); + static const int32_t PRECISION_RAW_X_FUZZ = 4; + static const int32_t PRECISION_RAW_Y_FUZZ = 8; - ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs)); - ASSERT_EQ(AMOTION_EVENT_ACTION_HOVER_MOVE, motionArgs.action); - ASSERT_NO_FATAL_FAILURE(assertPointerCoords(motionArgs.pointerCoords[0], - toDisplayX(150), toDisplayY(250), 0, 0, 0, 0, 0, 0, 0, 0)); + static const std::array kRawCorners; +}; - // exit hover when pointer goes away - processKey(mapper, BTN_TOOL_FINGER, 0); - processSync(mapper); - ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs)); - ASSERT_EQ(AMOTION_EVENT_ACTION_HOVER_EXIT, motionArgs.action); - ASSERT_NO_FATAL_FAILURE(assertPointerCoords(motionArgs.pointerCoords[0], - toDisplayX(150), toDisplayY(250), 0, 0, 0, 0, 0, 0, 0, 0)); +const std::array TouchscreenPrecisionTestsFixture::kRawCorners = {{ + {PRECISION_RAW_X_MIN, PRECISION_RAW_Y_MIN}, // left-top + {PRECISION_RAW_X_MAX, PRECISION_RAW_Y_MIN}, // right-top + {PRECISION_RAW_X_MAX, PRECISION_RAW_Y_MAX}, // right-bottom + {PRECISION_RAW_X_MIN, PRECISION_RAW_Y_MAX}, // left-bottom +}}; + +// Tests for how the touchscreen is oriented relative to the natural orientation of the display. +// For example, if a touchscreen is configured with an orientation of 90 degrees, it is a portrait +// touchscreen panel that is used on a device whose natural display orientation is in landscape. +TEST_P(TouchscreenPrecisionTestsFixture, OrientationPrecision) { + enum class Orientation { + ORIENTATION_0 = ui::toRotationInt(ui::ROTATION_0), + ORIENTATION_90 = ui::toRotationInt(ui::ROTATION_90), + ORIENTATION_180 = ui::toRotationInt(ui::ROTATION_180), + ORIENTATION_270 = ui::toRotationInt(ui::ROTATION_270), + ftl_last = ORIENTATION_270, + }; + using Orientation::ORIENTATION_0, Orientation::ORIENTATION_90, Orientation::ORIENTATION_180, + Orientation::ORIENTATION_270; + static const std::map /*mappedCorners*/> kMappedCorners = { + {ORIENTATION_0, {{{0, 0}, {479.5, 0}, {479.5, 799.75}, {0, 799.75}}}}, + {ORIENTATION_90, {{{0, 479.5}, {0, 0}, {799.75, 0}, {799.75, 479.5}}}}, + {ORIENTATION_180, {{{479.5, 799.75}, {0, 799.75}, {0, 0}, {479.5, 0}}}}, + {ORIENTATION_270, {{{799.75, 0}, {799.75, 479.5}, {0, 479.5}, {0, 0}}}}, + }; + + const auto touchscreenOrientation = static_cast(ui::toRotationInt(GetParam())); + + // Configure the touchscreen as being installed in the one of the four different orientations + // relative to the display. + addConfigurationProperty("touch.deviceType", "touchScreen"); + addConfigurationProperty("touch.orientation", ftl::enum_string(touchscreenOrientation).c_str()); + prepareDisplay(ui::ROTATION_0); + + SingleTouchInputMapper& mapper = constructAndAddMapper(); + + // If the touchscreen is installed in a rotated orientation relative to the display (i.e. in + // orientations of either 90 or 270) this means the display's natural resolution will be + // flipped. + const bool displayRotated = + touchscreenOrientation == ORIENTATION_90 || touchscreenOrientation == ORIENTATION_270; + const int32_t width = displayRotated ? DISPLAY_HEIGHT : DISPLAY_WIDTH; + const int32_t height = displayRotated ? DISPLAY_WIDTH : DISPLAY_HEIGHT; + const Rect physicalFrame{0, 0, width, height}; + configurePhysicalDisplay(ui::ROTATION_0, physicalFrame, width, height); + + const auto& expectedPoints = kMappedCorners.at(touchscreenOrientation); + const float expectedPrecisionX = displayRotated ? 4 : 2; + const float expectedPrecisionY = displayRotated ? 2 : 4; + + // Test all four corners. + for (int i = 0; i < 4; i++) { + const auto& raw = kRawCorners[i]; + processDown(mapper, raw.x, raw.y); + processSync(mapper); + const auto& expected = expectedPoints[i]; + ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled( + AllOf(WithMotionAction(AMOTION_EVENT_ACTION_DOWN), + WithCoords(expected.x, expected.y), + WithPrecision(expectedPrecisionX, expectedPrecisionY)))) + << "Failed to process raw point (" << raw.x << ", " << raw.y << ") " + << "with touchscreen orientation " + << ftl::enum_string(touchscreenOrientation).c_str() << ", expected point (" + << expected.x << ", " << expected.y << ")."; + processUp(mapper); + processSync(mapper); + ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled( + AllOf(WithMotionAction(AMOTION_EVENT_ACTION_UP), + WithCoords(expected.x, expected.y)))); + } } -TEST_F(SingleTouchInputMapperTest, Process_WhenAbsPressureIsPresent_HoversIfItsValueIsZero) { +TEST_P(TouchscreenPrecisionTestsFixture, RotationPrecisionWhenOrientationAware) { + static const std::map /*mappedCorners*/> + kMappedCorners = { + {ui::ROTATION_0, {{{0, 0}, {479.5, 0}, {479.5, 799.75}, {0, 799.75}}}}, + {ui::ROTATION_90, {{{0.5, 0}, {480, 0}, {480, 799.75}, {0.5, 799.75}}}}, + {ui::ROTATION_180, {{{0.5, 0.25}, {480, 0.25}, {480, 800}, {0.5, 800}}}}, + {ui::ROTATION_270, {{{0, 0.25}, {479.5, 0.25}, {479.5, 800}, {0, 800}}}}, + }; + + const ui::Rotation displayRotation = GetParam(); + addConfigurationProperty("touch.deviceType", "touchScreen"); - prepareDisplay(DISPLAY_ORIENTATION_0); - prepareButtons(); - prepareAxes(POSITION | PRESSURE); - SingleTouchInputMapper& mapper = addMapperAndConfigure(); + prepareDisplay(displayRotation); - NotifyMotionArgs motionArgs; + SingleTouchInputMapper& mapper = constructAndAddMapper(); - // initially hovering because pressure is 0 - processDown(mapper, 100, 200); - processPressure(mapper, 0); - processSync(mapper); - ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs)); - ASSERT_EQ(AMOTION_EVENT_ACTION_HOVER_ENTER, motionArgs.action); - ASSERT_NO_FATAL_FAILURE(assertPointerCoords(motionArgs.pointerCoords[0], - toDisplayX(100), toDisplayY(200), 0, 0, 0, 0, 0, 0, 0, 0)); + const auto& expectedPoints = kMappedCorners.at(displayRotation); - ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs)); - ASSERT_EQ(AMOTION_EVENT_ACTION_HOVER_MOVE, motionArgs.action); - ASSERT_NO_FATAL_FAILURE(assertPointerCoords(motionArgs.pointerCoords[0], - toDisplayX(100), toDisplayY(200), 0, 0, 0, 0, 0, 0, 0, 0)); + // Test all four corners. + for (int i = 0; i < 4; i++) { + const auto& expected = expectedPoints[i]; + const auto& raw = kRawCorners[i]; + processDown(mapper, raw.x, raw.y); + processSync(mapper); + ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled( + AllOf(WithMotionAction(AMOTION_EVENT_ACTION_DOWN), + WithCoords(expected.x, expected.y), WithPrecision(2, 4)))) + << "Failed to process raw point (" << raw.x << ", " << raw.y << ") " + << "with display rotation " << ui::toCString(displayRotation) + << ", expected point (" << expected.x << ", " << expected.y << ")."; + processUp(mapper); + processSync(mapper); + ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled( + AllOf(WithMotionAction(AMOTION_EVENT_ACTION_UP), + WithCoords(expected.x, expected.y)))); + } +} - // move a little - processMove(mapper, 150, 250); - processSync(mapper); - ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs)); - ASSERT_EQ(AMOTION_EVENT_ACTION_HOVER_MOVE, motionArgs.action); - ASSERT_NO_FATAL_FAILURE(assertPointerCoords(motionArgs.pointerCoords[0], - toDisplayX(150), toDisplayY(250), 0, 0, 0, 0, 0, 0, 0, 0)); +TEST_P(TouchscreenPrecisionTestsFixture, RotationPrecisionOrientationAwareInOri270) { + static const std::map /*mappedCorners*/> + kMappedCorners = { + {ui::ROTATION_0, {{{799.75, 0}, {799.75, 479.5}, {0, 479.5}, {0, 0}}}}, + {ui::ROTATION_90, {{{800, 0}, {800, 479.5}, {0.25, 479.5}, {0.25, 0}}}}, + {ui::ROTATION_180, {{{800, 0.5}, {800, 480}, {0.25, 480}, {0.25, 0.5}}}}, + {ui::ROTATION_270, {{{799.75, 0.5}, {799.75, 480}, {0, 480}, {0, 0.5}}}}, + }; - // down when pressure is non-zero - processPressure(mapper, RAW_PRESSURE_MAX); - processSync(mapper); - ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs)); - ASSERT_EQ(AMOTION_EVENT_ACTION_HOVER_EXIT, motionArgs.action); - ASSERT_NO_FATAL_FAILURE(assertPointerCoords(motionArgs.pointerCoords[0], - toDisplayX(150), toDisplayY(250), 0, 0, 0, 0, 0, 0, 0, 0)); + const ui::Rotation displayRotation = GetParam(); - ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs)); - ASSERT_EQ(AMOTION_EVENT_ACTION_DOWN, motionArgs.action); - ASSERT_NO_FATAL_FAILURE(assertPointerCoords(motionArgs.pointerCoords[0], - toDisplayX(150), toDisplayY(250), 1, 0, 0, 0, 0, 0, 0, 0)); + addConfigurationProperty("touch.deviceType", "touchScreen"); + addConfigurationProperty("touch.orientation", "ORIENTATION_270"); - // up when pressure becomes 0, hover restored - processPressure(mapper, 0); - processSync(mapper); - ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs)); - ASSERT_EQ(AMOTION_EVENT_ACTION_UP, motionArgs.action); - ASSERT_NO_FATAL_FAILURE(assertPointerCoords(motionArgs.pointerCoords[0], - toDisplayX(150), toDisplayY(250), 1, 0, 0, 0, 0, 0, 0, 0)); + SingleTouchInputMapper& mapper = constructAndAddMapper(); - ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs)); - ASSERT_EQ(AMOTION_EVENT_ACTION_HOVER_ENTER, motionArgs.action); - ASSERT_NO_FATAL_FAILURE(assertPointerCoords(motionArgs.pointerCoords[0], - toDisplayX(150), toDisplayY(250), 0, 0, 0, 0, 0, 0, 0, 0)); + // Ori 270, so width and height swapped + const Rect physicalFrame{0, 0, DISPLAY_HEIGHT, DISPLAY_WIDTH}; + prepareDisplay(displayRotation); + configurePhysicalDisplay(displayRotation, physicalFrame, DISPLAY_HEIGHT, DISPLAY_WIDTH); - ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs)); - ASSERT_EQ(AMOTION_EVENT_ACTION_HOVER_MOVE, motionArgs.action); - ASSERT_NO_FATAL_FAILURE(assertPointerCoords(motionArgs.pointerCoords[0], - toDisplayX(150), toDisplayY(250), 0, 0, 0, 0, 0, 0, 0, 0)); + const auto& expectedPoints = kMappedCorners.at(displayRotation); - // exit hover when pointer goes away - processUp(mapper); - processSync(mapper); - ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs)); - ASSERT_EQ(AMOTION_EVENT_ACTION_HOVER_EXIT, motionArgs.action); - ASSERT_NO_FATAL_FAILURE(assertPointerCoords(motionArgs.pointerCoords[0], - toDisplayX(150), toDisplayY(250), 0, 0, 0, 0, 0, 0, 0, 0)); + // Test all four corners. + for (int i = 0; i < 4; i++) { + const auto& expected = expectedPoints[i]; + const auto& raw = kRawCorners[i]; + processDown(mapper, raw.x, raw.y); + processSync(mapper); + ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled( + AllOf(WithMotionAction(AMOTION_EVENT_ACTION_DOWN), + WithCoords(expected.x, expected.y), WithPrecision(4, 2)))) + << "Failed to process raw point (" << raw.x << ", " << raw.y << ") " + << "with display rotation " << ui::toCString(displayRotation) + << ", expected point (" << expected.x << ", " << expected.y << ")."; + processUp(mapper); + processSync(mapper); + ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled( + AllOf(WithMotionAction(AMOTION_EVENT_ACTION_UP), + WithCoords(expected.x, expected.y)))); + } } -TEST_F(SingleTouchInputMapperTest, Reset_RecreatesTouchState) { +TEST_P(TouchscreenPrecisionTestsFixture, MotionRangesAreOrientedInRotatedDisplay) { + const ui::Rotation displayRotation = GetParam(); + addConfigurationProperty("touch.deviceType", "touchScreen"); - prepareDisplay(DISPLAY_ORIENTATION_0); - prepareButtons(); - prepareAxes(POSITION | PRESSURE); - SingleTouchInputMapper& mapper = addMapperAndConfigure(); - NotifyMotionArgs motionArgs; + prepareDisplay(displayRotation); - // Set the initial state for the touch pointer. - mFakeEventHub->setAbsoluteAxisValue(EVENTHUB_ID, ABS_X, 100); - mFakeEventHub->setAbsoluteAxisValue(EVENTHUB_ID, ABS_Y, 200); - mFakeEventHub->setAbsoluteAxisValue(EVENTHUB_ID, ABS_PRESSURE, RAW_PRESSURE_MAX); - mFakeEventHub->setScanCodeState(EVENTHUB_ID, BTN_TOUCH, 1); + __attribute__((unused)) SingleTouchInputMapper& mapper = + constructAndAddMapper(); - // Reset the mapper. When the mapper is reset, we expect it to attempt to recreate the touch - // state by reading the current axis values. - mapper.reset(ARBITRARY_TIME); + const InputDeviceInfo deviceInfo = mDevice->getDeviceInfo(); + // MotionRanges use display pixels as their units + const auto* xRange = deviceInfo.getMotionRange(AMOTION_EVENT_AXIS_X, AINPUT_SOURCE_TOUCHSCREEN); + const auto* yRange = deviceInfo.getMotionRange(AMOTION_EVENT_AXIS_Y, AINPUT_SOURCE_TOUCHSCREEN); - // Send a sync to simulate an empty touch frame where nothing changes. The mapper should use - // the recreated touch state to generate a down event. - processSync(mapper); - ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs)); - ASSERT_EQ(AMOTION_EVENT_ACTION_DOWN, motionArgs.action); + // The MotionRanges should be oriented in the rotated display's coordinate space + const bool displayRotated = + displayRotation == ui::ROTATION_90 || displayRotation == ui::ROTATION_270; - ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasNotCalled()); + constexpr float MAX_X = 479.5; + constexpr float MAX_Y = 799.75; + EXPECT_EQ(xRange->min, 0.f); + EXPECT_EQ(yRange->min, 0.f); + EXPECT_EQ(xRange->max, displayRotated ? MAX_Y : MAX_X); + EXPECT_EQ(yRange->max, displayRotated ? MAX_X : MAX_Y); + + EXPECT_EQ(xRange->flat, 8.f); + EXPECT_EQ(yRange->flat, 8.f); + + EXPECT_EQ(xRange->fuzz, 2.f); + EXPECT_EQ(yRange->fuzz, 2.f); + + EXPECT_EQ(xRange->resolution, 25.f); // pixels per millimeter + EXPECT_EQ(yRange->resolution, 25.f); // pixels per millimeter } -TEST_F(SingleTouchInputMapperTest, WhenViewportActiveStatusChanged_PointerGestureIsReset) { - std::shared_ptr fakePointerController = - std::make_shared(); - fakePointerController->setBounds(0, 0, DISPLAY_WIDTH - 1, DISPLAY_HEIGHT - 1); - fakePointerController->setPosition(100, 200); - fakePointerController->setButtonState(0); - mFakePolicy->setPointerController(fakePointerController); +// Run the precision tests for all rotations. +INSTANTIATE_TEST_SUITE_P(TouchscreenPrecisionTests, TouchscreenPrecisionTestsFixture, + ::testing::Values(ui::ROTATION_0, ui::ROTATION_90, ui::ROTATION_180, + ui::ROTATION_270), + [](const testing::TestParamInfo& testParamInfo) { + return ftl::enum_string(testParamInfo.param); + }); - addConfigurationProperty("touch.deviceType", "pointer"); - prepareDisplay(DISPLAY_ORIENTATION_0); - prepareButtons(); - mFakeEventHub->addKey(EVENTHUB_ID, BTN_TOOL_PEN, 0, AKEYCODE_UNKNOWN, 0); - prepareAxes(POSITION); - SingleTouchInputMapper& mapper = addMapperAndConfigure(); - ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyDeviceResetWasCalled()); +// --- ExternalStylusFusionTest --- - // Start a stylus gesture. - processKey(mapper, BTN_TOOL_PEN, 1); - processDown(mapper, 100, 200); - processSync(mapper); - ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled( - AllOf(WithAction(AMOTION_EVENT_ACTION_DOWN), - WithSource(AINPUT_SOURCE_MOUSE | AINPUT_SOURCE_STYLUS), - WithToolType(AMOTION_EVENT_TOOL_TYPE_STYLUS)))); - // TODO(b/257078296): Pointer mode generates extra event. - ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled( - AllOf(WithAction(AMOTION_EVENT_ACTION_MOVE), - WithSource(AINPUT_SOURCE_MOUSE | AINPUT_SOURCE_STYLUS), - WithToolType(AMOTION_EVENT_TOOL_TYPE_STYLUS)))); - ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasNotCalled()); +class ExternalStylusFusionTest : public SingleTouchInputMapperTest { +public: + SingleTouchInputMapper& initializeInputMapperWithExternalStylus() { + addConfigurationProperty("touch.deviceType", "touchScreen"); + prepareDisplay(ui::ROTATION_0); + prepareButtons(); + prepareAxes(POSITION); + auto& mapper = constructAndAddMapper(); + + mStylusState.when = ARBITRARY_TIME; + mStylusState.pressure = 0.f; + mStylusState.toolType = ToolType::STYLUS; + mReader->getContext()->setExternalStylusDevices({mExternalStylusDeviceInfo}); + configureDevice(InputReaderConfiguration::Change::EXTERNAL_STYLUS_PRESENCE); + processExternalStylusState(mapper); + return mapper; + } - // Make the viewport inactive. This will put the device in disabled mode, and the ongoing stylus - // gesture should be disabled. - auto viewport = mFakePolicy->getDisplayViewportByType(ViewportType::INTERNAL); - viewport->isActive = false; - mFakePolicy->updateViewport(*viewport); - configureDevice(InputReaderConfiguration::CHANGE_DISPLAY_INFO); - ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled( - AllOf(WithAction(AMOTION_EVENT_ACTION_CANCEL), - WithSource(AINPUT_SOURCE_MOUSE | AINPUT_SOURCE_STYLUS), - WithToolType(AMOTION_EVENT_TOOL_TYPE_STYLUS)))); - // TODO(b/257078296): Pointer mode generates extra event. - ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled( - AllOf(WithAction(AMOTION_EVENT_ACTION_CANCEL), - WithSource(AINPUT_SOURCE_MOUSE | AINPUT_SOURCE_STYLUS), - WithToolType(AMOTION_EVENT_TOOL_TYPE_STYLUS)))); - ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasNotCalled()); -} + std::list processExternalStylusState(InputMapper& mapper) { + std::list generatedArgs = mapper.updateExternalStylusState(mStylusState); + for (const NotifyArgs& args : generatedArgs) { + mFakeListener->notify(args); + } + // Loop the reader to flush the input listener queue. + mReader->loopOnce(); + return generatedArgs; + } -TEST_F(SingleTouchInputMapperTest, - Process_WhenViewportDisplayIdChanged_TouchIsCanceledAndDeviceIsReset) { - addConfigurationProperty("touch.deviceType", "touchScreen"); - prepareDisplay(DISPLAY_ORIENTATION_0); - prepareButtons(); - prepareAxes(POSITION); - SingleTouchInputMapper& mapper = addMapperAndConfigure(); - NotifyMotionArgs motionArgs; +protected: + StylusState mStylusState{}; + static constexpr uint32_t EXPECTED_SOURCE = + AINPUT_SOURCE_TOUCHSCREEN | AINPUT_SOURCE_BLUETOOTH_STYLUS; - // Down. - int32_t x = 100; - int32_t y = 200; - processDown(mapper, x, y); - processSync(mapper); + void testStartFusedStylusGesture(SingleTouchInputMapper& mapper) { + auto toolTypeSource = + AllOf(WithSource(EXPECTED_SOURCE), WithToolType(ToolType::STYLUS)); - // We should receive a down event - ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs)); - ASSERT_EQ(AMOTION_EVENT_ACTION_DOWN, motionArgs.action); + // The first pointer is withheld. + processDown(mapper, 100, 200); + processSync(mapper); + ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasNotCalled()); + ASSERT_NO_FATAL_FAILURE(mReader->getContext()->assertTimeoutWasRequested( + ARBITRARY_TIME + EXTERNAL_STYLUS_DATA_TIMEOUT)); + + // The external stylus reports pressure. The withheld finger pointer is released as a + // stylus. + mStylusState.pressure = 1.f; + processExternalStylusState(mapper); + ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled( + AllOf(toolTypeSource, WithMotionAction(AMOTION_EVENT_ACTION_DOWN)))); + ASSERT_NO_FATAL_FAILURE(mReader->getContext()->assertTimeoutWasNotRequested()); + + // Subsequent pointer events are not withheld. + processMove(mapper, 101, 201); + processSync(mapper); + ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled( + AllOf(toolTypeSource, WithMotionAction(AMOTION_EVENT_ACTION_MOVE)))); - // Change display id - clearViewports(); - prepareSecondaryDisplay(ViewportType::INTERNAL); + ASSERT_NO_FATAL_FAILURE(mReader->getContext()->assertTimeoutWasNotRequested()); + ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasNotCalled()); + } - // We should receive a cancel event - ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs)); - ASSERT_EQ(AMOTION_EVENT_ACTION_CANCEL, motionArgs.action); - // Then receive reset called - ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyDeviceResetWasCalled()); -} + void testSuccessfulFusionGesture(SingleTouchInputMapper& mapper) { + ASSERT_NO_FATAL_FAILURE(testStartFusedStylusGesture(mapper)); -TEST_F(SingleTouchInputMapperTest, - Process_WhenViewportActiveStatusChanged_TouchIsCanceledAndDeviceIsReset) { - addConfigurationProperty("touch.deviceType", "touchScreen"); - prepareDisplay(DISPLAY_ORIENTATION_0); - prepareButtons(); - prepareAxes(POSITION); - SingleTouchInputMapper& mapper = addMapperAndConfigure(); - ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyDeviceResetWasCalled()); - NotifyMotionArgs motionArgs; + // Releasing the touch pointer ends the gesture. + processUp(mapper); + processSync(mapper); + ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled( + AllOf(WithMotionAction(AMOTION_EVENT_ACTION_UP), WithSource(EXPECTED_SOURCE), + WithToolType(ToolType::STYLUS)))); - // Start a new gesture. - processDown(mapper, 100, 200); - processSync(mapper); - ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs)); - ASSERT_EQ(AMOTION_EVENT_ACTION_DOWN, motionArgs.action); + mStylusState.pressure = 0.f; + processExternalStylusState(mapper); + ASSERT_NO_FATAL_FAILURE(mReader->getContext()->assertTimeoutWasNotRequested()); + ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasNotCalled()); + } - // Make the viewport inactive. This will put the device in disabled mode. - auto viewport = mFakePolicy->getDisplayViewportByType(ViewportType::INTERNAL); - viewport->isActive = false; - mFakePolicy->updateViewport(*viewport); - configureDevice(InputReaderConfiguration::CHANGE_DISPLAY_INFO); + void testUnsuccessfulFusionGesture(SingleTouchInputMapper& mapper) { + auto toolTypeSource = + AllOf(WithSource(EXPECTED_SOURCE), WithToolType(ToolType::FINGER)); - // We should receive a cancel event for the ongoing gesture. - ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs)); - ASSERT_EQ(AMOTION_EVENT_ACTION_CANCEL, motionArgs.action); - // Then we should be notified that the device was reset. - ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyDeviceResetWasCalled()); + // The first pointer is withheld when an external stylus is connected, + // and a timeout is requested. + processDown(mapper, 100, 200); + processSync(mapper); + ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasNotCalled()); + ASSERT_NO_FATAL_FAILURE(mReader->getContext()->assertTimeoutWasRequested( + ARBITRARY_TIME + EXTERNAL_STYLUS_DATA_TIMEOUT)); - // No events are generated while the viewport is inactive. - processMove(mapper, 101, 201); - processSync(mapper); - processUp(mapper); - processSync(mapper); - ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasNotCalled()); + // If the timeout expires early, it is requested again. + handleTimeout(mapper, ARBITRARY_TIME + 1); + ASSERT_NO_FATAL_FAILURE(mReader->getContext()->assertTimeoutWasRequested( + ARBITRARY_TIME + EXTERNAL_STYLUS_DATA_TIMEOUT)); - // Start a new gesture while the viewport is still inactive. - processDown(mapper, 300, 400); - mFakeEventHub->setAbsoluteAxisValue(EVENTHUB_ID, ABS_X, 300); - mFakeEventHub->setAbsoluteAxisValue(EVENTHUB_ID, ABS_Y, 400); - mFakeEventHub->setScanCodeState(EVENTHUB_ID, BTN_TOUCH, 1); - processSync(mapper); + // When the timeout expires, the withheld touch is released as a finger pointer. + handleTimeout(mapper, ARBITRARY_TIME + EXTERNAL_STYLUS_DATA_TIMEOUT); + ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled( + AllOf(toolTypeSource, WithMotionAction(AMOTION_EVENT_ACTION_DOWN)))); - // Make the viewport active again. The device should resume processing events. - viewport->isActive = true; - mFakePolicy->updateViewport(*viewport); - configureDevice(InputReaderConfiguration::CHANGE_DISPLAY_INFO); + // Subsequent pointer events are not withheld. + processMove(mapper, 101, 201); + processSync(mapper); + ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled( + AllOf(toolTypeSource, WithMotionAction(AMOTION_EVENT_ACTION_MOVE)))); + processUp(mapper); + processSync(mapper); + ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled( + AllOf(toolTypeSource, WithMotionAction(AMOTION_EVENT_ACTION_UP)))); - // The device is reset because it changes back to direct mode, without generating any events. - ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyDeviceResetWasCalled()); - ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasNotCalled()); + ASSERT_NO_FATAL_FAILURE(mReader->getContext()->assertTimeoutWasNotRequested()); + ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasNotCalled()); + } - // In the next sync, the touch state that was recreated when the device was reset is reported. - processSync(mapper); - ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs)); - ASSERT_EQ(AMOTION_EVENT_ACTION_DOWN, motionArgs.action); +private: + InputDeviceInfo mExternalStylusDeviceInfo{}; +}; - // No more events. - ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasNotCalled()); - ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyDeviceResetWasNotCalled()); +TEST_F(ExternalStylusFusionTest, UsesBluetoothStylusSource) { + SingleTouchInputMapper& mapper = initializeInputMapperWithExternalStylus(); + ASSERT_EQ(EXPECTED_SOURCE, mapper.getSources()); } -// --- TouchDisplayProjectionTest --- +TEST_F(ExternalStylusFusionTest, UnsuccessfulFusion) { + SingleTouchInputMapper& mapper = initializeInputMapperWithExternalStylus(); + ASSERT_NO_FATAL_FAILURE(testUnsuccessfulFusionGesture(mapper)); +} -class TouchDisplayProjectionTest : public SingleTouchInputMapperTest { -public: - // The values inside DisplayViewport are expected to be pre-rotated. This updates the current - // DisplayViewport to pre-rotate the values. The viewport's physical display will be set to the - // rotated equivalent of the given un-rotated physical display bounds. - void configurePhysicalDisplay(int32_t orientation, Rect naturalPhysicalDisplay) { - uint32_t inverseRotationFlags; - auto width = DISPLAY_WIDTH; - auto height = DISPLAY_HEIGHT; - switch (orientation) { - case DISPLAY_ORIENTATION_90: - inverseRotationFlags = ui::Transform::ROT_270; - std::swap(width, height); - break; - case DISPLAY_ORIENTATION_180: - inverseRotationFlags = ui::Transform::ROT_180; - break; - case DISPLAY_ORIENTATION_270: - inverseRotationFlags = ui::Transform::ROT_90; - std::swap(width, height); - break; - case DISPLAY_ORIENTATION_0: - inverseRotationFlags = ui::Transform::ROT_0; - break; - default: - FAIL() << "Invalid orientation: " << orientation; - } +TEST_F(ExternalStylusFusionTest, SuccessfulFusion_TouchFirst) { + SingleTouchInputMapper& mapper = initializeInputMapperWithExternalStylus(); + ASSERT_NO_FATAL_FAILURE(testSuccessfulFusionGesture(mapper)); +} + +// Test a successful stylus fusion gesture where the pressure is reported by the external +// before the touch is reported by the touchscreen. +TEST_F(ExternalStylusFusionTest, SuccessfulFusion_PressureFirst) { + SingleTouchInputMapper& mapper = initializeInputMapperWithExternalStylus(); + auto toolTypeSource = + AllOf(WithSource(EXPECTED_SOURCE), WithToolType(ToolType::STYLUS)); + + // The external stylus reports pressure first. It is ignored for now. + mStylusState.pressure = 1.f; + processExternalStylusState(mapper); + ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasNotCalled()); + ASSERT_NO_FATAL_FAILURE(mReader->getContext()->assertTimeoutWasNotRequested()); + + // When the touch goes down afterwards, it is reported as a stylus pointer. + processDown(mapper, 100, 200); + processSync(mapper); + ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled( + AllOf(toolTypeSource, WithMotionAction(AMOTION_EVENT_ACTION_DOWN)))); + ASSERT_NO_FATAL_FAILURE(mReader->getContext()->assertTimeoutWasNotRequested()); + + processMove(mapper, 101, 201); + processSync(mapper); + ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled( + AllOf(toolTypeSource, WithMotionAction(AMOTION_EVENT_ACTION_MOVE)))); + processUp(mapper); + processSync(mapper); + ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled( + AllOf(toolTypeSource, WithMotionAction(AMOTION_EVENT_ACTION_UP)))); - const ui::Transform rotation(inverseRotationFlags, width, height); - const Rect rotatedPhysicalDisplay = rotation.transform(naturalPhysicalDisplay); + ASSERT_NO_FATAL_FAILURE(mReader->getContext()->assertTimeoutWasNotRequested()); + ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasNotCalled()); +} - std::optional internalViewport = - *mFakePolicy->getDisplayViewportByType(ViewportType::INTERNAL); - DisplayViewport& v = *internalViewport; - v.displayId = DISPLAY_ID; - v.orientation = orientation; +TEST_F(ExternalStylusFusionTest, FusionIsRepeatedForEachNewGesture) { + SingleTouchInputMapper& mapper = initializeInputMapperWithExternalStylus(); - v.logicalLeft = 0; - v.logicalTop = 0; - v.logicalRight = 100; - v.logicalBottom = 100; + ASSERT_NO_FATAL_FAILURE(testSuccessfulFusionGesture(mapper)); + ASSERT_NO_FATAL_FAILURE(testUnsuccessfulFusionGesture(mapper)); - v.physicalLeft = rotatedPhysicalDisplay.left; - v.physicalTop = rotatedPhysicalDisplay.top; - v.physicalRight = rotatedPhysicalDisplay.right; - v.physicalBottom = rotatedPhysicalDisplay.bottom; + ASSERT_NO_FATAL_FAILURE(testSuccessfulFusionGesture(mapper)); + ASSERT_NO_FATAL_FAILURE(testSuccessfulFusionGesture(mapper)); + ASSERT_NO_FATAL_FAILURE(testUnsuccessfulFusionGesture(mapper)); + ASSERT_NO_FATAL_FAILURE(testUnsuccessfulFusionGesture(mapper)); +} - v.deviceWidth = width; - v.deviceHeight = height; +TEST_F(ExternalStylusFusionTest, FusedPointerReportsPressureChanges) { + SingleTouchInputMapper& mapper = initializeInputMapperWithExternalStylus(); + auto toolTypeSource = + AllOf(WithSource(EXPECTED_SOURCE), WithToolType(ToolType::STYLUS)); - v.isActive = true; - v.uniqueId = UNIQUE_ID; - v.type = ViewportType::INTERNAL; - mFakePolicy->updateViewport(v); - configureDevice(InputReaderConfiguration::CHANGE_DISPLAY_INFO); - } + mStylusState.pressure = 0.8f; + processExternalStylusState(mapper); + processDown(mapper, 100, 200); + processSync(mapper); + ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled( + AllOf(toolTypeSource, WithMotionAction(AMOTION_EVENT_ACTION_DOWN), + WithPressure(0.8f)))); + ASSERT_NO_FATAL_FAILURE(mReader->getContext()->assertTimeoutWasNotRequested()); - void assertReceivedMove(const Point& point) { - NotifyMotionArgs motionArgs; - ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs)); - ASSERT_EQ(AMOTION_EVENT_ACTION_MOVE, motionArgs.action); - ASSERT_EQ(size_t(1), motionArgs.pointerCount); - ASSERT_NO_FATAL_FAILURE(assertPointerCoords(motionArgs.pointerCoords[0], point.x, point.y, - 1, 0, 0, 0, 0, 0, 0, 0)); - } -}; + // The external stylus reports a pressure change. We wait for some time for a touch event. + mStylusState.pressure = 0.6f; + processExternalStylusState(mapper); + ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasNotCalled()); + ASSERT_NO_FATAL_FAILURE( + mReader->getContext()->assertTimeoutWasRequested(ARBITRARY_TIME + TOUCH_DATA_TIMEOUT)); -TEST_F(TouchDisplayProjectionTest, IgnoresTouchesOutsidePhysicalDisplay) { - addConfigurationProperty("touch.deviceType", "touchScreen"); - prepareDisplay(DISPLAY_ORIENTATION_0); + // If a touch is reported within the timeout, it reports the updated pressure. + processMove(mapper, 101, 201); + processSync(mapper); + ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled( + AllOf(toolTypeSource, WithMotionAction(AMOTION_EVENT_ACTION_MOVE), + WithPressure(0.6f)))); + ASSERT_NO_FATAL_FAILURE(mReader->getContext()->assertTimeoutWasNotRequested()); - prepareButtons(); - prepareAxes(POSITION); - SingleTouchInputMapper& mapper = addMapperAndConfigure(); + // There is another pressure change. + mStylusState.pressure = 0.5f; + processExternalStylusState(mapper); + ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasNotCalled()); + ASSERT_NO_FATAL_FAILURE( + mReader->getContext()->assertTimeoutWasRequested(ARBITRARY_TIME + TOUCH_DATA_TIMEOUT)); - NotifyMotionArgs motionArgs; + // If a touch is not reported within the timeout, a move event is generated to report + // the new pressure. + handleTimeout(mapper, ARBITRARY_TIME + TOUCH_DATA_TIMEOUT); + ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled( + AllOf(toolTypeSource, WithMotionAction(AMOTION_EVENT_ACTION_MOVE), + WithPressure(0.5f)))); - // Configure the DisplayViewport such that the logical display maps to a subsection of - // the display panel called the physical display. Here, the physical display is bounded by the - // points (10, 20) and (70, 160) inside the display space, which is of the size 400 x 800. - static const Rect kPhysicalDisplay{10, 20, 70, 160}; - static const std::array kPointsOutsidePhysicalDisplay{ - {{-10, -10}, {0, 0}, {5, 100}, {50, 15}, {75, 100}, {50, 165}}}; + // If a zero pressure is reported before the touch goes up, the previous pressure value is + // repeated indefinitely. + mStylusState.pressure = 0.0f; + processExternalStylusState(mapper); + ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasNotCalled()); + ASSERT_NO_FATAL_FAILURE( + mReader->getContext()->assertTimeoutWasRequested(ARBITRARY_TIME + TOUCH_DATA_TIMEOUT)); + processMove(mapper, 102, 202); + processSync(mapper); + ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled( + AllOf(toolTypeSource, WithMotionAction(AMOTION_EVENT_ACTION_MOVE), + WithPressure(0.5f)))); + processMove(mapper, 103, 203); + processSync(mapper); + ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled( + AllOf(toolTypeSource, WithMotionAction(AMOTION_EVENT_ACTION_MOVE), + WithPressure(0.5f)))); - for (auto orientation : {DISPLAY_ORIENTATION_0, DISPLAY_ORIENTATION_90, DISPLAY_ORIENTATION_180, - DISPLAY_ORIENTATION_270}) { - configurePhysicalDisplay(orientation, kPhysicalDisplay); + processUp(mapper); + processSync(mapper); + ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled( + AllOf(WithMotionAction(AMOTION_EVENT_ACTION_UP), WithSource(EXPECTED_SOURCE), + WithToolType(ToolType::STYLUS)))); - // Touches outside the physical display should be ignored, and should not generate any - // events. Ensure touches at the following points that lie outside of the physical display - // area do not generate any events. - for (const auto& point : kPointsOutsidePhysicalDisplay) { - processDown(mapper, toRawX(point.x), toRawY(point.y)); - processSync(mapper); - processUp(mapper); - processSync(mapper); - ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasNotCalled()) - << "Unexpected event generated for touch outside physical display at point: " - << point.x << ", " << point.y; - } - } + ASSERT_NO_FATAL_FAILURE(mReader->getContext()->assertTimeoutWasNotRequested()); + ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasNotCalled()); } -TEST_F(TouchDisplayProjectionTest, EmitsTouchDownAfterEnteringPhysicalDisplay) { - addConfigurationProperty("touch.deviceType", "touchScreen"); - prepareDisplay(DISPLAY_ORIENTATION_0); +TEST_F(ExternalStylusFusionTest, FusedPointerReportsToolTypeChanges) { + SingleTouchInputMapper& mapper = initializeInputMapperWithExternalStylus(); + auto source = WithSource(EXPECTED_SOURCE); - prepareButtons(); - prepareAxes(POSITION); - SingleTouchInputMapper& mapper = addMapperAndConfigure(); + mStylusState.pressure = 1.f; + mStylusState.toolType = ToolType::ERASER; + processExternalStylusState(mapper); + processDown(mapper, 100, 200); + processSync(mapper); + ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled( + AllOf(source, WithMotionAction(AMOTION_EVENT_ACTION_DOWN), + WithToolType(ToolType::ERASER)))); + ASSERT_NO_FATAL_FAILURE(mReader->getContext()->assertTimeoutWasNotRequested()); - NotifyMotionArgs motionArgs; + // The external stylus reports a tool change. We wait for some time for a touch event. + mStylusState.toolType = ToolType::STYLUS; + processExternalStylusState(mapper); + ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasNotCalled()); + ASSERT_NO_FATAL_FAILURE( + mReader->getContext()->assertTimeoutWasRequested(ARBITRARY_TIME + TOUCH_DATA_TIMEOUT)); - // Configure the DisplayViewport such that the logical display maps to a subsection of - // the display panel called the physical display. Here, the physical display is bounded by the - // points (10, 20) and (70, 160) inside the display space, which is of the size 400 x 800. - static const Rect kPhysicalDisplay{10, 20, 70, 160}; + // If a touch is reported within the timeout, it reports the updated pressure. + processMove(mapper, 101, 201); + processSync(mapper); + ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled( + AllOf(source, WithMotionAction(AMOTION_EVENT_ACTION_MOVE), + WithToolType(ToolType::STYLUS)))); + ASSERT_NO_FATAL_FAILURE(mReader->getContext()->assertTimeoutWasNotRequested()); - for (auto orientation : {DISPLAY_ORIENTATION_0, DISPLAY_ORIENTATION_90, DISPLAY_ORIENTATION_180, - DISPLAY_ORIENTATION_270}) { - configurePhysicalDisplay(orientation, kPhysicalDisplay); + // There is another tool type change. + mStylusState.toolType = ToolType::FINGER; + processExternalStylusState(mapper); + ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasNotCalled()); + ASSERT_NO_FATAL_FAILURE( + mReader->getContext()->assertTimeoutWasRequested(ARBITRARY_TIME + TOUCH_DATA_TIMEOUT)); - // Touches that start outside the physical display should be ignored until it enters the - // physical display bounds, at which point it should generate a down event. Start a touch at - // the point (5, 100), which is outside the physical display bounds. - static const Point kOutsidePoint{5, 100}; - processDown(mapper, toRawX(kOutsidePoint.x), toRawY(kOutsidePoint.y)); - processSync(mapper); - ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasNotCalled()); + // If a touch is not reported within the timeout, a move event is generated to report + // the new tool type. + handleTimeout(mapper, ARBITRARY_TIME + TOUCH_DATA_TIMEOUT); + ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled( + AllOf(source, WithMotionAction(AMOTION_EVENT_ACTION_MOVE), + WithToolType(ToolType::FINGER)))); - // Move the touch into the physical display area. This should generate a pointer down. - processMove(mapper, toRawX(11), toRawY(21)); - processSync(mapper); - ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs)); - ASSERT_EQ(AMOTION_EVENT_ACTION_DOWN, motionArgs.action); - ASSERT_EQ(size_t(1), motionArgs.pointerCount); - ASSERT_NO_FATAL_FAILURE( - assertPointerCoords(motionArgs.pointerCoords[0], 11, 21, 1, 0, 0, 0, 0, 0, 0, 0)); + processUp(mapper); + processSync(mapper); + ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled( + AllOf(source, WithMotionAction(AMOTION_EVENT_ACTION_UP), + WithToolType(ToolType::FINGER)))); - // Move the touch inside the physical display area. This should generate a pointer move. - processMove(mapper, toRawX(69), toRawY(159)); - processSync(mapper); - assertReceivedMove({69, 159}); + ASSERT_NO_FATAL_FAILURE(mReader->getContext()->assertTimeoutWasNotRequested()); + ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasNotCalled()); +} - // Move outside the physical display area. Since the pointer is already down, this should - // now continue generating events. - processMove(mapper, toRawX(kOutsidePoint.x), toRawY(kOutsidePoint.y)); - processSync(mapper); - assertReceivedMove(kOutsidePoint); +TEST_F(ExternalStylusFusionTest, FusedPointerReportsButtons) { + SingleTouchInputMapper& mapper = initializeInputMapperWithExternalStylus(); + auto toolTypeSource = + AllOf(WithSource(EXPECTED_SOURCE), WithToolType(ToolType::STYLUS)); - // Release. This should generate a pointer up. - processUp(mapper); - processSync(mapper); - ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs)); - ASSERT_EQ(AMOTION_EVENT_ACTION_UP, motionArgs.action); - ASSERT_NO_FATAL_FAILURE(assertPointerCoords(motionArgs.pointerCoords[0], kOutsidePoint.x, - kOutsidePoint.y, 1, 0, 0, 0, 0, 0, 0, 0)); + ASSERT_NO_FATAL_FAILURE(testStartFusedStylusGesture(mapper)); - // Ensure no more events were generated. - ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyKeyWasNotCalled()); - ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasNotCalled()); - } + // The external stylus reports a button change. We wait for some time for a touch event. + mStylusState.buttons = AMOTION_EVENT_BUTTON_STYLUS_PRIMARY; + processExternalStylusState(mapper); + ASSERT_NO_FATAL_FAILURE( + mReader->getContext()->assertTimeoutWasRequested(ARBITRARY_TIME + TOUCH_DATA_TIMEOUT)); + + // If a touch is reported within the timeout, it reports the updated button state. + processMove(mapper, 101, 201); + processSync(mapper); + ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled( + AllOf(toolTypeSource, WithMotionAction(AMOTION_EVENT_ACTION_MOVE), + WithButtonState(AMOTION_EVENT_BUTTON_STYLUS_PRIMARY)))); + ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled( + AllOf(toolTypeSource, WithMotionAction(AMOTION_EVENT_ACTION_BUTTON_PRESS), + WithButtonState(AMOTION_EVENT_BUTTON_STYLUS_PRIMARY)))); + ASSERT_NO_FATAL_FAILURE(mReader->getContext()->assertTimeoutWasNotRequested()); + + // The button is now released. + mStylusState.buttons = 0; + processExternalStylusState(mapper); + ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasNotCalled()); + ASSERT_NO_FATAL_FAILURE( + mReader->getContext()->assertTimeoutWasRequested(ARBITRARY_TIME + TOUCH_DATA_TIMEOUT)); + + // If a touch is not reported within the timeout, a move event is generated to report + // the new button state. + handleTimeout(mapper, ARBITRARY_TIME + TOUCH_DATA_TIMEOUT); + ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled( + AllOf(toolTypeSource, WithMotionAction(AMOTION_EVENT_ACTION_BUTTON_RELEASE), + WithButtonState(0)))); + ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled( + AllOf(toolTypeSource, WithMotionAction(AMOTION_EVENT_ACTION_MOVE), + WithButtonState(0)))); + + processUp(mapper); + processSync(mapper); + ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled( + AllOf(toolTypeSource, WithMotionAction(AMOTION_EVENT_ACTION_UP), WithButtonState(0)))); + + ASSERT_NO_FATAL_FAILURE(mReader->getContext()->assertTimeoutWasNotRequested()); + ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasNotCalled()); } // --- MultiTouchInputMapperTest --- @@ -7145,8 +7642,10 @@ protected: void processSlot(MultiTouchInputMapper& mapper, int32_t slot); void processToolType(MultiTouchInputMapper& mapper, int32_t toolType); void processKey(MultiTouchInputMapper& mapper, int32_t code, int32_t value); + void processHidUsage(MultiTouchInputMapper& mapper, int32_t usageCode, int32_t value); void processMTSync(MultiTouchInputMapper& mapper); - void processSync(MultiTouchInputMapper& mapper); + void processSync(MultiTouchInputMapper& mapper, nsecs_t eventTime = ARBITRARY_TIME, + nsecs_t readTime = READ_TIME); }; void MultiTouchInputMapperTest::prepareAxes(int axes) { @@ -7249,20 +7748,27 @@ void MultiTouchInputMapperTest::processKey(MultiTouchInputMapper& mapper, int32_ process(mapper, ARBITRARY_TIME, READ_TIME, EV_KEY, code, value); } +void MultiTouchInputMapperTest::processHidUsage(MultiTouchInputMapper& mapper, int32_t usageCode, + int32_t value) { + process(mapper, ARBITRARY_TIME, READ_TIME, EV_MSC, MSC_SCAN, usageCode); + process(mapper, ARBITRARY_TIME, READ_TIME, EV_KEY, KEY_UNKNOWN, value); +} + void MultiTouchInputMapperTest::processMTSync(MultiTouchInputMapper& mapper) { process(mapper, ARBITRARY_TIME, READ_TIME, EV_SYN, SYN_MT_REPORT, 0); } -void MultiTouchInputMapperTest::processSync(MultiTouchInputMapper& mapper) { - process(mapper, ARBITRARY_TIME, READ_TIME, EV_SYN, SYN_REPORT, 0); +void MultiTouchInputMapperTest::processSync(MultiTouchInputMapper& mapper, nsecs_t eventTime, + nsecs_t readTime) { + process(mapper, eventTime, readTime, EV_SYN, SYN_REPORT, 0); } TEST_F(MultiTouchInputMapperTest, Process_NormalMultiTouchGesture_WithoutTrackingIds) { addConfigurationProperty("touch.deviceType", "touchScreen"); - prepareDisplay(DISPLAY_ORIENTATION_0); + prepareDisplay(ui::ROTATION_0); prepareAxes(POSITION); prepareVirtualKeys(); - MultiTouchInputMapper& mapper = addMapperAndConfigure(); + MultiTouchInputMapper& mapper = constructAndAddMapper(); mReader->getContext()->setGlobalMetaState(AMETA_SHIFT_LEFT_ON | AMETA_SHIFT_ON); @@ -7288,7 +7794,7 @@ TEST_F(MultiTouchInputMapperTest, Process_NormalMultiTouchGesture_WithoutTrackin ASSERT_EQ(0, motionArgs.edgeFlags); ASSERT_EQ(size_t(1), motionArgs.pointerCount); ASSERT_EQ(0, motionArgs.pointerProperties[0].id); - ASSERT_EQ(AMOTION_EVENT_TOOL_TYPE_FINGER, motionArgs.pointerProperties[0].toolType); + ASSERT_EQ(ToolType::FINGER, motionArgs.pointerProperties[0].toolType); ASSERT_NO_FATAL_FAILURE(assertPointerCoords(motionArgs.pointerCoords[0], toDisplayX(x1), toDisplayY(y1), 1, 0, 0, 0, 0, 0, 0, 0)); ASSERT_NEAR(X_PRECISION, motionArgs.xPrecision, EPSILON); @@ -7307,9 +7813,9 @@ TEST_F(MultiTouchInputMapperTest, Process_NormalMultiTouchGesture_WithoutTrackin ASSERT_EQ(0, motionArgs.edgeFlags); ASSERT_EQ(size_t(2), motionArgs.pointerCount); ASSERT_EQ(0, motionArgs.pointerProperties[0].id); - ASSERT_EQ(AMOTION_EVENT_TOOL_TYPE_FINGER, motionArgs.pointerProperties[0].toolType); + ASSERT_EQ(ToolType::FINGER, motionArgs.pointerProperties[0].toolType); ASSERT_EQ(1, motionArgs.pointerProperties[1].id); - ASSERT_EQ(AMOTION_EVENT_TOOL_TYPE_FINGER, motionArgs.pointerProperties[1].toolType); + ASSERT_EQ(ToolType::FINGER, motionArgs.pointerProperties[1].toolType); ASSERT_NO_FATAL_FAILURE(assertPointerCoords(motionArgs.pointerCoords[0], toDisplayX(x1), toDisplayY(y1), 1, 0, 0, 0, 0, 0, 0, 0)); ASSERT_NO_FATAL_FAILURE(assertPointerCoords(motionArgs.pointerCoords[1], @@ -7338,9 +7844,9 @@ TEST_F(MultiTouchInputMapperTest, Process_NormalMultiTouchGesture_WithoutTrackin ASSERT_EQ(0, motionArgs.edgeFlags); ASSERT_EQ(size_t(2), motionArgs.pointerCount); ASSERT_EQ(0, motionArgs.pointerProperties[0].id); - ASSERT_EQ(AMOTION_EVENT_TOOL_TYPE_FINGER, motionArgs.pointerProperties[0].toolType); + ASSERT_EQ(ToolType::FINGER, motionArgs.pointerProperties[0].toolType); ASSERT_EQ(1, motionArgs.pointerProperties[1].id); - ASSERT_EQ(AMOTION_EVENT_TOOL_TYPE_FINGER, motionArgs.pointerProperties[1].toolType); + ASSERT_EQ(ToolType::FINGER, motionArgs.pointerProperties[1].toolType); ASSERT_NO_FATAL_FAILURE(assertPointerCoords(motionArgs.pointerCoords[0], toDisplayX(x1), toDisplayY(y1), 1, 0, 0, 0, 0, 0, 0, 0)); ASSERT_NO_FATAL_FAILURE(assertPointerCoords(motionArgs.pointerCoords[1], @@ -7367,9 +7873,9 @@ TEST_F(MultiTouchInputMapperTest, Process_NormalMultiTouchGesture_WithoutTrackin ASSERT_EQ(0, motionArgs.edgeFlags); ASSERT_EQ(size_t(2), motionArgs.pointerCount); ASSERT_EQ(0, motionArgs.pointerProperties[0].id); - ASSERT_EQ(AMOTION_EVENT_TOOL_TYPE_FINGER, motionArgs.pointerProperties[0].toolType); + ASSERT_EQ(ToolType::FINGER, motionArgs.pointerProperties[0].toolType); ASSERT_EQ(1, motionArgs.pointerProperties[1].id); - ASSERT_EQ(AMOTION_EVENT_TOOL_TYPE_FINGER, motionArgs.pointerProperties[1].toolType); + ASSERT_EQ(ToolType::FINGER, motionArgs.pointerProperties[1].toolType); ASSERT_NO_FATAL_FAILURE(assertPointerCoords(motionArgs.pointerCoords[0], toDisplayX(x1), toDisplayY(y1), 1, 0, 0, 0, 0, 0, 0, 0)); ASSERT_NO_FATAL_FAILURE(assertPointerCoords(motionArgs.pointerCoords[1], @@ -7390,7 +7896,7 @@ TEST_F(MultiTouchInputMapperTest, Process_NormalMultiTouchGesture_WithoutTrackin ASSERT_EQ(0, motionArgs.edgeFlags); ASSERT_EQ(size_t(1), motionArgs.pointerCount); ASSERT_EQ(1, motionArgs.pointerProperties[0].id); - ASSERT_EQ(AMOTION_EVENT_TOOL_TYPE_FINGER, motionArgs.pointerProperties[0].toolType); + ASSERT_EQ(ToolType::FINGER, motionArgs.pointerProperties[0].toolType); ASSERT_NO_FATAL_FAILURE(assertPointerCoords(motionArgs.pointerCoords[0], toDisplayX(x2), toDisplayY(y2), 1, 0, 0, 0, 0, 0, 0, 0)); ASSERT_NEAR(X_PRECISION, motionArgs.xPrecision, EPSILON); @@ -7415,7 +7921,7 @@ TEST_F(MultiTouchInputMapperTest, Process_NormalMultiTouchGesture_WithoutTrackin ASSERT_EQ(0, motionArgs.edgeFlags); ASSERT_EQ(size_t(1), motionArgs.pointerCount); ASSERT_EQ(1, motionArgs.pointerProperties[0].id); - ASSERT_EQ(AMOTION_EVENT_TOOL_TYPE_FINGER, motionArgs.pointerProperties[0].toolType); + ASSERT_EQ(ToolType::FINGER, motionArgs.pointerProperties[0].toolType); ASSERT_NO_FATAL_FAILURE(assertPointerCoords(motionArgs.pointerCoords[0], toDisplayX(x2), toDisplayY(y2), 1, 0, 0, 0, 0, 0, 0, 0)); ASSERT_NEAR(X_PRECISION, motionArgs.xPrecision, EPSILON); @@ -7442,9 +7948,9 @@ TEST_F(MultiTouchInputMapperTest, Process_NormalMultiTouchGesture_WithoutTrackin ASSERT_EQ(0, motionArgs.edgeFlags); ASSERT_EQ(size_t(2), motionArgs.pointerCount); ASSERT_EQ(0, motionArgs.pointerProperties[0].id); - ASSERT_EQ(AMOTION_EVENT_TOOL_TYPE_FINGER, motionArgs.pointerProperties[0].toolType); + ASSERT_EQ(ToolType::FINGER, motionArgs.pointerProperties[0].toolType); ASSERT_EQ(1, motionArgs.pointerProperties[1].id); - ASSERT_EQ(AMOTION_EVENT_TOOL_TYPE_FINGER, motionArgs.pointerProperties[1].toolType); + ASSERT_EQ(ToolType::FINGER, motionArgs.pointerProperties[1].toolType); ASSERT_NO_FATAL_FAILURE(assertPointerCoords(motionArgs.pointerCoords[0], toDisplayX(x3), toDisplayY(y3), 1, 0, 0, 0, 0, 0, 0, 0)); ASSERT_NO_FATAL_FAILURE(assertPointerCoords(motionArgs.pointerCoords[1], @@ -7471,9 +7977,9 @@ TEST_F(MultiTouchInputMapperTest, Process_NormalMultiTouchGesture_WithoutTrackin ASSERT_EQ(0, motionArgs.edgeFlags); ASSERT_EQ(size_t(2), motionArgs.pointerCount); ASSERT_EQ(0, motionArgs.pointerProperties[0].id); - ASSERT_EQ(AMOTION_EVENT_TOOL_TYPE_FINGER, motionArgs.pointerProperties[0].toolType); + ASSERT_EQ(ToolType::FINGER, motionArgs.pointerProperties[0].toolType); ASSERT_EQ(1, motionArgs.pointerProperties[1].id); - ASSERT_EQ(AMOTION_EVENT_TOOL_TYPE_FINGER, motionArgs.pointerProperties[1].toolType); + ASSERT_EQ(ToolType::FINGER, motionArgs.pointerProperties[1].toolType); ASSERT_NO_FATAL_FAILURE(assertPointerCoords(motionArgs.pointerCoords[0], toDisplayX(x3), toDisplayY(y3), 1, 0, 0, 0, 0, 0, 0, 0)); ASSERT_NO_FATAL_FAILURE(assertPointerCoords(motionArgs.pointerCoords[1], @@ -7494,7 +8000,7 @@ TEST_F(MultiTouchInputMapperTest, Process_NormalMultiTouchGesture_WithoutTrackin ASSERT_EQ(0, motionArgs.edgeFlags); ASSERT_EQ(size_t(1), motionArgs.pointerCount); ASSERT_EQ(0, motionArgs.pointerProperties[0].id); - ASSERT_EQ(AMOTION_EVENT_TOOL_TYPE_FINGER, motionArgs.pointerProperties[0].toolType); + ASSERT_EQ(ToolType::FINGER, motionArgs.pointerProperties[0].toolType); ASSERT_NO_FATAL_FAILURE(assertPointerCoords(motionArgs.pointerCoords[0], toDisplayX(x3), toDisplayY(y3), 1, 0, 0, 0, 0, 0, 0, 0)); ASSERT_NEAR(X_PRECISION, motionArgs.xPrecision, EPSILON); @@ -7517,7 +8023,7 @@ TEST_F(MultiTouchInputMapperTest, Process_NormalMultiTouchGesture_WithoutTrackin ASSERT_EQ(0, motionArgs.edgeFlags); ASSERT_EQ(size_t(1), motionArgs.pointerCount); ASSERT_EQ(0, motionArgs.pointerProperties[0].id); - ASSERT_EQ(AMOTION_EVENT_TOOL_TYPE_FINGER, motionArgs.pointerProperties[0].toolType); + ASSERT_EQ(ToolType::FINGER, motionArgs.pointerProperties[0].toolType); ASSERT_NO_FATAL_FAILURE(assertPointerCoords(motionArgs.pointerCoords[0], toDisplayX(x3), toDisplayY(y3), 1, 0, 0, 0, 0, 0, 0, 0)); ASSERT_NEAR(X_PRECISION, motionArgs.xPrecision, EPSILON); @@ -7531,7 +8037,7 @@ TEST_F(MultiTouchInputMapperTest, Process_NormalMultiTouchGesture_WithoutTrackin TEST_F(MultiTouchInputMapperTest, AxisResolution_IsPopulated) { addConfigurationProperty("touch.deviceType", "touchScreen"); - prepareDisplay(DISPLAY_ORIENTATION_0); + prepareDisplay(ui::ROTATION_0); mFakeEventHub->addAbsoluteAxis(EVENTHUB_ID, ABS_MT_POSITION_X, RAW_X_MIN, RAW_X_MAX, /*flat*/ 0, /*fuzz*/ 0, /*resolution*/ 10); @@ -7546,7 +8052,7 @@ TEST_F(MultiTouchInputMapperTest, AxisResolution_IsPopulated) { mFakeEventHub->addAbsoluteAxis(EVENTHUB_ID, ABS_MT_WIDTH_MINOR, RAW_TOOL_MIN, RAW_TOOL_MAX, /*flat*/ 0, /*flat*/ 0, /*resolution*/ 15); - MultiTouchInputMapper& mapper = addMapperAndConfigure(); + MultiTouchInputMapper& mapper = constructAndAddMapper(); // X and Y axes assertAxisResolution(mapper, AMOTION_EVENT_AXIS_X, 10 / X_PRECISION); @@ -7561,7 +8067,7 @@ TEST_F(MultiTouchInputMapperTest, AxisResolution_IsPopulated) { TEST_F(MultiTouchInputMapperTest, TouchMajorAndMinorAxes_DoNotAppearIfNotSupported) { addConfigurationProperty("touch.deviceType", "touchScreen"); - prepareDisplay(DISPLAY_ORIENTATION_0); + prepareDisplay(ui::ROTATION_0); mFakeEventHub->addAbsoluteAxis(EVENTHUB_ID, ABS_MT_POSITION_X, RAW_X_MIN, RAW_X_MAX, /*flat*/ 0, /*fuzz*/ 0, /*resolution*/ 10); @@ -7570,7 +8076,7 @@ TEST_F(MultiTouchInputMapperTest, TouchMajorAndMinorAxes_DoNotAppearIfNotSupport // We do not add ABS_MT_TOUCH_MAJOR / MINOR or ABS_MT_WIDTH_MAJOR / MINOR axes - MultiTouchInputMapper& mapper = addMapperAndConfigure(); + MultiTouchInputMapper& mapper = constructAndAddMapper(); // Touch major and minor assertAxisNotPresent(mapper, AMOTION_EVENT_AXIS_TOUCH_MAJOR); @@ -7582,10 +8088,10 @@ TEST_F(MultiTouchInputMapperTest, TouchMajorAndMinorAxes_DoNotAppearIfNotSupport TEST_F(MultiTouchInputMapperTest, Process_NormalMultiTouchGesture_WithTrackingIds) { addConfigurationProperty("touch.deviceType", "touchScreen"); - prepareDisplay(DISPLAY_ORIENTATION_0); + prepareDisplay(ui::ROTATION_0); prepareAxes(POSITION | ID); prepareVirtualKeys(); - MultiTouchInputMapper& mapper = addMapperAndConfigure(); + MultiTouchInputMapper& mapper = constructAndAddMapper(); mReader->getContext()->setGlobalMetaState(AMETA_SHIFT_LEFT_ON | AMETA_SHIFT_ON); @@ -7605,7 +8111,7 @@ TEST_F(MultiTouchInputMapperTest, Process_NormalMultiTouchGesture_WithTrackingId ASSERT_EQ(AMOTION_EVENT_ACTION_DOWN, motionArgs.action); ASSERT_EQ(size_t(1), motionArgs.pointerCount); ASSERT_EQ(0, motionArgs.pointerProperties[0].id); - ASSERT_EQ(AMOTION_EVENT_TOOL_TYPE_FINGER, motionArgs.pointerProperties[0].toolType); + ASSERT_EQ(ToolType::FINGER, motionArgs.pointerProperties[0].toolType); ASSERT_NO_FATAL_FAILURE(assertPointerCoords(motionArgs.pointerCoords[0], toDisplayX(x1), toDisplayY(y1), 1, 0, 0, 0, 0, 0, 0, 0)); @@ -7613,9 +8119,9 @@ TEST_F(MultiTouchInputMapperTest, Process_NormalMultiTouchGesture_WithTrackingId ASSERT_EQ(ACTION_POINTER_1_DOWN, motionArgs.action); ASSERT_EQ(size_t(2), motionArgs.pointerCount); ASSERT_EQ(0, motionArgs.pointerProperties[0].id); - ASSERT_EQ(AMOTION_EVENT_TOOL_TYPE_FINGER, motionArgs.pointerProperties[0].toolType); + ASSERT_EQ(ToolType::FINGER, motionArgs.pointerProperties[0].toolType); ASSERT_EQ(1, motionArgs.pointerProperties[1].id); - ASSERT_EQ(AMOTION_EVENT_TOOL_TYPE_FINGER, motionArgs.pointerProperties[1].toolType); + ASSERT_EQ(ToolType::FINGER, motionArgs.pointerProperties[1].toolType); ASSERT_NO_FATAL_FAILURE(assertPointerCoords(motionArgs.pointerCoords[0], toDisplayX(x1), toDisplayY(y1), 1, 0, 0, 0, 0, 0, 0, 0)); ASSERT_NO_FATAL_FAILURE(assertPointerCoords(motionArgs.pointerCoords[1], @@ -7635,9 +8141,9 @@ TEST_F(MultiTouchInputMapperTest, Process_NormalMultiTouchGesture_WithTrackingId ASSERT_EQ(AMOTION_EVENT_ACTION_MOVE, motionArgs.action); ASSERT_EQ(size_t(2), motionArgs.pointerCount); ASSERT_EQ(0, motionArgs.pointerProperties[0].id); - ASSERT_EQ(AMOTION_EVENT_TOOL_TYPE_FINGER, motionArgs.pointerProperties[0].toolType); + ASSERT_EQ(ToolType::FINGER, motionArgs.pointerProperties[0].toolType); ASSERT_EQ(1, motionArgs.pointerProperties[1].id); - ASSERT_EQ(AMOTION_EVENT_TOOL_TYPE_FINGER, motionArgs.pointerProperties[1].toolType); + ASSERT_EQ(ToolType::FINGER, motionArgs.pointerProperties[1].toolType); ASSERT_NO_FATAL_FAILURE(assertPointerCoords(motionArgs.pointerCoords[0], toDisplayX(x1), toDisplayY(y1), 1, 0, 0, 0, 0, 0, 0, 0)); ASSERT_NO_FATAL_FAILURE(assertPointerCoords(motionArgs.pointerCoords[1], @@ -7654,9 +8160,9 @@ TEST_F(MultiTouchInputMapperTest, Process_NormalMultiTouchGesture_WithTrackingId ASSERT_EQ(ACTION_POINTER_0_UP, motionArgs.action); ASSERT_EQ(size_t(2), motionArgs.pointerCount); ASSERT_EQ(0, motionArgs.pointerProperties[0].id); - ASSERT_EQ(AMOTION_EVENT_TOOL_TYPE_FINGER, motionArgs.pointerProperties[0].toolType); + ASSERT_EQ(ToolType::FINGER, motionArgs.pointerProperties[0].toolType); ASSERT_EQ(1, motionArgs.pointerProperties[1].id); - ASSERT_EQ(AMOTION_EVENT_TOOL_TYPE_FINGER, motionArgs.pointerProperties[1].toolType); + ASSERT_EQ(ToolType::FINGER, motionArgs.pointerProperties[1].toolType); ASSERT_NO_FATAL_FAILURE(assertPointerCoords(motionArgs.pointerCoords[0], toDisplayX(x1), toDisplayY(y1), 1, 0, 0, 0, 0, 0, 0, 0)); ASSERT_NO_FATAL_FAILURE(assertPointerCoords(motionArgs.pointerCoords[1], @@ -7666,7 +8172,7 @@ TEST_F(MultiTouchInputMapperTest, Process_NormalMultiTouchGesture_WithTrackingId ASSERT_EQ(AMOTION_EVENT_ACTION_MOVE, motionArgs.action); ASSERT_EQ(size_t(1), motionArgs.pointerCount); ASSERT_EQ(1, motionArgs.pointerProperties[0].id); - ASSERT_EQ(AMOTION_EVENT_TOOL_TYPE_FINGER, motionArgs.pointerProperties[0].toolType); + ASSERT_EQ(ToolType::FINGER, motionArgs.pointerProperties[0].toolType); ASSERT_NO_FATAL_FAILURE(assertPointerCoords(motionArgs.pointerCoords[0], toDisplayX(x2), toDisplayY(y2), 1, 0, 0, 0, 0, 0, 0, 0)); @@ -7681,7 +8187,7 @@ TEST_F(MultiTouchInputMapperTest, Process_NormalMultiTouchGesture_WithTrackingId ASSERT_EQ(AMOTION_EVENT_ACTION_MOVE, motionArgs.action); ASSERT_EQ(size_t(1), motionArgs.pointerCount); ASSERT_EQ(1, motionArgs.pointerProperties[0].id); - ASSERT_EQ(AMOTION_EVENT_TOOL_TYPE_FINGER, motionArgs.pointerProperties[0].toolType); + ASSERT_EQ(ToolType::FINGER, motionArgs.pointerProperties[0].toolType); ASSERT_NO_FATAL_FAILURE(assertPointerCoords(motionArgs.pointerCoords[0], toDisplayX(x2), toDisplayY(y2), 1, 0, 0, 0, 0, 0, 0, 0)); @@ -7699,9 +8205,9 @@ TEST_F(MultiTouchInputMapperTest, Process_NormalMultiTouchGesture_WithTrackingId ASSERT_EQ(ACTION_POINTER_0_DOWN, motionArgs.action); ASSERT_EQ(size_t(2), motionArgs.pointerCount); ASSERT_EQ(0, motionArgs.pointerProperties[0].id); - ASSERT_EQ(AMOTION_EVENT_TOOL_TYPE_FINGER, motionArgs.pointerProperties[0].toolType); + ASSERT_EQ(ToolType::FINGER, motionArgs.pointerProperties[0].toolType); ASSERT_EQ(1, motionArgs.pointerProperties[1].id); - ASSERT_EQ(AMOTION_EVENT_TOOL_TYPE_FINGER, motionArgs.pointerProperties[1].toolType); + ASSERT_EQ(ToolType::FINGER, motionArgs.pointerProperties[1].toolType); ASSERT_NO_FATAL_FAILURE(assertPointerCoords(motionArgs.pointerCoords[0], toDisplayX(x3), toDisplayY(y3), 1, 0, 0, 0, 0, 0, 0, 0)); ASSERT_NO_FATAL_FAILURE(assertPointerCoords(motionArgs.pointerCoords[1], @@ -7718,9 +8224,9 @@ TEST_F(MultiTouchInputMapperTest, Process_NormalMultiTouchGesture_WithTrackingId ASSERT_EQ(ACTION_POINTER_1_UP, motionArgs.action); ASSERT_EQ(size_t(2), motionArgs.pointerCount); ASSERT_EQ(0, motionArgs.pointerProperties[0].id); - ASSERT_EQ(AMOTION_EVENT_TOOL_TYPE_FINGER, motionArgs.pointerProperties[0].toolType); + ASSERT_EQ(ToolType::FINGER, motionArgs.pointerProperties[0].toolType); ASSERT_EQ(1, motionArgs.pointerProperties[1].id); - ASSERT_EQ(AMOTION_EVENT_TOOL_TYPE_FINGER, motionArgs.pointerProperties[1].toolType); + ASSERT_EQ(ToolType::FINGER, motionArgs.pointerProperties[1].toolType); ASSERT_NO_FATAL_FAILURE(assertPointerCoords(motionArgs.pointerCoords[0], toDisplayX(x3), toDisplayY(y3), 1, 0, 0, 0, 0, 0, 0, 0)); ASSERT_NO_FATAL_FAILURE(assertPointerCoords(motionArgs.pointerCoords[1], @@ -7730,7 +8236,7 @@ TEST_F(MultiTouchInputMapperTest, Process_NormalMultiTouchGesture_WithTrackingId ASSERT_EQ(AMOTION_EVENT_ACTION_MOVE, motionArgs.action); ASSERT_EQ(size_t(1), motionArgs.pointerCount); ASSERT_EQ(0, motionArgs.pointerProperties[0].id); - ASSERT_EQ(AMOTION_EVENT_TOOL_TYPE_FINGER, motionArgs.pointerProperties[0].toolType); + ASSERT_EQ(ToolType::FINGER, motionArgs.pointerProperties[0].toolType); ASSERT_NO_FATAL_FAILURE(assertPointerCoords(motionArgs.pointerCoords[0], toDisplayX(x3), toDisplayY(y3), 1, 0, 0, 0, 0, 0, 0, 0)); @@ -7742,7 +8248,7 @@ TEST_F(MultiTouchInputMapperTest, Process_NormalMultiTouchGesture_WithTrackingId ASSERT_EQ(AMOTION_EVENT_ACTION_UP, motionArgs.action); ASSERT_EQ(size_t(1), motionArgs.pointerCount); ASSERT_EQ(0, motionArgs.pointerProperties[0].id); - ASSERT_EQ(AMOTION_EVENT_TOOL_TYPE_FINGER, motionArgs.pointerProperties[0].toolType); + ASSERT_EQ(ToolType::FINGER, motionArgs.pointerProperties[0].toolType); ASSERT_NO_FATAL_FAILURE(assertPointerCoords(motionArgs.pointerCoords[0], toDisplayX(x3), toDisplayY(y3), 1, 0, 0, 0, 0, 0, 0, 0)); @@ -7753,10 +8259,10 @@ TEST_F(MultiTouchInputMapperTest, Process_NormalMultiTouchGesture_WithTrackingId TEST_F(MultiTouchInputMapperTest, Process_NormalMultiTouchGesture_WithSlots) { addConfigurationProperty("touch.deviceType", "touchScreen"); - prepareDisplay(DISPLAY_ORIENTATION_0); + prepareDisplay(ui::ROTATION_0); prepareAxes(POSITION | ID | SLOT); prepareVirtualKeys(); - MultiTouchInputMapper& mapper = addMapperAndConfigure(); + MultiTouchInputMapper& mapper = constructAndAddMapper(); mReader->getContext()->setGlobalMetaState(AMETA_SHIFT_LEFT_ON | AMETA_SHIFT_ON); @@ -7775,7 +8281,7 @@ TEST_F(MultiTouchInputMapperTest, Process_NormalMultiTouchGesture_WithSlots) { ASSERT_EQ(AMOTION_EVENT_ACTION_DOWN, motionArgs.action); ASSERT_EQ(size_t(1), motionArgs.pointerCount); ASSERT_EQ(0, motionArgs.pointerProperties[0].id); - ASSERT_EQ(AMOTION_EVENT_TOOL_TYPE_FINGER, motionArgs.pointerProperties[0].toolType); + ASSERT_EQ(ToolType::FINGER, motionArgs.pointerProperties[0].toolType); ASSERT_NO_FATAL_FAILURE(assertPointerCoords(motionArgs.pointerCoords[0], toDisplayX(x1), toDisplayY(y1), 1, 0, 0, 0, 0, 0, 0, 0)); @@ -7783,9 +8289,9 @@ TEST_F(MultiTouchInputMapperTest, Process_NormalMultiTouchGesture_WithSlots) { ASSERT_EQ(ACTION_POINTER_1_DOWN, motionArgs.action); ASSERT_EQ(size_t(2), motionArgs.pointerCount); ASSERT_EQ(0, motionArgs.pointerProperties[0].id); - ASSERT_EQ(AMOTION_EVENT_TOOL_TYPE_FINGER, motionArgs.pointerProperties[0].toolType); + ASSERT_EQ(ToolType::FINGER, motionArgs.pointerProperties[0].toolType); ASSERT_EQ(1, motionArgs.pointerProperties[1].id); - ASSERT_EQ(AMOTION_EVENT_TOOL_TYPE_FINGER, motionArgs.pointerProperties[1].toolType); + ASSERT_EQ(ToolType::FINGER, motionArgs.pointerProperties[1].toolType); ASSERT_NO_FATAL_FAILURE(assertPointerCoords(motionArgs.pointerCoords[0], toDisplayX(x1), toDisplayY(y1), 1, 0, 0, 0, 0, 0, 0, 0)); ASSERT_NO_FATAL_FAILURE(assertPointerCoords(motionArgs.pointerCoords[1], @@ -7803,9 +8309,9 @@ TEST_F(MultiTouchInputMapperTest, Process_NormalMultiTouchGesture_WithSlots) { ASSERT_EQ(AMOTION_EVENT_ACTION_MOVE, motionArgs.action); ASSERT_EQ(size_t(2), motionArgs.pointerCount); ASSERT_EQ(0, motionArgs.pointerProperties[0].id); - ASSERT_EQ(AMOTION_EVENT_TOOL_TYPE_FINGER, motionArgs.pointerProperties[0].toolType); + ASSERT_EQ(ToolType::FINGER, motionArgs.pointerProperties[0].toolType); ASSERT_EQ(1, motionArgs.pointerProperties[1].id); - ASSERT_EQ(AMOTION_EVENT_TOOL_TYPE_FINGER, motionArgs.pointerProperties[1].toolType); + ASSERT_EQ(ToolType::FINGER, motionArgs.pointerProperties[1].toolType); ASSERT_NO_FATAL_FAILURE(assertPointerCoords(motionArgs.pointerCoords[0], toDisplayX(x1), toDisplayY(y1), 1, 0, 0, 0, 0, 0, 0, 0)); ASSERT_NO_FATAL_FAILURE(assertPointerCoords(motionArgs.pointerCoords[1], @@ -7823,9 +8329,9 @@ TEST_F(MultiTouchInputMapperTest, Process_NormalMultiTouchGesture_WithSlots) { ASSERT_EQ(ACTION_POINTER_0_UP, motionArgs.action); ASSERT_EQ(size_t(2), motionArgs.pointerCount); ASSERT_EQ(0, motionArgs.pointerProperties[0].id); - ASSERT_EQ(AMOTION_EVENT_TOOL_TYPE_FINGER, motionArgs.pointerProperties[0].toolType); + ASSERT_EQ(ToolType::FINGER, motionArgs.pointerProperties[0].toolType); ASSERT_EQ(1, motionArgs.pointerProperties[1].id); - ASSERT_EQ(AMOTION_EVENT_TOOL_TYPE_FINGER, motionArgs.pointerProperties[1].toolType); + ASSERT_EQ(ToolType::FINGER, motionArgs.pointerProperties[1].toolType); ASSERT_NO_FATAL_FAILURE(assertPointerCoords(motionArgs.pointerCoords[0], toDisplayX(x1), toDisplayY(y1), 1, 0, 0, 0, 0, 0, 0, 0)); ASSERT_NO_FATAL_FAILURE(assertPointerCoords(motionArgs.pointerCoords[1], @@ -7835,7 +8341,7 @@ TEST_F(MultiTouchInputMapperTest, Process_NormalMultiTouchGesture_WithSlots) { ASSERT_EQ(AMOTION_EVENT_ACTION_MOVE, motionArgs.action); ASSERT_EQ(size_t(1), motionArgs.pointerCount); ASSERT_EQ(1, motionArgs.pointerProperties[0].id); - ASSERT_EQ(AMOTION_EVENT_TOOL_TYPE_FINGER, motionArgs.pointerProperties[0].toolType); + ASSERT_EQ(ToolType::FINGER, motionArgs.pointerProperties[0].toolType); ASSERT_NO_FATAL_FAILURE(assertPointerCoords(motionArgs.pointerCoords[0], toDisplayX(x2), toDisplayY(y2), 1, 0, 0, 0, 0, 0, 0, 0)); @@ -7848,7 +8354,7 @@ TEST_F(MultiTouchInputMapperTest, Process_NormalMultiTouchGesture_WithSlots) { ASSERT_EQ(AMOTION_EVENT_ACTION_MOVE, motionArgs.action); ASSERT_EQ(size_t(1), motionArgs.pointerCount); ASSERT_EQ(1, motionArgs.pointerProperties[0].id); - ASSERT_EQ(AMOTION_EVENT_TOOL_TYPE_FINGER, motionArgs.pointerProperties[0].toolType); + ASSERT_EQ(ToolType::FINGER, motionArgs.pointerProperties[0].toolType); ASSERT_NO_FATAL_FAILURE(assertPointerCoords(motionArgs.pointerCoords[0], toDisplayX(x2), toDisplayY(y2), 1, 0, 0, 0, 0, 0, 0, 0)); @@ -7864,9 +8370,9 @@ TEST_F(MultiTouchInputMapperTest, Process_NormalMultiTouchGesture_WithSlots) { ASSERT_EQ(ACTION_POINTER_0_DOWN, motionArgs.action); ASSERT_EQ(size_t(2), motionArgs.pointerCount); ASSERT_EQ(0, motionArgs.pointerProperties[0].id); - ASSERT_EQ(AMOTION_EVENT_TOOL_TYPE_FINGER, motionArgs.pointerProperties[0].toolType); + ASSERT_EQ(ToolType::FINGER, motionArgs.pointerProperties[0].toolType); ASSERT_EQ(1, motionArgs.pointerProperties[1].id); - ASSERT_EQ(AMOTION_EVENT_TOOL_TYPE_FINGER, motionArgs.pointerProperties[1].toolType); + ASSERT_EQ(ToolType::FINGER, motionArgs.pointerProperties[1].toolType); ASSERT_NO_FATAL_FAILURE(assertPointerCoords(motionArgs.pointerCoords[0], toDisplayX(x3), toDisplayY(y3), 1, 0, 0, 0, 0, 0, 0, 0)); ASSERT_NO_FATAL_FAILURE(assertPointerCoords(motionArgs.pointerCoords[1], @@ -7884,9 +8390,9 @@ TEST_F(MultiTouchInputMapperTest, Process_NormalMultiTouchGesture_WithSlots) { ASSERT_EQ(ACTION_POINTER_1_UP, motionArgs.action); ASSERT_EQ(size_t(2), motionArgs.pointerCount); ASSERT_EQ(0, motionArgs.pointerProperties[0].id); - ASSERT_EQ(AMOTION_EVENT_TOOL_TYPE_FINGER, motionArgs.pointerProperties[0].toolType); + ASSERT_EQ(ToolType::FINGER, motionArgs.pointerProperties[0].toolType); ASSERT_EQ(1, motionArgs.pointerProperties[1].id); - ASSERT_EQ(AMOTION_EVENT_TOOL_TYPE_FINGER, motionArgs.pointerProperties[1].toolType); + ASSERT_EQ(ToolType::FINGER, motionArgs.pointerProperties[1].toolType); ASSERT_NO_FATAL_FAILURE(assertPointerCoords(motionArgs.pointerCoords[0], toDisplayX(x3), toDisplayY(y3), 1, 0, 0, 0, 0, 0, 0, 0)); ASSERT_NO_FATAL_FAILURE(assertPointerCoords(motionArgs.pointerCoords[1], @@ -7896,7 +8402,7 @@ TEST_F(MultiTouchInputMapperTest, Process_NormalMultiTouchGesture_WithSlots) { ASSERT_EQ(AMOTION_EVENT_ACTION_MOVE, motionArgs.action); ASSERT_EQ(size_t(1), motionArgs.pointerCount); ASSERT_EQ(0, motionArgs.pointerProperties[0].id); - ASSERT_EQ(AMOTION_EVENT_TOOL_TYPE_FINGER, motionArgs.pointerProperties[0].toolType); + ASSERT_EQ(ToolType::FINGER, motionArgs.pointerProperties[0].toolType); ASSERT_NO_FATAL_FAILURE(assertPointerCoords(motionArgs.pointerCoords[0], toDisplayX(x3), toDisplayY(y3), 1, 0, 0, 0, 0, 0, 0, 0)); @@ -7908,7 +8414,7 @@ TEST_F(MultiTouchInputMapperTest, Process_NormalMultiTouchGesture_WithSlots) { ASSERT_EQ(AMOTION_EVENT_ACTION_UP, motionArgs.action); ASSERT_EQ(size_t(1), motionArgs.pointerCount); ASSERT_EQ(0, motionArgs.pointerProperties[0].id); - ASSERT_EQ(AMOTION_EVENT_TOOL_TYPE_FINGER, motionArgs.pointerProperties[0].toolType); + ASSERT_EQ(ToolType::FINGER, motionArgs.pointerProperties[0].toolType); ASSERT_NO_FATAL_FAILURE(assertPointerCoords(motionArgs.pointerCoords[0], toDisplayX(x3), toDisplayY(y3), 1, 0, 0, 0, 0, 0, 0, 0)); @@ -7919,9 +8425,9 @@ TEST_F(MultiTouchInputMapperTest, Process_NormalMultiTouchGesture_WithSlots) { TEST_F(MultiTouchInputMapperTest, Process_AllAxes_WithDefaultCalibration) { addConfigurationProperty("touch.deviceType", "touchScreen"); - prepareDisplay(DISPLAY_ORIENTATION_0); + prepareDisplay(ui::ROTATION_0); prepareAxes(POSITION | TOUCH | TOOL | PRESSURE | ORIENTATION | ID | MINOR | DISTANCE); - MultiTouchInputMapper& mapper = addMapperAndConfigure(); + MultiTouchInputMapper& mapper = constructAndAddMapper(); // These calculations are based on the input device calibration documentation. int32_t rawX = 100; @@ -7968,10 +8474,10 @@ TEST_F(MultiTouchInputMapperTest, Process_AllAxes_WithDefaultCalibration) { TEST_F(MultiTouchInputMapperTest, Process_TouchAndToolAxes_GeometricCalibration) { addConfigurationProperty("touch.deviceType", "touchScreen"); - prepareDisplay(DISPLAY_ORIENTATION_0); + prepareDisplay(ui::ROTATION_0); prepareAxes(POSITION | TOUCH | TOOL | MINOR); addConfigurationProperty("touch.size.calibration", "geometric"); - MultiTouchInputMapper& mapper = addMapperAndConfigure(); + MultiTouchInputMapper& mapper = constructAndAddMapper(); // These calculations are based on the input device calibration documentation. int32_t rawX = 100; @@ -8005,13 +8511,13 @@ TEST_F(MultiTouchInputMapperTest, Process_TouchAndToolAxes_GeometricCalibration) TEST_F(MultiTouchInputMapperTest, Process_TouchAndToolAxes_SummedLinearCalibration) { addConfigurationProperty("touch.deviceType", "touchScreen"); - prepareDisplay(DISPLAY_ORIENTATION_0); + prepareDisplay(ui::ROTATION_0); prepareAxes(POSITION | TOUCH | TOOL); addConfigurationProperty("touch.size.calibration", "diameter"); addConfigurationProperty("touch.size.scale", "10"); addConfigurationProperty("touch.size.bias", "160"); addConfigurationProperty("touch.size.isSummed", "1"); - MultiTouchInputMapper& mapper = addMapperAndConfigure(); + MultiTouchInputMapper& mapper = constructAndAddMapper(); // These calculations are based on the input device calibration documentation. // Note: We only provide a single common touch/tool value because the device is assumed @@ -8056,12 +8562,12 @@ TEST_F(MultiTouchInputMapperTest, Process_TouchAndToolAxes_SummedLinearCalibrati TEST_F(MultiTouchInputMapperTest, Process_TouchAndToolAxes_AreaCalibration) { addConfigurationProperty("touch.deviceType", "touchScreen"); - prepareDisplay(DISPLAY_ORIENTATION_0); + prepareDisplay(ui::ROTATION_0); prepareAxes(POSITION | TOUCH | TOOL); addConfigurationProperty("touch.size.calibration", "area"); addConfigurationProperty("touch.size.scale", "43"); addConfigurationProperty("touch.size.bias", "3"); - MultiTouchInputMapper& mapper = addMapperAndConfigure(); + MultiTouchInputMapper& mapper = constructAndAddMapper(); // These calculations are based on the input device calibration documentation. int32_t rawX = 100; @@ -8089,14 +8595,14 @@ TEST_F(MultiTouchInputMapperTest, Process_TouchAndToolAxes_AreaCalibration) { TEST_F(MultiTouchInputMapperTest, Process_PressureAxis_AmplitudeCalibration) { addConfigurationProperty("touch.deviceType", "touchScreen"); - prepareDisplay(DISPLAY_ORIENTATION_0); + prepareDisplay(ui::ROTATION_0); prepareAxes(POSITION | PRESSURE); addConfigurationProperty("touch.pressure.calibration", "amplitude"); addConfigurationProperty("touch.pressure.scale", "0.01"); - MultiTouchInputMapper& mapper = addMapperAndConfigure(); + MultiTouchInputMapper& mapper = constructAndAddMapper(); InputDeviceInfo info; - mapper.populateDeviceInfo(&info); + mapper.populateDeviceInfo(info); ASSERT_NO_FATAL_FAILURE(assertMotionRange(info, AINPUT_MOTION_RANGE_PRESSURE, AINPUT_SOURCE_TOUCHSCREEN, 0.0f, RAW_PRESSURE_MAX * 0.01, 0.0f, 0.0f)); @@ -8123,9 +8629,9 @@ TEST_F(MultiTouchInputMapperTest, Process_PressureAxis_AmplitudeCalibration) { TEST_F(MultiTouchInputMapperTest, Process_ShouldHandleAllButtons) { addConfigurationProperty("touch.deviceType", "touchScreen"); - prepareDisplay(DISPLAY_ORIENTATION_0); + prepareDisplay(ui::ROTATION_0); prepareAxes(POSITION | ID | SLOT); - MultiTouchInputMapper& mapper = addMapperAndConfigure(); + MultiTouchInputMapper& mapper = constructAndAddMapper(); NotifyMotionArgs motionArgs; NotifyKeyArgs keyArgs; @@ -8364,11 +8870,68 @@ TEST_F(MultiTouchInputMapperTest, Process_ShouldHandleAllButtons) { ASSERT_EQ(0, motionArgs.buttonState); } +TEST_F(MultiTouchInputMapperTest, Process_ShouldHandleMappedStylusButtons) { + addConfigurationProperty("touch.deviceType", "touchScreen"); + prepareDisplay(ui::ROTATION_0); + prepareAxes(POSITION | ID | SLOT); + MultiTouchInputMapper& mapper = constructAndAddMapper(); + + mFakeEventHub->addKey(EVENTHUB_ID, BTN_A, 0, AKEYCODE_STYLUS_BUTTON_PRIMARY, 0); + mFakeEventHub->addKey(EVENTHUB_ID, 0, 0xabcd, AKEYCODE_STYLUS_BUTTON_SECONDARY, 0); + + // Touch down. + processId(mapper, 1); + processPosition(mapper, 100, 200); + processSync(mapper); + ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled( + AllOf(WithMotionAction(AMOTION_EVENT_ACTION_DOWN), WithButtonState(0)))); + + // Press and release button mapped to the primary stylus button. + processKey(mapper, BTN_A, 1); + processSync(mapper); + ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled( + AllOf(WithMotionAction(AMOTION_EVENT_ACTION_MOVE), + WithButtonState(AMOTION_EVENT_BUTTON_STYLUS_PRIMARY)))); + ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled( + AllOf(WithMotionAction(AMOTION_EVENT_ACTION_BUTTON_PRESS), + WithButtonState(AMOTION_EVENT_BUTTON_STYLUS_PRIMARY)))); + + processKey(mapper, BTN_A, 0); + processSync(mapper); + ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled( + AllOf(WithMotionAction(AMOTION_EVENT_ACTION_BUTTON_RELEASE), WithButtonState(0)))); + ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled( + AllOf(WithMotionAction(AMOTION_EVENT_ACTION_MOVE), WithButtonState(0)))); + + // Press and release the HID usage mapped to the secondary stylus button. + processHidUsage(mapper, 0xabcd, 1); + processSync(mapper); + ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled( + AllOf(WithMotionAction(AMOTION_EVENT_ACTION_MOVE), + WithButtonState(AMOTION_EVENT_BUTTON_STYLUS_SECONDARY)))); + ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled( + AllOf(WithMotionAction(AMOTION_EVENT_ACTION_BUTTON_PRESS), + WithButtonState(AMOTION_EVENT_BUTTON_STYLUS_SECONDARY)))); + + processHidUsage(mapper, 0xabcd, 0); + processSync(mapper); + ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled( + AllOf(WithMotionAction(AMOTION_EVENT_ACTION_BUTTON_RELEASE), WithButtonState(0)))); + ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled( + AllOf(WithMotionAction(AMOTION_EVENT_ACTION_MOVE), WithButtonState(0)))); + + // Release touch. + processId(mapper, -1); + processSync(mapper); + ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled( + AllOf(WithMotionAction(AMOTION_EVENT_ACTION_UP), WithButtonState(0)))); +} + TEST_F(MultiTouchInputMapperTest, Process_ShouldHandleAllToolTypes) { addConfigurationProperty("touch.deviceType", "touchScreen"); - prepareDisplay(DISPLAY_ORIENTATION_0); + prepareDisplay(ui::ROTATION_0); prepareAxes(POSITION | ID | SLOT | TOOL_TYPE); - MultiTouchInputMapper& mapper = addMapperAndConfigure(); + MultiTouchInputMapper& mapper = constructAndAddMapper(); NotifyMotionArgs motionArgs; @@ -8378,14 +8941,14 @@ TEST_F(MultiTouchInputMapperTest, Process_ShouldHandleAllToolTypes) { processSync(mapper); ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs)); ASSERT_EQ(AMOTION_EVENT_ACTION_DOWN, motionArgs.action); - ASSERT_EQ(AMOTION_EVENT_TOOL_TYPE_FINGER, motionArgs.pointerProperties[0].toolType); + ASSERT_EQ(ToolType::FINGER, motionArgs.pointerProperties[0].toolType); // eraser processKey(mapper, BTN_TOOL_RUBBER, 1); processSync(mapper); ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs)); ASSERT_EQ(AMOTION_EVENT_ACTION_MOVE, motionArgs.action); - ASSERT_EQ(AMOTION_EVENT_TOOL_TYPE_ERASER, motionArgs.pointerProperties[0].toolType); + ASSERT_EQ(ToolType::ERASER, motionArgs.pointerProperties[0].toolType); // stylus processKey(mapper, BTN_TOOL_RUBBER, 0); @@ -8393,7 +8956,7 @@ TEST_F(MultiTouchInputMapperTest, Process_ShouldHandleAllToolTypes) { processSync(mapper); ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs)); ASSERT_EQ(AMOTION_EVENT_ACTION_MOVE, motionArgs.action); - ASSERT_EQ(AMOTION_EVENT_TOOL_TYPE_STYLUS, motionArgs.pointerProperties[0].toolType); + ASSERT_EQ(ToolType::STYLUS, motionArgs.pointerProperties[0].toolType); // brush processKey(mapper, BTN_TOOL_PEN, 0); @@ -8401,7 +8964,7 @@ TEST_F(MultiTouchInputMapperTest, Process_ShouldHandleAllToolTypes) { processSync(mapper); ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs)); ASSERT_EQ(AMOTION_EVENT_ACTION_MOVE, motionArgs.action); - ASSERT_EQ(AMOTION_EVENT_TOOL_TYPE_STYLUS, motionArgs.pointerProperties[0].toolType); + ASSERT_EQ(ToolType::STYLUS, motionArgs.pointerProperties[0].toolType); // pencil processKey(mapper, BTN_TOOL_BRUSH, 0); @@ -8409,7 +8972,7 @@ TEST_F(MultiTouchInputMapperTest, Process_ShouldHandleAllToolTypes) { processSync(mapper); ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs)); ASSERT_EQ(AMOTION_EVENT_ACTION_MOVE, motionArgs.action); - ASSERT_EQ(AMOTION_EVENT_TOOL_TYPE_STYLUS, motionArgs.pointerProperties[0].toolType); + ASSERT_EQ(ToolType::STYLUS, motionArgs.pointerProperties[0].toolType); // air-brush processKey(mapper, BTN_TOOL_PENCIL, 0); @@ -8417,7 +8980,7 @@ TEST_F(MultiTouchInputMapperTest, Process_ShouldHandleAllToolTypes) { processSync(mapper); ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs)); ASSERT_EQ(AMOTION_EVENT_ACTION_MOVE, motionArgs.action); - ASSERT_EQ(AMOTION_EVENT_TOOL_TYPE_STYLUS, motionArgs.pointerProperties[0].toolType); + ASSERT_EQ(ToolType::STYLUS, motionArgs.pointerProperties[0].toolType); // mouse processKey(mapper, BTN_TOOL_AIRBRUSH, 0); @@ -8425,7 +8988,7 @@ TEST_F(MultiTouchInputMapperTest, Process_ShouldHandleAllToolTypes) { processSync(mapper); ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs)); ASSERT_EQ(AMOTION_EVENT_ACTION_MOVE, motionArgs.action); - ASSERT_EQ(AMOTION_EVENT_TOOL_TYPE_MOUSE, motionArgs.pointerProperties[0].toolType); + ASSERT_EQ(ToolType::MOUSE, motionArgs.pointerProperties[0].toolType); // lens processKey(mapper, BTN_TOOL_MOUSE, 0); @@ -8433,7 +8996,7 @@ TEST_F(MultiTouchInputMapperTest, Process_ShouldHandleAllToolTypes) { processSync(mapper); ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs)); ASSERT_EQ(AMOTION_EVENT_ACTION_MOVE, motionArgs.action); - ASSERT_EQ(AMOTION_EVENT_TOOL_TYPE_MOUSE, motionArgs.pointerProperties[0].toolType); + ASSERT_EQ(ToolType::MOUSE, motionArgs.pointerProperties[0].toolType); // double-tap processKey(mapper, BTN_TOOL_LENS, 0); @@ -8441,7 +9004,7 @@ TEST_F(MultiTouchInputMapperTest, Process_ShouldHandleAllToolTypes) { processSync(mapper); ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs)); ASSERT_EQ(AMOTION_EVENT_ACTION_MOVE, motionArgs.action); - ASSERT_EQ(AMOTION_EVENT_TOOL_TYPE_FINGER, motionArgs.pointerProperties[0].toolType); + ASSERT_EQ(ToolType::FINGER, motionArgs.pointerProperties[0].toolType); // triple-tap processKey(mapper, BTN_TOOL_DOUBLETAP, 0); @@ -8449,7 +9012,7 @@ TEST_F(MultiTouchInputMapperTest, Process_ShouldHandleAllToolTypes) { processSync(mapper); ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs)); ASSERT_EQ(AMOTION_EVENT_ACTION_MOVE, motionArgs.action); - ASSERT_EQ(AMOTION_EVENT_TOOL_TYPE_FINGER, motionArgs.pointerProperties[0].toolType); + ASSERT_EQ(ToolType::FINGER, motionArgs.pointerProperties[0].toolType); // quad-tap processKey(mapper, BTN_TOOL_TRIPLETAP, 0); @@ -8457,7 +9020,7 @@ TEST_F(MultiTouchInputMapperTest, Process_ShouldHandleAllToolTypes) { processSync(mapper); ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs)); ASSERT_EQ(AMOTION_EVENT_ACTION_MOVE, motionArgs.action); - ASSERT_EQ(AMOTION_EVENT_TOOL_TYPE_FINGER, motionArgs.pointerProperties[0].toolType); + ASSERT_EQ(ToolType::FINGER, motionArgs.pointerProperties[0].toolType); // finger processKey(mapper, BTN_TOOL_QUADTAP, 0); @@ -8465,42 +9028,42 @@ TEST_F(MultiTouchInputMapperTest, Process_ShouldHandleAllToolTypes) { processSync(mapper); ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs)); ASSERT_EQ(AMOTION_EVENT_ACTION_MOVE, motionArgs.action); - ASSERT_EQ(AMOTION_EVENT_TOOL_TYPE_FINGER, motionArgs.pointerProperties[0].toolType); + ASSERT_EQ(ToolType::FINGER, motionArgs.pointerProperties[0].toolType); // stylus trumps finger processKey(mapper, BTN_TOOL_PEN, 1); processSync(mapper); ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs)); ASSERT_EQ(AMOTION_EVENT_ACTION_MOVE, motionArgs.action); - ASSERT_EQ(AMOTION_EVENT_TOOL_TYPE_STYLUS, motionArgs.pointerProperties[0].toolType); + ASSERT_EQ(ToolType::STYLUS, motionArgs.pointerProperties[0].toolType); // eraser trumps stylus processKey(mapper, BTN_TOOL_RUBBER, 1); processSync(mapper); ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs)); ASSERT_EQ(AMOTION_EVENT_ACTION_MOVE, motionArgs.action); - ASSERT_EQ(AMOTION_EVENT_TOOL_TYPE_ERASER, motionArgs.pointerProperties[0].toolType); + ASSERT_EQ(ToolType::ERASER, motionArgs.pointerProperties[0].toolType); // mouse trumps eraser processKey(mapper, BTN_TOOL_MOUSE, 1); processSync(mapper); ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs)); ASSERT_EQ(AMOTION_EVENT_ACTION_MOVE, motionArgs.action); - ASSERT_EQ(AMOTION_EVENT_TOOL_TYPE_MOUSE, motionArgs.pointerProperties[0].toolType); + ASSERT_EQ(ToolType::MOUSE, motionArgs.pointerProperties[0].toolType); // MT tool type trumps BTN tool types: MT_TOOL_FINGER processToolType(mapper, MT_TOOL_FINGER); // this is the first time we send MT_TOOL_TYPE processSync(mapper); ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs)); ASSERT_EQ(AMOTION_EVENT_ACTION_MOVE, motionArgs.action); - ASSERT_EQ(AMOTION_EVENT_TOOL_TYPE_FINGER, motionArgs.pointerProperties[0].toolType); + ASSERT_EQ(ToolType::FINGER, motionArgs.pointerProperties[0].toolType); // MT tool type trumps BTN tool types: MT_TOOL_PEN processToolType(mapper, MT_TOOL_PEN); processSync(mapper); ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs)); ASSERT_EQ(AMOTION_EVENT_ACTION_MOVE, motionArgs.action); - ASSERT_EQ(AMOTION_EVENT_TOOL_TYPE_STYLUS, motionArgs.pointerProperties[0].toolType); + ASSERT_EQ(ToolType::STYLUS, motionArgs.pointerProperties[0].toolType); // back to default tool type processToolType(mapper, -1); // use a deliberately undefined tool type, for testing @@ -8511,15 +9074,15 @@ TEST_F(MultiTouchInputMapperTest, Process_ShouldHandleAllToolTypes) { processSync(mapper); ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs)); ASSERT_EQ(AMOTION_EVENT_ACTION_MOVE, motionArgs.action); - ASSERT_EQ(AMOTION_EVENT_TOOL_TYPE_FINGER, motionArgs.pointerProperties[0].toolType); + ASSERT_EQ(ToolType::FINGER, motionArgs.pointerProperties[0].toolType); } TEST_F(MultiTouchInputMapperTest, Process_WhenBtnTouchPresent_HoversIfItsValueIsZero) { addConfigurationProperty("touch.deviceType", "touchScreen"); - prepareDisplay(DISPLAY_ORIENTATION_0); + prepareDisplay(ui::ROTATION_0); prepareAxes(POSITION | ID | SLOT); mFakeEventHub->addKey(EVENTHUB_ID, BTN_TOUCH, 0, AKEYCODE_UNKNOWN, 0); - MultiTouchInputMapper& mapper = addMapperAndConfigure(); + MultiTouchInputMapper& mapper = constructAndAddMapper(); NotifyMotionArgs motionArgs; @@ -8587,9 +9150,9 @@ TEST_F(MultiTouchInputMapperTest, Process_WhenBtnTouchPresent_HoversIfItsValueIs TEST_F(MultiTouchInputMapperTest, Process_WhenAbsMTPressureIsPresent_HoversIfItsValueIsZero) { addConfigurationProperty("touch.deviceType", "touchScreen"); - prepareDisplay(DISPLAY_ORIENTATION_0); + prepareDisplay(ui::ROTATION_0); prepareAxes(POSITION | ID | SLOT | PRESSURE); - MultiTouchInputMapper& mapper = addMapperAndConfigure(); + MultiTouchInputMapper& mapper = constructAndAddMapper(); NotifyMotionArgs motionArgs; @@ -8670,7 +9233,7 @@ TEST_F(MultiTouchInputMapperTest, Configure_AssignsDisplayPort) { addConfigurationProperty("touch.deviceType", "touchScreen"); prepareAxes(POSITION); - MultiTouchInputMapper& mapper = addMapperAndConfigure(); + MultiTouchInputMapper& mapper = constructAndAddMapper(); mFakePolicy->addInputPortAssociation(DEVICE_LOCATION, hdmi1); mFakePolicy->addInputPortAssociation(usb2, hdmi2); @@ -8687,7 +9250,7 @@ TEST_F(MultiTouchInputMapperTest, Configure_AssignsDisplayPort) { ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasNotCalled()); // Add viewport for display 1 on hdmi1 - prepareDisplay(DISPLAY_ORIENTATION_0, hdmi1); + prepareDisplay(ui::ROTATION_0, hdmi1); // Send a touch event again processPosition(mapper, 100, 100); processSync(mapper); @@ -8700,12 +9263,12 @@ TEST_F(MultiTouchInputMapperTest, Configure_AssignsDisplayPort) { TEST_F(MultiTouchInputMapperTest, Configure_AssignsDisplayUniqueId) { addConfigurationProperty("touch.deviceType", "touchScreen"); prepareAxes(POSITION); - MultiTouchInputMapper& mapper = addMapperAndConfigure(); + MultiTouchInputMapper& mapper = constructAndAddMapper(); mFakePolicy->addInputUniqueIdAssociation(DEVICE_LOCATION, VIRTUAL_DISPLAY_UNIQUE_ID); - prepareDisplay(DISPLAY_ORIENTATION_0); - prepareVirtualDisplay(DISPLAY_ORIENTATION_0); + prepareDisplay(ui::ROTATION_0); + prepareVirtualDisplay(ui::ROTATION_0); // Send a touch event processPosition(mapper, 100, 100); @@ -8722,15 +9285,14 @@ TEST_F(MultiTouchInputMapperTest, Process_Pointer_ShouldHandleDisplayId) { std::make_shared(); fakePointerController->setBounds(0, 0, DISPLAY_WIDTH - 1, DISPLAY_HEIGHT - 1); fakePointerController->setPosition(100, 200); - fakePointerController->setButtonState(0); mFakePolicy->setPointerController(fakePointerController); mFakePolicy->setDefaultPointerDisplayId(SECONDARY_DISPLAY_ID); prepareSecondaryDisplay(ViewportType::EXTERNAL); - prepareDisplay(DISPLAY_ORIENTATION_0); + prepareDisplay(ui::ROTATION_0); prepareAxes(POSITION); - MultiTouchInputMapper& mapper = addMapperAndConfigure(); + MultiTouchInputMapper& mapper = constructAndAddMapper(); // Check source is mouse that would obtain the PointerController. ASSERT_EQ(AINPUT_SOURCE_MOUSE, mapper.getSources()); @@ -8750,21 +9312,21 @@ TEST_F(MultiTouchInputMapperTest, Process_Pointer_ShouldHandleDisplayId) { TEST_F(MultiTouchInputMapperTest, Process_SendsReadTime) { addConfigurationProperty("touch.deviceType", "touchScreen"); prepareAxes(POSITION); - MultiTouchInputMapper& mapper = addMapperAndConfigure(); + MultiTouchInputMapper& mapper = constructAndAddMapper(); - prepareDisplay(DISPLAY_ORIENTATION_0); - process(mapper, 10, 11 /*readTime*/, EV_ABS, ABS_MT_TRACKING_ID, 1); - process(mapper, 15, 16 /*readTime*/, EV_ABS, ABS_MT_POSITION_X, 100); - process(mapper, 20, 21 /*readTime*/, EV_ABS, ABS_MT_POSITION_Y, 100); - process(mapper, 25, 26 /*readTime*/, EV_SYN, SYN_REPORT, 0); + prepareDisplay(ui::ROTATION_0); + process(mapper, 10, /*readTime=*/11, EV_ABS, ABS_MT_TRACKING_ID, 1); + process(mapper, 15, /*readTime=*/16, EV_ABS, ABS_MT_POSITION_X, 100); + process(mapper, 20, /*readTime=*/21, EV_ABS, ABS_MT_POSITION_Y, 100); + process(mapper, 25, /*readTime=*/26, EV_SYN, SYN_REPORT, 0); NotifyMotionArgs args; ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&args)); ASSERT_EQ(26, args.readTime); - process(mapper, 30, 31 /*readTime*/, EV_ABS, ABS_MT_POSITION_X, 110); - process(mapper, 30, 32 /*readTime*/, EV_ABS, ABS_MT_POSITION_Y, 220); - process(mapper, 30, 33 /*readTime*/, EV_SYN, SYN_REPORT, 0); + process(mapper, 30, /*readTime=*/31, EV_ABS, ABS_MT_POSITION_X, 110); + process(mapper, 30, /*readTime=*/32, EV_ABS, ABS_MT_POSITION_Y, 220); + process(mapper, 30, /*readTime=*/33, EV_SYN, SYN_REPORT, 0); ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&args)); ASSERT_EQ(33, args.readTime); @@ -8776,12 +9338,12 @@ TEST_F(MultiTouchInputMapperTest, Process_SendsReadTime) { */ TEST_F(MultiTouchInputMapperTest, WhenViewportIsNotActive_TouchesAreDropped) { addConfigurationProperty("touch.deviceType", "touchScreen"); - mFakePolicy->addDisplayViewport(DISPLAY_ID, DISPLAY_WIDTH, DISPLAY_HEIGHT, - DISPLAY_ORIENTATION_0, false /*isActive*/, UNIQUE_ID, NO_PORT, - ViewportType::INTERNAL); - configureDevice(InputReaderConfiguration::CHANGE_DISPLAY_INFO); + // Don't set touch.enableForInactiveViewport to verify the default behavior. + mFakePolicy->addDisplayViewport(DISPLAY_ID, DISPLAY_WIDTH, DISPLAY_HEIGHT, ui::ROTATION_0, + /*isActive=*/false, UNIQUE_ID, NO_PORT, ViewportType::INTERNAL); + configureDevice(InputReaderConfiguration::Change::DISPLAY_INFO); prepareAxes(POSITION); - MultiTouchInputMapper& mapper = addMapperAndConfigure(); + MultiTouchInputMapper& mapper = constructAndAddMapper(); NotifyMotionArgs motionArgs; processPosition(mapper, 100, 100); @@ -8790,19 +9352,40 @@ TEST_F(MultiTouchInputMapperTest, WhenViewportIsNotActive_TouchesAreDropped) { mFakeListener->assertNotifyMotionWasNotCalled(); } +/** + * When the viewport is not active (isActive=false) and touch.enableForInactiveViewport is true, + * the touch mapper can process the events and the events can be delivered to the listener. + */ +TEST_F(MultiTouchInputMapperTest, WhenViewportIsNotActive_TouchesAreProcessed) { + addConfigurationProperty("touch.deviceType", "touchScreen"); + addConfigurationProperty("touch.enableForInactiveViewport", "1"); + mFakePolicy->addDisplayViewport(DISPLAY_ID, DISPLAY_WIDTH, DISPLAY_HEIGHT, ui::ROTATION_0, + /*isActive=*/false, UNIQUE_ID, NO_PORT, ViewportType::INTERNAL); + configureDevice(InputReaderConfiguration::Change::DISPLAY_INFO); + prepareAxes(POSITION); + MultiTouchInputMapper& mapper = constructAndAddMapper(); + + NotifyMotionArgs motionArgs; + processPosition(mapper, 100, 100); + processSync(mapper); + + ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs)); + EXPECT_EQ(AMOTION_EVENT_ACTION_DOWN, motionArgs.action); +} + TEST_F(MultiTouchInputMapperTest, Process_DeactivateViewport_AbortTouches) { addConfigurationProperty("touch.deviceType", "touchScreen"); - mFakePolicy->addDisplayViewport(DISPLAY_ID, DISPLAY_WIDTH, DISPLAY_HEIGHT, - DISPLAY_ORIENTATION_0, true /*isActive*/, UNIQUE_ID, NO_PORT, - ViewportType::INTERNAL); + addConfigurationProperty("touch.enableForInactiveViewport", "0"); + mFakePolicy->addDisplayViewport(DISPLAY_ID, DISPLAY_WIDTH, DISPLAY_HEIGHT, ui::ROTATION_0, + /*isActive=*/true, UNIQUE_ID, NO_PORT, ViewportType::INTERNAL); std::optional optionalDisplayViewport = mFakePolicy->getDisplayViewportByUniqueId(UNIQUE_ID); ASSERT_TRUE(optionalDisplayViewport.has_value()); DisplayViewport displayViewport = *optionalDisplayViewport; - configureDevice(InputReaderConfiguration::CHANGE_DISPLAY_INFO); + configureDevice(InputReaderConfiguration::Change::DISPLAY_INFO); prepareAxes(POSITION); - MultiTouchInputMapper& mapper = addMapperAndConfigure(); + MultiTouchInputMapper& mapper = constructAndAddMapper(); // Finger down int32_t x = 100, y = 100; @@ -8816,7 +9399,7 @@ TEST_F(MultiTouchInputMapperTest, Process_DeactivateViewport_AbortTouches) { // Deactivate display viewport displayViewport.isActive = false; ASSERT_TRUE(mFakePolicy->updateViewport(displayViewport)); - configureDevice(InputReaderConfiguration::CHANGE_DISPLAY_INFO); + configureDevice(InputReaderConfiguration::Change::DISPLAY_INFO); // The ongoing touch should be canceled immediately ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs)); @@ -8831,7 +9414,7 @@ TEST_F(MultiTouchInputMapperTest, Process_DeactivateViewport_AbortTouches) { // Reactivate display viewport displayViewport.isActive = true; ASSERT_TRUE(mFakePolicy->updateViewport(displayViewport)); - configureDevice(InputReaderConfiguration::CHANGE_DISPLAY_INFO); + configureDevice(InputReaderConfiguration::Change::DISPLAY_INFO); // Finger move again starts new gesture x += 10, y += 10; @@ -8845,7 +9428,7 @@ TEST_F(MultiTouchInputMapperTest, Process_Pointer_ShowTouches) { // Setup the first touch screen device. prepareAxes(POSITION | ID | SLOT); addConfigurationProperty("touch.deviceType", "touchScreen"); - MultiTouchInputMapper& mapper = addMapperAndConfigure(); + MultiTouchInputMapper& mapper = constructAndAddMapper(); // Create the second touch screen device, and enable multi fingers. const std::string USB2 = "USB2"; @@ -8857,21 +9440,25 @@ TEST_F(MultiTouchInputMapperTest, Process_Pointer_ShowTouches) { ftl::Flags(0)); mFakeEventHub->addAbsoluteAxis(SECOND_EVENTHUB_ID, ABS_MT_POSITION_X, RAW_X_MIN, RAW_X_MAX, - 0 /*flat*/, 0 /*fuzz*/); + /*flat=*/0, /*fuzz=*/0); mFakeEventHub->addAbsoluteAxis(SECOND_EVENTHUB_ID, ABS_MT_POSITION_Y, RAW_Y_MIN, RAW_Y_MAX, - 0 /*flat*/, 0 /*fuzz*/); + /*flat=*/0, /*fuzz=*/0); mFakeEventHub->addAbsoluteAxis(SECOND_EVENTHUB_ID, ABS_MT_TRACKING_ID, RAW_ID_MIN, RAW_ID_MAX, - 0 /*flat*/, 0 /*fuzz*/); + /*flat=*/0, /*fuzz=*/0); mFakeEventHub->addAbsoluteAxis(SECOND_EVENTHUB_ID, ABS_MT_SLOT, RAW_SLOT_MIN, RAW_SLOT_MAX, - 0 /*flat*/, 0 /*fuzz*/); - mFakeEventHub->setAbsoluteAxisValue(SECOND_EVENTHUB_ID, ABS_MT_SLOT, 0 /*value*/); + /*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. - MultiTouchInputMapper& mapper2 = device2->addMapper(SECOND_EVENTHUB_ID); - device2->configure(ARBITRARY_TIME, mFakePolicy->getReaderConfiguration(), 0 /*changes*/); - device2->reset(ARBITRARY_TIME); + 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 = @@ -8886,13 +9473,13 @@ TEST_F(MultiTouchInputMapperTest, Process_Pointer_ShowTouches) { mFakePolicy->setShowTouches(true); // Create displays. - prepareDisplay(DISPLAY_ORIENTATION_0, hdmi1); + prepareDisplay(ui::ROTATION_0, hdmi1); prepareSecondaryDisplay(ViewportType::EXTERNAL, hdmi2); // Default device will reconfigure above, need additional reconfiguration for another device. - device2->configure(ARBITRARY_TIME, mFakePolicy->getReaderConfiguration(), - InputReaderConfiguration::CHANGE_DISPLAY_INFO | - InputReaderConfiguration::CHANGE_SHOW_TOUCHES); + 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; @@ -8922,8 +9509,8 @@ TEST_F(MultiTouchInputMapperTest, Process_Pointer_ShowTouches) { // Disable the show touches configuration and ensure the spots are cleared. mFakePolicy->setShowTouches(false); - device2->configure(ARBITRARY_TIME, mFakePolicy->getReaderConfiguration(), - InputReaderConfiguration::CHANGE_SHOW_TOUCHES); + unused += device2->configure(ARBITRARY_TIME, mFakePolicy->getReaderConfiguration(), + InputReaderConfiguration::Change::SHOW_TOUCHES); ASSERT_TRUE(fakePointerController->getSpots().empty()); } @@ -8931,8 +9518,8 @@ TEST_F(MultiTouchInputMapperTest, Process_Pointer_ShowTouches) { TEST_F(MultiTouchInputMapperTest, VideoFrames_ReceivedByListener) { prepareAxes(POSITION); addConfigurationProperty("touch.deviceType", "touchScreen"); - prepareDisplay(DISPLAY_ORIENTATION_0); - MultiTouchInputMapper& mapper = addMapperAndConfigure(); + prepareDisplay(ui::ROTATION_0); + MultiTouchInputMapper& mapper = constructAndAddMapper(); NotifyMotionArgs motionArgs; // Unrotated video frame @@ -8956,14 +9543,13 @@ TEST_F(MultiTouchInputMapperTest, VideoFrames_ReceivedByListener) { TEST_F(MultiTouchInputMapperTest, VideoFrames_AreNotRotated) { prepareAxes(POSITION); addConfigurationProperty("touch.deviceType", "touchScreen"); - MultiTouchInputMapper& mapper = addMapperAndConfigure(); + MultiTouchInputMapper& mapper = constructAndAddMapper(); // Unrotated video frame TouchVideoFrame frame(3, 2, {1, 2, 3, 4, 5, 6}, {1, 2}); NotifyMotionArgs motionArgs; // Test all 4 orientations - for (int32_t orientation : {DISPLAY_ORIENTATION_0, DISPLAY_ORIENTATION_90, - DISPLAY_ORIENTATION_180, DISPLAY_ORIENTATION_270}) { + for (ui::Rotation orientation : ftl::enum_range()) { SCOPED_TRACE("Orientation " + StringPrintf("%i", orientation)); clearViewports(); prepareDisplay(orientation); @@ -8982,14 +9568,13 @@ TEST_F(MultiTouchInputMapperTest, VideoFrames_WhenNotOrientationAware_AreRotated // Since InputReader works in the un-rotated coordinate space, only devices that are not // orientation-aware are affected by display rotation. addConfigurationProperty("touch.orientationAware", "0"); - MultiTouchInputMapper& mapper = addMapperAndConfigure(); + MultiTouchInputMapper& mapper = constructAndAddMapper(); // Unrotated video frame TouchVideoFrame frame(3, 2, {1, 2, 3, 4, 5, 6}, {1, 2}); NotifyMotionArgs motionArgs; // Test all 4 orientations - for (int32_t orientation : {DISPLAY_ORIENTATION_0, DISPLAY_ORIENTATION_90, - DISPLAY_ORIENTATION_180, DISPLAY_ORIENTATION_270}) { + for (ui::Rotation orientation : ftl::enum_range()) { SCOPED_TRACE("Orientation " + StringPrintf("%i", orientation)); clearViewports(); prepareDisplay(orientation); @@ -9014,7 +9599,7 @@ TEST_F(MultiTouchInputMapperTest, VideoFrames_WhenNotOrientationAware_AreRotated TEST_F(MultiTouchInputMapperTest, VideoFrames_MultipleFramesAreNotRotated) { prepareAxes(POSITION); addConfigurationProperty("touch.deviceType", "touchScreen"); - MultiTouchInputMapper& mapper = addMapperAndConfigure(); + MultiTouchInputMapper& mapper = constructAndAddMapper(); // Unrotated video frames. There's no rule that they must all have the same dimensions, // so mix these. TouchVideoFrame frame1(3, 2, {1, 2, 3, 4, 5, 6}, {1, 2}); @@ -9023,7 +9608,7 @@ TEST_F(MultiTouchInputMapperTest, VideoFrames_MultipleFramesAreNotRotated) { std::vector frames{frame1, frame2, frame3}; NotifyMotionArgs motionArgs; - prepareDisplay(DISPLAY_ORIENTATION_90); + prepareDisplay(ui::ROTATION_90); mFakeEventHub->setVideoFrames({{EVENTHUB_ID, frames}}); processPosition(mapper, 100, 200); processSync(mapper); @@ -9037,7 +9622,7 @@ TEST_F(MultiTouchInputMapperTest, VideoFrames_WhenNotOrientationAware_MultipleFr // Since InputReader works in the un-rotated coordinate space, only devices that are not // orientation-aware are affected by display rotation. addConfigurationProperty("touch.orientationAware", "0"); - MultiTouchInputMapper& mapper = addMapperAndConfigure(); + MultiTouchInputMapper& mapper = constructAndAddMapper(); // Unrotated video frames. There's no rule that they must all have the same dimensions, // so mix these. TouchVideoFrame frame1(3, 2, {1, 2, 3, 4, 5, 6}, {1, 2}); @@ -9046,7 +9631,7 @@ TEST_F(MultiTouchInputMapperTest, VideoFrames_WhenNotOrientationAware_MultipleFr std::vector frames{frame1, frame2, frame3}; NotifyMotionArgs motionArgs; - prepareDisplay(DISPLAY_ORIENTATION_90); + prepareDisplay(ui::ROTATION_90); mFakeEventHub->setVideoFrames({{EVENTHUB_ID, frames}}); processPosition(mapper, 100, 200); processSync(mapper); @@ -9056,7 +9641,7 @@ TEST_F(MultiTouchInputMapperTest, VideoFrames_WhenNotOrientationAware_MultipleFr // compared to the display. This is so that when the window transform (which contains the // display rotation) is applied later by InputDispatcher, the coordinates end up in the // window's coordinate space. - frame.rotate(getInverseRotation(DISPLAY_ORIENTATION_90)); + frame.rotate(getInverseRotation(ui::ROTATION_90)); }); ASSERT_EQ(frames, motionArgs.videoFrames); } @@ -9074,7 +9659,7 @@ TEST_F(MultiTouchInputMapperTest, Configure_EnabledForAssociatedDisplay) { addConfigurationProperty("touch.deviceType", "touchScreen"); prepareAxes(POSITION); - MultiTouchInputMapper& mapper = addMapperAndConfigure(); + MultiTouchInputMapper& mapper = constructAndAddMapper(); ASSERT_EQ(mDevice->isEnabled(), false); @@ -9093,9 +9678,9 @@ TEST_F(MultiTouchInputMapperTest, Configure_EnabledForAssociatedDisplay) { TEST_F(MultiTouchInputMapperTest, Process_ShouldHandleSingleTouch) { addConfigurationProperty("touch.deviceType", "touchScreen"); - prepareDisplay(DISPLAY_ORIENTATION_0); + prepareDisplay(ui::ROTATION_0); prepareAxes(POSITION | ID | SLOT | TOOL_TYPE); - MultiTouchInputMapper& mapper = addMapperAndConfigure(); + MultiTouchInputMapper& mapper = constructAndAddMapper(); NotifyMotionArgs motionArgs; @@ -9106,7 +9691,7 @@ TEST_F(MultiTouchInputMapperTest, Process_ShouldHandleSingleTouch) { processSync(mapper); ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs)); ASSERT_EQ(AMOTION_EVENT_ACTION_DOWN, motionArgs.action); - ASSERT_EQ(AMOTION_EVENT_TOOL_TYPE_FINGER, motionArgs.pointerProperties[0].toolType); + ASSERT_EQ(ToolType::FINGER, motionArgs.pointerProperties[0].toolType); // finger move processId(mapper, 1); @@ -9114,14 +9699,14 @@ TEST_F(MultiTouchInputMapperTest, Process_ShouldHandleSingleTouch) { processSync(mapper); ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs)); ASSERT_EQ(AMOTION_EVENT_ACTION_MOVE, motionArgs.action); - ASSERT_EQ(AMOTION_EVENT_TOOL_TYPE_FINGER, motionArgs.pointerProperties[0].toolType); + ASSERT_EQ(ToolType::FINGER, motionArgs.pointerProperties[0].toolType); // finger up. processId(mapper, -1); processSync(mapper); ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs)); ASSERT_EQ(AMOTION_EVENT_ACTION_UP, motionArgs.action); - ASSERT_EQ(AMOTION_EVENT_TOOL_TYPE_FINGER, motionArgs.pointerProperties[0].toolType); + ASSERT_EQ(ToolType::FINGER, motionArgs.pointerProperties[0].toolType); // new finger down processId(mapper, 1); @@ -9129,7 +9714,7 @@ TEST_F(MultiTouchInputMapperTest, Process_ShouldHandleSingleTouch) { processSync(mapper); ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs)); ASSERT_EQ(AMOTION_EVENT_ACTION_DOWN, motionArgs.action); - ASSERT_EQ(AMOTION_EVENT_TOOL_TYPE_FINGER, motionArgs.pointerProperties[0].toolType); + ASSERT_EQ(ToolType::FINGER, motionArgs.pointerProperties[0].toolType); } /** @@ -9138,9 +9723,9 @@ TEST_F(MultiTouchInputMapperTest, Process_ShouldHandleSingleTouch) { */ TEST_F(MultiTouchInputMapperTest, Process_ShouldHandlePalmToolType_SinglePointer) { addConfigurationProperty("touch.deviceType", "touchScreen"); - prepareDisplay(DISPLAY_ORIENTATION_0); + prepareDisplay(ui::ROTATION_0); prepareAxes(POSITION | ID | SLOT | TOOL_TYPE); - MultiTouchInputMapper& mapper = addMapperAndConfigure(); + MultiTouchInputMapper& mapper = constructAndAddMapper(); NotifyMotionArgs motionArgs; @@ -9151,7 +9736,7 @@ TEST_F(MultiTouchInputMapperTest, Process_ShouldHandlePalmToolType_SinglePointer processSync(mapper); ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs)); ASSERT_EQ(AMOTION_EVENT_ACTION_DOWN, motionArgs.action); - ASSERT_EQ(AMOTION_EVENT_TOOL_TYPE_FINGER, motionArgs.pointerProperties[0].toolType); + ASSERT_EQ(ToolType::FINGER, motionArgs.pointerProperties[0].toolType); // Tool changed to MT_TOOL_PALM expect sending the cancel event. processToolType(mapper, MT_TOOL_PALM); @@ -9177,7 +9762,7 @@ TEST_F(MultiTouchInputMapperTest, Process_ShouldHandlePalmToolType_SinglePointer processSync(mapper); ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs)); ASSERT_EQ(AMOTION_EVENT_ACTION_DOWN, motionArgs.action); - ASSERT_EQ(AMOTION_EVENT_TOOL_TYPE_FINGER, motionArgs.pointerProperties[0].toolType); + ASSERT_EQ(ToolType::FINGER, motionArgs.pointerProperties[0].toolType); } /** @@ -9186,9 +9771,9 @@ TEST_F(MultiTouchInputMapperTest, Process_ShouldHandlePalmToolType_SinglePointer */ TEST_F(MultiTouchInputMapperTest, Process_ShouldHandlePalmToolType_TwoPointers) { addConfigurationProperty("touch.deviceType", "touchScreen"); - prepareDisplay(DISPLAY_ORIENTATION_0); + prepareDisplay(ui::ROTATION_0); prepareAxes(POSITION | ID | SLOT | TOOL_TYPE); - MultiTouchInputMapper& mapper = addMapperAndConfigure(); + MultiTouchInputMapper& mapper = constructAndAddMapper(); NotifyMotionArgs motionArgs; @@ -9199,7 +9784,7 @@ TEST_F(MultiTouchInputMapperTest, Process_ShouldHandlePalmToolType_TwoPointers) processSync(mapper); ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs)); ASSERT_EQ(AMOTION_EVENT_ACTION_DOWN, motionArgs.action); - ASSERT_EQ(AMOTION_EVENT_TOOL_TYPE_FINGER, motionArgs.pointerProperties[0].toolType); + ASSERT_EQ(ToolType::FINGER, motionArgs.pointerProperties[0].toolType); // Second finger down. processSlot(mapper, SECOND_SLOT); @@ -9208,7 +9793,7 @@ TEST_F(MultiTouchInputMapperTest, Process_ShouldHandlePalmToolType_TwoPointers) processSync(mapper); ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs)); ASSERT_EQ(ACTION_POINTER_1_DOWN, motionArgs.action); - ASSERT_EQ(AMOTION_EVENT_TOOL_TYPE_FINGER, motionArgs.pointerProperties[1].toolType); + ASSERT_EQ(ToolType::FINGER, motionArgs.pointerProperties[1].toolType); // If the tool type of the first finger changes to MT_TOOL_PALM, // we expect to receive ACTION_POINTER_UP with cancel flag. @@ -9261,9 +9846,9 @@ TEST_F(MultiTouchInputMapperTest, Process_ShouldHandlePalmToolType_TwoPointers) */ TEST_F(MultiTouchInputMapperTest, Process_ShouldHandlePalmToolType_ShouldCancelWhenAllTouchIsPalm) { addConfigurationProperty("touch.deviceType", "touchScreen"); - prepareDisplay(DISPLAY_ORIENTATION_0); + prepareDisplay(ui::ROTATION_0); prepareAxes(POSITION | ID | SLOT | TOOL_TYPE); - MultiTouchInputMapper& mapper = addMapperAndConfigure(); + MultiTouchInputMapper& mapper = constructAndAddMapper(); NotifyMotionArgs motionArgs; @@ -9274,7 +9859,7 @@ TEST_F(MultiTouchInputMapperTest, Process_ShouldHandlePalmToolType_ShouldCancelW processSync(mapper); ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs)); ASSERT_EQ(AMOTION_EVENT_ACTION_DOWN, motionArgs.action); - ASSERT_EQ(AMOTION_EVENT_TOOL_TYPE_FINGER, motionArgs.pointerProperties[0].toolType); + ASSERT_EQ(ToolType::FINGER, motionArgs.pointerProperties[0].toolType); // Second finger down. processSlot(mapper, SECOND_SLOT); @@ -9283,7 +9868,7 @@ TEST_F(MultiTouchInputMapperTest, Process_ShouldHandlePalmToolType_ShouldCancelW processSync(mapper); ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs)); ASSERT_EQ(ACTION_POINTER_1_DOWN, motionArgs.action); - ASSERT_EQ(AMOTION_EVENT_TOOL_TYPE_FINGER, motionArgs.pointerProperties[0].toolType); + ASSERT_EQ(ToolType::FINGER, motionArgs.pointerProperties[0].toolType); // If the tool type of the first finger changes to MT_TOOL_PALM, // we expect to receive ACTION_POINTER_UP with cancel flag. @@ -9318,7 +9903,7 @@ TEST_F(MultiTouchInputMapperTest, Process_ShouldHandlePalmToolType_ShouldCancelW processSync(mapper); ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs)); ASSERT_EQ(AMOTION_EVENT_ACTION_DOWN, motionArgs.action); - ASSERT_EQ(AMOTION_EVENT_TOOL_TYPE_FINGER, motionArgs.pointerProperties[0].toolType); + ASSERT_EQ(ToolType::FINGER, motionArgs.pointerProperties[0].toolType); ASSERT_EQ(uint32_t(1), motionArgs.pointerCount); // third finger move @@ -9359,9 +9944,9 @@ TEST_F(MultiTouchInputMapperTest, Process_ShouldHandlePalmToolType_ShouldCancelW */ TEST_F(MultiTouchInputMapperTest, Process_ShouldHandlePalmToolType_KeepFirstPointer) { addConfigurationProperty("touch.deviceType", "touchScreen"); - prepareDisplay(DISPLAY_ORIENTATION_0); + prepareDisplay(ui::ROTATION_0); prepareAxes(POSITION | ID | SLOT | TOOL_TYPE); - MultiTouchInputMapper& mapper = addMapperAndConfigure(); + MultiTouchInputMapper& mapper = constructAndAddMapper(); NotifyMotionArgs motionArgs; @@ -9372,7 +9957,7 @@ TEST_F(MultiTouchInputMapperTest, Process_ShouldHandlePalmToolType_KeepFirstPoin processSync(mapper); ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs)); ASSERT_EQ(AMOTION_EVENT_ACTION_DOWN, motionArgs.action); - ASSERT_EQ(AMOTION_EVENT_TOOL_TYPE_FINGER, motionArgs.pointerProperties[0].toolType); + ASSERT_EQ(ToolType::FINGER, motionArgs.pointerProperties[0].toolType); // Second finger down. processSlot(mapper, SECOND_SLOT); @@ -9381,7 +9966,7 @@ TEST_F(MultiTouchInputMapperTest, Process_ShouldHandlePalmToolType_KeepFirstPoin processSync(mapper); ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs)); ASSERT_EQ(ACTION_POINTER_1_DOWN, motionArgs.action); - ASSERT_EQ(AMOTION_EVENT_TOOL_TYPE_FINGER, motionArgs.pointerProperties[0].toolType); + ASSERT_EQ(ToolType::FINGER, motionArgs.pointerProperties[0].toolType); // If the tool type of the second finger changes to MT_TOOL_PALM, // we expect to receive ACTION_POINTER_UP with cancel flag. @@ -9431,9 +10016,9 @@ TEST_F(MultiTouchInputMapperTest, Process_ShouldHandlePalmToolType_KeepFirstPoin */ TEST_F(MultiTouchInputMapperTest, Process_MultiTouch_WithInvalidTrackingId) { addConfigurationProperty("touch.deviceType", "touchScreen"); - prepareDisplay(DISPLAY_ORIENTATION_0); + prepareDisplay(ui::ROTATION_0); prepareAxes(POSITION | ID | SLOT | PRESSURE); - MultiTouchInputMapper& mapper = addMapperAndConfigure(); + MultiTouchInputMapper& mapper = constructAndAddMapper(); NotifyMotionArgs motionArgs; @@ -9488,19 +10073,17 @@ TEST_F(MultiTouchInputMapperTest, Process_MultiTouch_WithInvalidTrackingId) { TEST_F(MultiTouchInputMapperTest, Reset_PreservesLastTouchState) { addConfigurationProperty("touch.deviceType", "touchScreen"); - prepareDisplay(DISPLAY_ORIENTATION_0); + prepareDisplay(ui::ROTATION_0); prepareAxes(POSITION | ID | SLOT | PRESSURE); - MultiTouchInputMapper& mapper = addMapperAndConfigure(); - - NotifyMotionArgs motionArgs; + MultiTouchInputMapper& mapper = constructAndAddMapper(); // First finger down. processId(mapper, FIRST_TRACKING_ID); processPosition(mapper, 100, 200); processPressure(mapper, RAW_PRESSURE_MAX); processSync(mapper); - ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs)); - ASSERT_EQ(AMOTION_EVENT_ACTION_DOWN, motionArgs.action); + ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled( + WithMotionAction(AMOTION_EVENT_ACTION_DOWN))); // Second finger down. processSlot(mapper, SECOND_SLOT); @@ -9509,55 +10092,152 @@ TEST_F(MultiTouchInputMapperTest, Reset_PreservesLastTouchState) { processPressure(mapper, RAW_PRESSURE_MAX); processSync(mapper); ASSERT_NO_FATAL_FAILURE( - mFakeListener->assertNotifyMotionWasCalled(&motionArgs)); - ASSERT_EQ(ACTION_POINTER_1_DOWN, motionArgs.action); + mFakeListener->assertNotifyMotionWasCalled(WithMotionAction(ACTION_POINTER_1_DOWN))); // Reset the mapper. When the mapper is reset, we expect the current multi-touch state to be - // preserved. Resetting should not generate any events. - mapper.reset(ARBITRARY_TIME); - ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasNotCalled()); + // preserved. Resetting should cancel the ongoing gesture. + resetMapper(mapper, ARBITRARY_TIME); + ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled( + WithMotionAction(AMOTION_EVENT_ACTION_CANCEL))); // Send a sync to simulate an empty touch frame where nothing changes. The mapper should use // the existing touch state to generate a down event. processPosition(mapper, 301, 302); processSync(mapper); - ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs)); - ASSERT_EQ(AMOTION_EVENT_ACTION_DOWN, motionArgs.action); - ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs)); - ASSERT_EQ(ACTION_POINTER_1_DOWN, motionArgs.action); + ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled( + AllOf(WithMotionAction(AMOTION_EVENT_ACTION_DOWN), WithPressure(1.f)))); + ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled( + AllOf(WithMotionAction(ACTION_POINTER_1_DOWN), WithPressure(1.f)))); ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasNotCalled()); } TEST_F(MultiTouchInputMapperTest, Reset_PreservesLastTouchState_NoPointersDown) { addConfigurationProperty("touch.deviceType", "touchScreen"); - prepareDisplay(DISPLAY_ORIENTATION_0); + prepareDisplay(ui::ROTATION_0); prepareAxes(POSITION | ID | SLOT | PRESSURE); - MultiTouchInputMapper& mapper = addMapperAndConfigure(); + MultiTouchInputMapper& mapper = constructAndAddMapper(); - NotifyMotionArgs motionArgs; + // First finger touches down and releases. + processId(mapper, FIRST_TRACKING_ID); + processPosition(mapper, 100, 200); + processPressure(mapper, RAW_PRESSURE_MAX); + processSync(mapper); + ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled( + WithMotionAction(AMOTION_EVENT_ACTION_DOWN))); + processId(mapper, INVALID_TRACKING_ID); + processSync(mapper); + ASSERT_NO_FATAL_FAILURE( + mFakeListener->assertNotifyMotionWasCalled(WithMotionAction(AMOTION_EVENT_ACTION_UP))); + + // Reset the mapper. When the mapper is reset, we expect it to restore the latest + // raw state where no pointers are down. + resetMapper(mapper, ARBITRARY_TIME); + ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasNotCalled()); + + // Send an empty sync frame. Since there are no pointers, no events are generated. + processSync(mapper); + ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasNotCalled()); +} + +TEST_F(MultiTouchInputMapperTest, StylusSourceIsAddedDynamicallyFromToolType) { + addConfigurationProperty("touch.deviceType", "touchScreen"); + prepareDisplay(ui::ROTATION_0); + prepareAxes(POSITION | ID | SLOT | PRESSURE | TOOL_TYPE); + MultiTouchInputMapper& mapper = constructAndAddMapper(); + ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyDeviceResetWasCalled()); + + // Even if the device supports reporting the ABS_MT_TOOL_TYPE axis, which could give it the + // ability to report MT_TOOL_PEN, we do not report the device as coming from a stylus source. + // Due to limitations in the evdev protocol, we cannot say for certain that a device is capable + // of reporting stylus events just because it supports ABS_MT_TOOL_TYPE. + ASSERT_EQ(AINPUT_SOURCE_TOUCHSCREEN, mapper.getSources()); + + // However, if the device ever ends up reporting an event with MT_TOOL_PEN, it should be + // reported with the stylus source. + processId(mapper, FIRST_TRACKING_ID); + processToolType(mapper, MT_TOOL_PEN); + processPosition(mapper, 100, 200); + processPressure(mapper, RAW_PRESSURE_MAX); + processSync(mapper); + ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled( + AllOf(WithMotionAction(AMOTION_EVENT_ACTION_DOWN), + WithSource(AINPUT_SOURCE_TOUCHSCREEN | AINPUT_SOURCE_STYLUS), + WithToolType(ToolType::STYLUS)))); + + // Now that we know the device supports styluses, ensure that the device is re-configured with + // the stylus source. + ASSERT_EQ(AINPUT_SOURCE_TOUCHSCREEN | AINPUT_SOURCE_STYLUS, mapper.getSources()); + { + const auto& devices = mReader->getInputDevices(); + auto deviceInfo = + std::find_if(devices.begin(), devices.end(), + [](const InputDeviceInfo& info) { return info.getId() == DEVICE_ID; }); + LOG_ALWAYS_FATAL_IF(deviceInfo == devices.end(), "Cannot find InputDevice"); + ASSERT_EQ(AINPUT_SOURCE_TOUCHSCREEN | AINPUT_SOURCE_STYLUS, deviceInfo->getSources()); + } + + // Ensure the device was not reset to prevent interruptions of any ongoing gestures. + ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyDeviceResetWasNotCalled()); + + processId(mapper, INVALID_TRACKING_ID); + processSync(mapper); + ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled( + AllOf(WithMotionAction(AMOTION_EVENT_ACTION_UP), + WithSource(AINPUT_SOURCE_TOUCHSCREEN | AINPUT_SOURCE_STYLUS), + 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(); - // First finger touches down and releases. processId(mapper, FIRST_TRACKING_ID); + processPressure(mapper, RAW_PRESSURE_MIN); processPosition(mapper, 100, 200); - processPressure(mapper, RAW_PRESSURE_MAX); - processSync(mapper); - ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs)); - ASSERT_EQ(AMOTION_EVENT_ACTION_DOWN, motionArgs.action); - processId(mapper, INVALID_TRACKING_ID); + 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( - mFakeListener->assertNotifyMotionWasCalled(&motionArgs)); - ASSERT_EQ(AMOTION_EVENT_ACTION_UP, motionArgs.action); + fakePointerController->assertPosition(toDisplayX(100), toDisplayY(200))); +} - // Reset the mapper. When the mapper is reset, we expect it to restore the latest - // raw state where no pointers are down. - mapper.reset(ARBITRARY_TIME); - ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasNotCalled()); +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(); - // Send an empty sync frame. Since there are no pointers, no events are generated. + 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->assertNotifyMotionWasNotCalled()); + 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 --- @@ -9573,8 +10253,8 @@ protected: TEST_F(MultiTouchInputMapperTest_ExternalDevice, Viewports_Fallback) { prepareAxes(POSITION); addConfigurationProperty("touch.deviceType", "touchScreen"); - prepareDisplay(DISPLAY_ORIENTATION_0); - MultiTouchInputMapper& mapper = addMapperAndConfigure(); + prepareDisplay(ui::ROTATION_0); + MultiTouchInputMapper& mapper = constructAndAddMapper(); ASSERT_EQ(AINPUT_SOURCE_TOUCHSCREEN, mapper.getSources()); @@ -9601,16 +10281,15 @@ TEST_F(MultiTouchInputMapperTest, Process_TouchpadCapture) { std::make_shared(); fakePointerController->setBounds(0, 0, DISPLAY_WIDTH - 1, DISPLAY_HEIGHT - 1); fakePointerController->setPosition(0, 0); - fakePointerController->setButtonState(0); // prepare device and capture - prepareDisplay(DISPLAY_ORIENTATION_0); + 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->setPointerCapture(true); mFakePolicy->setPointerController(fakePointerController); - MultiTouchInputMapper& mapper = addMapperAndConfigure(); + MultiTouchInputMapper& mapper = constructAndAddMapper(); // captured touchpad should be a touchpad source NotifyDeviceResetArgs resetArgs; @@ -9685,130 +10364,544 @@ TEST_F(MultiTouchInputMapperTest, Process_TouchpadCapture) { processPosition(mapper, 50 + RAW_X_MIN, 800 + RAW_Y_MIN); processSync(mapper); - // expect coord[0] to contain new touch 0 location, coord[1] to contain previous location - ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&args)); - ASSERT_EQ(AMOTION_EVENT_ACTION_MOVE, args.action); - ASSERT_NO_FATAL_FAILURE( - assertPointerCoords(args.pointerCoords[0], 50, 800, 1, 0, 0, 0, 0, 0, 0, 0)); - ASSERT_NO_FATAL_FAILURE( - assertPointerCoords(args.pointerCoords[1], 540, 690, 1, 0, 0, 0, 0, 0, 0, 0)); + // expect coord[0] to contain new touch 0 location, coord[1] to contain previous location + ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&args)); + ASSERT_EQ(AMOTION_EVENT_ACTION_MOVE, args.action); + ASSERT_NO_FATAL_FAILURE( + assertPointerCoords(args.pointerCoords[0], 50, 800, 1, 0, 0, 0, 0, 0, 0, 0)); + ASSERT_NO_FATAL_FAILURE( + assertPointerCoords(args.pointerCoords[1], 540, 690, 1, 0, 0, 0, 0, 0, 0, 0)); + + // BUTTON DOWN + processKey(mapper, BTN_LEFT, 1); + processSync(mapper); + + // touchinputmapper design sends a move before button press + ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&args)); + ASSERT_EQ(AMOTION_EVENT_ACTION_MOVE, args.action); + ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&args)); + ASSERT_EQ(AMOTION_EVENT_ACTION_BUTTON_PRESS, args.action); + + // BUTTON UP + processKey(mapper, BTN_LEFT, 0); + processSync(mapper); + + // touchinputmapper design sends a move after button release + ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&args)); + ASSERT_EQ(AMOTION_EVENT_ACTION_BUTTON_RELEASE, args.action); + ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&args)); + ASSERT_EQ(AMOTION_EVENT_ACTION_MOVE, args.action); + + // FINGER 0 UP + processId(mapper, -1); + processSync(mapper); + ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&args)); + ASSERT_EQ(AMOTION_EVENT_ACTION_POINTER_UP | 0x0000, args.action); + + // FINGER 1 MOVE + processSlot(mapper, 1); + processPosition(mapper, 320 + RAW_X_MIN, 900 + RAW_Y_MIN); + processSync(mapper); + + // expect coord[0] to contain new location of touch 1, and properties[0].id to contain 1 + ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&args)); + ASSERT_EQ(AMOTION_EVENT_ACTION_MOVE, args.action); + ASSERT_EQ(1U, args.pointerCount); + ASSERT_EQ(1, args.pointerProperties[0].id); + ASSERT_NO_FATAL_FAILURE( + assertPointerCoords(args.pointerCoords[0], 320, 900, 1, 0, 0, 0, 0, 0, 0, 0)); + + // FINGER 1 UP + processId(mapper, -1); + processKey(mapper, BTN_TOUCH, 0); + processSync(mapper); + ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&args)); + ASSERT_EQ(AMOTION_EVENT_ACTION_UP, args.action); + + // non captured touchpad should be a mouse source + mFakePolicy->setPointerCapture(false); + configureDevice(InputReaderConfiguration::Change::POINTER_CAPTURE); + ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyDeviceResetWasCalled(&resetArgs)); + ASSERT_EQ(AINPUT_SOURCE_MOUSE, mapper.getSources()); +} + +TEST_F(MultiTouchInputMapperTest, Process_UnCapturedTouchpadPointer) { + std::shared_ptr fakePointerController = + std::make_shared(); + fakePointerController->setBounds(0, 0, DISPLAY_WIDTH - 1, DISPLAY_HEIGHT - 1); + fakePointerController->setPosition(0, 0); + + // prepare device and capture + 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 + processId(mapper, 3); + processPosition(mapper, 100, 100); + processKey(mapper, BTN_TOUCH, 1); + processSync(mapper); + + // start at (100,100), cursor should be at (0,0) * scale + NotifyMotionArgs args; + ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&args)); + ASSERT_EQ(AMOTION_EVENT_ACTION_HOVER_MOVE, args.action); + ASSERT_NO_FATAL_FAILURE( + assertPointerCoords(args.pointerCoords[0], 0, 0, 0, 0, 0, 0, 0, 0, 0, 0)); + + // FINGER 0 MOVE + processPosition(mapper, 200, 200); + processSync(mapper); + + // compute scaling to help with touch position checking + float rawDiagonal = hypotf(RAW_X_MAX - RAW_X_MIN, RAW_Y_MAX - RAW_Y_MIN); + float displayDiagonal = hypotf(DISPLAY_WIDTH, DISPLAY_HEIGHT); + float scale = + mFakePolicy->getPointerGestureMovementSpeedRatio() * displayDiagonal / rawDiagonal; + + // translate from (100,100) -> (200,200), cursor should have changed to (100,100) * scale) + ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&args)); + ASSERT_EQ(AMOTION_EVENT_ACTION_HOVER_MOVE, args.action); + ASSERT_NO_FATAL_FAILURE(assertPointerCoords(args.pointerCoords[0], 100 * scale, 100 * scale, 0, + 0, 0, 0, 0, 0, 0, 0)); + + // BUTTON DOWN + processKey(mapper, BTN_LEFT, 1); + processSync(mapper); + + // touchinputmapper design sends a move before button press + ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&args)); + ASSERT_EQ(AMOTION_EVENT_ACTION_DOWN, args.action); + ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&args)); + ASSERT_EQ(AMOTION_EVENT_ACTION_BUTTON_PRESS, args.action); + + // BUTTON UP + processKey(mapper, BTN_LEFT, 0); + processSync(mapper); + + // touchinputmapper design sends a move after button release + ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&args)); + ASSERT_EQ(AMOTION_EVENT_ACTION_BUTTON_RELEASE, args.action); + ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&args)); + ASSERT_EQ(AMOTION_EVENT_ACTION_UP, args.action); +} + +TEST_F(MultiTouchInputMapperTest, WhenCapturedAndNotCaptured_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(false); + 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); + configureDevice(InputReaderConfiguration::Change::POINTER_CAPTURE); + ASSERT_EQ(AINPUT_SOURCE_TOUCHPAD, mapper.getSources()); +} + +// --- BluetoothMultiTouchInputMapperTest --- + +class BluetoothMultiTouchInputMapperTest : public MultiTouchInputMapperTest { +protected: + void SetUp() override { + InputMapperTest::SetUp(DEVICE_CLASSES | InputDeviceClass::EXTERNAL, BUS_BLUETOOTH); + } +}; + +TEST_F(BluetoothMultiTouchInputMapperTest, TimestampSmoothening) { + addConfigurationProperty("touch.deviceType", "touchScreen"); + prepareDisplay(ui::ROTATION_0); + prepareAxes(POSITION | ID | SLOT | PRESSURE); + MultiTouchInputMapper& mapper = constructAndAddMapper(); + + nsecs_t kernelEventTime = ARBITRARY_TIME; + nsecs_t expectedEventTime = ARBITRARY_TIME; + // Touch down. + processId(mapper, FIRST_TRACKING_ID); + processPosition(mapper, 100, 200); + processPressure(mapper, RAW_PRESSURE_MAX); + processSync(mapper, ARBITRARY_TIME); + ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled( + AllOf(WithMotionAction(AMOTION_EVENT_ACTION_DOWN), WithEventTime(ARBITRARY_TIME)))); + + // Process several events that come in quick succession, according to their timestamps. + for (int i = 0; i < 3; i++) { + constexpr static nsecs_t delta = ms2ns(1); + static_assert(delta < MIN_BLUETOOTH_TIMESTAMP_DELTA); + kernelEventTime += delta; + expectedEventTime += MIN_BLUETOOTH_TIMESTAMP_DELTA; + + processPosition(mapper, 101 + i, 201 + i); + processSync(mapper, kernelEventTime); + ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled( + AllOf(WithMotionAction(AMOTION_EVENT_ACTION_MOVE), + WithEventTime(expectedEventTime)))); + } + + // Release the touch. + processId(mapper, INVALID_TRACKING_ID); + processPressure(mapper, RAW_PRESSURE_MIN); + processSync(mapper, ARBITRARY_TIME + ms2ns(50)); + ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled( + AllOf(WithMotionAction(AMOTION_EVENT_ACTION_UP), + WithEventTime(ARBITRARY_TIME + ms2ns(50))))); +} + +// --- MultiTouchPointerModeTest --- + +class MultiTouchPointerModeTest : public MultiTouchInputMapperTest { +protected: + float mPointerMovementScale; + 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); + 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->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); + mPointerMovementScale = + mFakePolicy->getPointerGestureMovementSpeedRatio() * displayDiagonal / rawDiagonal; + mPointerXZoomScale = + mFakePolicy->getPointerGestureZoomSpeedRatio() * displayDiagonal / rawDiagonal; + } + + void prepareAbsoluteAxisResolution(int xAxisResolution, int yAxisResolution) { + mFakeEventHub->addAbsoluteAxis(EVENTHUB_ID, ABS_MT_POSITION_X, RAW_X_MIN, RAW_X_MAX, + /*flat*/ 0, + /*fuzz*/ 0, /*resolution*/ xAxisResolution); + mFakeEventHub->addAbsoluteAxis(EVENTHUB_ID, ABS_MT_POSITION_Y, RAW_Y_MIN, RAW_Y_MAX, + /*flat*/ 0, + /*fuzz*/ 0, /*resolution*/ yAxisResolution); + } +}; + +/** + * Two fingers down on a pointer mode touch pad. The width + * of the two finger is larger than 1/4 of the touch pack diagnal length. However, it + * is smaller than the fixed min physical length 30mm. Two fingers' distance must + * be greater than the both value to be freeform gesture, so that after two + * fingers start to move downwards, the gesture should be swipe. + */ +TEST_F(MultiTouchPointerModeTest, PointerGestureMaxSwipeWidthSwipe) { + // The min freeform gesture width is 25units/mm x 30mm = 750 + // which is greater than fraction of the diagnal length of the touchpad (349). + // Thus, MaxSwipWidth is 750. + preparePointerMode(/*xResolution=*/25, /*yResolution=*/25); + MultiTouchInputMapper& mapper = constructAndAddMapper(); + NotifyMotionArgs motionArgs; + + // Two fingers down at once. + // The two fingers are 450 units apart, expects the current gesture to be PRESS + // Pointer's initial position is used the [0,0] coordinate. + int32_t x1 = 100, y1 = 125, x2 = 550, y2 = 125; + + processId(mapper, FIRST_TRACKING_ID); + processPosition(mapper, x1, y1); + processMTSync(mapper); + processId(mapper, SECOND_TRACKING_ID); + processPosition(mapper, x2, y2); + processMTSync(mapper); + processSync(mapper); + + ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs)); + ASSERT_EQ(1U, motionArgs.pointerCount); + ASSERT_EQ(AMOTION_EVENT_ACTION_DOWN, motionArgs.action); + ASSERT_EQ(ToolType::FINGER, motionArgs.pointerProperties[0].toolType); + ASSERT_EQ(MotionClassification::NONE, motionArgs.classification); + ASSERT_NO_FATAL_FAILURE( + assertPointerCoords(motionArgs.pointerCoords[0], 0, 0, 1, 0, 0, 0, 0, 0, 0, 0)); + + // It should be recognized as a SWIPE gesture when two fingers start to move down, + // that there should be 1 pointer. + int32_t movingDistance = 200; + y1 += movingDistance; + y2 += movingDistance; + + processId(mapper, FIRST_TRACKING_ID); + processPosition(mapper, x1, y1); + processMTSync(mapper); + processId(mapper, SECOND_TRACKING_ID); + processPosition(mapper, x2, y2); + processMTSync(mapper); + processSync(mapper); + + ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs)); + ASSERT_EQ(1U, motionArgs.pointerCount); + ASSERT_EQ(AMOTION_EVENT_ACTION_MOVE, motionArgs.action); + ASSERT_EQ(ToolType::FINGER, motionArgs.pointerProperties[0].toolType); + ASSERT_EQ(MotionClassification::TWO_FINGER_SWIPE, motionArgs.classification); + ASSERT_NO_FATAL_FAILURE(assertPointerCoords(motionArgs.pointerCoords[0], 0, + movingDistance * mPointerMovementScale, 1, 0, 0, 0, + 0, 0, 0, 0)); +} + +/** + * Two fingers down on a pointer mode touch pad. The width of the two finger is larger + * than the minimum freeform gesture width, 30mm. However, it is smaller than 1/4 of + * the touch pack diagnal length. Two fingers' distance must be greater than the both + * value to be freeform gesture, so that after two fingers start to move downwards, + * the gesture should be swipe. + */ +TEST_F(MultiTouchPointerModeTest, PointerGestureMaxSwipeWidthLowResolutionSwipe) { + // The min freeform gesture width is 5units/mm x 30mm = 150 + // which is greater than fraction of the diagnal length of the touchpad (349). + // Thus, MaxSwipWidth is the fraction of the diagnal length, 349. + preparePointerMode(/*xResolution=*/5, /*yResolution=*/5); + MultiTouchInputMapper& mapper = constructAndAddMapper(); + NotifyMotionArgs motionArgs; + + // Two fingers down at once. + // The two fingers are 250 units apart, expects the current gesture to be PRESS + // Pointer's initial position is used the [0,0] coordinate. + int32_t x1 = 100, y1 = 125, x2 = 350, y2 = 125; + + processId(mapper, FIRST_TRACKING_ID); + processPosition(mapper, x1, y1); + processMTSync(mapper); + processId(mapper, SECOND_TRACKING_ID); + processPosition(mapper, x2, y2); + processMTSync(mapper); + processSync(mapper); + + ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs)); + ASSERT_EQ(1U, motionArgs.pointerCount); + ASSERT_EQ(AMOTION_EVENT_ACTION_DOWN, motionArgs.action); + ASSERT_EQ(ToolType::FINGER, motionArgs.pointerProperties[0].toolType); + ASSERT_EQ(MotionClassification::NONE, motionArgs.classification); + ASSERT_NO_FATAL_FAILURE( + assertPointerCoords(motionArgs.pointerCoords[0], 0, 0, 1, 0, 0, 0, 0, 0, 0, 0)); + + // It should be recognized as a SWIPE gesture when two fingers start to move down, + // and there should be 1 pointer. + int32_t movingDistance = 200; + y1 += movingDistance; + y2 += movingDistance; + + processId(mapper, FIRST_TRACKING_ID); + processPosition(mapper, x1, y1); + processMTSync(mapper); + processId(mapper, SECOND_TRACKING_ID); + processPosition(mapper, x2, y2); + processMTSync(mapper); + processSync(mapper); + + ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs)); + ASSERT_EQ(1U, motionArgs.pointerCount); + ASSERT_EQ(AMOTION_EVENT_ACTION_MOVE, motionArgs.action); + ASSERT_EQ(ToolType::FINGER, motionArgs.pointerProperties[0].toolType); + ASSERT_EQ(MotionClassification::TWO_FINGER_SWIPE, motionArgs.classification); + // New coordinate is the scaled relative coordinate from the initial coordinate. + ASSERT_NO_FATAL_FAILURE(assertPointerCoords(motionArgs.pointerCoords[0], 0, + movingDistance * mPointerMovementScale, 1, 0, 0, 0, + 0, 0, 0, 0)); +} + +/** + * Touch the touch pad with two fingers with a distance wider than the minimum freeform + * gesture width and 1/4 of the diagnal length of the touchpad. Expect to receive + * freeform gestures after two fingers start to move downwards. + */ +TEST_F(MultiTouchPointerModeTest, PointerGestureMaxSwipeWidthFreeform) { + preparePointerMode(/*xResolution=*/25, /*yResolution=*/25); + MultiTouchInputMapper& mapper = constructAndAddMapper(); - // BUTTON DOWN - processKey(mapper, BTN_LEFT, 1); - processSync(mapper); + NotifyMotionArgs motionArgs; - // touchinputmapper design sends a move before button press - ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&args)); - ASSERT_EQ(AMOTION_EVENT_ACTION_MOVE, args.action); - ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&args)); - ASSERT_EQ(AMOTION_EVENT_ACTION_BUTTON_PRESS, args.action); + // Two fingers down at once. Wider than the max swipe width. + // The gesture is expected to be PRESS, then transformed to FREEFORM + int32_t x1 = 100, y1 = 125, x2 = 900, y2 = 125; - // BUTTON UP - processKey(mapper, BTN_LEFT, 0); + processId(mapper, FIRST_TRACKING_ID); + processPosition(mapper, x1, y1); + processMTSync(mapper); + processId(mapper, SECOND_TRACKING_ID); + processPosition(mapper, x2, y2); + processMTSync(mapper); processSync(mapper); - // touchinputmapper design sends a move after button release - ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&args)); - ASSERT_EQ(AMOTION_EVENT_ACTION_BUTTON_RELEASE, args.action); - ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&args)); - ASSERT_EQ(AMOTION_EVENT_ACTION_MOVE, args.action); + ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs)); + ASSERT_EQ(1U, motionArgs.pointerCount); + ASSERT_EQ(AMOTION_EVENT_ACTION_DOWN, motionArgs.action); + ASSERT_EQ(ToolType::FINGER, motionArgs.pointerProperties[0].toolType); + ASSERT_EQ(MotionClassification::NONE, motionArgs.classification); + // One pointer for PRESS, and its coordinate is used as the origin for pointer coordinates. + ASSERT_NO_FATAL_FAILURE( + assertPointerCoords(motionArgs.pointerCoords[0], 0, 0, 1, 0, 0, 0, 0, 0, 0, 0)); - // FINGER 0 UP - processId(mapper, -1); - processSync(mapper); - ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&args)); - ASSERT_EQ(AMOTION_EVENT_ACTION_POINTER_UP | 0x0000, args.action); + int32_t movingDistance = 200; - // FINGER 1 MOVE - processSlot(mapper, 1); - processPosition(mapper, 320 + RAW_X_MIN, 900 + RAW_Y_MIN); + // Move two fingers down, expect a cancel event because gesture is changing to freeform, + // then two down events for two pointers. + y1 += movingDistance; + y2 += movingDistance; + + processId(mapper, FIRST_TRACKING_ID); + processPosition(mapper, x1, y1); + processMTSync(mapper); + processId(mapper, SECOND_TRACKING_ID); + processPosition(mapper, x2, y2); + processMTSync(mapper); processSync(mapper); - // expect coord[0] to contain new location of touch 1, and properties[0].id to contain 1 - ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&args)); - ASSERT_EQ(AMOTION_EVENT_ACTION_MOVE, args.action); - ASSERT_EQ(1U, args.pointerCount); - ASSERT_EQ(1, args.pointerProperties[0].id); - ASSERT_NO_FATAL_FAILURE( - assertPointerCoords(args.pointerCoords[0], 320, 900, 1, 0, 0, 0, 0, 0, 0, 0)); + ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs)); + // The previous PRESS gesture is cancelled, because it is transformed to freeform + ASSERT_EQ(1U, motionArgs.pointerCount); + ASSERT_EQ(AMOTION_EVENT_ACTION_CANCEL, motionArgs.action); + ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs)); + ASSERT_EQ(ToolType::FINGER, motionArgs.pointerProperties[0].toolType); + ASSERT_EQ(1U, motionArgs.pointerCount); + ASSERT_EQ(AMOTION_EVENT_ACTION_DOWN, motionArgs.action); + ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs)); + ASSERT_EQ(ToolType::FINGER, motionArgs.pointerProperties[0].toolType); + ASSERT_EQ(MotionClassification::NONE, motionArgs.classification); + ASSERT_EQ(2U, motionArgs.pointerCount); + ASSERT_EQ(AMOTION_EVENT_ACTION_POINTER_DOWN, motionArgs.action & AMOTION_EVENT_ACTION_MASK); + ASSERT_EQ(ToolType::FINGER, motionArgs.pointerProperties[0].toolType); + ASSERT_EQ(MotionClassification::NONE, motionArgs.classification); + // Two pointers' scaled relative coordinates from their initial centroid. + // Initial y coordinates are 0 as y1 and y2 have the same value. + float cookedX1 = (x1 - x2) / 2 * mPointerXZoomScale; + float cookedX2 = (x2 - x1) / 2 * mPointerXZoomScale; + // When pointers move, the new coordinates equal to the initial coordinates plus + // scaled moving distance. + ASSERT_NO_FATAL_FAILURE(assertPointerCoords(motionArgs.pointerCoords[0], cookedX1, + movingDistance * mPointerMovementScale, 1, 0, 0, 0, + 0, 0, 0, 0)); + ASSERT_NO_FATAL_FAILURE(assertPointerCoords(motionArgs.pointerCoords[1], cookedX2, + movingDistance * mPointerMovementScale, 1, 0, 0, 0, + 0, 0, 0, 0)); + + // Move two fingers down again, expect one MOVE motion event. + y1 += movingDistance; + y2 += movingDistance; - // FINGER 1 UP - processId(mapper, -1); - processKey(mapper, BTN_TOUCH, 0); + processId(mapper, FIRST_TRACKING_ID); + processPosition(mapper, x1, y1); + processMTSync(mapper); + processId(mapper, SECOND_TRACKING_ID); + processPosition(mapper, x2, y2); + processMTSync(mapper); processSync(mapper); - ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&args)); - ASSERT_EQ(AMOTION_EVENT_ACTION_UP, args.action); - // non captured touchpad should be a mouse source - mFakePolicy->setPointerCapture(false); - configureDevice(InputReaderConfiguration::CHANGE_POINTER_CAPTURE); - ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyDeviceResetWasCalled(&resetArgs)); - ASSERT_EQ(AINPUT_SOURCE_MOUSE, mapper.getSources()); -} + ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs)); + ASSERT_EQ(2U, motionArgs.pointerCount); + ASSERT_EQ(AMOTION_EVENT_ACTION_MOVE, motionArgs.action); + ASSERT_EQ(ToolType::FINGER, motionArgs.pointerProperties[0].toolType); + ASSERT_EQ(MotionClassification::NONE, motionArgs.classification); + ASSERT_NO_FATAL_FAILURE(assertPointerCoords(motionArgs.pointerCoords[0], cookedX1, + movingDistance * 2 * mPointerMovementScale, 1, 0, 0, + 0, 0, 0, 0, 0)); + ASSERT_NO_FATAL_FAILURE(assertPointerCoords(motionArgs.pointerCoords[1], cookedX2, + movingDistance * 2 * mPointerMovementScale, 1, 0, 0, + 0, 0, 0, 0, 0)); +} + +TEST_F(MultiTouchPointerModeTest, TwoFingerSwipeOffsets) { + preparePointerMode(/*xResolution=*/25, /*yResolution=*/25); + MultiTouchInputMapper& mapper = constructAndAddMapper(); + NotifyMotionArgs motionArgs; -TEST_F(MultiTouchInputMapperTest, Process_UnCapturedTouchpadPointer) { - std::shared_ptr fakePointerController = - std::make_shared(); - fakePointerController->setBounds(0, 0, DISPLAY_WIDTH - 1, DISPLAY_HEIGHT - 1); - fakePointerController->setPosition(0, 0); - fakePointerController->setButtonState(0); + // Place two fingers down. + int32_t x1 = 100, y1 = 125, x2 = 550, y2 = 125; - // prepare device and capture - prepareDisplay(DISPLAY_ORIENTATION_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 = addMapperAndConfigure(); - // run uncaptured pointer tests - pushes out generic events - // FINGER 0 DOWN - processId(mapper, 3); - processPosition(mapper, 100, 100); - processKey(mapper, BTN_TOUCH, 1); + processId(mapper, FIRST_TRACKING_ID); + processPosition(mapper, x1, y1); + processMTSync(mapper); + processId(mapper, SECOND_TRACKING_ID); + processPosition(mapper, x2, y2); + processMTSync(mapper); processSync(mapper); - // start at (100,100), cursor should be at (0,0) * scale - NotifyMotionArgs args; - ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&args)); - ASSERT_EQ(AMOTION_EVENT_ACTION_HOVER_MOVE, args.action); - ASSERT_NO_FATAL_FAILURE( - assertPointerCoords(args.pointerCoords[0], 0, 0, 0, 0, 0, 0, 0, 0, 0, 0)); + ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs)); + ASSERT_EQ(1U, motionArgs.pointerCount); + ASSERT_EQ(AMOTION_EVENT_ACTION_DOWN, motionArgs.action); + ASSERT_EQ(MotionClassification::NONE, motionArgs.classification); + ASSERT_EQ(0, motionArgs.pointerCoords[0].getAxisValue(AMOTION_EVENT_AXIS_GESTURE_X_OFFSET)); + ASSERT_EQ(0, motionArgs.pointerCoords[0].getAxisValue(AMOTION_EVENT_AXIS_GESTURE_Y_OFFSET)); - // FINGER 0 MOVE - processPosition(mapper, 200, 200); - processSync(mapper); + // Move the two fingers down and to the left. + int32_t movingDistance = 200; + x1 -= movingDistance; + y1 += movingDistance; + x2 -= movingDistance; + y2 += movingDistance; - // compute scaling to help with touch position checking - float rawDiagonal = hypotf(RAW_X_MAX - RAW_X_MIN, RAW_Y_MAX - RAW_Y_MIN); - float displayDiagonal = hypotf(DISPLAY_WIDTH, DISPLAY_HEIGHT); - float scale = - mFakePolicy->getPointerGestureMovementSpeedRatio() * displayDiagonal / rawDiagonal; + processId(mapper, FIRST_TRACKING_ID); + processPosition(mapper, x1, y1); + processMTSync(mapper); + processId(mapper, SECOND_TRACKING_ID); + processPosition(mapper, x2, y2); + processMTSync(mapper); + processSync(mapper); - // translate from (100,100) -> (200,200), cursor should have changed to (100,100) * scale) - ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&args)); - ASSERT_EQ(AMOTION_EVENT_ACTION_HOVER_MOVE, args.action); - ASSERT_NO_FATAL_FAILURE(assertPointerCoords(args.pointerCoords[0], 100 * scale, 100 * scale, 0, - 0, 0, 0, 0, 0, 0, 0)); + ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs)); + ASSERT_EQ(1U, motionArgs.pointerCount); + ASSERT_EQ(AMOTION_EVENT_ACTION_MOVE, motionArgs.action); + ASSERT_EQ(MotionClassification::TWO_FINGER_SWIPE, motionArgs.classification); + ASSERT_LT(motionArgs.pointerCoords[0].getAxisValue(AMOTION_EVENT_AXIS_GESTURE_X_OFFSET), 0); + ASSERT_GT(motionArgs.pointerCoords[0].getAxisValue(AMOTION_EVENT_AXIS_GESTURE_Y_OFFSET), 0); } -TEST_F(MultiTouchInputMapperTest, WhenCapturedAndNotCaptured_GetSources) { - std::shared_ptr fakePointerController = - std::make_shared(); - - prepareDisplay(DISPLAY_ORIENTATION_0); - prepareAxes(POSITION | ID | SLOT); - mFakeEventHub->addKey(EVENTHUB_ID, BTN_LEFT, 0, AKEYCODE_UNKNOWN, 0); - mFakePolicy->setPointerController(fakePointerController); - mFakePolicy->setPointerCapture(false); - MultiTouchInputMapper& mapper = addMapperAndConfigure(); +TEST_F(MultiTouchPointerModeTest, WhenViewportActiveStatusChanged_PointerGestureIsReset) { + preparePointerMode(/*xResolution=*/25, /*yResolution=*/25); + mFakeEventHub->addKey(EVENTHUB_ID, BTN_TOOL_PEN, 0, AKEYCODE_UNKNOWN, 0); + MultiTouchInputMapper& mapper = constructAndAddMapper(); + ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyDeviceResetWasCalled()); - // uncaptured touchpad should be a pointer device - ASSERT_EQ(AINPUT_SOURCE_MOUSE, mapper.getSources()); + // Start a stylus gesture. + processKey(mapper, BTN_TOOL_PEN, 1); + processId(mapper, FIRST_TRACKING_ID); + processPosition(mapper, 100, 200); + processSync(mapper); + ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled( + AllOf(WithMotionAction(AMOTION_EVENT_ACTION_DOWN), + WithSource(AINPUT_SOURCE_MOUSE | AINPUT_SOURCE_STYLUS), + WithToolType(ToolType::STYLUS)))); + // TODO(b/257078296): Pointer mode generates extra event. + ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled( + AllOf(WithMotionAction(AMOTION_EVENT_ACTION_MOVE), + WithSource(AINPUT_SOURCE_MOUSE | AINPUT_SOURCE_STYLUS), + WithToolType(ToolType::STYLUS)))); + ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasNotCalled()); - // captured touchpad should be a touchpad device - mFakePolicy->setPointerCapture(true); - configureDevice(InputReaderConfiguration::CHANGE_POINTER_CAPTURE); - ASSERT_EQ(AINPUT_SOURCE_TOUCHPAD, mapper.getSources()); + // Make the viewport inactive. This will put the device in disabled mode, and the ongoing stylus + // gesture should be disabled. + auto viewport = mFakePolicy->getDisplayViewportByType(ViewportType::INTERNAL); + viewport->isActive = false; + mFakePolicy->updateViewport(*viewport); + configureDevice(InputReaderConfiguration::Change::DISPLAY_INFO); + ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled( + AllOf(WithMotionAction(AMOTION_EVENT_ACTION_CANCEL), + WithSource(AINPUT_SOURCE_MOUSE | AINPUT_SOURCE_STYLUS), + WithToolType(ToolType::STYLUS)))); + // TODO(b/257078296): Pointer mode generates extra event. + ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled( + AllOf(WithMotionAction(AMOTION_EVENT_ACTION_CANCEL), + WithSource(AINPUT_SOURCE_MOUSE | AINPUT_SOURCE_STYLUS), + WithToolType(ToolType::STYLUS)))); + ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasNotCalled()); } // --- JoystickInputMapperTest --- @@ -9836,7 +10929,7 @@ protected: process(mapper, ARBITRARY_TIME, READ_TIME, EV_SYN, SYN_REPORT, 0); } - void prepareVirtualDisplay(int32_t orientation) { + void prepareVirtualDisplay(ui::Rotation orientation) { setDisplayInfoAndReconfigure(VIRTUAL_DISPLAY_ID, VIRTUAL_DISPLAY_WIDTH, VIRTUAL_DISPLAY_HEIGHT, orientation, VIRTUAL_DISPLAY_UNIQUE_ID, NO_PORT, ViewportType::VIRTUAL); @@ -9850,11 +10943,11 @@ const int32_t JoystickInputMapperTest::RAW_Y_MAX = 32767; TEST_F(JoystickInputMapperTest, Configure_AssignsDisplayUniqueId) { prepareAxes(); - JoystickInputMapper& mapper = addMapperAndConfigure(); + JoystickInputMapper& mapper = constructAndAddMapper(); mFakePolicy->addInputUniqueIdAssociation(DEVICE_LOCATION, VIRTUAL_DISPLAY_UNIQUE_ID); - prepareVirtualDisplay(DISPLAY_ORIENTATION_0); + prepareVirtualDisplay(ui::ROTATION_0); // Send an axis event processAxis(mapper, ABS_X, 100); @@ -9892,7 +10985,7 @@ protected: virtual void SetUp(ftl::Flags classes) { mFakeEventHub = std::make_unique(); - mFakePolicy = new FakeInputReaderPolicy(); + mFakePolicy = sp::make(); mFakeListener = std::make_unique(); mReader = std::make_unique(mFakeEventHub, mFakePolicy, *mFakeListener); @@ -9906,14 +10999,6 @@ protected: mFakePolicy.clear(); } - void configureDevice(uint32_t changes) { - if (!changes || (changes & InputReaderConfiguration::CHANGE_DISPLAY_INFO)) { - mReader->requestRefreshConfiguration(changes); - mReader->loopOnce(); - } - mDevice->configure(ARBITRARY_TIME, mFakePolicy->getReaderConfiguration(), changes); - } - std::shared_ptr newDevice(int32_t deviceId, const std::string& name, const std::string& location, int32_t eventHubId, ftl::Flags classes) { @@ -9957,15 +11042,17 @@ protected: TEST_F(BatteryControllerTest, GetBatteryCapacity) { PeripheralController& controller = addControllerAndConfigure(); - ASSERT_TRUE(controller.getBatteryCapacity(DEFAULT_BATTERY)); - ASSERT_EQ(controller.getBatteryCapacity(DEFAULT_BATTERY).value_or(-1), BATTERY_CAPACITY); + ASSERT_TRUE(controller.getBatteryCapacity(FakeEventHub::DEFAULT_BATTERY)); + ASSERT_EQ(controller.getBatteryCapacity(FakeEventHub::DEFAULT_BATTERY).value_or(-1), + FakeEventHub::BATTERY_CAPACITY); } TEST_F(BatteryControllerTest, GetBatteryStatus) { PeripheralController& controller = addControllerAndConfigure(); - ASSERT_TRUE(controller.getBatteryStatus(DEFAULT_BATTERY)); - ASSERT_EQ(controller.getBatteryStatus(DEFAULT_BATTERY).value_or(-1), BATTERY_STATUS); + ASSERT_TRUE(controller.getBatteryStatus(FakeEventHub::DEFAULT_BATTERY)); + ASSERT_EQ(controller.getBatteryStatus(FakeEventHub::DEFAULT_BATTERY).value_or(-1), + FakeEventHub::BATTERY_STATUS); } // --- LightControllerTest --- @@ -9978,7 +11065,7 @@ protected: TEST_F(LightControllerTest, MonoLight) { RawLightInfo infoMono = {.id = 1, - .name = "Mono", + .name = "mono_light", .maxBrightness = 255, .flags = InputLightClass::BRIGHTNESS, .path = ""}; @@ -9989,7 +11076,29 @@ TEST_F(LightControllerTest, MonoLight) { controller.populateDeviceInfo(&info); std::vector lights = info.getLights(); ASSERT_EQ(1U, lights.size()); - ASSERT_EQ(InputDeviceLightType::MONO, lights[0].type); + ASSERT_EQ(InputDeviceLightType::INPUT, lights[0].type); + ASSERT_TRUE(lights[0].capabilityFlags.test(InputDeviceLightCapability::BRIGHTNESS)); + + ASSERT_TRUE(controller.setLightColor(lights[0].id, LIGHT_BRIGHTNESS)); + ASSERT_EQ(controller.getLightColor(lights[0].id).value_or(-1), LIGHT_BRIGHTNESS); +} + +TEST_F(LightControllerTest, MonoKeyboardBacklight) { + RawLightInfo infoMono = {.id = 1, + .name = "mono_keyboard_backlight", + .maxBrightness = 255, + .flags = InputLightClass::BRIGHTNESS | + InputLightClass::KEYBOARD_BACKLIGHT, + .path = ""}; + mFakeEventHub->addRawLightInfo(infoMono.id, std::move(infoMono)); + + PeripheralController& controller = addControllerAndConfigure(); + InputDeviceInfo info; + controller.populateDeviceInfo(&info); + std::vector lights = info.getLights(); + ASSERT_EQ(1U, lights.size()); + ASSERT_EQ(InputDeviceLightType::KEYBOARD_BACKLIGHT, lights[0].type); + ASSERT_TRUE(lights[0].capabilityFlags.test(InputDeviceLightCapability::BRIGHTNESS)); ASSERT_TRUE(controller.setLightColor(lights[0].id, LIGHT_BRIGHTNESS)); ASSERT_EQ(controller.getLightColor(lights[0].id).value_or(-1), LIGHT_BRIGHTNESS); @@ -10020,7 +11129,85 @@ TEST_F(LightControllerTest, RGBLight) { controller.populateDeviceInfo(&info); std::vector lights = info.getLights(); ASSERT_EQ(1U, lights.size()); - ASSERT_EQ(InputDeviceLightType::RGB, lights[0].type); + ASSERT_EQ(InputDeviceLightType::INPUT, lights[0].type); + ASSERT_TRUE(lights[0].capabilityFlags.test(InputDeviceLightCapability::BRIGHTNESS)); + ASSERT_TRUE(lights[0].capabilityFlags.test(InputDeviceLightCapability::RGB)); + + ASSERT_TRUE(controller.setLightColor(lights[0].id, LIGHT_COLOR)); + ASSERT_EQ(controller.getLightColor(lights[0].id).value_or(-1), LIGHT_COLOR); +} + +TEST_F(LightControllerTest, CorrectRGBKeyboardBacklight) { + RawLightInfo infoRed = {.id = 1, + .name = "red_keyboard_backlight", + .maxBrightness = 255, + .flags = InputLightClass::BRIGHTNESS | InputLightClass::RED | + InputLightClass::KEYBOARD_BACKLIGHT, + .path = ""}; + RawLightInfo infoGreen = {.id = 2, + .name = "green_keyboard_backlight", + .maxBrightness = 255, + .flags = InputLightClass::BRIGHTNESS | InputLightClass::GREEN | + InputLightClass::KEYBOARD_BACKLIGHT, + .path = ""}; + RawLightInfo infoBlue = {.id = 3, + .name = "blue_keyboard_backlight", + .maxBrightness = 255, + .flags = InputLightClass::BRIGHTNESS | InputLightClass::BLUE | + InputLightClass::KEYBOARD_BACKLIGHT, + .path = ""}; + mFakeEventHub->addRawLightInfo(infoRed.id, std::move(infoRed)); + mFakeEventHub->addRawLightInfo(infoGreen.id, std::move(infoGreen)); + mFakeEventHub->addRawLightInfo(infoBlue.id, std::move(infoBlue)); + + PeripheralController& controller = addControllerAndConfigure(); + InputDeviceInfo info; + controller.populateDeviceInfo(&info); + std::vector lights = info.getLights(); + ASSERT_EQ(1U, lights.size()); + ASSERT_EQ(InputDeviceLightType::KEYBOARD_BACKLIGHT, lights[0].type); + ASSERT_TRUE(lights[0].capabilityFlags.test(InputDeviceLightCapability::BRIGHTNESS)); + ASSERT_TRUE(lights[0].capabilityFlags.test(InputDeviceLightCapability::RGB)); + + ASSERT_TRUE(controller.setLightColor(lights[0].id, LIGHT_COLOR)); + ASSERT_EQ(controller.getLightColor(lights[0].id).value_or(-1), LIGHT_COLOR); +} + +TEST_F(LightControllerTest, IncorrectRGBKeyboardBacklight) { + RawLightInfo infoRed = {.id = 1, + .name = "red", + .maxBrightness = 255, + .flags = InputLightClass::BRIGHTNESS | InputLightClass::RED, + .path = ""}; + RawLightInfo infoGreen = {.id = 2, + .name = "green", + .maxBrightness = 255, + .flags = InputLightClass::BRIGHTNESS | InputLightClass::GREEN, + .path = ""}; + RawLightInfo infoBlue = {.id = 3, + .name = "blue", + .maxBrightness = 255, + .flags = InputLightClass::BRIGHTNESS | InputLightClass::BLUE, + .path = ""}; + RawLightInfo infoGlobal = {.id = 3, + .name = "global_keyboard_backlight", + .maxBrightness = 255, + .flags = InputLightClass::BRIGHTNESS | InputLightClass::GLOBAL | + InputLightClass::KEYBOARD_BACKLIGHT, + .path = ""}; + mFakeEventHub->addRawLightInfo(infoRed.id, std::move(infoRed)); + mFakeEventHub->addRawLightInfo(infoGreen.id, std::move(infoGreen)); + mFakeEventHub->addRawLightInfo(infoBlue.id, std::move(infoBlue)); + mFakeEventHub->addRawLightInfo(infoBlue.id, std::move(infoGlobal)); + + PeripheralController& controller = addControllerAndConfigure(); + InputDeviceInfo info; + controller.populateDeviceInfo(&info); + std::vector lights = info.getLights(); + ASSERT_EQ(1U, lights.size()); + ASSERT_EQ(InputDeviceLightType::INPUT, lights[0].type); + ASSERT_TRUE(lights[0].capabilityFlags.test(InputDeviceLightCapability::BRIGHTNESS)); + ASSERT_TRUE(lights[0].capabilityFlags.test(InputDeviceLightCapability::RGB)); ASSERT_TRUE(controller.setLightColor(lights[0].id, LIGHT_COLOR)); ASSERT_EQ(controller.getLightColor(lights[0].id).value_or(-1), LIGHT_COLOR); @@ -10028,7 +11215,7 @@ TEST_F(LightControllerTest, RGBLight) { TEST_F(LightControllerTest, MultiColorRGBLight) { RawLightInfo infoColor = {.id = 1, - .name = "red", + .name = "multi_color", .maxBrightness = 255, .flags = InputLightClass::BRIGHTNESS | InputLightClass::MULTI_INTENSITY | @@ -10042,7 +11229,34 @@ TEST_F(LightControllerTest, MultiColorRGBLight) { controller.populateDeviceInfo(&info); std::vector lights = info.getLights(); ASSERT_EQ(1U, lights.size()); - ASSERT_EQ(InputDeviceLightType::MULTI_COLOR, lights[0].type); + ASSERT_EQ(InputDeviceLightType::INPUT, lights[0].type); + ASSERT_TRUE(lights[0].capabilityFlags.test(InputDeviceLightCapability::BRIGHTNESS)); + ASSERT_TRUE(lights[0].capabilityFlags.test(InputDeviceLightCapability::RGB)); + + ASSERT_TRUE(controller.setLightColor(lights[0].id, LIGHT_COLOR)); + ASSERT_EQ(controller.getLightColor(lights[0].id).value_or(-1), LIGHT_COLOR); +} + +TEST_F(LightControllerTest, MultiColorRGBKeyboardBacklight) { + RawLightInfo infoColor = {.id = 1, + .name = "multi_color_keyboard_backlight", + .maxBrightness = 255, + .flags = InputLightClass::BRIGHTNESS | + InputLightClass::MULTI_INTENSITY | + InputLightClass::MULTI_INDEX | + InputLightClass::KEYBOARD_BACKLIGHT, + .path = ""}; + + mFakeEventHub->addRawLightInfo(infoColor.id, std::move(infoColor)); + + PeripheralController& controller = addControllerAndConfigure(); + InputDeviceInfo info; + controller.populateDeviceInfo(&info); + std::vector lights = info.getLights(); + ASSERT_EQ(1U, lights.size()); + ASSERT_EQ(InputDeviceLightType::KEYBOARD_BACKLIGHT, lights[0].type); + ASSERT_TRUE(lights[0].capabilityFlags.test(InputDeviceLightCapability::BRIGHTNESS)); + ASSERT_TRUE(lights[0].capabilityFlags.test(InputDeviceLightCapability::RGB)); ASSERT_TRUE(controller.setLightColor(lights[0].id, LIGHT_COLOR)); ASSERT_EQ(controller.getLightColor(lights[0].id).value_or(-1), LIGHT_COLOR); @@ -10080,6 +11294,8 @@ TEST_F(LightControllerTest, PlayerIdLight) { std::vector lights = info.getLights(); ASSERT_EQ(1U, lights.size()); 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)); diff --git a/services/inputflinger/tests/InstrumentedInputReader.cpp b/services/inputflinger/tests/InstrumentedInputReader.cpp new file mode 100644 index 0000000000000000000000000000000000000000..1f8cd12b75a9848c9967e0e543fd1e684a97567f --- /dev/null +++ b/services/inputflinger/tests/InstrumentedInputReader.cpp @@ -0,0 +1,50 @@ +/* + * 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 "InstrumentedInputReader.h" + +namespace android { + +InstrumentedInputReader::InstrumentedInputReader(std::shared_ptr eventHub, + const sp& policy, + InputListenerInterface& listener) + : InputReader(eventHub, policy, listener), mFakeContext(this) {} + +void InstrumentedInputReader::pushNextDevice(std::shared_ptr device) { + mNextDevices.push(device); +} + +std::shared_ptr InstrumentedInputReader::newDevice(int32_t deviceId, + const std::string& name, + const std::string& location) { + InputDeviceIdentifier identifier; + identifier.name = name; + identifier.location = location; + int32_t generation = deviceId + 1; + return std::make_shared(&mFakeContext, deviceId, generation, identifier); +} + +std::shared_ptr InstrumentedInputReader::createDeviceLocked( + int32_t eventHubId, const InputDeviceIdentifier& identifier) REQUIRES(mLock) { + if (!mNextDevices.empty()) { + std::shared_ptr device(std::move(mNextDevices.front())); + mNextDevices.pop(); + return device; + } + return InputReader::createDeviceLocked(eventHubId, identifier); +} + +} // namespace android diff --git a/services/inputflinger/tests/InstrumentedInputReader.h b/services/inputflinger/tests/InstrumentedInputReader.h new file mode 100644 index 0000000000000000000000000000000000000000..7f8d5562ef06300bfb930838876672c74c8657e7 --- /dev/null +++ b/services/inputflinger/tests/InstrumentedInputReader.h @@ -0,0 +1,123 @@ +/* + * 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 +#include + +#include +#include +#include +#include + +namespace android { + +class InstrumentedInputReader : public InputReader { +public: + InstrumentedInputReader(std::shared_ptr eventHub, + const sp& policy, + InputListenerInterface& listener); + virtual ~InstrumentedInputReader() {} + + void pushNextDevice(std::shared_ptr device); + + std::shared_ptr newDevice(int32_t deviceId, const std::string& name, + const std::string& location = ""); + + // Make the protected loopOnce method accessible to tests. + using InputReader::loopOnce; + +protected: + virtual std::shared_ptr createDeviceLocked( + int32_t eventHubId, const InputDeviceIdentifier& identifier); + + class FakeInputReaderContext : public ContextImpl { + public: + FakeInputReaderContext(InputReader* reader) + : ContextImpl(reader), + mGlobalMetaState(0), + mUpdateGlobalMetaStateWasCalled(false), + mGeneration(1) {} + + virtual ~FakeInputReaderContext() {} + + void assertUpdateGlobalMetaStateWasCalled() { + ASSERT_TRUE(mUpdateGlobalMetaStateWasCalled) + << "Expected updateGlobalMetaState() to have been called."; + mUpdateGlobalMetaStateWasCalled = false; + } + + void setGlobalMetaState(int32_t state) { mGlobalMetaState = state; } + + uint32_t getGeneration() { return mGeneration; } + + void updateGlobalMetaState() override { + mUpdateGlobalMetaStateWasCalled = true; + ContextImpl::updateGlobalMetaState(); + } + + int32_t getGlobalMetaState() override { + return mGlobalMetaState | ContextImpl::getGlobalMetaState(); + } + + int32_t bumpGeneration() override { + mGeneration = ContextImpl::bumpGeneration(); + return mGeneration; + } + + void requestTimeoutAtTime(nsecs_t when) override { mRequestedTimeout = when; } + + void assertTimeoutWasRequested(nsecs_t when) { + ASSERT_TRUE(mRequestedTimeout) << "Expected timeout at time " << when + << " but there was no timeout requested."; + ASSERT_EQ(when, *mRequestedTimeout); + mRequestedTimeout.reset(); + } + + void assertTimeoutWasNotRequested() { + ASSERT_FALSE(mRequestedTimeout) << "Expected no timeout to have been requested," + " but one was requested at time " + << *mRequestedTimeout; + } + + void getExternalStylusDevices(std::vector& outDevices) override { + outDevices = mExternalStylusDevices; + } + + void setExternalStylusDevices(std::vector&& devices) { + mExternalStylusDevices = devices; + } + + private: + int32_t mGlobalMetaState; + bool mUpdateGlobalMetaStateWasCalled; + int32_t mGeneration; + std::optional mRequestedTimeout; + std::vector mExternalStylusDevices; + } mFakeContext; + + friend class InputReaderTest; + +public: + FakeInputReaderContext* getContext() { return &mFakeContext; } + +private: + std::queue> mNextDevices; +}; + +} // namespace android diff --git a/services/inputflinger/tests/InterfaceMocks.h b/services/inputflinger/tests/InterfaceMocks.h new file mode 100644 index 0000000000000000000000000000000000000000..d720a902dce7471fa96f150262b80890921821e3 --- /dev/null +++ b/services/inputflinger/tests/InterfaceMocks.h @@ -0,0 +1,146 @@ +/* + * 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. + */ + +#pragma once + +#include +#include + +namespace android { + +class MockInputReaderContext : public InputReaderContext { +public: + MOCK_METHOD(void, updateGlobalMetaState, (), (override)); + int32_t getGlobalMetaState() override { return 0; }; + + MOCK_METHOD(void, disableVirtualKeysUntil, (nsecs_t time), (override)); + 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)); + MOCK_METHOD(int32_t, bumpGeneration, (), (override)); + + MOCK_METHOD(void, getExternalStylusDevices, (std::vector & outDevices), + (override)); + MOCK_METHOD(std::list, dispatchExternalStylusState, (const StylusState& outState), + (override)); + + MOCK_METHOD(InputReaderPolicyInterface*, getPolicy, (), (override)); + MOCK_METHOD(EventHubInterface*, getEventHub, (), (override)); + + int32_t getNextId() override { return 1; }; + + MOCK_METHOD(void, updateLedMetaState, (int32_t metaState), (override)); + MOCK_METHOD(int32_t, getLedMetaState, (), (override)); +}; + +class MockEventHubInterface : public EventHubInterface { +public: + MOCK_METHOD(ftl::Flags, getDeviceClasses, (int32_t deviceId), (const)); + MOCK_METHOD(InputDeviceIdentifier, getDeviceIdentifier, (int32_t deviceId), (const)); + MOCK_METHOD(int32_t, getDeviceControllerNumber, (int32_t deviceId), (const)); + MOCK_METHOD(std::optional, getConfiguration, (int32_t deviceId), (const)); + MOCK_METHOD(status_t, getAbsoluteAxisInfo, + (int32_t deviceId, int axis, RawAbsoluteAxisInfo* outAxisInfo), (const)); + MOCK_METHOD(bool, hasRelativeAxis, (int32_t deviceId, int axis), (const)); + MOCK_METHOD(bool, hasInputProperty, (int32_t deviceId, int property), (const)); + MOCK_METHOD(bool, hasMscEvent, (int32_t deviceId, int mscEvent), (const)); + MOCK_METHOD(void, addKeyRemapping, (int32_t deviceId, int fromKeyCode, int toKeyCode), (const)); + MOCK_METHOD(status_t, mapKey, + (int32_t deviceId, int scanCode, int usageCode, int32_t metaState, + int32_t* outKeycode, int32_t* outMetaState, uint32_t* outFlags), + (const)); + MOCK_METHOD(status_t, mapAxis, (int32_t deviceId, int scanCode, AxisInfo* outAxisInfo), + (const)); + MOCK_METHOD(void, setExcludedDevices, (const std::vector& devices)); + MOCK_METHOD(std::vector, getEvents, (int timeoutMillis)); + MOCK_METHOD(std::vector, getVideoFrames, (int32_t deviceId)); + MOCK_METHOD((base::Result>), mapSensor, + (int32_t deviceId, int32_t absCode), (const, override)); + MOCK_METHOD(std::vector, getRawBatteryIds, (int32_t deviceId), (const, override)); + MOCK_METHOD(std::optional, getRawBatteryInfo, + (int32_t deviceId, int32_t BatteryId), (const, override)); + MOCK_METHOD(std::vector, getRawLightIds, (int32_t deviceId), (const, override)); + MOCK_METHOD(std::optional, getRawLightInfo, (int32_t deviceId, int32_t lightId), + (const, override)); + MOCK_METHOD(std::optional, getLightBrightness, (int32_t deviceId, int32_t lightId), + (const, override)); + MOCK_METHOD(void, setLightBrightness, (int32_t deviceId, int32_t lightId, int32_t brightness), + (override)); + MOCK_METHOD((std::optional>), getLightIntensities, + (int32_t deviceId, int32_t lightId), (const, override)); + MOCK_METHOD(void, setLightIntensities, + (int32_t deviceId, int32_t lightId, + (std::unordered_map)intensities), + (override)); + + MOCK_METHOD(std::optional, getRawLayoutInfo, (int32_t deviceId), + (const, override)); + MOCK_METHOD(int32_t, getScanCodeState, (int32_t deviceId, int32_t scanCode), (const, override)); + MOCK_METHOD(int32_t, getKeyCodeState, (int32_t deviceId, int32_t keyCode), (const, override)); + MOCK_METHOD(int32_t, getSwitchState, (int32_t deviceId, int32_t sw), (const, override)); + + MOCK_METHOD(status_t, getAbsoluteAxisValue, (int32_t deviceId, int32_t axis, int32_t* outValue), + (const, override)); + MOCK_METHOD(int32_t, getKeyCodeForKeyLocation, (int32_t deviceId, int32_t locationKeyCode), + (const, override)); + MOCK_METHOD(bool, markSupportedKeyCodes, + (int32_t deviceId, const std::vector& keyCodes, uint8_t* outFlags), + (const, override)); + + MOCK_METHOD(bool, hasScanCode, (int32_t deviceId, int32_t scanCode), (const, override)); + + MOCK_METHOD(bool, hasKeyCode, (int32_t deviceId, int32_t keyCode), (const, override)); + + MOCK_METHOD(bool, hasLed, (int32_t deviceId, int32_t led), (const, override)); + + MOCK_METHOD(void, setLedState, (int32_t deviceId, int32_t led, bool on), (override)); + + MOCK_METHOD(void, getVirtualKeyDefinitions, + (int32_t deviceId, std::vector& outVirtualKeys), + (const, override)); + + MOCK_METHOD(const std::shared_ptr, getKeyCharacterMap, (int32_t deviceId), + (const, override)); + + MOCK_METHOD(bool, setKeyboardLayoutOverlay, + (int32_t deviceId, std::shared_ptr map), (override)); + + MOCK_METHOD(void, vibrate, (int32_t deviceId, const VibrationElement& effect), (override)); + MOCK_METHOD(void, cancelVibrate, (int32_t deviceId), (override)); + + MOCK_METHOD(std::vector, getVibratorIds, (int32_t deviceId), (const, override)); + MOCK_METHOD(std::optional, getBatteryCapacity, (int32_t deviceId, int32_t batteryId), + (const, override)); + + MOCK_METHOD(std::optional, getBatteryStatus, (int32_t deviceId, int32_t batteryId), + (const, override)); + MOCK_METHOD(void, requestReopenDevices, (), (override)); + MOCK_METHOD(void, wake, (), (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)); + MOCK_METHOD(status_t, disableDevice, (int32_t deviceId), (override)); + MOCK_METHOD(void, sysfsNodeChanged, (const std::string& sysfsNodePath), (override)); +}; + +} // namespace android diff --git a/services/inputflinger/tests/LatencyTracker_test.cpp b/services/inputflinger/tests/LatencyTracker_test.cpp index 89c0741dfddc3e32c05ac0ae5ae3b733330ce401..fa149dba0553857c57b84f28d72cb19e930c861c 100644 --- a/services/inputflinger/tests/LatencyTracker_test.cpp +++ b/services/inputflinger/tests/LatencyTracker_test.cpp @@ -36,15 +36,15 @@ const std::chrono::duration ANR_TIMEOUT = std::chrono::milliseconds( InputEventTimeline getTestTimeline() { InputEventTimeline t( - /*isDown*/ true, - /*eventTime*/ 2, - /*readTime*/ 3); - ConnectionTimeline expectedCT(/*deliveryTime*/ 6, /* consumeTime*/ 7, /*finishTime*/ 8); + /*isDown=*/true, + /*eventTime=*/2, + /*readTime=*/3); + ConnectionTimeline expectedCT(/*deliveryTime=*/6, /*consumeTime=*/7, /*finishTime=*/8); std::array graphicsTimeline; graphicsTimeline[GraphicsTimeline::GPU_COMPLETED_TIME] = 9; graphicsTimeline[GraphicsTimeline::PRESENT_TIME] = 10; expectedCT.setGraphicsTimeline(std::move(graphicsTimeline)); - t.connectionTimelines.emplace(new BBinder(), std::move(expectedCT)); + t.connectionTimelines.emplace(sp::make(), std::move(expectedCT)); return t; } @@ -56,8 +56,8 @@ protected: sp connection2; void SetUp() override { - connection1 = new BBinder(); - connection2 = new BBinder(); + connection1 = sp::make(); + connection2 = sp::make(); mTracker = std::make_unique(this); } @@ -87,7 +87,8 @@ private: void LatencyTrackerTest::triggerEventReporting(nsecs_t lastEventTime) { const nsecs_t triggerEventTime = lastEventTime + std::chrono::nanoseconds(ANR_TIMEOUT).count() + 1; - mTracker->trackListener(1 /*inputEventId*/, true /*isDown*/, triggerEventTime, 3 /*readTime*/); + mTracker->trackListener(/*inputEventId=*/1, /*isDown=*/true, triggerEventTime, + /*readTime=*/3); } void LatencyTrackerTest::assertReceivedTimeline(const InputEventTimeline& timeline) { @@ -136,8 +137,9 @@ void LatencyTrackerTest::assertReceivedTimelines(const std::vectortrackListener(1 /*inputEventId*/, false /*isDown*/, 2 /*eventTime*/, 3 /*readTime*/); - triggerEventReporting(2 /*eventTime*/); + mTracker->trackListener(/*inputEventId=*/1, /*isDown=*/false, /*eventTime=*/2, + /*readTime=*/3); + triggerEventReporting(/*eventTime=*/2); assertReceivedTimeline(InputEventTimeline{false, 2, 3}); } @@ -145,9 +147,9 @@ TEST_F(LatencyTrackerTest, TrackListener_DoesNotTriggerReporting) { * A single call to trackFinishedEvent should not cause a timeline to be reported. */ TEST_F(LatencyTrackerTest, TrackFinishedEvent_DoesNotTriggerReporting) { - mTracker->trackFinishedEvent(1 /*inputEventId*/, connection1, 2 /*deliveryTime*/, - 3 /*consumeTime*/, 4 /*finishTime*/); - triggerEventReporting(4 /*eventTime*/); + mTracker->trackFinishedEvent(/*inputEventId=*/1, connection1, /*deliveryTime=*/2, + /*consumeTime=*/3, /*finishTime=*/4); + triggerEventReporting(/*eventTime=*/4); assertReceivedTimelines({}); } @@ -158,8 +160,8 @@ TEST_F(LatencyTrackerTest, TrackGraphicsLatency_DoesNotTriggerReporting) { std::array graphicsTimeline; graphicsTimeline[GraphicsTimeline::GPU_COMPLETED_TIME] = 2; graphicsTimeline[GraphicsTimeline::PRESENT_TIME] = 3; - mTracker->trackGraphicsLatency(1 /*inputEventId*/, connection2, graphicsTimeline); - triggerEventReporting(3 /*eventTime*/); + mTracker->trackGraphicsLatency(/*inputEventId=*/1, connection2, graphicsTimeline); + triggerEventReporting(/*eventTime=*/3); assertReceivedTimelines({}); } @@ -189,10 +191,10 @@ TEST_F(LatencyTrackerTest, WhenDuplicateEventsAreReported_DoesNotCrash) { // In the following 2 calls to trackListener, the inputEventId's are the same, but event times // are different. - mTracker->trackListener(inputEventId, isDown, 1 /*eventTime*/, readTime); - mTracker->trackListener(inputEventId, isDown, 2 /*eventTime*/, readTime); + mTracker->trackListener(inputEventId, isDown, /*eventTime=*/1, readTime); + mTracker->trackListener(inputEventId, isDown, /*eventTime=*/2, readTime); - triggerEventReporting(2 /*eventTime*/); + triggerEventReporting(/*eventTime=*/2); // Since we sent duplicate input events, the tracker should just delete all of them, because it // does not have enough information to properly track them. assertReceivedTimelines({}); @@ -215,13 +217,13 @@ TEST_F(LatencyTrackerTest, MultipleEvents_AreReportedConsistently) { constexpr int32_t inputEventId2 = 10; InputEventTimeline timeline2( - /*isDown*/ false, - /*eventTime*/ 20, - /*readTime*/ 30); + /*isDown=*/false, + /*eventTime=*/20, + /*readTime=*/30); timeline2.connectionTimelines.emplace(connection2, - ConnectionTimeline(/*deliveryTime*/ 60, - /*consumeTime*/ 70, - /*finishTime*/ 80)); + ConnectionTimeline(/*deliveryTime=*/60, + /*consumeTime=*/70, + /*finishTime=*/80)); ConnectionTimeline& connectionTimeline2 = timeline2.connectionTimelines.begin()->second; std::array graphicsTimeline2; graphicsTimeline2[GraphicsTimeline::GPU_COMPLETED_TIME] = 90; @@ -258,15 +260,15 @@ TEST_F(LatencyTrackerTest, IncompleteEvents_AreHandledConsistently) { const sp& token = timeline.connectionTimelines.begin()->first; for (size_t i = 1; i <= 100; i++) { - mTracker->trackListener(i /*inputEventId*/, timeline.isDown, timeline.eventTime, + mTracker->trackListener(/*inputEventId=*/i, timeline.isDown, timeline.eventTime, timeline.readTime); expectedTimelines.push_back( InputEventTimeline{timeline.isDown, timeline.eventTime, timeline.readTime}); } // Now, complete the first event that was sent. - mTracker->trackFinishedEvent(1 /*inputEventId*/, token, expectedCT.deliveryTime, + mTracker->trackFinishedEvent(/*inputEventId=*/1, token, expectedCT.deliveryTime, expectedCT.consumeTime, expectedCT.finishTime); - mTracker->trackGraphicsLatency(1 /*inputEventId*/, token, expectedCT.graphicsTimeline); + mTracker->trackGraphicsLatency(/*inputEventId=*/1, token, expectedCT.graphicsTimeline); expectedTimelines[0].connectionTimelines.emplace(token, std::move(expectedCT)); triggerEventReporting(timeline.eventTime); diff --git a/services/inputflinger/tests/NotifyArgs_test.cpp b/services/inputflinger/tests/NotifyArgs_test.cpp new file mode 100644 index 0000000000000000000000000000000000000000..15367568ab35e5b9d888df76f16eab39d70894d7 --- /dev/null +++ b/services/inputflinger/tests/NotifyArgs_test.cpp @@ -0,0 +1,85 @@ +/* + * 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. + */ + +#include +#include + +#include +#include "android/input.h" +#include "input/Input.h" +#include "input/TouchVideoFrame.h" + +namespace android { + +// --- NotifyArgsTest --- + +/** + * Validate basic copy assignment. + */ +TEST(NotifyMotionArgsTest, TestCopyAssignmentOperator) { + int32_t id = 123; + nsecs_t downTime = systemTime(); + nsecs_t eventTime = downTime++; + nsecs_t readTime = downTime++; + int32_t deviceId = 7; + uint32_t source = AINPUT_SOURCE_TOUCHSCREEN; + int32_t displayId = 42; + uint32_t policyFlags = POLICY_FLAG_GESTURE; + int32_t action = AMOTION_EVENT_ACTION_HOVER_MOVE; + int32_t actionButton = AMOTION_EVENT_BUTTON_PRIMARY; + int32_t flags = AMOTION_EVENT_FLAG_WINDOW_IS_OBSCURED; + int32_t metaState = AMETA_SCROLL_LOCK_ON; + uint32_t buttonState = AMOTION_EVENT_BUTTON_PRIMARY | AMOTION_EVENT_BUTTON_SECONDARY; + MotionClassification classification = MotionClassification::DEEP_PRESS; + int32_t edgeFlags = AMOTION_EVENT_EDGE_FLAG_TOP; + uint32_t pointerCount = 2; + PointerProperties pointerProperties[pointerCount]; + PointerCoords pointerCoords[pointerCount]; + float x = 0; + float y = 10; + + for (size_t i = 0; i < pointerCount; i++) { + pointerProperties[i].clear(); + pointerProperties[i].id = i; + pointerProperties[i].toolType = ToolType::FINGER; + + pointerCoords[i].clear(); + pointerCoords[i].setAxisValue(AMOTION_EVENT_AXIS_X, x++); + pointerCoords[i].setAxisValue(AMOTION_EVENT_AXIS_Y, y++); + } + + float xPrecision = 1.2f; + float yPrecision = 3.4f; + float xCursorPosition = 5.6f; + float yCursorPosition = 7.8f; + + std::vector videoData = {1, 2, 3, 4}; + timeval timestamp = {5, 6}; + TouchVideoFrame frame(2, 2, std::move(videoData), timestamp); + std::vector videoFrames = {frame}; + const NotifyMotionArgs args(id, eventTime, readTime, deviceId, source, displayId, policyFlags, + action, actionButton, flags, metaState, buttonState, classification, + edgeFlags, pointerCount, pointerProperties, pointerCoords, + xPrecision, yPrecision, xCursorPosition, yCursorPosition, downTime, + videoFrames); + + NotifyMotionArgs otherArgs{}; + otherArgs = args; + + EXPECT_EQ(args, otherArgs); +} + +} // namespace android \ No newline at end of file diff --git a/services/inputflinger/tests/PreferStylusOverTouch_test.cpp b/services/inputflinger/tests/PreferStylusOverTouch_test.cpp index 8e2ab88e804bd9e12dd17a92f694445dce1c84bc..9818176cb02734c47703c149fe0315ef09a79259 100644 --- a/services/inputflinger/tests/PreferStylusOverTouch_test.cpp +++ b/services/inputflinger/tests/PreferStylusOverTouch_test.cpp @@ -15,6 +15,7 @@ */ #include +#include #include "../PreferStylusOverTouchBlocker.h" namespace android { @@ -33,14 +34,8 @@ static constexpr int32_t POINTER_1_DOWN = constexpr int32_t TOUCHSCREEN = AINPUT_SOURCE_TOUCHSCREEN; constexpr int32_t STYLUS = AINPUT_SOURCE_STYLUS; -struct PointerData { - float x; - float y; -}; - static NotifyMotionArgs generateMotionArgs(nsecs_t downTime, nsecs_t eventTime, int32_t action, - const std::vector& points, - uint32_t source) { + const std::vector& points, uint32_t source) { size_t pointerCount = points.size(); if (action == DOWN || action == UP) { EXPECT_EQ(1U, pointerCount) << "Actions DOWN and UP can only contain a single pointer"; @@ -50,8 +45,8 @@ static NotifyMotionArgs generateMotionArgs(nsecs_t downTime, nsecs_t eventTime, PointerCoords pointerCoords[pointerCount]; const int32_t deviceId = isFromSource(source, TOUCHSCREEN) ? TOUCH_DEVICE_ID : STYLUS_DEVICE_ID; - const int32_t toolType = isFromSource(source, TOUCHSCREEN) ? AMOTION_EVENT_TOOL_TYPE_FINGER - : AMOTION_EVENT_TOOL_TYPE_STYLUS; + const ToolType toolType = + isFromSource(source, TOUCHSCREEN) ? ToolType::FINGER : ToolType::STYLUS; for (size_t i = 0; i < pointerCount; i++) { pointerProperties[i].clear(); pointerProperties[i].id = i; @@ -69,13 +64,13 @@ static NotifyMotionArgs generateMotionArgs(nsecs_t downTime, nsecs_t eventTime, } // Define a valid motion event. - NotifyMotionArgs args(/* id */ 0, eventTime, 0 /*readTime*/, deviceId, source, 0 /*displayId*/, + NotifyMotionArgs args(/*id=*/0, eventTime, /*readTime=*/0, deviceId, source, /*displayId=*/0, 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, + /*flags=*/0, AMETA_NONE, /*buttonState=*/0, MotionClassification::NONE, + AMOTION_EVENT_EDGE_FLAG_NONE, pointerCount, pointerProperties, + pointerCoords, /*xPrecision=*/0, /*yPrecision=*/0, AMOTION_EVENT_INVALID_CURSOR_POSITION, - AMOTION_EVENT_INVALID_CURSOR_POSITION, downTime, /* videoFrames */ {}); + AMOTION_EVENT_INVALID_CURSOR_POSITION, downTime, /*videoFrames=*/{}); return args; } @@ -114,26 +109,26 @@ private: TEST_F(PreferStylusOverTouchTest, TouchGestureIsNotBlocked) { NotifyMotionArgs args; - args = generateMotionArgs(0 /*downTime*/, 0 /*eventTime*/, DOWN, {{1, 2}}, TOUCHSCREEN); + args = generateMotionArgs(/*downTime=*/0, /*eventTime=*/0, DOWN, {{1, 2}}, TOUCHSCREEN); assertNotBlocked(args); - args = generateMotionArgs(0 /*downTime*/, 1 /*eventTime*/, MOVE, {{1, 3}}, TOUCHSCREEN); + args = generateMotionArgs(/*downTime=*/0, /*eventTime=*/1, MOVE, {{1, 3}}, TOUCHSCREEN); assertNotBlocked(args); - args = generateMotionArgs(0 /*downTime*/, 2 /*eventTime*/, UP, {{1, 3}}, TOUCHSCREEN); + args = generateMotionArgs(/*downTime=*/0, /*eventTime=*/2, UP, {{1, 3}}, TOUCHSCREEN); assertNotBlocked(args); } TEST_F(PreferStylusOverTouchTest, StylusGestureIsNotBlocked) { NotifyMotionArgs args; - args = generateMotionArgs(0 /*downTime*/, 0 /*eventTime*/, DOWN, {{1, 2}}, STYLUS); + args = generateMotionArgs(/*downTime=*/0, /*eventTime=*/0, DOWN, {{1, 2}}, STYLUS); assertNotBlocked(args); - args = generateMotionArgs(0 /*downTime*/, 1 /*eventTime*/, MOVE, {{1, 3}}, STYLUS); + args = generateMotionArgs(/*downTime=*/0, /*eventTime=*/1, MOVE, {{1, 3}}, STYLUS); assertNotBlocked(args); - args = generateMotionArgs(0 /*downTime*/, 2 /*eventTime*/, UP, {{1, 3}}, STYLUS); + args = generateMotionArgs(/*downTime=*/0, /*eventTime=*/2, UP, {{1, 3}}, STYLUS); assertNotBlocked(args); } @@ -144,24 +139,24 @@ TEST_F(PreferStylusOverTouchTest, StylusGestureIsNotBlocked) { TEST_F(PreferStylusOverTouchTest, TouchIsCanceledWhenStylusGoesDown) { NotifyMotionArgs args; - args = generateMotionArgs(0 /*downTime*/, 0 /*eventTime*/, DOWN, {{1, 2}}, TOUCHSCREEN); + args = generateMotionArgs(/*downTime=*/0, /*eventTime=*/0, DOWN, {{1, 2}}, TOUCHSCREEN); assertNotBlocked(args); - args = generateMotionArgs(0 /*downTime*/, 1 /*eventTime*/, MOVE, {{1, 3}}, TOUCHSCREEN); + args = generateMotionArgs(/*downTime=*/0, /*eventTime=*/1, MOVE, {{1, 3}}, TOUCHSCREEN); assertNotBlocked(args); - args = generateMotionArgs(3 /*downTime*/, 3 /*eventTime*/, DOWN, {{10, 30}}, STYLUS); + args = generateMotionArgs(/*downTime=*/3, /*eventTime=*/3, DOWN, {{10, 30}}, STYLUS); NotifyMotionArgs cancelArgs = - generateMotionArgs(0 /*downTime*/, 1 /*eventTime*/, CANCEL, {{1, 3}}, TOUCHSCREEN); + generateMotionArgs(/*downTime=*/0, /*eventTime=*/1, CANCEL, {{1, 3}}, TOUCHSCREEN); cancelArgs.flags |= AMOTION_EVENT_FLAG_CANCELED; assertResponse(args, {cancelArgs, args}); // Both stylus and touch events continue. Stylus should be not blocked, and touch should be // blocked - args = generateMotionArgs(3 /*downTime*/, 4 /*eventTime*/, MOVE, {{10, 31}}, STYLUS); + args = generateMotionArgs(/*downTime=*/3, /*eventTime=*/4, MOVE, {{10, 31}}, STYLUS); assertNotBlocked(args); - args = generateMotionArgs(0 /*downTime*/, 5 /*eventTime*/, MOVE, {{1, 4}}, TOUCHSCREEN); + args = generateMotionArgs(/*downTime=*/0, /*eventTime=*/5, MOVE, {{1, 4}}, TOUCHSCREEN); assertDropped(args); } @@ -171,17 +166,17 @@ TEST_F(PreferStylusOverTouchTest, TouchIsCanceledWhenStylusGoesDown) { TEST_F(PreferStylusOverTouchTest, StylusDownAfterTouch) { NotifyMotionArgs args; - args = generateMotionArgs(0 /*downTime*/, 0 /*eventTime*/, DOWN, {{1, 2}}, TOUCHSCREEN); + args = generateMotionArgs(/*downTime=*/0, /*eventTime=*/0, DOWN, {{1, 2}}, TOUCHSCREEN); assertNotBlocked(args); - args = generateMotionArgs(0 /*downTime*/, 1 /*eventTime*/, MOVE, {{1, 3}}, TOUCHSCREEN); + args = generateMotionArgs(/*downTime=*/0, /*eventTime=*/1, MOVE, {{1, 3}}, TOUCHSCREEN); assertNotBlocked(args); - args = generateMotionArgs(0 /*downTime*/, 2 /*eventTime*/, UP, {{1, 3}}, TOUCHSCREEN); + args = generateMotionArgs(/*downTime=*/0, /*eventTime=*/2, UP, {{1, 3}}, TOUCHSCREEN); assertNotBlocked(args); // Stylus goes down - args = generateMotionArgs(3 /*downTime*/, 3 /*eventTime*/, DOWN, {{10, 30}}, STYLUS); + args = generateMotionArgs(/*downTime=*/3, /*eventTime=*/3, DOWN, {{10, 30}}, STYLUS); assertNotBlocked(args); } @@ -194,21 +189,21 @@ TEST_F(PreferStylusOverTouchTest, NewTouchIsBlockedWhenStylusIsDown) { constexpr nsecs_t stylusDownTime = 0; constexpr nsecs_t touchDownTime = 1; - args = generateMotionArgs(stylusDownTime, 0 /*eventTime*/, DOWN, {{10, 30}}, STYLUS); + args = generateMotionArgs(stylusDownTime, /*eventTime=*/0, DOWN, {{10, 30}}, STYLUS); assertNotBlocked(args); - args = generateMotionArgs(touchDownTime, 1 /*eventTime*/, DOWN, {{1, 2}}, TOUCHSCREEN); + args = generateMotionArgs(touchDownTime, /*eventTime=*/1, DOWN, {{1, 2}}, TOUCHSCREEN); assertDropped(args); // Stylus should continue to work - args = generateMotionArgs(stylusDownTime, 2 /*eventTime*/, MOVE, {{10, 31}}, STYLUS); + args = generateMotionArgs(stylusDownTime, /*eventTime=*/2, MOVE, {{10, 31}}, STYLUS); assertNotBlocked(args); // Touch should continue to be blocked - args = generateMotionArgs(touchDownTime, 1 /*eventTime*/, MOVE, {{1, 3}}, TOUCHSCREEN); + args = generateMotionArgs(touchDownTime, /*eventTime=*/1, MOVE, {{1, 3}}, TOUCHSCREEN); assertDropped(args); - args = generateMotionArgs(0 /*downTime*/, 5 /*eventTime*/, MOVE, {{1, 4}}, TOUCHSCREEN); + args = generateMotionArgs(/*downTime=*/0, /*eventTime=*/5, MOVE, {{1, 4}}, TOUCHSCREEN); assertDropped(args); } @@ -222,23 +217,23 @@ TEST_F(PreferStylusOverTouchTest, NewTouchWorksAfterStylusIsLifted) { constexpr nsecs_t touchDownTime = 4; // Stylus goes down and up - args = generateMotionArgs(stylusDownTime, 0 /*eventTime*/, DOWN, {{10, 30}}, STYLUS); + args = generateMotionArgs(stylusDownTime, /*eventTime=*/0, DOWN, {{10, 30}}, STYLUS); assertNotBlocked(args); - args = generateMotionArgs(stylusDownTime, 2 /*eventTime*/, MOVE, {{10, 31}}, STYLUS); + args = generateMotionArgs(stylusDownTime, /*eventTime=*/2, MOVE, {{10, 31}}, STYLUS); assertNotBlocked(args); - args = generateMotionArgs(stylusDownTime, 3 /*eventTime*/, UP, {{10, 31}}, STYLUS); + args = generateMotionArgs(stylusDownTime, /*eventTime=*/3, UP, {{10, 31}}, STYLUS); assertNotBlocked(args); // New touch goes down. It should not be blocked args = generateMotionArgs(touchDownTime, touchDownTime, DOWN, {{1, 2}}, TOUCHSCREEN); assertNotBlocked(args); - args = generateMotionArgs(touchDownTime, 5 /*eventTime*/, MOVE, {{1, 3}}, TOUCHSCREEN); + args = generateMotionArgs(touchDownTime, /*eventTime=*/5, MOVE, {{1, 3}}, TOUCHSCREEN); assertNotBlocked(args); - args = generateMotionArgs(touchDownTime, 6 /*eventTime*/, UP, {{1, 3}}, TOUCHSCREEN); + args = generateMotionArgs(touchDownTime, /*eventTime=*/6, UP, {{1, 3}}, TOUCHSCREEN); assertNotBlocked(args); } @@ -251,25 +246,25 @@ TEST_F(PreferStylusOverTouchTest, AfterStylusIsLiftedCurrentTouchIsBlocked) { constexpr nsecs_t stylusDownTime = 0; constexpr nsecs_t touchDownTime = 1; - assertNotBlocked(generateMotionArgs(stylusDownTime, 0 /*eventTime*/, DOWN, {{10, 30}}, STYLUS)); + assertNotBlocked(generateMotionArgs(stylusDownTime, /*eventTime=*/0, DOWN, {{10, 30}}, STYLUS)); - args = generateMotionArgs(touchDownTime, 1 /*eventTime*/, DOWN, {{1, 2}}, TOUCHSCREEN); + args = generateMotionArgs(touchDownTime, /*eventTime=*/1, DOWN, {{1, 2}}, TOUCHSCREEN); assertDropped(args); // Lift the stylus - args = generateMotionArgs(stylusDownTime, 2 /*eventTime*/, UP, {{10, 30}}, STYLUS); + args = generateMotionArgs(stylusDownTime, /*eventTime=*/2, UP, {{10, 30}}, STYLUS); assertNotBlocked(args); // Touch should continue to be blocked - args = generateMotionArgs(touchDownTime, 3 /*eventTime*/, MOVE, {{1, 3}}, TOUCHSCREEN); + args = generateMotionArgs(touchDownTime, /*eventTime=*/3, MOVE, {{1, 3}}, TOUCHSCREEN); assertDropped(args); - args = generateMotionArgs(touchDownTime, 4 /*eventTime*/, UP, {{1, 3}}, TOUCHSCREEN); + args = generateMotionArgs(touchDownTime, /*eventTime=*/4, UP, {{1, 3}}, TOUCHSCREEN); assertDropped(args); // New touch should go through, though. constexpr nsecs_t newTouchDownTime = 5; - args = generateMotionArgs(newTouchDownTime, 5 /*eventTime*/, DOWN, {{10, 20}}, TOUCHSCREEN); + args = generateMotionArgs(newTouchDownTime, /*eventTime=*/5, DOWN, {{10, 20}}, TOUCHSCREEN); assertNotBlocked(args); } @@ -281,22 +276,22 @@ TEST_F(PreferStylusOverTouchTest, MixedStylusAndTouchPointersAreIgnored) { NotifyMotionArgs args; // Event from a stylus device, but with finger tool type - args = generateMotionArgs(1 /*downTime*/, 1 /*eventTime*/, DOWN, {{1, 2}}, STYLUS); + args = generateMotionArgs(/*downTime=*/1, /*eventTime=*/1, DOWN, {{1, 2}}, STYLUS); // Keep source stylus, but make the tool type touch - args.pointerProperties[0].toolType = AMOTION_EVENT_TOOL_TYPE_FINGER; + args.pointerProperties[0].toolType = ToolType::FINGER; assertNotBlocked(args); // Second pointer (stylus pointer) goes down, from the same device - args = generateMotionArgs(1 /*downTime*/, 2 /*eventTime*/, POINTER_1_DOWN, {{1, 2}, {10, 20}}, + args = generateMotionArgs(/*downTime=*/1, /*eventTime=*/2, POINTER_1_DOWN, {{1, 2}, {10, 20}}, STYLUS); // Keep source stylus, but make the tool type touch - args.pointerProperties[0].toolType = AMOTION_EVENT_TOOL_TYPE_STYLUS; + args.pointerProperties[0].toolType = ToolType::STYLUS; assertNotBlocked(args); // Second pointer (stylus pointer) goes down, from the same device - args = generateMotionArgs(1 /*downTime*/, 3 /*eventTime*/, MOVE, {{2, 3}, {11, 21}}, STYLUS); + args = generateMotionArgs(/*downTime=*/1, /*eventTime=*/3, MOVE, {{2, 3}, {11, 21}}, STYLUS); // Keep source stylus, but make the tool type touch - args.pointerProperties[0].toolType = AMOTION_EVENT_TOOL_TYPE_FINGER; + args.pointerProperties[0].toolType = ToolType::FINGER; assertNotBlocked(args); } @@ -305,16 +300,16 @@ TEST_F(PreferStylusOverTouchTest, MixedStylusAndTouchPointersAreIgnored) { */ TEST_F(PreferStylusOverTouchTest, TouchFromTwoDevicesAndStylus) { NotifyMotionArgs touch1Down = - generateMotionArgs(1 /*downTime*/, 1 /*eventTime*/, DOWN, {{1, 2}}, TOUCHSCREEN); + generateMotionArgs(/*downTime=*/1, /*eventTime=*/1, DOWN, {{1, 2}}, TOUCHSCREEN); assertNotBlocked(touch1Down); NotifyMotionArgs touch2Down = - generateMotionArgs(2 /*downTime*/, 2 /*eventTime*/, DOWN, {{3, 4}}, TOUCHSCREEN); + generateMotionArgs(/*downTime=*/2, /*eventTime=*/2, DOWN, {{3, 4}}, TOUCHSCREEN); touch2Down.deviceId = SECOND_TOUCH_DEVICE_ID; assertNotBlocked(touch2Down); NotifyMotionArgs stylusDown = - generateMotionArgs(3 /*downTime*/, 3 /*eventTime*/, DOWN, {{10, 30}}, STYLUS); + generateMotionArgs(/*downTime=*/3, /*eventTime=*/3, DOWN, {{10, 30}}, STYLUS); NotifyMotionArgs cancelArgs1 = touch1Down; cancelArgs1.action = CANCEL; cancelArgs1.flags |= AMOTION_EVENT_FLAG_CANCELED; @@ -333,12 +328,12 @@ TEST_F(PreferStylusOverTouchTest, TouchFromTwoDevicesAndStylus) { TEST_F(PreferStylusOverTouchTest, AllTouchMustLiftAfterCanceledByStylus) { // First device touches down NotifyMotionArgs touch1Down = - generateMotionArgs(1 /*downTime*/, 1 /*eventTime*/, DOWN, {{1, 2}}, TOUCHSCREEN); + generateMotionArgs(/*downTime=*/1, /*eventTime=*/1, DOWN, {{1, 2}}, TOUCHSCREEN); assertNotBlocked(touch1Down); // Stylus goes down - touch should be canceled NotifyMotionArgs stylusDown = - generateMotionArgs(2 /*downTime*/, 2 /*eventTime*/, DOWN, {{10, 30}}, STYLUS); + generateMotionArgs(/*downTime=*/2, /*eventTime=*/2, DOWN, {{10, 30}}, STYLUS); NotifyMotionArgs cancelArgs1 = touch1Down; cancelArgs1.action = CANCEL; cancelArgs1.flags |= AMOTION_EVENT_FLAG_CANCELED; @@ -346,44 +341,44 @@ TEST_F(PreferStylusOverTouchTest, AllTouchMustLiftAfterCanceledByStylus) { // Stylus goes up NotifyMotionArgs stylusUp = - generateMotionArgs(2 /*downTime*/, 3 /*eventTime*/, UP, {{10, 30}}, STYLUS); + generateMotionArgs(/*downTime=*/2, /*eventTime=*/3, UP, {{10, 30}}, STYLUS); assertNotBlocked(stylusUp); // Touch from the first device remains blocked NotifyMotionArgs touch1Move = - generateMotionArgs(1 /*downTime*/, 4 /*eventTime*/, MOVE, {{2, 3}}, TOUCHSCREEN); + generateMotionArgs(/*downTime=*/1, /*eventTime=*/4, MOVE, {{2, 3}}, TOUCHSCREEN); assertDropped(touch1Move); // Second touch goes down. It should not be blocked because stylus has already lifted. NotifyMotionArgs touch2Down = - generateMotionArgs(5 /*downTime*/, 5 /*eventTime*/, DOWN, {{31, 32}}, TOUCHSCREEN); + generateMotionArgs(/*downTime=*/5, /*eventTime=*/5, DOWN, {{31, 32}}, TOUCHSCREEN); touch2Down.deviceId = SECOND_TOUCH_DEVICE_ID; assertNotBlocked(touch2Down); // First device is lifted up. It's already been canceled, so the UP event should be dropped. NotifyMotionArgs touch1Up = - generateMotionArgs(1 /*downTime*/, 6 /*eventTime*/, UP, {{2, 3}}, TOUCHSCREEN); + generateMotionArgs(/*downTime=*/1, /*eventTime=*/6, UP, {{2, 3}}, TOUCHSCREEN); assertDropped(touch1Up); // Touch from second device touch should continue to work NotifyMotionArgs touch2Move = - generateMotionArgs(5 /*downTime*/, 7 /*eventTime*/, MOVE, {{32, 33}}, TOUCHSCREEN); + generateMotionArgs(/*downTime=*/5, /*eventTime=*/7, MOVE, {{32, 33}}, TOUCHSCREEN); touch2Move.deviceId = SECOND_TOUCH_DEVICE_ID; assertNotBlocked(touch2Move); // Second touch lifts up NotifyMotionArgs touch2Up = - generateMotionArgs(5 /*downTime*/, 8 /*eventTime*/, UP, {{32, 33}}, TOUCHSCREEN); + generateMotionArgs(/*downTime=*/5, /*eventTime=*/8, UP, {{32, 33}}, TOUCHSCREEN); touch2Up.deviceId = SECOND_TOUCH_DEVICE_ID; assertNotBlocked(touch2Up); // Now that all touch has been lifted, new touch from either first or second device should work NotifyMotionArgs touch3Down = - generateMotionArgs(9 /*downTime*/, 9 /*eventTime*/, DOWN, {{1, 2}}, TOUCHSCREEN); + generateMotionArgs(/*downTime=*/9, /*eventTime=*/9, DOWN, {{1, 2}}, TOUCHSCREEN); assertNotBlocked(touch3Down); NotifyMotionArgs touch4Down = - generateMotionArgs(10 /*downTime*/, 10 /*eventTime*/, DOWN, {{100, 200}}, TOUCHSCREEN); + generateMotionArgs(/*downTime=*/10, /*eventTime=*/10, DOWN, {{100, 200}}, TOUCHSCREEN); touch4Down.deviceId = SECOND_TOUCH_DEVICE_ID; assertNotBlocked(touch4Down); } @@ -408,47 +403,47 @@ TEST_F(PreferStylusOverTouchTest, AllTouchMustLiftAfterCanceledByStylus) { TEST_F(PreferStylusOverTouchTest, MixedStylusAndTouchDeviceIsCanceledAtFirst) { // Touch from device 1 goes down NotifyMotionArgs touchDown = - generateMotionArgs(1 /*downTime*/, 1 /*eventTime*/, DOWN, {{1, 2}}, TOUCHSCREEN); + generateMotionArgs(/*downTime=*/1, /*eventTime=*/1, DOWN, {{1, 2}}, TOUCHSCREEN); touchDown.source = STYLUS; assertNotBlocked(touchDown); // Stylus from device 2 goes down. Touch should be canceled. NotifyMotionArgs args = - generateMotionArgs(2 /*downTime*/, 2 /*eventTime*/, DOWN, {{10, 20}}, STYLUS); + generateMotionArgs(/*downTime=*/2, /*eventTime=*/2, DOWN, {{10, 20}}, STYLUS); NotifyMotionArgs cancelTouchArgs = touchDown; cancelTouchArgs.action = CANCEL; cancelTouchArgs.flags |= AMOTION_EVENT_FLAG_CANCELED; assertResponse(args, {cancelTouchArgs, args}); // Introduce a stylus pointer into the device 1 stream. It should be ignored. - args = generateMotionArgs(1 /*downTime*/, 3 /*eventTime*/, POINTER_1_DOWN, {{1, 2}, {3, 4}}, + args = generateMotionArgs(/*downTime=*/1, /*eventTime=*/3, POINTER_1_DOWN, {{1, 2}, {3, 4}}, TOUCHSCREEN); - args.pointerProperties[1].toolType = AMOTION_EVENT_TOOL_TYPE_STYLUS; + args.pointerProperties[1].toolType = ToolType::STYLUS; args.source = STYLUS; assertDropped(args); // Lift up touch from the mixed touch/stylus device - args = generateMotionArgs(1 /*downTime*/, 4 /*eventTime*/, CANCEL, {{1, 2}, {3, 4}}, + args = generateMotionArgs(/*downTime=*/1, /*eventTime=*/4, CANCEL, {{1, 2}, {3, 4}}, TOUCHSCREEN); - args.pointerProperties[1].toolType = AMOTION_EVENT_TOOL_TYPE_STYLUS; + args.pointerProperties[1].toolType = ToolType::STYLUS; args.source = STYLUS; assertDropped(args); // Stylus from device 2 is still down. Since the device 1 is now identified as a mixed // touch/stylus device, its events should go through, even if they are touch. - args = generateMotionArgs(5 /*downTime*/, 5 /*eventTime*/, DOWN, {{21, 22}}, TOUCHSCREEN); + args = generateMotionArgs(/*downTime=*/5, /*eventTime=*/5, DOWN, {{21, 22}}, TOUCHSCREEN); touchDown.source = STYLUS; assertResponse(args, {args}); // Reconfigure such that only the stylus device remains InputDeviceInfo stylusDevice; - stylusDevice.initialize(STYLUS_DEVICE_ID, 1 /*generation*/, 1 /*controllerNumber*/, - {} /*identifier*/, "stylus device", false /*external*/, - false /*hasMic*/); + stylusDevice.initialize(STYLUS_DEVICE_ID, /*generation=*/1, /*controllerNumber=*/1, + /*identifier=*/{}, "stylus device", /*external=*/false, + /*hasMic=*/false, 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. - args = generateMotionArgs(6 /*downTime*/, 6 /*eventTime*/, DOWN, {{1, 2}}, TOUCHSCREEN); + args = generateMotionArgs(/*downTime=*/6, /*eventTime=*/6, DOWN, {{1, 2}}, TOUCHSCREEN); args.source = STYLUS; assertDropped(args); } @@ -461,41 +456,41 @@ TEST_F(PreferStylusOverTouchTest, TouchIsBlockedWhenTwoStyliAreUsed) { NotifyMotionArgs args; // First stylus is down - assertNotBlocked(generateMotionArgs(0 /*downTime*/, 0 /*eventTime*/, DOWN, {{10, 30}}, STYLUS)); + assertNotBlocked(generateMotionArgs(/*downTime=*/0, /*eventTime=*/0, DOWN, {{10, 30}}, STYLUS)); // Second stylus is down - args = generateMotionArgs(1 /*downTime*/, 1 /*eventTime*/, DOWN, {{20, 40}}, STYLUS); + args = generateMotionArgs(/*downTime=*/1, /*eventTime=*/1, DOWN, {{20, 40}}, STYLUS); args.deviceId = SECOND_STYLUS_DEVICE_ID; assertNotBlocked(args); // Touch goes down. It should be ignored. - args = generateMotionArgs(2 /*downTime*/, 2 /*eventTime*/, DOWN, {{1, 2}}, TOUCHSCREEN); + args = generateMotionArgs(/*downTime=*/2, /*eventTime=*/2, DOWN, {{1, 2}}, TOUCHSCREEN); assertDropped(args); // Lift the first stylus - args = generateMotionArgs(0 /*downTime*/, 3 /*eventTime*/, UP, {{10, 30}}, STYLUS); + args = generateMotionArgs(/*downTime=*/0, /*eventTime=*/3, UP, {{10, 30}}, STYLUS); assertNotBlocked(args); // Touch should continue to be blocked - args = generateMotionArgs(2 /*downTime*/, 4 /*eventTime*/, UP, {{1, 2}}, TOUCHSCREEN); + args = generateMotionArgs(/*downTime=*/2, /*eventTime=*/4, UP, {{1, 2}}, TOUCHSCREEN); assertDropped(args); // New touch should be blocked because second stylus is still down - args = generateMotionArgs(5 /*downTime*/, 5 /*eventTime*/, DOWN, {{5, 6}}, TOUCHSCREEN); + args = generateMotionArgs(/*downTime=*/5, /*eventTime=*/5, DOWN, {{5, 6}}, TOUCHSCREEN); assertDropped(args); // Second stylus goes up - args = generateMotionArgs(1 /*downTime*/, 6 /*eventTime*/, UP, {{20, 40}}, STYLUS); + args = generateMotionArgs(/*downTime=*/1, /*eventTime=*/6, UP, {{20, 40}}, STYLUS); args.deviceId = SECOND_STYLUS_DEVICE_ID; assertNotBlocked(args); // Current touch gesture should continue to be blocked // Touch should continue to be blocked - args = generateMotionArgs(5 /*downTime*/, 7 /*eventTime*/, UP, {{5, 6}}, TOUCHSCREEN); + args = generateMotionArgs(/*downTime=*/5, /*eventTime=*/7, UP, {{5, 6}}, TOUCHSCREEN); assertDropped(args); // Now that all styli were lifted, new touch should go through - args = generateMotionArgs(8 /*downTime*/, 8 /*eventTime*/, DOWN, {{7, 8}}, TOUCHSCREEN); + args = generateMotionArgs(/*downTime=*/8, /*eventTime=*/8, DOWN, {{7, 8}}, TOUCHSCREEN); assertNotBlocked(args); } diff --git a/services/inputflinger/tests/PropertyProvider_test.cpp b/services/inputflinger/tests/PropertyProvider_test.cpp new file mode 100644 index 0000000000000000000000000000000000000000..8a40e78b8971e7399e35348ada466572f0fb2b8b --- /dev/null +++ b/services/inputflinger/tests/PropertyProvider_test.cpp @@ -0,0 +1,351 @@ +/* + * 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 +#include + +#include "TestConstants.h" +#include "include/gestures.h" + +namespace android { + +using testing::ElementsAre; + +class PropertyProviderTest : public testing::Test { +protected: + PropertyProvider mProvider; +}; + +TEST_F(PropertyProviderTest, Int_Create) { + const size_t COUNT = 4; + int intData[COUNT] = {0, 0, 0, 0}; + int initialValues[COUNT] = {1, 2, 3, 4}; + gesturePropProvider.create_int_fn(&mProvider, "Some Integers", intData, COUNT, initialValues); + + ASSERT_TRUE(mProvider.hasProperty("Some Integers")); + GesturesProp& prop = mProvider.getProperty("Some Integers"); + EXPECT_EQ(prop.getName(), "Some Integers"); + EXPECT_EQ(prop.getCount(), COUNT); + EXPECT_THAT(intData, ElementsAre(1, 2, 3, 4)); +} + +TEST_F(PropertyProviderTest, Int_Get) { + const size_t COUNT = 4; + int intData[COUNT] = {0, 0, 0, 0}; + int initialValues[COUNT] = {9, 9, 9, 9}; + GesturesProp* propPtr = gesturePropProvider.create_int_fn(&mProvider, "Some Integers", intData, + COUNT, initialValues); + + // Get handlers are supposed to be called before the property's data is accessed, so they can + // update it if necessary. This getter updates the values, so that the ordering can be checked. + GesturesPropGetHandler getter{[](void* handlerData) -> GesturesPropBool { + int* array = static_cast(handlerData); + array[0] = 1; + array[1] = 2; + array[2] = 3; + array[3] = 4; + return true; + }}; + gesturePropProvider.register_handlers_fn(&mProvider, propPtr, /* handler_data= */ intData, + getter, nullptr); + + ASSERT_TRUE(mProvider.hasProperty("Some Integers")); + GesturesProp& prop = mProvider.getProperty("Some Integers"); + EXPECT_THAT(prop.getIntValues(), ElementsAre(1, 2, 3, 4)); +} + +TEST_F(PropertyProviderTest, Int_Set) { + const size_t COUNT = 4; + int intData[COUNT] = {0, 0, 0, 0}; + int initialValues[COUNT] = {9, 9, 9, 9}; + GesturesProp* propPtr = gesturePropProvider.create_int_fn(&mProvider, "Some Integers", intData, + COUNT, initialValues); + + struct SetterData { + bool setterCalled; + int* propertyData; + }; + SetterData setterData = {false, intData}; + GesturesPropSetHandler setter{[](void* handlerData) { + SetterData* data = static_cast(handlerData); + // Set handlers should be called after the property's data has changed, so check the data. + EXPECT_EQ(data->propertyData[0], 1); + EXPECT_EQ(data->propertyData[1], 2); + EXPECT_EQ(data->propertyData[2], 3); + EXPECT_EQ(data->propertyData[3], 4); + data->setterCalled = true; + }}; + gesturePropProvider.register_handlers_fn(&mProvider, propPtr, /* handler_data= */ &setterData, + nullptr, setter); + + ASSERT_TRUE(mProvider.hasProperty("Some Integers")); + GesturesProp& prop = mProvider.getProperty("Some Integers"); + prop.setIntValues({1, 2, 3, 4}); + EXPECT_THAT(intData, ElementsAre(1, 2, 3, 4)); + EXPECT_TRUE(setterData.setterCalled); + EXPECT_THAT(prop.getIntValues(), ElementsAre(1, 2, 3, 4)); +} + +TEST_F(PropertyProviderTest, Bool_Create) { + const size_t COUNT = 3; + GesturesPropBool boolData[COUNT] = {false, false, false}; + GesturesPropBool initialValues[COUNT] = {true, false, false}; + gesturePropProvider.create_bool_fn(&mProvider, "Some Booleans", boolData, COUNT, initialValues); + + ASSERT_TRUE(mProvider.hasProperty("Some Booleans")); + GesturesProp& prop = mProvider.getProperty("Some Booleans"); + EXPECT_EQ(prop.getName(), "Some Booleans"); + EXPECT_EQ(prop.getCount(), COUNT); + EXPECT_THAT(boolData, ElementsAre(true, false, false)); +} + +TEST_F(PropertyProviderTest, Bool_Get) { + const size_t COUNT = 3; + GesturesPropBool boolData[COUNT] = {false, false, false}; + GesturesPropBool initialValues[COUNT] = {true, false, false}; + GesturesProp* propPtr = gesturePropProvider.create_bool_fn(&mProvider, "Some Booleans", + boolData, COUNT, initialValues); + + // Get handlers are supposed to be called before the property's data is accessed, so they can + // update it if necessary. This getter updates the values, so that the ordering can be checked. + GesturesPropGetHandler getter{[](void* handlerData) -> GesturesPropBool { + GesturesPropBool* array = static_cast(handlerData); + array[0] = false; + array[1] = true; + array[2] = true; + return true; + }}; + gesturePropProvider.register_handlers_fn(&mProvider, propPtr, /* handler_data= */ boolData, + getter, nullptr); + + ASSERT_TRUE(mProvider.hasProperty("Some Booleans")); + GesturesProp& prop = mProvider.getProperty("Some Booleans"); + EXPECT_THAT(prop.getBoolValues(), ElementsAre(false, true, true)); +} + +TEST_F(PropertyProviderTest, Bool_Set) { + const size_t COUNT = 3; + GesturesPropBool boolData[COUNT] = {false, false, false}; + GesturesPropBool initialValues[COUNT] = {true, false, false}; + GesturesProp* propPtr = gesturePropProvider.create_bool_fn(&mProvider, "Some Booleans", + boolData, COUNT, initialValues); + + struct SetterData { + bool setterCalled; + GesturesPropBool* propertyData; + }; + SetterData setterData = {false, boolData}; + GesturesPropSetHandler setter{[](void* handlerData) { + SetterData* data = static_cast(handlerData); + // Set handlers should be called after the property's data has changed, so check the data. + EXPECT_EQ(data->propertyData[0], false); + EXPECT_EQ(data->propertyData[1], true); + EXPECT_EQ(data->propertyData[2], true); + data->setterCalled = true; + }}; + gesturePropProvider.register_handlers_fn(&mProvider, propPtr, /* handler_data= */ &setterData, + nullptr, setter); + + ASSERT_TRUE(mProvider.hasProperty("Some Booleans")); + GesturesProp& prop = mProvider.getProperty("Some Booleans"); + prop.setBoolValues({false, true, true}); + EXPECT_THAT(boolData, ElementsAre(false, true, true)); + EXPECT_TRUE(setterData.setterCalled); + EXPECT_THAT(prop.getBoolValues(), ElementsAre(false, true, true)); +} + +TEST_F(PropertyProviderTest, Real_Create) { + const size_t COUNT = 3; + double realData[COUNT] = {0.0, 0.0, 0.0}; + double initialValues[COUNT] = {3.14, 0.7, -5.0}; + gesturePropProvider.create_real_fn(&mProvider, "Some Reals", realData, COUNT, initialValues); + + ASSERT_TRUE(mProvider.hasProperty("Some Reals")); + GesturesProp& prop = mProvider.getProperty("Some Reals"); + EXPECT_EQ(prop.getName(), "Some Reals"); + EXPECT_EQ(prop.getCount(), COUNT); + EXPECT_THAT(realData, ElementsAre(3.14, 0.7, -5.0)); +} + +TEST_F(PropertyProviderTest, Real_Get) { + const size_t COUNT = 3; + double realData[COUNT] = {0.0, 0.0, 0.0}; + double initialValues[COUNT] = {-1.0, -1.0, -1.0}; + GesturesProp* propPtr = gesturePropProvider.create_real_fn(&mProvider, "Some Reals", realData, + COUNT, initialValues); + + // Get handlers are supposed to be called before the property's data is accessed, so they can + // update it if necessary. This getter updates the values, so that the ordering can be checked. + GesturesPropGetHandler getter{[](void* handlerData) -> GesturesPropBool { + double* array = static_cast(handlerData); + array[0] = 3.14; + array[1] = 0.7; + array[2] = -5.0; + return true; + }}; + gesturePropProvider.register_handlers_fn(&mProvider, propPtr, /* handler_data= */ realData, + getter, nullptr); + + ASSERT_TRUE(mProvider.hasProperty("Some Reals")); + GesturesProp& prop = mProvider.getProperty("Some Reals"); + EXPECT_THAT(prop.getRealValues(), ElementsAre(3.14, 0.7, -5.0)); +} + +TEST_F(PropertyProviderTest, Real_Set) { + const size_t COUNT = 3; + double realData[COUNT] = {0.0, 0.0, 0.0}; + double initialValues[COUNT] = {-1.0, -1.0, -1.0}; + GesturesProp* propPtr = gesturePropProvider.create_real_fn(&mProvider, "Some Reals", realData, + COUNT, initialValues); + + struct SetterData { + bool setterCalled; + double* propertyData; + }; + SetterData setterData = {false, realData}; + GesturesPropSetHandler setter{[](void* handlerData) { + SetterData* data = static_cast(handlerData); + // Set handlers should be called after the property's data has changed, so check the data. + EXPECT_EQ(data->propertyData[0], 3.14); + EXPECT_EQ(data->propertyData[1], 0.7); + EXPECT_EQ(data->propertyData[2], -5.0); + data->setterCalled = true; + }}; + gesturePropProvider.register_handlers_fn(&mProvider, propPtr, /* handler_data= */ &setterData, + nullptr, setter); + + ASSERT_TRUE(mProvider.hasProperty("Some Reals")); + GesturesProp& prop = mProvider.getProperty("Some Reals"); + prop.setRealValues({3.14, 0.7, -5.0}); + EXPECT_THAT(realData, ElementsAre(3.14, 0.7, -5.0)); + EXPECT_TRUE(setterData.setterCalled); + EXPECT_THAT(prop.getRealValues(), ElementsAre(3.14, 0.7, -5.0)); +} + +TEST_F(PropertyProviderTest, String_Create) { + const char* str = nullptr; + std::string initialValue = "Foo"; + gesturePropProvider.create_string_fn(&mProvider, "A String", &str, initialValue.c_str()); + + ASSERT_TRUE(mProvider.hasProperty("A String")); + GesturesProp& prop = mProvider.getProperty("A String"); + EXPECT_EQ(prop.getName(), "A String"); + EXPECT_EQ(prop.getCount(), 1u); + EXPECT_STREQ(str, "Foo"); +} + +TEST_F(PropertyProviderTest, String_Get) { + const char* str = nullptr; + std::string initialValue = "Foo"; + GesturesProp* propPtr = gesturePropProvider.create_string_fn(&mProvider, "A String", &str, + initialValue.c_str()); + + // Get handlers are supposed to be called before the property's data is accessed, so they can + // update it if necessary. This getter updates the values, so that the ordering can be checked. + struct GetterData { + const char** strPtr; + std::string newValue; // Have to store the new value outside getter so it stays allocated. + }; + GetterData getterData = {&str, "Bar"}; + GesturesPropGetHandler getter{[](void* handlerData) -> GesturesPropBool { + GetterData* data = static_cast(handlerData); + *data->strPtr = data->newValue.c_str(); + return true; + }}; + gesturePropProvider.register_handlers_fn(&mProvider, propPtr, /* handler_data= */ &getterData, + getter, nullptr); + + ASSERT_TRUE(mProvider.hasProperty("A String")); + GesturesProp& prop = mProvider.getProperty("A String"); + EXPECT_EQ(prop.getStringValue(), "Bar"); +} + +TEST_F(PropertyProviderTest, Free) { + int intData = 0; + int initialValue = 42; + GesturesProp* propPtr = + gesturePropProvider.create_int_fn(&mProvider, "Foo", &intData, 1, &initialValue); + gesturePropProvider.free_fn(&mProvider, propPtr); + + EXPECT_FALSE(mProvider.hasProperty("Foo")); +} + +class PropertyProviderIdcLoadingTest : public testing::Test { +protected: + void SetUp() override { + int initialInt = 0; + GesturesPropBool initialBool = false; + double initialReal = 0.0; + gesturePropProvider.create_int_fn(&mProvider, "An Integer", &mIntData, 1, &initialInt); + gesturePropProvider.create_bool_fn(&mProvider, "A Boolean", &mBoolData, 1, &initialBool); + gesturePropProvider.create_real_fn(&mProvider, "A Real", &mRealData, 1, &initialReal); + } + + PropertyProvider mProvider; + + int mIntData; + GesturesPropBool mBoolData; + double mRealData; +}; + +TEST_F(PropertyProviderIdcLoadingTest, AllCorrect) { + PropertyMap idcProps; + idcProps.addProperty("gestureProp.An_Integer", "42"); + idcProps.addProperty("gestureProp.A_Boolean", "1"); + idcProps.addProperty("gestureProp.A_Real", "3.14159"); + + mProvider.loadPropertiesFromIdcFile(idcProps); + EXPECT_THAT(mProvider.getProperty("An Integer").getIntValues(), ElementsAre(42)); + EXPECT_THAT(mProvider.getProperty("A Boolean").getBoolValues(), ElementsAre(true)); + EXPECT_NEAR(mProvider.getProperty("A Real").getRealValues()[0], 3.14159, EPSILON); +} + +TEST_F(PropertyProviderIdcLoadingTest, InvalidPropsIgnored) { + int intArrayData[2]; + int initialInts[2] = {0, 1}; + gesturePropProvider.create_int_fn(&mProvider, "Two Integers", intArrayData, 2, initialInts); + + PropertyMap idcProps; + // Wrong type + idcProps.addProperty("gestureProp.An_Integer", "37.25"); + // Wrong size + idcProps.addProperty("gestureProp.Two_Integers", "42"); + // Doesn't exist + idcProps.addProperty("gestureProp.Some_Nonexistent_Property", "1"); + // A valid assignment that should still be applied despite the others being invalid + idcProps.addProperty("gestureProp.A_Real", "3.14159"); + + mProvider.loadPropertiesFromIdcFile(idcProps); + EXPECT_THAT(mProvider.getProperty("An Integer").getIntValues(), ElementsAre(0)); + EXPECT_THAT(mProvider.getProperty("Two Integers").getIntValues(), ElementsAre(0, 1)); + EXPECT_FALSE(mProvider.hasProperty("Some Nonexistent Property")); + EXPECT_NEAR(mProvider.getProperty("A Real").getRealValues()[0], 3.14159, EPSILON); +} + +TEST_F(PropertyProviderIdcLoadingTest, FunkyName) { + int data; + int initialData = 0; + gesturePropProvider.create_int_fn(&mProvider, " I lOvE sNAKes ", &data, 1, &initialData); + + PropertyMap idcProps; + idcProps.addProperty("gestureProp.__I_lOvE_sNAKes_", "42"); + + mProvider.loadPropertiesFromIdcFile(idcProps); + EXPECT_THAT(mProvider.getProperty(" I lOvE sNAKes ").getIntValues(), ElementsAre(42)); +} + +} // namespace android diff --git a/services/inputflinger/tests/TestConstants.h b/services/inputflinger/tests/TestConstants.h new file mode 100644 index 0000000000000000000000000000000000000000..ad48b0fbe0df0c11dbd62bb3194b278c2732f1dd --- /dev/null +++ b/services/inputflinger/tests/TestConstants.h @@ -0,0 +1,37 @@ +/* + * 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 { + +using std::chrono_literals::operator""ms; + +// Timeout for waiting for an expected event +static constexpr std::chrono::duration WAIT_TIMEOUT = 100ms; + +// An arbitrary time value. +static constexpr nsecs_t ARBITRARY_TIME = 1234; +static constexpr nsecs_t READ_TIME = 4321; + +// Error tolerance for floating point assertions. +static const float EPSILON = 0.001f; + +} // namespace android diff --git a/services/inputflinger/tests/TestInputListener.cpp b/services/inputflinger/tests/TestInputListener.cpp index 57b382c7184fcd7a1f00fab86fb2b1c1e48596c3..fc917dd582ffa4cf4b9819e06e7a85132bf8f63e 100644 --- a/services/inputflinger/tests/TestInputListener.cpp +++ b/services/inputflinger/tests/TestInputListener.cpp @@ -29,6 +29,14 @@ TestInputListener::TestInputListener(std::chrono::milliseconds eventHappenedTime TestInputListener::~TestInputListener() {} +void TestInputListener::assertNotifyInputDevicesChangedWasCalled( + NotifyInputDevicesChangedArgs* outEventArgs) { + ASSERT_NO_FATAL_FAILURE( + assertCalled(outEventArgs, + "Expected notifyInputDevicesChanged() " + "to have been called.")); +} + void TestInputListener::assertNotifyConfigurationChangedWasCalled( NotifyConfigurationChangedArgs* outEventArgs) { ASSERT_NO_FATAL_FAILURE( @@ -59,26 +67,34 @@ void TestInputListener::assertNotifyKeyWasCalled(NotifyKeyArgs* outEventArgs) { assertCalled(outEventArgs, "Expected notifyKey() to have been called.")); } +void TestInputListener::assertNotifyKeyWasCalled(const ::testing::Matcher& matcher) { + NotifyKeyArgs outEventArgs; + ASSERT_NO_FATAL_FAILURE(assertNotifyKeyWasCalled(&outEventArgs)); + ASSERT_THAT(outEventArgs, matcher); +} + void TestInputListener::assertNotifyKeyWasNotCalled() { ASSERT_NO_FATAL_FAILURE(assertNotCalled("notifyKey() should not be called.")); } -void TestInputListener::assertNotifyMotionWasCalled(NotifyMotionArgs* outEventArgs) { +void TestInputListener::assertNotifyMotionWasCalled(NotifyMotionArgs* outEventArgs, + std::optional waitUntil) { ASSERT_NO_FATAL_FAILURE( assertCalled(outEventArgs, - "Expected notifyMotion() to have been called.")); + "Expected notifyMotion() to have been called.", + waitUntil)); } void TestInputListener::assertNotifyMotionWasCalled( - const ::testing::Matcher& matcher) { + const ::testing::Matcher& matcher, std::optional waitUntil) { NotifyMotionArgs outEventArgs; - ASSERT_NO_FATAL_FAILURE(assertNotifyMotionWasCalled(&outEventArgs)); + ASSERT_NO_FATAL_FAILURE(assertNotifyMotionWasCalled(&outEventArgs, waitUntil)); ASSERT_THAT(outEventArgs, matcher); } -void TestInputListener::assertNotifyMotionWasNotCalled() { +void TestInputListener::assertNotifyMotionWasNotCalled(std::optional waitUntil) { ASSERT_NO_FATAL_FAILURE( - assertNotCalled("notifyMotion() should not be called.")); + assertNotCalled("notifyMotion() should not be called.", waitUntil)); } void TestInputListener::assertNotifySwitchWasCalled(NotifySwitchArgs* outEventArgs) { @@ -113,15 +129,18 @@ void TestInputListener::assertNotifyCaptureWasNotCalled() { } template -void TestInputListener::assertCalled(NotifyArgsType* outEventArgs, std::string message) { +void TestInputListener::assertCalled(NotifyArgsType* outEventArgs, std::string message, + std::optional waitUntil) { std::unique_lock lock(mLock); base::ScopedLockAssertion assumeLocked(mLock); std::vector& queue = std::get>(mQueues); if (queue.empty()) { - const bool eventReceived = - mCondition.wait_for(lock, mEventHappenedTimeout, - [&queue]() REQUIRES(mLock) { return !queue.empty(); }); + const auto time = + waitUntil.value_or(std::chrono::system_clock::now() + mEventHappenedTimeout); + const bool eventReceived = mCondition.wait_until(lock, time, [&queue]() REQUIRES(mLock) { + return !queue.empty(); + }); if (!eventReceived) { FAIL() << "Timed out waiting for event: " << message.c_str(); } @@ -133,58 +152,64 @@ void TestInputListener::assertCalled(NotifyArgsType* outEventArgs, std::string m } template -void TestInputListener::assertNotCalled(std::string message) { +void TestInputListener::assertNotCalled(std::string message, std::optional waitUntil) { std::unique_lock lock(mLock); base::ScopedLockAssertion assumeLocked(mLock); std::vector& queue = std::get>(mQueues); - const bool eventReceived = - mCondition.wait_for(lock, mEventDidNotHappenTimeout, - [&queue]() REQUIRES(mLock) { return !queue.empty(); }); + const auto time = + waitUntil.value_or(std::chrono::system_clock::now() + mEventDidNotHappenTimeout); + const bool eventReceived = mCondition.wait_until(lock, time, [&queue]() REQUIRES(mLock) { + return !queue.empty(); + }); if (eventReceived) { FAIL() << "Unexpected event: " << message.c_str(); } } template -void TestInputListener::notify(const NotifyArgsType* args) { +void TestInputListener::addToQueue(const NotifyArgsType& args) { std::scoped_lock lock(mLock); std::vector& queue = std::get>(mQueues); - queue.push_back(*args); + queue.push_back(args); mCondition.notify_all(); } -void TestInputListener::notifyConfigurationChanged(const NotifyConfigurationChangedArgs* args) { - notify(args); +void TestInputListener::notifyInputDevicesChanged(const NotifyInputDevicesChangedArgs& args) { + addToQueue(args); +} + +void TestInputListener::notifyConfigurationChanged(const NotifyConfigurationChangedArgs& args) { + addToQueue(args); } -void TestInputListener::notifyDeviceReset(const NotifyDeviceResetArgs* args) { - notify(args); +void TestInputListener::notifyDeviceReset(const NotifyDeviceResetArgs& args) { + addToQueue(args); } -void TestInputListener::notifyKey(const NotifyKeyArgs* args) { - notify(args); +void TestInputListener::notifyKey(const NotifyKeyArgs& args) { + addToQueue(args); } -void TestInputListener::notifyMotion(const NotifyMotionArgs* args) { - notify(args); +void TestInputListener::notifyMotion(const NotifyMotionArgs& args) { + addToQueue(args); } -void TestInputListener::notifySwitch(const NotifySwitchArgs* args) { - notify(args); +void TestInputListener::notifySwitch(const NotifySwitchArgs& args) { + addToQueue(args); } -void TestInputListener::notifyPointerCaptureChanged(const NotifyPointerCaptureChangedArgs* args) { - notify(args); +void TestInputListener::notifyPointerCaptureChanged(const NotifyPointerCaptureChangedArgs& args) { + addToQueue(args); } -void TestInputListener::notifySensor(const NotifySensorArgs* args) { - notify(args); +void TestInputListener::notifySensor(const NotifySensorArgs& args) { + addToQueue(args); } -void TestInputListener::notifyVibratorState(const NotifyVibratorStateArgs* args) { - notify(args); +void TestInputListener::notifyVibratorState(const NotifyVibratorStateArgs& args) { + addToQueue(args); } } // namespace android diff --git a/services/inputflinger/tests/TestInputListener.h b/services/inputflinger/tests/TestInputListener.h index 0bdfc6b9fbd9db5c068d140168477d06cad4cbd5..deb60483fb8eb46475ee3bd5a4d71948a45decf1 100644 --- a/services/inputflinger/tests/TestInputListener.h +++ b/services/inputflinger/tests/TestInputListener.h @@ -14,8 +14,7 @@ * limitations under the License. */ -#ifndef _UI_TEST_INPUT_LISTENER_H -#define _UI_TEST_INPUT_LISTENER_H +#pragma once #include #include @@ -34,6 +33,11 @@ public: std::chrono::milliseconds eventDidNotHappenTimeout = 0ms); virtual ~TestInputListener(); + using TimePoint = std::chrono::time_point; + + void assertNotifyInputDevicesChangedWasCalled( + NotifyInputDevicesChangedArgs* outEventArgs = nullptr); + void assertNotifyConfigurationChangedWasCalled( NotifyConfigurationChangedArgs* outEventArgs = nullptr); @@ -45,13 +49,17 @@ public: void assertNotifyKeyWasCalled(NotifyKeyArgs* outEventArgs = nullptr); + void assertNotifyKeyWasCalled(const ::testing::Matcher& matcher); + void assertNotifyKeyWasNotCalled(); - void assertNotifyMotionWasCalled(NotifyMotionArgs* outEventArgs = nullptr); + void assertNotifyMotionWasCalled(NotifyMotionArgs* outEventArgs = nullptr, + std::optional waitUntil = {}); - void assertNotifyMotionWasCalled(const ::testing::Matcher& matcher); + void assertNotifyMotionWasCalled(const ::testing::Matcher& matcher, + std::optional waitUntil = {}); - void assertNotifyMotionWasNotCalled(); + void assertNotifyMotionWasNotCalled(std::optional waitUntil = {}); void assertNotifySwitchWasCalled(NotifySwitchArgs* outEventArgs = nullptr); @@ -62,36 +70,40 @@ public: private: template - void assertCalled(NotifyArgsType* outEventArgs, std::string message); + void assertCalled(NotifyArgsType* outEventArgs, std::string message, + std::optional waitUntil = {}); template - void assertNotCalled(std::string message); + void assertNotCalled(std::string message, std::optional timeout = {}); template - void notify(const NotifyArgsType* args); + void addToQueue(const NotifyArgsType& args); + + virtual void notifyInputDevicesChanged(const NotifyInputDevicesChangedArgs& args) override; - virtual void notifyConfigurationChanged(const NotifyConfigurationChangedArgs* args) override; + virtual void notifyConfigurationChanged(const NotifyConfigurationChangedArgs& args) override; - virtual void notifyDeviceReset(const NotifyDeviceResetArgs* args) override; + virtual void notifyDeviceReset(const NotifyDeviceResetArgs& args) override; - virtual void notifyKey(const NotifyKeyArgs* args) override; + virtual void notifyKey(const NotifyKeyArgs& args) override; - virtual void notifyMotion(const NotifyMotionArgs* args) override; + virtual void notifyMotion(const NotifyMotionArgs& args) override; - virtual void notifySwitch(const NotifySwitchArgs* args) override; + virtual void notifySwitch(const NotifySwitchArgs& args) override; - virtual void notifySensor(const NotifySensorArgs* args) override; + virtual void notifySensor(const NotifySensorArgs& args) override; - virtual void notifyVibratorState(const NotifyVibratorStateArgs* args) override; + virtual void notifyVibratorState(const NotifyVibratorStateArgs& args) override; - virtual void notifyPointerCaptureChanged(const NotifyPointerCaptureChangedArgs* args) override; + virtual void notifyPointerCaptureChanged(const NotifyPointerCaptureChangedArgs& args) override; std::mutex mLock; std::condition_variable mCondition; const std::chrono::milliseconds mEventHappenedTimeout; const std::chrono::milliseconds mEventDidNotHappenTimeout; - std::tuple, // + std::tuple, // + std::vector, // std::vector, // std::vector, // std::vector, // @@ -103,4 +115,3 @@ private: }; } // namespace android -#endif diff --git a/services/inputflinger/tests/TestInputListenerMatchers.h b/services/inputflinger/tests/TestInputListenerMatchers.h new file mode 100644 index 0000000000000000000000000000000000000000..db6f2548e8c04f3801ff26efdfcdf64dc4a357f5 --- /dev/null +++ b/services/inputflinger/tests/TestInputListenerMatchers.h @@ -0,0 +1,215 @@ +/* + * 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. + */ + +#pragma once + +#include + +#include +#include +#include +#include + +namespace android { + +MATCHER_P(WithMotionAction, action, "MotionEvent with specified action") { + bool matches = action == arg.action; + if (!matches) { + *result_listener << "expected action " << MotionEvent::actionToString(action) + << ", but got " << MotionEvent::actionToString(arg.action); + } + if (action == AMOTION_EVENT_ACTION_CANCEL) { + if (!matches) { + *result_listener << "; "; + } + *result_listener << "expected FLAG_CANCELED to be set with ACTION_CANCEL, but was not set"; + matches &= (arg.flags & AMOTION_EVENT_FLAG_CANCELED) != 0; + } + return matches; +} + +MATCHER_P(WithKeyAction, action, "KeyEvent with specified action") { + *result_listener << "expected action " << KeyEvent::actionToString(action) << ", but got " + << KeyEvent::actionToString(arg.action); + return arg.action == action; +} + +MATCHER_P(WithSource, source, "InputEvent with specified source") { + *result_listener << "expected source " << inputEventSourceToString(source) << ", but got " + << inputEventSourceToString(arg.source); + return arg.source == source; +} + +MATCHER_P(WithDisplayId, displayId, "InputEvent with specified displayId") { + *result_listener << "expected displayId " << displayId << ", but got " << arg.displayId; + return arg.displayId == displayId; +} + +MATCHER_P(WithDeviceId, deviceId, "InputEvent with specified deviceId") { + *result_listener << "expected deviceId " << deviceId << ", but got " << arg.deviceId; + return arg.deviceId == deviceId; +} + +MATCHER_P(WithKeyCode, keyCode, "KeyEvent with specified key code") { + *result_listener << "expected key code " << keyCode << ", but got " << arg.keyCode; + return arg.keyCode == keyCode; +} + +MATCHER_P(WithPointerCount, count, "MotionEvent with specified number of pointers") { + *result_listener << "expected " << count << " pointer(s), but got " << arg.pointerCount; + return arg.pointerCount == count; +} + +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; +} + +MATCHER_P2(WithCoords, x, y, "InputEvent with specified coords") { + const auto argX = arg.pointerCoords[0].getX(); + const auto argY = arg.pointerCoords[0].getY(); + *result_listener << "expected coords (" << x << ", " << y << "), but got (" << argX << ", " + << argY << ")"; + return argX == x && argY == y; +} + +MATCHER_P3(WithPointerCoords, pointer, x, y, "InputEvent with specified coords for pointer") { + const auto argX = arg.pointerCoords[pointer].getX(); + const auto argY = arg.pointerCoords[pointer].getY(); + *result_listener << "expected pointer " << pointer << " to have coords (" << x << ", " << y + << "), but got (" << argX << ", " << argY << ")"; + return argX == x && argY == y; +} + +MATCHER_P2(WithRelativeMotion, x, y, "InputEvent with specified relative motion") { + const auto argX = arg.pointerCoords[0].getAxisValue(AMOTION_EVENT_AXIS_RELATIVE_X); + const auto argY = arg.pointerCoords[0].getAxisValue(AMOTION_EVENT_AXIS_RELATIVE_Y); + *result_listener << "expected relative motion (" << x << ", " << y << "), but got (" << argX + << ", " << argY << ")"; + return argX == x && argY == y; +} + +MATCHER_P3(WithGestureOffset, dx, dy, epsilon, + "InputEvent with specified touchpad gesture offset") { + const auto argGestureX = arg.pointerCoords[0].getAxisValue(AMOTION_EVENT_AXIS_GESTURE_X_OFFSET); + const auto argGestureY = arg.pointerCoords[0].getAxisValue(AMOTION_EVENT_AXIS_GESTURE_Y_OFFSET); + const double xDiff = fabs(argGestureX - dx); + const double yDiff = fabs(argGestureY - dy); + *result_listener << "expected gesture offset (" << dx << ", " << dy << ") within " << epsilon + << ", but got (" << argGestureX << ", " << argGestureY << ")"; + return xDiff <= epsilon && yDiff <= epsilon; +} + +MATCHER_P3(WithGestureScrollDistance, x, y, epsilon, + "InputEvent with specified touchpad gesture scroll distance") { + const auto argXDistance = + arg.pointerCoords[0].getAxisValue(AMOTION_EVENT_AXIS_GESTURE_SCROLL_X_DISTANCE); + const auto argYDistance = + arg.pointerCoords[0].getAxisValue(AMOTION_EVENT_AXIS_GESTURE_SCROLL_Y_DISTANCE); + const double xDiff = fabs(argXDistance - x); + const double yDiff = fabs(argYDistance - y); + *result_listener << "expected gesture offset (" << x << ", " << y << ") within " << epsilon + << ", but got (" << argXDistance << ", " << argYDistance << ")"; + return xDiff <= epsilon && yDiff <= epsilon; +} + +MATCHER_P2(WithGesturePinchScaleFactor, factor, epsilon, + "InputEvent with specified touchpad pinch gesture scale factor") { + const auto argScaleFactor = + arg.pointerCoords[0].getAxisValue(AMOTION_EVENT_AXIS_GESTURE_PINCH_SCALE_FACTOR); + *result_listener << "expected gesture scale factor " << factor << " within " << epsilon + << " but got " << argScaleFactor; + return fabs(argScaleFactor - factor) <= epsilon; +} + +MATCHER_P(WithPressure, pressure, "InputEvent with specified pressure") { + const auto argPressure = arg.pointerCoords[0].getAxisValue(AMOTION_EVENT_AXIS_PRESSURE); + *result_listener << "expected pressure " << pressure << ", but got " << argPressure; + return argPressure == pressure; +} + +MATCHER_P2(WithTouchDimensions, maj, min, "InputEvent with specified touch dimensions") { + const auto argMajor = arg.pointerCoords[0].getAxisValue(AMOTION_EVENT_AXIS_TOUCH_MAJOR); + const auto argMinor = arg.pointerCoords[0].getAxisValue(AMOTION_EVENT_AXIS_TOUCH_MINOR); + *result_listener << "expected touch dimensions " << maj << " major x " << min + << " minor, but got " << argMajor << " major x " << argMinor << " minor"; + return argMajor == maj && argMinor == min; +} + +MATCHER_P2(WithToolDimensions, maj, min, "InputEvent with specified tool dimensions") { + const auto argMajor = arg.pointerCoords[0].getAxisValue(AMOTION_EVENT_AXIS_TOOL_MAJOR); + const auto argMinor = arg.pointerCoords[0].getAxisValue(AMOTION_EVENT_AXIS_TOOL_MINOR); + *result_listener << "expected tool dimensions " << maj << " major x " << min + << " minor, but got " << argMajor << " major x " << argMinor << " minor"; + return argMajor == maj && argMinor == min; +} + +MATCHER_P(WithToolType, toolType, "InputEvent with specified tool type") { + const auto argToolType = arg.pointerProperties[0].toolType; + *result_listener << "expected tool type " << ftl::enum_string(toolType) << ", but got " + << ftl::enum_string(argToolType); + return argToolType == toolType; +} + +MATCHER_P2(WithPointerToolType, pointer, toolType, + "InputEvent with specified tool type for pointer") { + const auto argToolType = arg.pointerProperties[pointer].toolType; + *result_listener << "expected pointer " << pointer << " to have tool type " + << ftl::enum_string(toolType) << ", but got " << ftl::enum_string(argToolType); + return argToolType == toolType; +} + +MATCHER_P(WithFlags, flags, "InputEvent with specified flags") { + *result_listener << "expected flags " << flags << ", but got " << arg.flags; + return arg.flags == static_cast(flags); +} + +MATCHER_P(WithMotionClassification, classification, + "InputEvent with specified MotionClassification") { + *result_listener << "expected classification " << motionClassificationToString(classification) + << ", but got " << motionClassificationToString(arg.classification); + return arg.classification == classification; +} + +MATCHER_P(WithButtonState, buttons, "InputEvent with specified button state") { + *result_listener << "expected button state " << buttons << ", but got " << arg.buttonState; + return arg.buttonState == buttons; +} + +MATCHER_P(WithActionButton, actionButton, "InputEvent with specified action button") { + *result_listener << "expected action button " << actionButton << ", but got " + << arg.actionButton; + return arg.actionButton == actionButton; +} + +MATCHER_P(WithEventTime, eventTime, "InputEvent with specified eventTime") { + *result_listener << "expected event time " << eventTime << ", but got " << arg.eventTime; + return arg.eventTime == eventTime; +} + +MATCHER_P(WithDownTime, downTime, "InputEvent with specified downTime") { + *result_listener << "expected down time " << downTime << ", but got " << arg.downTime; + return arg.downTime == downTime; +} + +MATCHER_P2(WithPrecision, xPrecision, yPrecision, "MotionEvent with specified precision") { + *result_listener << "expected x-precision " << xPrecision << " and y-precision " << yPrecision + << ", but got " << arg.xPrecision << " and " << arg.yPrecision; + return arg.xPrecision == xPrecision && arg.yPrecision == yPrecision; +} + +} // namespace android diff --git a/services/inputflinger/tests/TouchpadInputMapper_test.cpp b/services/inputflinger/tests/TouchpadInputMapper_test.cpp new file mode 100644 index 0000000000000000000000000000000000000000..92cd462c9a8c6e7077d8d5bc5b2bacf9791cafec --- /dev/null +++ b/services/inputflinger/tests/TouchpadInputMapper_test.cpp @@ -0,0 +1,155 @@ +/* + * 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 "TouchpadInputMapper.h" + +#include +#include + +#include +#include "FakePointerController.h" +#include "InputMapperTest.h" +#include "InterfaceMocks.h" +#include "TestInputListenerMatchers.h" + +#define TAG "TouchpadInputMapper_test" + +namespace android { + +using testing::Return; +using testing::VariantWith; +constexpr auto ACTION_DOWN = AMOTION_EVENT_ACTION_DOWN; +constexpr auto ACTION_UP = AMOTION_EVENT_ACTION_UP; +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; + +/** + * Unit tests for TouchpadInputMapper. + */ +class TouchpadInputMapperTest : public InputMapperUnitTest { +protected: + void SetUp() override { + InputMapperUnitTest::SetUp(); + + // Present scan codes: BTN_TOUCH and BTN_TOOL_FINGER + expectScanCodes(/*present=*/true, + {BTN_LEFT, BTN_RIGHT, BTN_TOOL_FINGER, BTN_TOOL_QUINTTAP, BTN_TOUCH, + BTN_TOOL_DOUBLETAP, BTN_TOOL_TRIPLETAP, BTN_TOOL_QUADTAP}); + // Missing scan codes that the mapper checks for. + expectScanCodes(/*present=*/false, + {BTN_TOOL_PEN, BTN_TOOL_RUBBER, BTN_TOOL_BRUSH, BTN_TOOL_PENCIL, + BTN_TOOL_AIRBRUSH}); + + // Current scan code state - all keys are UP by default + setScanCodeState(KeyState::UP, {BTN_TOUCH, BTN_STYLUS, + BTN_STYLUS2, BTN_0, + BTN_TOOL_FINGER, BTN_TOOL_PEN, + BTN_TOOL_RUBBER, BTN_TOOL_BRUSH, + BTN_TOOL_PENCIL, BTN_TOOL_AIRBRUSH, + BTN_TOOL_MOUSE, BTN_TOOL_LENS, + BTN_TOOL_DOUBLETAP, BTN_TOOL_TRIPLETAP, + BTN_TOOL_QUADTAP, BTN_TOOL_QUINTTAP, + BTN_LEFT, BTN_RIGHT, + BTN_MIDDLE, BTN_BACK, + BTN_SIDE, BTN_FORWARD, + BTN_EXTRA, BTN_TASK}); + + setKeyCodeState(KeyState::UP, + {AKEYCODE_STYLUS_BUTTON_PRIMARY, AKEYCODE_STYLUS_BUTTON_SECONDARY}); + + // Key mappings + EXPECT_CALL(mMockEventHub, + mapKey(EVENTHUB_ID, BTN_LEFT, /*usageCode=*/0, /*metaState=*/0, testing::_, + testing::_, testing::_)) + .WillRepeatedly(Return(NAME_NOT_FOUND)); + + // Input properties - only INPUT_PROP_BUTTONPAD + EXPECT_CALL(mMockEventHub, hasInputProperty(EVENTHUB_ID, INPUT_PROP_BUTTONPAD)) + .WillRepeatedly(Return(true)); + EXPECT_CALL(mMockEventHub, hasInputProperty(EVENTHUB_ID, INPUT_PROP_SEMI_MT)) + .WillRepeatedly(Return(false)); + + // Axes that the device has + setupAxis(ABS_MT_SLOT, /*valid=*/true, /*min=*/0, /*max=*/4, /*resolution=*/0); + setupAxis(ABS_MT_POSITION_X, /*valid=*/true, /*min=*/0, /*max=*/2000, /*resolution=*/24); + setupAxis(ABS_MT_POSITION_Y, /*valid=*/true, /*min=*/0, /*max=*/1000, /*resolution=*/24); + setupAxis(ABS_MT_PRESSURE, /*valid=*/true, /*min*/ 0, /*max=*/255, /*resolution=*/0); + // Axes that the device does not have + setupAxis(ABS_MT_ORIENTATION, /*valid=*/false, /*min=*/0, /*max=*/0, /*resolution=*/0); + setupAxis(ABS_MT_TOUCH_MAJOR, /*valid=*/false, /*min=*/0, /*max=*/0, /*resolution=*/0); + setupAxis(ABS_MT_TOUCH_MINOR, /*valid=*/false, /*min=*/0, /*max=*/0, /*resolution=*/0); + setupAxis(ABS_MT_WIDTH_MAJOR, /*valid=*/false, /*min=*/0, /*max=*/0, /*resolution=*/0); + setupAxis(ABS_MT_WIDTH_MINOR, /*valid=*/false, /*min=*/0, /*max=*/0, /*resolution=*/0); + + EXPECT_CALL(mMockEventHub, getAbsoluteAxisValue(EVENTHUB_ID, ABS_MT_SLOT, testing::_)) + .WillRepeatedly([](int32_t eventHubId, int32_t, int32_t* outValue) { + *outValue = 0; + return OK; + }); + mMapper = createInputMapper(*mDeviceContext, mReaderConfiguration); + } +}; + +/** + * 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(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_MOVE)), + VariantWith(WithMotionAction(ACTION_DOWN)), + VariantWith(WithMotionAction(BUTTON_PRESS)), + VariantWith(WithMotionAction(BUTTON_RELEASE)), + VariantWith(WithMotionAction(ACTION_UP)))); + + // 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()); +} + +} // namespace android diff --git a/services/inputflinger/tests/UinputDevice.cpp b/services/inputflinger/tests/UinputDevice.cpp index 9c939198f3c96f7f4425416a20a60eb3cc763e88..97a26141e045608d031a6990f4910cf283d5cebe 100644 --- a/services/inputflinger/tests/UinputDevice.cpp +++ b/services/inputflinger/tests/UinputDevice.cpp @@ -17,13 +17,15 @@ #include "UinputDevice.h" #include +#include #include namespace android { // --- UinputDevice --- -UinputDevice::UinputDevice(const char* name) : mName(name) {} +UinputDevice::UinputDevice(const char* name, int16_t productId) + : mName(name), mProductId(productId) {} UinputDevice::~UinputDevice() { if (ioctl(mDeviceFd, UI_DEV_DESTROY)) { @@ -42,7 +44,7 @@ void UinputDevice::init() { strlcpy(device.name, mName, UINPUT_MAX_NAME_SIZE); device.id.bustype = BUS_USB; device.id.vendor = 0x01; - device.id.product = 0x01; + device.id.product = mProductId; device.id.version = 1; ASSERT_NO_FATAL_FAILURE(configureDevice(mDeviceFd, &device)); @@ -58,11 +60,11 @@ void UinputDevice::init() { } void UinputDevice::injectEvent(uint16_t type, uint16_t code, int32_t value) { + // uinput ignores the timestamp struct input_event event = {}; event.type = type; event.code = code; event.value = value; - event.time = {}; // uinput ignores the timestamp if (write(mDeviceFd, &event, sizeof(input_event)) < 0) { std::string msg = base::StringPrintf("Could not write event %" PRIu16 " %" PRIu16 @@ -75,8 +77,8 @@ void UinputDevice::injectEvent(uint16_t type, uint16_t code, int32_t value) { // --- UinputKeyboard --- -UinputKeyboard::UinputKeyboard(std::initializer_list keys) - : UinputDevice(UinputKeyboard::KEYBOARD_NAME), mKeys(keys.begin(), keys.end()) {} +UinputKeyboard::UinputKeyboard(const char* name, int16_t productId, std::initializer_list keys) + : UinputDevice(name, productId), mKeys(keys.begin(), keys.end()) {} void UinputKeyboard::configureDevice(int fd, uinput_user_dev* device) { // enable key press/release event @@ -120,22 +122,52 @@ void UinputKeyboard::pressAndReleaseKey(int key) { // --- UinputHomeKey --- -UinputHomeKey::UinputHomeKey() : UinputKeyboard({KEY_HOME}) {} +UinputHomeKey::UinputHomeKey() : UinputKeyboard(DEVICE_NAME, PRODUCT_ID, {KEY_HOME}) {} void UinputHomeKey::pressAndReleaseHomeKey() { pressAndReleaseKey(KEY_HOME); } -// --- UinputSteamController -UinputSteamController::UinputSteamController() : UinputKeyboard({BTN_GEAR_DOWN, BTN_GEAR_UP}) {} +// --- UinputSteamController --- + +UinputSteamController::UinputSteamController() + : UinputKeyboard(DEVICE_NAME, PRODUCT_ID, {BTN_GEAR_DOWN, BTN_GEAR_UP}) {} + +// --- UinputExternalStylus --- + +UinputExternalStylus::UinputExternalStylus() + : UinputKeyboard(DEVICE_NAME, PRODUCT_ID, {BTN_STYLUS, BTN_STYLUS2, BTN_STYLUS3}) {} + +// --- UinputExternalStylusWithPressure --- + +UinputExternalStylusWithPressure::UinputExternalStylusWithPressure() + : UinputKeyboard(DEVICE_NAME, PRODUCT_ID, {BTN_STYLUS, BTN_STYLUS2, BTN_STYLUS3}) {} + +void UinputExternalStylusWithPressure::configureDevice(int fd, uinput_user_dev* device) { + UinputKeyboard::configureDevice(fd, device); + + ioctl(fd, UI_SET_EVBIT, EV_ABS); + ioctl(fd, UI_SET_ABSBIT, ABS_PRESSURE); + device->absmin[ABS_PRESSURE] = RAW_PRESSURE_MIN; + device->absmax[ABS_PRESSURE] = RAW_PRESSURE_MAX; +} + +void UinputExternalStylusWithPressure::setPressure(int32_t pressure) { + injectEvent(EV_ABS, ABS_PRESSURE, pressure); + injectEvent(EV_SYN, SYN_REPORT, 0); +} // --- UinputTouchScreen --- -UinputTouchScreen::UinputTouchScreen(const Rect* size) - : UinputDevice(UinputTouchScreen::DEVICE_NAME), mSize(*size) {} + +UinputTouchScreen::UinputTouchScreen(const Rect& size) + : UinputKeyboard(DEVICE_NAME, PRODUCT_ID, + {BTN_TOUCH, BTN_TOOL_PEN, BTN_STYLUS, BTN_STYLUS2, BTN_STYLUS3}), + mSize(size) {} void UinputTouchScreen::configureDevice(int fd, uinput_user_dev* device) { + UinputKeyboard::configureDevice(fd, device); + // Setup the touch screen device - ioctl(fd, UI_SET_EVBIT, EV_KEY); ioctl(fd, UI_SET_EVBIT, EV_REL); ioctl(fd, UI_SET_EVBIT, EV_ABS); ioctl(fd, UI_SET_ABSBIT, ABS_MT_SLOT); @@ -145,7 +177,6 @@ void UinputTouchScreen::configureDevice(int fd, uinput_user_dev* device) { ioctl(fd, UI_SET_ABSBIT, ABS_MT_TRACKING_ID); ioctl(fd, UI_SET_ABSBIT, ABS_MT_TOOL_TYPE); ioctl(fd, UI_SET_PROPBIT, INPUT_PROP_DIRECT); - ioctl(fd, UI_SET_KEYBIT, BTN_TOUCH); device->absmin[ABS_MT_SLOT] = RAW_SLOT_MIN; device->absmax[ABS_MT_SLOT] = RAW_SLOT_MAX; @@ -157,6 +188,8 @@ void UinputTouchScreen::configureDevice(int fd, uinput_user_dev* device) { device->absmax[ABS_MT_POSITION_Y] = mSize.bottom - 1; device->absmin[ABS_MT_TRACKING_ID] = RAW_ID_MIN; device->absmax[ABS_MT_TRACKING_ID] = RAW_ID_MAX; + device->absmin[ABS_MT_TOOL_TYPE] = MT_TOOL_FINGER; + device->absmax[ABS_MT_TOOL_TYPE] = MT_TOOL_MAX; } void UinputTouchScreen::sendSlot(int32_t slot) { diff --git a/services/inputflinger/tests/UinputDevice.h b/services/inputflinger/tests/UinputDevice.h index a37fc2b790d8fa41f5626cc917e27dc5c03f0447..51e331df8f83112541b6ca44580e08a2ff50e9a1 100644 --- a/services/inputflinger/tests/UinputDevice.h +++ b/services/inputflinger/tests/UinputDevice.h @@ -14,8 +14,7 @@ * limitations under the License. */ -#ifndef _UI_TEST_INPUT_UINPUT_INJECTOR_H -#define _UI_TEST_INPUT_UINPUT_INJECTOR_H +#pragma once #include #include @@ -33,7 +32,7 @@ namespace android { template std::unique_ptr createUinputDevice(Ts... args) { // Using `new` to access non-public constructors. - std::unique_ptr dev(new D(&args...)); + std::unique_ptr dev(new D(args...)); EXPECT_NO_FATAL_FAILURE(dev->init()); return dev; } @@ -52,8 +51,9 @@ public: protected: const char* mName; + const int16_t mProductId; - UinputDevice(const char* name); + explicit UinputDevice(const char* name, int16_t productId); // Signals which types of events this device supports before it is created. // This must be overridden by subclasses. @@ -72,7 +72,8 @@ private: class UinputKeyboard : public UinputDevice { public: - static constexpr const char* KEYBOARD_NAME = "Test Keyboard Device"; + static constexpr const char* KEYBOARD_NAME = "Test Uinput Keyboard Device"; + static constexpr int16_t PRODUCT_ID = 42; // Injects key press and sync. void pressKey(int key); @@ -85,11 +86,12 @@ public: friend std::unique_ptr createUinputDevice(Ts... args); protected: - UinputKeyboard(std::initializer_list keys = {}); + explicit UinputKeyboard(const char* name, int16_t productId = PRODUCT_ID, + std::initializer_list keys = {}); -private: void configureDevice(int fd, uinput_user_dev* device) override; +private: std::set mKeys; }; @@ -98,6 +100,9 @@ private: // A keyboard device that has a single HOME key. class UinputHomeKey : public UinputKeyboard { public: + static constexpr const char* DEVICE_NAME = "Test Uinput Home Key"; + static constexpr int16_t PRODUCT_ID = 43; + // Injects 4 events: key press, sync, key release, and sync. void pressAndReleaseHomeKey(); @@ -105,24 +110,69 @@ public: friend std::unique_ptr createUinputDevice(Ts... args); private: - UinputHomeKey(); + explicit UinputHomeKey(); }; +// --- UinputSteamController --- + // A joystick device that sends a BTN_GEAR_DOWN / BTN_WHEEL key. class UinputSteamController : public UinputKeyboard { public: + static constexpr const char* DEVICE_NAME = "Test Uinput Steam Controller"; + static constexpr int16_t PRODUCT_ID = 44; + + template + friend std::unique_ptr createUinputDevice(Ts... args); + +private: + explicit UinputSteamController(); +}; + +// --- UinputExternalStylus --- + +// A stylus that reports button presses. +class UinputExternalStylus : public UinputKeyboard { +public: + static constexpr const char* DEVICE_NAME = "Test Uinput External Stylus"; + static constexpr int16_t PRODUCT_ID = 45; + + template + friend std::unique_ptr createUinputDevice(Ts... args); + +private: + explicit UinputExternalStylus(); +}; + +// --- UinputExternalStylusWithPressure --- + +// A stylus that reports button presses and pressure values. +class UinputExternalStylusWithPressure : public UinputKeyboard { +public: + static constexpr const char* DEVICE_NAME = "Test Uinput External Stylus With Pressure"; + static constexpr int16_t PRODUCT_ID = 46; + + static constexpr int32_t RAW_PRESSURE_MIN = 0; + static constexpr int32_t RAW_PRESSURE_MAX = 255; + + void setPressure(int32_t pressure); + template friend std::unique_ptr createUinputDevice(Ts... args); private: - UinputSteamController(); + void configureDevice(int fd, uinput_user_dev* device) override; + + explicit UinputExternalStylusWithPressure(); }; // --- UinputTouchScreen --- -// A touch screen device with specific size. -class UinputTouchScreen : public UinputDevice { + +// A multi-touch touchscreen device with specific size that also supports styluses. +class UinputTouchScreen : public UinputKeyboard { public: - static constexpr const char* DEVICE_NAME = "Test Touch Screen"; + static constexpr const char* DEVICE_NAME = "Test Uinput Touch Screen"; + static constexpr int16_t PRODUCT_ID = 47; + static const int32_t RAW_TOUCH_MIN = 0; static const int32_t RAW_TOUCH_MAX = 31; static const int32_t RAW_ID_MIN = 0; @@ -147,7 +197,7 @@ public: const Point getCenterPoint(); protected: - UinputTouchScreen(const Rect* size); + explicit UinputTouchScreen(const Rect& size); private: void configureDevice(int fd, uinput_user_dev* device) override; @@ -155,5 +205,3 @@ private: }; } // namespace android - -#endif // _UI_TEST_INPUT_UINPUT_INJECTOR_H diff --git a/services/inputflinger/tests/UnwantedInteractionBlocker_test.cpp b/services/inputflinger/tests/UnwantedInteractionBlocker_test.cpp index 29fa0011859663fb4ea6f621778faba9894cf373..1fff2c75905bdb9842c23d7ec01984c6e230c006 100644 --- a/services/inputflinger/tests/UnwantedInteractionBlocker_test.cpp +++ b/services/inputflinger/tests/UnwantedInteractionBlocker_test.cpp @@ -24,6 +24,7 @@ #include "ui/events/ozone/evdev/touch_filter/neural_stylus_palm_detection_filter.h" #include "TestInputListener.h" +#include "TestInputListenerMatchers.h" using ::testing::AllOf; @@ -55,21 +56,6 @@ constexpr int CANCEL = AMOTION_EVENT_ACTION_CANCEL; constexpr int32_t FLAG_CANCELED = AMOTION_EVENT_FLAG_CANCELED; -MATCHER_P(WithAction, action, "MotionEvent with specified action") { - bool result = true; - if (action == CANCEL) { - result &= (arg.flags & FLAG_CANCELED) != 0; - } - result &= arg.action == action; - *result_listener << "expected to receive " << MotionEvent::actionToString(action) - << " but received " << MotionEvent::actionToString(arg.action) << " instead."; - return result; -} - -MATCHER_P(WithFlags, flags, "MotionEvent with specified flags") { - return arg.flags == flags; -} - static nsecs_t toNs(std::chrono::nanoseconds duration) { return duration.count(); } @@ -93,7 +79,7 @@ static NotifyMotionArgs generateMotionArgs(nsecs_t downTime, nsecs_t eventTime, for (size_t i = 0; i < pointerCount; i++) { pointerProperties[i].clear(); pointerProperties[i].id = i; - pointerProperties[i].toolType = AMOTION_EVENT_TOOL_TYPE_FINGER; + pointerProperties[i].toolType = ToolType::FINGER; pointerCoords[i].clear(); pointerCoords[i].setAxisValue(AMOTION_EVENT_AXIS_X, points[i].x); @@ -102,8 +88,8 @@ static NotifyMotionArgs generateMotionArgs(nsecs_t downTime, nsecs_t eventTime, } // Define a valid motion event. - NotifyMotionArgs args(/* id */ 0, eventTime, 0 /*readTime*/, DEVICE_ID, - AINPUT_SOURCE_TOUCHSCREEN, 0 /*displayId*/, POLICY_FLAG_PASS_TO_USER, + NotifyMotionArgs args(/* id */ 0, eventTime, /*readTime=*/0, DEVICE_ID, + AINPUT_SOURCE_TOUCHSCREEN, /*displayId=*/0, POLICY_FLAG_PASS_TO_USER, action, /* actionButton */ 0, /* flags */ 0, AMETA_NONE, /* buttonState */ 0, MotionClassification::NONE, AMOTION_EVENT_EDGE_FLAG_NONE, pointerCount, @@ -119,7 +105,7 @@ static InputDeviceInfo generateTestDeviceInfo() { auto info = InputDeviceInfo(); info.initialize(DEVICE_ID, /*generation*/ 1, /*controllerNumber*/ 1, identifier, "alias", - /*isExternal*/ false, /*hasMic*/ false); + /*isExternal*/ false, /*hasMic*/ false, 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); @@ -423,19 +409,19 @@ protected: void SetUp() override { mBlocker = std::make_unique(mTestListener, - /*enablePalmRejection*/ true); + /*enablePalmRejection=*/true); } }; /** - * Create a basic configuration change and send it to input classifier. + * Create a basic configuration change and send it to input processor. * Expect that the event is received by the next input stage, unmodified. */ TEST_F(UnwantedInteractionBlockerTest, ConfigurationChangedIsPassedToNextListener) { - // Create a basic configuration change and send to classifier - NotifyConfigurationChangedArgs args(1 /*sequenceNum*/, 2 /*eventTime*/); + // Create a basic configuration change and send to blocker + NotifyConfigurationChangedArgs args(/*sequenceNum=*/1, /*eventTime=*/2); - mBlocker->notifyConfigurationChanged(&args); + mBlocker->notifyConfigurationChanged(args); NotifyConfigurationChangedArgs outArgs; ASSERT_NO_FATAL_FAILURE(mTestListener.assertNotifyConfigurationChangedWasCalled(&outArgs)); ASSERT_EQ(args, outArgs); @@ -446,16 +432,14 @@ TEST_F(UnwantedInteractionBlockerTest, ConfigurationChangedIsPassedToNextListene * to next stage unmodified. */ TEST_F(UnwantedInteractionBlockerTest, KeyIsPassedToNextListener) { - // Create a basic key event and send to classifier - NotifyKeyArgs args(1 /*sequenceNum*/, 2 /*eventTime*/, 21 /*readTime*/, 3 /*deviceId*/, - AINPUT_SOURCE_KEYBOARD, ADISPLAY_ID_DEFAULT, 0 /*policyFlags*/, - AKEY_EVENT_ACTION_DOWN, 4 /*flags*/, AKEYCODE_HOME, 5 /*scanCode*/, - AMETA_NONE, 6 /*downTime*/); - - mBlocker->notifyKey(&args); - NotifyKeyArgs outArgs; - ASSERT_NO_FATAL_FAILURE(mTestListener.assertNotifyKeyWasCalled(&outArgs)); - ASSERT_EQ(args, outArgs); + // 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, + AKEY_EVENT_ACTION_DOWN, /*flags=*/4, AKEYCODE_HOME, /*scanCode=*/5, + AMETA_NONE, /*downTime=*/6); + + mBlocker->notifyKey(args); + ASSERT_NO_FATAL_FAILURE(mTestListener.assertNotifyKeyWasCalled(testing::Eq(args))); } /** @@ -465,11 +449,9 @@ TEST_F(UnwantedInteractionBlockerTest, KeyIsPassedToNextListener) { */ TEST_F(UnwantedInteractionBlockerTest, DownEventIsPassedToNextListener) { NotifyMotionArgs motionArgs = - generateMotionArgs(0 /*downTime*/, 0 /*eventTime*/, DOWN, {{1, 2, 3}}); - mBlocker->notifyMotion(&motionArgs); - NotifyMotionArgs args; - ASSERT_NO_FATAL_FAILURE(mTestListener.assertNotifyMotionWasCalled(&args)); - ASSERT_EQ(motionArgs, args); + generateMotionArgs(/*downTime=*/0, /*eventTime=*/0, DOWN, {{1, 2, 3}}); + mBlocker->notifyMotion(motionArgs); + ASSERT_NO_FATAL_FAILURE(mTestListener.assertNotifyMotionWasCalled(testing::Eq(motionArgs))); } /** @@ -477,10 +459,10 @@ TEST_F(UnwantedInteractionBlockerTest, DownEventIsPassedToNextListener) { * Expect that the event is received by the next input stage, unmodified. */ TEST_F(UnwantedInteractionBlockerTest, SwitchIsPassedToNextListener) { - NotifySwitchArgs args(1 /*sequenceNum*/, 2 /*eventTime*/, 3 /*policyFlags*/, 4 /*switchValues*/, - 5 /*switchMask*/); + NotifySwitchArgs args(/*sequenceNum=*/1, /*eventTime=*/2, /*policyFlags=*/3, + /*switchValues=*/4, /*switchMask=*/5); - mBlocker->notifySwitch(&args); + mBlocker->notifySwitch(args); NotifySwitchArgs outArgs; ASSERT_NO_FATAL_FAILURE(mTestListener.assertNotifySwitchWasCalled(&outArgs)); ASSERT_EQ(args, outArgs); @@ -491,9 +473,9 @@ TEST_F(UnwantedInteractionBlockerTest, SwitchIsPassedToNextListener) { * Expect that the event is received by the next input stage, unmodified. */ TEST_F(UnwantedInteractionBlockerTest, DeviceResetIsPassedToNextListener) { - NotifyDeviceResetArgs args(1 /*sequenceNum*/, 2 /*eventTime*/, DEVICE_ID); + NotifyDeviceResetArgs args(/*sequenceNum=*/1, /*eventTime=*/2, DEVICE_ID); - mBlocker->notifyDeviceReset(&args); + mBlocker->notifyDeviceReset(args); NotifyDeviceResetArgs outArgs; ASSERT_NO_FATAL_FAILURE(mTestListener.assertNotifyDeviceResetWasCalled(&outArgs)); ASSERT_EQ(args, outArgs); @@ -505,25 +487,20 @@ TEST_F(UnwantedInteractionBlockerTest, DeviceResetIsPassedToNextListener) { * a crash due to inconsistent event stream could have occurred. */ TEST_F(UnwantedInteractionBlockerTest, NoCrashWhenResetHappens) { - NotifyMotionArgs args; - mBlocker->notifyInputDevicesChanged({generateTestDeviceInfo()}); - mBlocker->notifyMotion( - &(args = generateMotionArgs(0 /*downTime*/, 1 /*eventTime*/, DOWN, {{1, 2, 3}}))); - mBlocker->notifyMotion( - &(args = generateMotionArgs(0 /*downTime*/, 2 /*eventTime*/, MOVE, {{4, 5, 6}}))); - NotifyDeviceResetArgs resetArgs(1 /*sequenceNum*/, 3 /*eventTime*/, DEVICE_ID); - mBlocker->notifyDeviceReset(&resetArgs); + mBlocker->notifyInputDevicesChanged({/*id=*/0, {generateTestDeviceInfo()}}); + mBlocker->notifyMotion(generateMotionArgs(/*downTime=*/0, /*eventTime=*/1, DOWN, {{1, 2, 3}})); + mBlocker->notifyMotion(generateMotionArgs(/*downTime=*/0, /*eventTime=*/2, MOVE, {{4, 5, 6}})); + mBlocker->notifyDeviceReset({/*sequenceNum=*/1, /*eventTime=*/3, DEVICE_ID}); // Start a new gesture with a DOWN event, even though the previous event stream was incomplete. - mBlocker->notifyMotion( - &(args = generateMotionArgs(0 /*downTime*/, 4 /*eventTime*/, DOWN, {{7, 8, 9}}))); + mBlocker->notifyMotion(generateMotionArgs(/*downTime=*/0, /*eventTime=*/4, DOWN, {{7, 8, 9}})); } TEST_F(UnwantedInteractionBlockerTest, NoCrashWhenStylusSourceWithFingerToolIsReceived) { - mBlocker->notifyInputDevicesChanged({generateTestDeviceInfo()}); - NotifyMotionArgs args = generateMotionArgs(0 /*downTime*/, 1 /*eventTime*/, DOWN, {{1, 2, 3}}); - args.pointerProperties[0].toolType = AMOTION_EVENT_TOOL_TYPE_FINGER; + mBlocker->notifyInputDevicesChanged({/*id=*/0, {generateTestDeviceInfo()}}); + NotifyMotionArgs args = generateMotionArgs(/*downTime=*/0, /*eventTime=*/1, DOWN, {{1, 2, 3}}); + args.pointerProperties[0].toolType = ToolType::FINGER; args.source = AINPUT_SOURCE_STYLUS; - mBlocker->notifyMotion(&args); + mBlocker->notifyMotion(args); } /** @@ -531,48 +508,41 @@ TEST_F(UnwantedInteractionBlockerTest, NoCrashWhenStylusSourceWithFingerToolIsRe * UnwantedInteractionBlocker has not changed, there should not be a reset. */ TEST_F(UnwantedInteractionBlockerTest, NoResetIfDeviceInfoChanges) { - NotifyMotionArgs args; - mBlocker->notifyInputDevicesChanged({generateTestDeviceInfo()}); - mBlocker->notifyMotion( - &(args = generateMotionArgs(0 /*downTime*/, 1 /*eventTime*/, DOWN, {{1, 2, 3}}))); - mBlocker->notifyMotion( - &(args = generateMotionArgs(0 /*downTime*/, 2 /*eventTime*/, MOVE, {{4, 5, 6}}))); + mBlocker->notifyInputDevicesChanged({/*id=*/0, {generateTestDeviceInfo()}}); + mBlocker->notifyMotion(generateMotionArgs(/*downTime=*/0, /*eventTime=*/1, DOWN, {{1, 2, 3}})); + mBlocker->notifyMotion(generateMotionArgs(/*downTime=*/0, /*eventTime=*/2, MOVE, {{4, 5, 6}})); // Now pretend the device changed, even though nothing is different for DEVICE_ID in practice. - mBlocker->notifyInputDevicesChanged({generateTestDeviceInfo()}); + mBlocker->notifyInputDevicesChanged({/*id=*/0, {generateTestDeviceInfo()}}); // The MOVE event continues the gesture that started before 'devices changed', so it should not // cause a crash. - mBlocker->notifyMotion( - &(args = generateMotionArgs(0 /*downTime*/, 4 /*eventTime*/, MOVE, {{7, 8, 9}}))); + mBlocker->notifyMotion(generateMotionArgs(/*downTime=*/0, /*eventTime=*/4, MOVE, {{7, 8, 9}})); } /** * Send a touch event, and then a stylus event. Make sure that both work. */ TEST_F(UnwantedInteractionBlockerTest, StylusAfterTouchWorks) { - NotifyMotionArgs args; - mBlocker->notifyInputDevicesChanged({generateTestDeviceInfo()}); - args = generateMotionArgs(0 /*downTime*/, 0 /*eventTime*/, DOWN, {{1, 2, 3}}); - mBlocker->notifyMotion(&args); - args = generateMotionArgs(0 /*downTime*/, 1 /*eventTime*/, MOVE, {{4, 5, 6}}); - mBlocker->notifyMotion(&args); - args = generateMotionArgs(0 /*downTime*/, 2 /*eventTime*/, UP, {{4, 5, 6}}); - mBlocker->notifyMotion(&args); + mBlocker->notifyInputDevicesChanged({/*id=*/0, {generateTestDeviceInfo()}}); + mBlocker->notifyMotion(generateMotionArgs(/*downTime=*/0, /*eventTime=*/0, DOWN, {{1, 2, 3}})); + mBlocker->notifyMotion(generateMotionArgs(/*downTime=*/0, /*eventTime=*/1, MOVE, {{4, 5, 6}})); + mBlocker->notifyMotion(generateMotionArgs(/*downTime=*/0, /*eventTime=*/2, UP, {{4, 5, 6}})); // Now touch down stylus - args = generateMotionArgs(3 /*downTime*/, 3 /*eventTime*/, DOWN, {{10, 20, 30}}); - args.pointerProperties[0].toolType = AMOTION_EVENT_TOOL_TYPE_STYLUS; + NotifyMotionArgs args; + args = generateMotionArgs(/*downTime=*/3, /*eventTime=*/3, DOWN, {{10, 20, 30}}); + args.pointerProperties[0].toolType = ToolType::STYLUS; args.source |= AINPUT_SOURCE_STYLUS; - mBlocker->notifyMotion(&args); - args = generateMotionArgs(3 /*downTime*/, 4 /*eventTime*/, MOVE, {{40, 50, 60}}); - args.pointerProperties[0].toolType = AMOTION_EVENT_TOOL_TYPE_STYLUS; + mBlocker->notifyMotion(args); + args = generateMotionArgs(/*downTime=*/3, /*eventTime=*/4, MOVE, {{40, 50, 60}}); + args.pointerProperties[0].toolType = ToolType::STYLUS; args.source |= AINPUT_SOURCE_STYLUS; - mBlocker->notifyMotion(&args); - args = generateMotionArgs(3 /*downTime*/, 5 /*eventTime*/, UP, {{40, 50, 60}}); - args.pointerProperties[0].toolType = AMOTION_EVENT_TOOL_TYPE_STYLUS; + mBlocker->notifyMotion(args); + args = generateMotionArgs(/*downTime=*/3, /*eventTime=*/5, UP, {{40, 50, 60}}); + args.pointerProperties[0].toolType = ToolType::STYLUS; args.source |= AINPUT_SOURCE_STYLUS; - mBlocker->notifyMotion(&args); + mBlocker->notifyMotion(args); } /** @@ -582,17 +552,14 @@ TEST_F(UnwantedInteractionBlockerTest, StylusAfterTouchWorks) { * options */ TEST_F(UnwantedInteractionBlockerTest, DumpCanBeAccessedOnAnotherThread) { - mBlocker->notifyInputDevicesChanged({generateTestDeviceInfo()}); - NotifyMotionArgs args1 = generateMotionArgs(0 /*downTime*/, 0 /*eventTime*/, DOWN, {{1, 2, 3}}); - mBlocker->notifyMotion(&args1); + mBlocker->notifyInputDevicesChanged({/*id=*/0, {generateTestDeviceInfo()}}); + mBlocker->notifyMotion(generateMotionArgs(/*downTime=*/0, /*eventTime=*/0, DOWN, {{1, 2, 3}})); std::thread dumpThread([this]() { std::string dump; mBlocker->dump(dump); }); - NotifyMotionArgs args2 = generateMotionArgs(0 /*downTime*/, 1 /*eventTime*/, MOVE, {{4, 5, 6}}); - mBlocker->notifyMotion(&args2); - NotifyMotionArgs args3 = generateMotionArgs(0 /*downTime*/, 2 /*eventTime*/, UP, {{4, 5, 6}}); - mBlocker->notifyMotion(&args3); + mBlocker->notifyMotion(generateMotionArgs(/*downTime=*/0, /*eventTime=*/1, MOVE, {{4, 5, 6}})); + mBlocker->notifyMotion(generateMotionArgs(/*downTime=*/0, /*eventTime=*/2, UP, {{4, 5, 6}})); dumpThread.join(); } @@ -601,23 +568,20 @@ TEST_F(UnwantedInteractionBlockerTest, DumpCanBeAccessedOnAnotherThread) { * of the touch is large. This is an integration test that checks that this filter kicks in. */ TEST_F(UnwantedInteractionBlockerTest, HeuristicFilterWorks) { - mBlocker->notifyInputDevicesChanged({generateTestDeviceInfo()}); + mBlocker->notifyInputDevicesChanged({/*id=*/0, {generateTestDeviceInfo()}}); // Small touch down - NotifyMotionArgs args1 = generateMotionArgs(0 /*downTime*/, 0 /*eventTime*/, DOWN, {{1, 2, 3}}); - mBlocker->notifyMotion(&args1); - mTestListener.assertNotifyMotionWasCalled(WithAction(DOWN)); + mBlocker->notifyMotion(generateMotionArgs(/*downTime=*/0, /*eventTime=*/0, DOWN, {{1, 2, 3}})); + mTestListener.assertNotifyMotionWasCalled(WithMotionAction(DOWN)); // Large touch oval on the next move - NotifyMotionArgs args2 = - generateMotionArgs(0 /*downTime*/, RESAMPLE_PERIOD, MOVE, {{4, 5, 200}}); - mBlocker->notifyMotion(&args2); - mTestListener.assertNotifyMotionWasCalled(WithAction(MOVE)); + mBlocker->notifyMotion( + generateMotionArgs(/*downTime=*/0, RESAMPLE_PERIOD, MOVE, {{4, 5, 200}})); + mTestListener.assertNotifyMotionWasCalled(WithMotionAction(MOVE)); // Lift up the touch to force the model to decide on whether it's a palm - NotifyMotionArgs args3 = - generateMotionArgs(0 /*downTime*/, 2 * RESAMPLE_PERIOD, UP, {{4, 5, 200}}); - mBlocker->notifyMotion(&args3); - mTestListener.assertNotifyMotionWasCalled(WithAction(CANCEL)); + mBlocker->notifyMotion( + generateMotionArgs(/*downTime=*/0, 2 * RESAMPLE_PERIOD, UP, {{4, 5, 200}})); + mTestListener.assertNotifyMotionWasCalled(WithMotionAction(CANCEL)); } /** @@ -627,28 +591,28 @@ TEST_F(UnwantedInteractionBlockerTest, HeuristicFilterWorks) { * This is similar to `HeuristicFilterWorks` test, but for stylus tool. */ TEST_F(UnwantedInteractionBlockerTest, StylusIsNotBlocked) { - InputDeviceInfo info = generateTestDeviceInfo(); - info.addSource(AINPUT_SOURCE_STYLUS); - mBlocker->notifyInputDevicesChanged({info}); - NotifyMotionArgs args1 = generateMotionArgs(0 /*downTime*/, 0 /*eventTime*/, DOWN, {{1, 2, 3}}); - args1.pointerProperties[0].toolType = AMOTION_EVENT_TOOL_TYPE_STYLUS; - mBlocker->notifyMotion(&args1); - mTestListener.assertNotifyMotionWasCalled(WithAction(DOWN)); + NotifyInputDevicesChangedArgs deviceChangedArgs = {/*id=*/0, {generateTestDeviceInfo()}}; + deviceChangedArgs.inputDeviceInfos[0].addSource(AINPUT_SOURCE_STYLUS); + mBlocker->notifyInputDevicesChanged(deviceChangedArgs); + NotifyMotionArgs args1 = generateMotionArgs(/*downTime=*/0, /*eventTime=*/0, DOWN, {{1, 2, 3}}); + args1.pointerProperties[0].toolType = ToolType::STYLUS; + mBlocker->notifyMotion(args1); + mTestListener.assertNotifyMotionWasCalled(WithMotionAction(DOWN)); // Move the stylus, setting large TOUCH_MAJOR/TOUCH_MINOR dimensions NotifyMotionArgs args2 = - generateMotionArgs(0 /*downTime*/, RESAMPLE_PERIOD, MOVE, {{4, 5, 200}}); - args2.pointerProperties[0].toolType = AMOTION_EVENT_TOOL_TYPE_STYLUS; - mBlocker->notifyMotion(&args2); - mTestListener.assertNotifyMotionWasCalled(WithAction(MOVE)); + generateMotionArgs(/*downTime=*/0, RESAMPLE_PERIOD, MOVE, {{4, 5, 200}}); + args2.pointerProperties[0].toolType = ToolType::STYLUS; + mBlocker->notifyMotion(args2); + mTestListener.assertNotifyMotionWasCalled(WithMotionAction(MOVE)); // Lift up the stylus. If it were a touch event, this would force the model to decide on whether // it's a palm. NotifyMotionArgs args3 = - generateMotionArgs(0 /*downTime*/, 2 * RESAMPLE_PERIOD, UP, {{4, 5, 200}}); - args3.pointerProperties[0].toolType = AMOTION_EVENT_TOOL_TYPE_STYLUS; - mBlocker->notifyMotion(&args3); - mTestListener.assertNotifyMotionWasCalled(WithAction(UP)); + generateMotionArgs(/*downTime=*/0, 2 * RESAMPLE_PERIOD, UP, {{4, 5, 200}}); + args3.pointerProperties[0].toolType = ToolType::STYLUS; + mBlocker->notifyMotion(args3); + mTestListener.assertNotifyMotionWasCalled(WithMotionAction(UP)); } /** @@ -657,51 +621,51 @@ TEST_F(UnwantedInteractionBlockerTest, StylusIsNotBlocked) { * Stylus event should continue to work even after touch is detected as a palm. */ TEST_F(UnwantedInteractionBlockerTest, TouchIsBlockedWhenMixedWithStylus) { - InputDeviceInfo info = generateTestDeviceInfo(); - info.addSource(AINPUT_SOURCE_STYLUS); - mBlocker->notifyInputDevicesChanged({info}); + NotifyInputDevicesChangedArgs deviceChangedArgs = {/*id=*/0, {generateTestDeviceInfo()}}; + deviceChangedArgs.inputDeviceInfos[0].addSource(AINPUT_SOURCE_STYLUS); + mBlocker->notifyInputDevicesChanged(deviceChangedArgs); // Touch down - NotifyMotionArgs args1 = generateMotionArgs(0 /*downTime*/, 0 /*eventTime*/, DOWN, {{1, 2, 3}}); - mBlocker->notifyMotion(&args1); - mTestListener.assertNotifyMotionWasCalled(WithAction(DOWN)); + NotifyMotionArgs args1 = generateMotionArgs(/*downTime=*/0, /*eventTime=*/0, DOWN, {{1, 2, 3}}); + mBlocker->notifyMotion(args1); + mTestListener.assertNotifyMotionWasCalled(WithMotionAction(DOWN)); // Stylus pointer down - NotifyMotionArgs args2 = generateMotionArgs(0 /*downTime*/, RESAMPLE_PERIOD, POINTER_1_DOWN, + NotifyMotionArgs args2 = generateMotionArgs(/*downTime=*/0, RESAMPLE_PERIOD, POINTER_1_DOWN, {{1, 2, 3}, {10, 20, 30}}); - args2.pointerProperties[1].toolType = AMOTION_EVENT_TOOL_TYPE_STYLUS; - mBlocker->notifyMotion(&args2); - mTestListener.assertNotifyMotionWasCalled(WithAction(POINTER_1_DOWN)); + args2.pointerProperties[1].toolType = ToolType::STYLUS; + mBlocker->notifyMotion(args2); + mTestListener.assertNotifyMotionWasCalled(WithMotionAction(POINTER_1_DOWN)); // Large touch oval on the next finger move - NotifyMotionArgs args3 = generateMotionArgs(0 /*downTime*/, 2 * RESAMPLE_PERIOD, MOVE, + NotifyMotionArgs args3 = generateMotionArgs(/*downTime=*/0, 2 * RESAMPLE_PERIOD, MOVE, {{1, 2, 300}, {11, 21, 30}}); - args3.pointerProperties[1].toolType = AMOTION_EVENT_TOOL_TYPE_STYLUS; - mBlocker->notifyMotion(&args3); - mTestListener.assertNotifyMotionWasCalled(WithAction(MOVE)); + args3.pointerProperties[1].toolType = ToolType::STYLUS; + mBlocker->notifyMotion(args3); + mTestListener.assertNotifyMotionWasCalled(WithMotionAction(MOVE)); // Lift up the finger pointer. It should be canceled due to the heuristic filter. - NotifyMotionArgs args4 = generateMotionArgs(0 /*downTime*/, 3 * RESAMPLE_PERIOD, POINTER_0_UP, + NotifyMotionArgs args4 = generateMotionArgs(/*downTime=*/0, 3 * RESAMPLE_PERIOD, POINTER_0_UP, {{1, 2, 300}, {11, 21, 30}}); - args4.pointerProperties[1].toolType = AMOTION_EVENT_TOOL_TYPE_STYLUS; - mBlocker->notifyMotion(&args4); + args4.pointerProperties[1].toolType = ToolType::STYLUS; + mBlocker->notifyMotion(args4); mTestListener.assertNotifyMotionWasCalled( - AllOf(WithAction(POINTER_0_UP), WithFlags(FLAG_CANCELED))); + AllOf(WithMotionAction(POINTER_0_UP), WithFlags(FLAG_CANCELED))); NotifyMotionArgs args5 = - generateMotionArgs(0 /*downTime*/, 4 * RESAMPLE_PERIOD, MOVE, {{12, 22, 30}}); + generateMotionArgs(/*downTime=*/0, 4 * RESAMPLE_PERIOD, MOVE, {{12, 22, 30}}); args5.pointerProperties[0].id = args4.pointerProperties[1].id; - args5.pointerProperties[0].toolType = AMOTION_EVENT_TOOL_TYPE_STYLUS; - mBlocker->notifyMotion(&args5); - mTestListener.assertNotifyMotionWasCalled(WithAction(MOVE)); + args5.pointerProperties[0].toolType = ToolType::STYLUS; + mBlocker->notifyMotion(args5); + mTestListener.assertNotifyMotionWasCalled(WithMotionAction(MOVE)); // Lift up the stylus pointer NotifyMotionArgs args6 = - generateMotionArgs(0 /*downTime*/, 5 * RESAMPLE_PERIOD, UP, {{4, 5, 200}}); + generateMotionArgs(/*downTime=*/0, 5 * RESAMPLE_PERIOD, UP, {{4, 5, 200}}); args6.pointerProperties[0].id = args4.pointerProperties[1].id; - args6.pointerProperties[0].toolType = AMOTION_EVENT_TOOL_TYPE_STYLUS; - mBlocker->notifyMotion(&args6); - mTestListener.assertNotifyMotionWasCalled(WithAction(UP)); + args6.pointerProperties[0].toolType = ToolType::STYLUS; + mBlocker->notifyMotion(args6); + mTestListener.assertNotifyMotionWasCalled(WithMotionAction(UP)); } using UnwantedInteractionBlockerTestDeathTest = UnwantedInteractionBlockerTest; @@ -713,18 +677,16 @@ using UnwantedInteractionBlockerTestDeathTest = UnwantedInteractionBlockerTest; TEST_F(UnwantedInteractionBlockerTestDeathTest, InconsistentEventAfterResetCausesACrash) { ScopedSilentDeath _silentDeath; NotifyMotionArgs args; - mBlocker->notifyInputDevicesChanged({generateTestDeviceInfo()}); - mBlocker->notifyMotion( - &(args = generateMotionArgs(0 /*downTime*/, 1 /*eventTime*/, DOWN, {{1, 2, 3}}))); - mBlocker->notifyMotion( - &(args = generateMotionArgs(0 /*downTime*/, 2 /*eventTime*/, MOVE, {{4, 5, 6}}))); - NotifyDeviceResetArgs resetArgs(1 /*sequenceNum*/, 3 /*eventTime*/, DEVICE_ID); - mBlocker->notifyDeviceReset(&resetArgs); + mBlocker->notifyInputDevicesChanged({/*id=*/0, {generateTestDeviceInfo()}}); + mBlocker->notifyMotion(generateMotionArgs(/*downTime=*/0, /*eventTime=*/1, DOWN, {{1, 2, 3}})); + mBlocker->notifyMotion(generateMotionArgs(/*downTime=*/0, /*eventTime=*/2, MOVE, {{4, 5, 6}})); + NotifyDeviceResetArgs resetArgs(/*sequenceNum=*/1, /*eventTime=*/3, DEVICE_ID); + mBlocker->notifyDeviceReset(resetArgs); // Sending MOVE without a DOWN -> should crash! ASSERT_DEATH( { - mBlocker->notifyMotion(&(args = generateMotionArgs(0 /*downTime*/, 4 /*eventTime*/, - MOVE, {{7, 8, 9}}))); + mBlocker->notifyMotion( + generateMotionArgs(/*downTime=*/0, /*eventTime=*/4, MOVE, {{7, 8, 9}})); }, "Could not find slot"); } @@ -734,9 +696,13 @@ TEST_F(UnwantedInteractionBlockerTestDeathTest, InconsistentEventAfterResetCause */ TEST_F(UnwantedInteractionBlockerTestDeathTest, WhenMoveWithoutDownCausesACrash) { ScopedSilentDeath _silentDeath; - NotifyMotionArgs args = generateMotionArgs(0 /*downTime*/, 1 /*eventTime*/, MOVE, {{1, 2, 3}}); - mBlocker->notifyInputDevicesChanged({generateTestDeviceInfo()}); - ASSERT_DEATH({ mBlocker->notifyMotion(&args); }, "Could not find slot"); + mBlocker->notifyInputDevicesChanged({/*id=*/0, {generateTestDeviceInfo()}}); + ASSERT_DEATH( + { + mBlocker->notifyMotion( + generateMotionArgs(/*downTime=*/0, /*eventTime=*/1, MOVE, {{1, 2, 3}})); + }, + "Could not find slot"); } class PalmRejectorTest : public testing::Test { @@ -755,7 +721,7 @@ TEST_F(PalmRejectorTestDeathTest, InconsistentEventCausesACrash) { ScopedSilentDeath _silentDeath; constexpr nsecs_t downTime = 0; NotifyMotionArgs args = - generateMotionArgs(downTime, 2 /*eventTime*/, MOVE, {{1406.0, 650.0, 52.0}}); + generateMotionArgs(downTime, /*eventTime=*/2, MOVE, {{1406.0, 650.0, 52.0}}); ASSERT_DEATH({ mPalmRejector->processMotion(args); }, "Could not find slot"); } diff --git a/services/inputflinger/tests/fuzzers/Android.bp b/services/inputflinger/tests/fuzzers/Android.bp index 455a1e21331ec09b0988ad8cefdb0dede3582ea6..55c2db6c91ede26afb903471e9c147ea174b13a2 100644 --- a/services/inputflinger/tests/fuzzers/Android.bp +++ b/services/inputflinger/tests/fuzzers/Android.bp @@ -21,7 +21,6 @@ package { default_applicable_licenses: ["frameworks_native_license"], } - cc_fuzz { name: "inputflinger_latencytracker_fuzzer", defaults: [ @@ -34,7 +33,6 @@ cc_fuzz { "libbase", "libbinder", "liblog", - "libui", "libutils", "libinput", "libinputflinger", @@ -43,6 +41,107 @@ cc_fuzz { "LatencyTrackerFuzzer.cpp", ], fuzz_config: { - cc: ["android-framework-input@google.com"], + cc: ["android-framework-input@google.com"], }, } + +cc_defaults { + name: "inputflinger_fuzz_defaults", + defaults: [ + "inputflinger_defaults", + ], + include_dirs: [ + "frameworks/native/services/inputflinger", + ], + shared_libs: [ + "android.hardware.input.classifier@1.0", + "android.hardware.input.processor-V1-ndk", + "libbase", + "libbinder", + "libcutils", + "liblog", + "libutils", + "libinput", + "libinputflinger", + "libinputreader", + "libinputflinger_base", + "libstatslog", + ], + header_libs: [ + "libbatteryservice_headers", + "libinputreader_headers", + ], + fuzz_config: { + cc: ["android-framework-input@google.com"], + }, +} + +cc_fuzz { + name: "inputflinger_cursor_input_fuzzer", + defaults: [ + "inputflinger_fuzz_defaults", + ], + srcs: [ + "CursorInputFuzzer.cpp", + ], +} + +cc_fuzz { + name: "inputflinger_keyboard_input_fuzzer", + defaults: [ + "inputflinger_fuzz_defaults", + ], + srcs: [ + "KeyboardInputFuzzer.cpp", + ], +} + +cc_fuzz { + name: "inputflinger_multitouch_input_fuzzer", + defaults: [ + "inputflinger_fuzz_defaults", + ], + srcs: [ + "MultiTouchInputFuzzer.cpp", + ], +} + +cc_fuzz { + name: "inputflinger_switch_input_fuzzer", + defaults: [ + "inputflinger_fuzz_defaults", + ], + srcs: [ + "SwitchInputFuzzer.cpp", + ], +} + +cc_fuzz { + name: "inputflinger_input_reader_fuzzer", + defaults: [ + "inputflinger_fuzz_defaults", + ], + srcs: [ + "InputReaderFuzzer.cpp", + ], +} + +cc_fuzz { + name: "inputflinger_blocking_queue_fuzzer", + defaults: [ + "inputflinger_fuzz_defaults", + ], + srcs: [ + "BlockingQueueFuzzer.cpp", + ], +} + +cc_fuzz { + name: "inputflinger_input_classifier_fuzzer", + defaults: [ + "inputflinger_fuzz_defaults", + ], + srcs: [ + "InputClassifierFuzzer.cpp", + ], +} diff --git a/services/inputflinger/tests/fuzzers/BlockingQueueFuzzer.cpp b/services/inputflinger/tests/fuzzers/BlockingQueueFuzzer.cpp new file mode 100644 index 0000000000000000000000000000000000000000..d2595bfc9dee4745ef76504c2b4c0bae29082052 --- /dev/null +++ b/services/inputflinger/tests/fuzzers/BlockingQueueFuzzer.cpp @@ -0,0 +1,69 @@ +/* + * 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 "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; + +namespace android { + +extern "C" int LLVMFuzzerTestOneInput(uint8_t *data, size_t size) { + FuzzedDataProvider fdp(data, size); + size_t capacity = fdp.ConsumeIntegralInRange(1, MAX_CAPACITY); + size_t filled = 0; + BlockingQueue queue(capacity); + + while (fdp.remaining_bytes() > 0) { + fdp.PickValueInArray>({ + [&]() -> void { + size_t numPushes = fdp.ConsumeIntegralInRange(0, capacity + 1); + for (size_t i = 0; i < numPushes; i++) { + queue.push(fdp.ConsumeIntegral()); + } + filled = std::min(capacity, filled + numPushes); + }, + [&]() -> void { + // Pops blocks if it is empty, so only pop up to num elements inserted. + size_t numPops = fdp.ConsumeIntegralInRange(0, filled); + for (size_t i = 0; i < numPops; i++) { + queue.pop(); + } + filled > numPops ? filled -= numPops : filled = 0; + }, + [&]() -> void { + queue.clear(); + filled = 0; + }, + [&]() -> void { + int32_t eraseElement = fdp.ConsumeIntegral(); + queue.erase([&](int32_t element) { + if (element == eraseElement) { + filled--; + return true; + } + return false; + }); + }, + })(); + } + + return 0; +} + +} // namespace android diff --git a/services/inputflinger/tests/fuzzers/CursorInputFuzzer.cpp b/services/inputflinger/tests/fuzzers/CursorInputFuzzer.cpp new file mode 100644 index 0000000000000000000000000000000000000000..8098ef2edd2443f4a86c89017dd2c9f64dc76523 --- /dev/null +++ b/services/inputflinger/tests/fuzzers/CursorInputFuzzer.cpp @@ -0,0 +1,106 @@ +/* + * 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 + +namespace android { + +static void addProperty(FuzzContainer& fuzzer, std::shared_ptr fdp) { + // Pick a random property to set for the mapper to have set. + fdp->PickValueInArray>( + {[&]() -> void { fuzzer.addProperty("cursor.mode", "pointer"); }, + [&]() -> void { fuzzer.addProperty("cursor.mode", "navigation"); }, + [&]() -> void { + fuzzer.addProperty("cursor.mode", fdp->ConsumeRandomLengthString(100).data()); + }, + [&]() -> void { + fuzzer.addProperty("cursor.orientationAware", + fdp->ConsumeRandomLengthString(100).data()); + }})(); +} + +extern "C" int LLVMFuzzerTestOneInput(uint8_t* data, size_t size) { + std::shared_ptr fdp = + std::make_shared(data, size); + FuzzContainer fuzzer(fdp); + + auto policyConfig = fuzzer.getPolicyConfig(); + CursorInputMapper& mapper = fuzzer.getMapper(policyConfig); + + // Loop through mapper operations until randomness is exhausted. + while (fdp->remaining_bytes() > 0) { + fdp->PickValueInArray>({ + [&]() -> void { addProperty(fuzzer, fdp); }, + [&]() -> void { + std::string dump; + mapper.dump(dump); + }, + [&]() -> void { mapper.getSources(); }, + [&]() -> void { + std::list unused = + mapper.reconfigure(fdp->ConsumeIntegral(), policyConfig, + InputReaderConfiguration::Change( + fdp->ConsumeIntegral())); + }, + [&]() -> void { + // Need to reconfigure with 0 or you risk a NPE. + std::list unused = + mapper.reconfigure(fdp->ConsumeIntegral(), policyConfig, + InputReaderConfiguration::Change(0)); + InputDeviceInfo info; + mapper.populateDeviceInfo(info); + }, + [&]() -> void { + int32_t type, code; + type = fdp->ConsumeBool() ? fdp->PickValueInArray(kValidTypes) + : fdp->ConsumeIntegral(); + code = fdp->ConsumeBool() ? fdp->PickValueInArray(kValidCodes) + : fdp->ConsumeIntegral(); + + // Need to reconfigure with 0 or you risk a NPE. + std::list unused = + mapper.reconfigure(fdp->ConsumeIntegral(), policyConfig, + InputReaderConfiguration::Change(0)); + RawEvent rawEvent{fdp->ConsumeIntegral(), + fdp->ConsumeIntegral(), + fdp->ConsumeIntegral(), + type, + code, + fdp->ConsumeIntegral()}; + unused += mapper.process(&rawEvent); + }, + [&]() -> void { + std::list unused = mapper.reset(fdp->ConsumeIntegral()); + }, + [&]() -> void { + mapper.getScanCodeState(fdp->ConsumeIntegral(), + fdp->ConsumeIntegral()); + }, + [&]() -> void { + // Need to reconfigure with 0 or you risk a NPE. + std::list unused = + mapper.reconfigure(fdp->ConsumeIntegral(), policyConfig, + InputReaderConfiguration::Change(0)); + mapper.getAssociatedDisplayId(); + }, + })(); + } + + return 0; +} + +} // namespace android diff --git a/services/inputflinger/tests/fuzzers/FuzzContainer.h b/services/inputflinger/tests/fuzzers/FuzzContainer.h new file mode 100644 index 0000000000000000000000000000000000000000..b9929289f1eacd91588ac23b06809387df2c160c --- /dev/null +++ b/services/inputflinger/tests/fuzzers/FuzzContainer.h @@ -0,0 +1,87 @@ +/* + * 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 +#include +#include + +namespace android { + +class FuzzContainer { + std::shared_ptr mFuzzEventHub; + sp mFuzzPolicy; + FuzzInputListener mFuzzListener; + std::unique_ptr mFuzzContext; + std::unique_ptr mFuzzDevice; + InputReaderConfiguration mPolicyConfig; + std::shared_ptr mFdp; + +public: + FuzzContainer(std::shared_ptr fdp) : mFdp(fdp) { + // Setup parameters. + std::string deviceName = mFdp->ConsumeRandomLengthString(16); + std::string deviceLocation = mFdp->ConsumeRandomLengthString(12); + int32_t deviceID = mFdp->ConsumeIntegralInRange(0, 5); + int32_t deviceGeneration = mFdp->ConsumeIntegralInRange(/*from*/ 0, /*to*/ 5); + + // Create mocked objects. + mFuzzEventHub = std::make_shared(mFdp); + mFuzzPolicy = sp::make(mFdp); + mFuzzContext = std::make_unique(mFuzzEventHub, mFuzzPolicy, + mFuzzListener, mFdp); + + InputDeviceIdentifier identifier; + identifier.name = deviceName; + identifier.location = deviceLocation; + mFuzzDevice = std::make_unique(mFuzzContext.get(), deviceID, deviceGeneration, + identifier); + mFuzzPolicy->getReaderConfiguration(&mPolicyConfig); + } + + ~FuzzContainer() {} + + void configureDevice() { + nsecs_t arbitraryTime = mFdp->ConsumeIntegral(); + std::list out; + out += mFuzzDevice->configure(arbitraryTime, mPolicyConfig, /*changes=*/{}); + out += mFuzzDevice->reset(arbitraryTime); + for (const NotifyArgs& args : out) { + mFuzzListener.notify(args); + } + } + + void addProperty(std::string key, std::string value) { + mFuzzEventHub->addProperty(key, value); + configureDevice(); + } + + InputReaderConfiguration& getPolicyConfig() { return mPolicyConfig; } + + template + T& getMapper(Args... args) { + int32_t eventhubId = mFdp->ConsumeIntegral(); + // ensure a device entry exists for this eventHubId + mFuzzDevice->addEmptyEventHubDevice(eventhubId); + configureDevice(); + + return mFuzzDevice->template constructAndAddMapper(eventhubId, args...); + } +}; + +} // namespace android diff --git a/services/inputflinger/tests/fuzzers/InputClassifierFuzzer.cpp b/services/inputflinger/tests/fuzzers/InputClassifierFuzzer.cpp new file mode 100644 index 0000000000000000000000000000000000000000..f8ebc97ccd664f7488ff5c3e97eb79fad96ca699 --- /dev/null +++ b/services/inputflinger/tests/fuzzers/InputClassifierFuzzer.cpp @@ -0,0 +1,118 @@ +/* + * 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 "InputCommonConverter.h" +#include "InputProcessor.h" + +namespace android { + +static constexpr int32_t MAX_AXES = 64; + +// Used by two fuzz operations and a bit lengthy, so pulled out into a function. +NotifyMotionArgs generateFuzzedMotionArgs(FuzzedDataProvider &fdp) { + // Create a basic motion event for testing + PointerProperties properties; + properties.id = 0; + properties.toolType = getFuzzedToolType(fdp); + PointerCoords coords; + coords.clear(); + for (int32_t i = 0; i < fdp.ConsumeIntegralInRange(0, MAX_AXES); i++) { + coords.setAxisValue(fdp.ConsumeIntegral(), fdp.ConsumeFloatingPoint()); + } + + const nsecs_t downTime = 2; + const nsecs_t readTime = downTime + fdp.ConsumeIntegralInRange(0, 1E8); + NotifyMotionArgs motionArgs(/*sequenceNum=*/fdp.ConsumeIntegral(), + /*eventTime=*/downTime, readTime, + /*deviceId=*/fdp.ConsumeIntegral(), AINPUT_SOURCE_ANY, + ADISPLAY_ID_DEFAULT, + /*policyFlags=*/fdp.ConsumeIntegral(), + AMOTION_EVENT_ACTION_DOWN, + /*actionButton=*/fdp.ConsumeIntegral(), + /*flags=*/fdp.ConsumeIntegral(), AMETA_NONE, + /*buttonState=*/fdp.ConsumeIntegral(), + MotionClassification::NONE, AMOTION_EVENT_EDGE_FLAG_NONE, + /*pointerCount=*/1, &properties, &coords, + /*xPrecision=*/fdp.ConsumeFloatingPoint(), + /*yPrecision=*/fdp.ConsumeFloatingPoint(), + AMOTION_EVENT_INVALID_CURSOR_POSITION, + AMOTION_EVENT_INVALID_CURSOR_POSITION, downTime, + /*videoFrames=*/{}); + return motionArgs; +} + +extern "C" int LLVMFuzzerTestOneInput(uint8_t *data, size_t size) { + FuzzedDataProvider fdp(data, size); + + std::unique_ptr mFuzzListener = std::make_unique(); + std::unique_ptr mClassifier = + std::make_unique(*mFuzzListener); + + while (fdp.remaining_bytes() > 0) { + fdp.PickValueInArray>({ + [&]() -> void { + // SendToNextStage_NotifyConfigurationChangedArgs + mClassifier->notifyConfigurationChanged( + {/*sequenceNum=*/fdp.ConsumeIntegral(), + /*eventTime=*/fdp.ConsumeIntegral()}); + }, + [&]() -> void { + // SendToNextStage_NotifyKeyArgs + const nsecs_t eventTime = fdp.ConsumeIntegral(); + const nsecs_t readTime = + eventTime + fdp.ConsumeIntegralInRange(0, 1E8); + mClassifier->notifyKey({/*sequenceNum=*/fdp.ConsumeIntegral(), + eventTime, readTime, + /*deviceId=*/fdp.ConsumeIntegral(), + AINPUT_SOURCE_KEYBOARD, ADISPLAY_ID_DEFAULT, + /*policyFlags=*/fdp.ConsumeIntegral(), + AKEY_EVENT_ACTION_DOWN, + /*flags=*/fdp.ConsumeIntegral(), AKEYCODE_HOME, + /*scanCode=*/fdp.ConsumeIntegral(), AMETA_NONE, + /*downTime=*/fdp.ConsumeIntegral()}); + }, + [&]() -> void { + // SendToNextStage_NotifyMotionArgs + mClassifier->notifyMotion(generateFuzzedMotionArgs(fdp)); + }, + [&]() -> void { + // SendToNextStage_NotifySwitchArgs + mClassifier->notifySwitch({/*sequenceNum=*/fdp.ConsumeIntegral(), + /*eventTime=*/fdp.ConsumeIntegral(), + /*policyFlags=*/fdp.ConsumeIntegral(), + /*switchValues=*/fdp.ConsumeIntegral(), + /*switchMask=*/fdp.ConsumeIntegral()}); + }, + [&]() -> void { + // SendToNextStage_NotifyDeviceResetArgs + mClassifier->notifyDeviceReset({/*sequenceNum=*/fdp.ConsumeIntegral(), + /*eventTime=*/fdp.ConsumeIntegral(), + /*deviceId=*/fdp.ConsumeIntegral()}); + }, + [&]() -> void { + // InputClassifierConverterTest + const NotifyMotionArgs motionArgs = generateFuzzedMotionArgs(fdp); + aidl::android::hardware::input::common::MotionEvent motionEvent = + notifyMotionArgsToHalMotionEvent(motionArgs); + }, + })(); + } + return 0; +} + +} // namespace android diff --git a/services/inputflinger/tests/fuzzers/InputReaderFuzzer.cpp b/services/inputflinger/tests/fuzzers/InputReaderFuzzer.cpp new file mode 100644 index 0000000000000000000000000000000000000000..9223287114ce0ae9e1d29dab19757427ec23d4d4 --- /dev/null +++ b/services/inputflinger/tests/fuzzers/InputReaderFuzzer.cpp @@ -0,0 +1,298 @@ +/* + * 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 + +namespace android { + +constexpr InputDeviceSensorType kInputDeviceSensorType[] = { + InputDeviceSensorType::ACCELEROMETER, + InputDeviceSensorType::MAGNETIC_FIELD, + InputDeviceSensorType::ORIENTATION, + InputDeviceSensorType::GYROSCOPE, + InputDeviceSensorType::LIGHT, + InputDeviceSensorType::PRESSURE, + InputDeviceSensorType::TEMPERATURE, + InputDeviceSensorType::PROXIMITY, + InputDeviceSensorType::GRAVITY, + InputDeviceSensorType::LINEAR_ACCELERATION, + InputDeviceSensorType::ROTATION_VECTOR, + InputDeviceSensorType::RELATIVE_HUMIDITY, + InputDeviceSensorType::AMBIENT_TEMPERATURE, + InputDeviceSensorType::MAGNETIC_FIELD_UNCALIBRATED, + InputDeviceSensorType::GAME_ROTATION_VECTOR, + InputDeviceSensorType::GYROSCOPE_UNCALIBRATED, + InputDeviceSensorType::SIGNIFICANT_MOTION, +}; + +class FuzzInputReader : public InputReaderInterface { +public: + FuzzInputReader(std::shared_ptr fuzzEventHub, + const sp& fuzzPolicy, + InputListenerInterface& fuzzListener) { + reader = std::make_unique(fuzzEventHub, fuzzPolicy, fuzzListener); + } + + void dump(std::string& dump) { reader->dump(dump); } + + 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(); } + + std::vector getInputDevices() const { return reader->getInputDevices(); } + + int32_t getScanCodeState(int32_t deviceId, uint32_t sourceMask, int32_t scanCode) { + return reader->getScanCodeState(deviceId, sourceMask, scanCode); + } + + int32_t getKeyCodeState(int32_t deviceId, uint32_t sourceMask, int32_t keyCode) { + return reader->getKeyCodeState(deviceId, sourceMask, keyCode); + } + + int32_t getSwitchState(int32_t deviceId, uint32_t sourceMask, int32_t sw) { + return reader->getSwitchState(deviceId, sourceMask, sw); + } + + void toggleCapsLockState(int32_t deviceId) { reader->toggleCapsLockState(deviceId); } + + bool hasKeys(int32_t deviceId, uint32_t sourceMask, const std::vector& keyCodes, + uint8_t* outFlags) { + return reader->hasKeys(deviceId, sourceMask, keyCodes, outFlags); + } + + void requestRefreshConfiguration(ConfigurationChanges changes) { + reader->requestRefreshConfiguration(changes); + } + + void vibrate(int32_t deviceId, const VibrationSequence& sequence, ssize_t repeat, + int32_t token) { + reader->vibrate(deviceId, sequence, repeat, token); + } + + void cancelVibrate(int32_t deviceId, int32_t token) { reader->cancelVibrate(deviceId, token); } + + bool isVibrating(int32_t deviceId) { return reader->isVibrating(deviceId); } + + std::vector getVibratorIds(int32_t deviceId) { + return reader->getVibratorIds(deviceId); + } + + std::optional getBatteryCapacity(int32_t deviceId) { + return reader->getBatteryCapacity(deviceId); + } + + std::optional getBatteryStatus(int32_t deviceId) { + return reader->getBatteryStatus(deviceId); + } + + std::optional getBatteryDevicePath(int32_t deviceId) { + return reader->getBatteryDevicePath(deviceId); + } + + std::vector getLights(int32_t deviceId) { + return reader->getLights(deviceId); + } + + std::vector getSensors(int32_t deviceId) { + return reader->getSensors(deviceId); + } + + bool canDispatchToDisplay(int32_t deviceId, int32_t displayId) { + return reader->canDispatchToDisplay(deviceId, displayId); + } + + bool enableSensor(int32_t deviceId, InputDeviceSensorType sensorType, + std::chrono::microseconds samplingPeriod, + std::chrono::microseconds maxBatchReportLatency) { + return reader->enableSensor(deviceId, sensorType, samplingPeriod, maxBatchReportLatency); + } + + void disableSensor(int32_t deviceId, InputDeviceSensorType sensorType) { + return reader->disableSensor(deviceId, sensorType); + } + + void flushSensor(int32_t deviceId, InputDeviceSensorType sensorType) { + return reader->flushSensor(deviceId, sensorType); + } + + bool setLightColor(int32_t deviceId, int32_t lightId, int32_t color) { + return reader->setLightColor(deviceId, lightId, color); + } + + bool setLightPlayerId(int32_t deviceId, int32_t lightId, int32_t playerId) { + return reader->setLightPlayerId(deviceId, lightId, playerId); + } + + std::optional getLightColor(int32_t deviceId, int32_t lightId) { + return reader->getLightColor(deviceId, lightId); + } + + std::optional getLightPlayerId(int32_t deviceId, int32_t lightId) { + return reader->getLightPlayerId(deviceId, lightId); + } + + void addKeyRemapping(int32_t deviceId, int32_t fromKeyCode, int32_t toKeyCode) const { + reader->addKeyRemapping(deviceId, fromKeyCode, toKeyCode); + } + + int32_t getKeyCodeForKeyLocation(int32_t deviceId, int32_t locationKeyCode) const { + return reader->getKeyCodeForKeyLocation(deviceId, locationKeyCode); + } + + std::optional getBluetoothAddress(int32_t deviceId) const { + return reader->getBluetoothAddress(deviceId); + } + + void sysfsNodeChanged(const std::string& sysfsNodePath) { + reader->sysfsNodeChanged(sysfsNodePath); + } + +private: + std::unique_ptr reader; +}; + +extern "C" int LLVMFuzzerTestOneInput(uint8_t* data, size_t size) { + std::shared_ptr fdp = + std::make_shared(data, size); + + FuzzInputListener fuzzListener; + sp fuzzPolicy = sp::make(fdp); + std::shared_ptr fuzzEventHub = std::make_shared(fdp); + std::unique_ptr reader = + std::make_unique(fuzzEventHub, fuzzPolicy, fuzzListener); + size_t patternCount = fdp->ConsumeIntegralInRange(1, 260); + VibrationSequence pattern(patternCount); + for (size_t i = 0; i < patternCount; ++i) { + VibrationElement element(i); + element.addChannel(/*vibratorId=*/fdp->ConsumeIntegral(), + /*amplitude=*/fdp->ConsumeIntegral()); + pattern.addElement(element); + } + reader->vibrate(fdp->ConsumeIntegral(), pattern, + /*repeat=*/fdp->ConsumeIntegral(), + /*token=*/fdp->ConsumeIntegral()); + reader->start(); + + // Loop through mapper operations until randomness is exhausted. + while (fdp->remaining_bytes() > 0) { + fdp->PickValueInArray>({ + [&]() -> void { + std::string dump; + reader->dump(dump); + }, + [&]() -> void { reader->monitor(); }, + [&]() -> void { reader->getInputDevices(); }, + [&]() -> void { reader->isInputDeviceEnabled(fdp->ConsumeIntegral()); }, + [&]() -> void { + reader->getScanCodeState(fdp->ConsumeIntegral(), + fdp->ConsumeIntegral(), + fdp->ConsumeIntegral()); + }, + [&]() -> void { + reader->getKeyCodeState(fdp->ConsumeIntegral(), + fdp->ConsumeIntegral(), + fdp->ConsumeIntegral()); + }, + [&]() -> void { + reader->getSwitchState(fdp->ConsumeIntegral(), + fdp->ConsumeIntegral(), + fdp->ConsumeIntegral()); + }, + [&]() -> void { reader->toggleCapsLockState(fdp->ConsumeIntegral()); }, + [&]() -> void { + size_t count = fdp->ConsumeIntegralInRange(1, 1024); + std::vector outFlags(count); + std::vector keyCodes; + for (size_t i = 0; i < count; ++i) { + keyCodes.push_back(fdp->ConsumeIntegral()); + } + reader->hasKeys(fdp->ConsumeIntegral(), + fdp->ConsumeIntegral(), keyCodes, outFlags.data()); + }, + [&]() -> void { + reader->requestRefreshConfiguration( + InputReaderConfiguration::Change(fdp->ConsumeIntegral())); + }, + [&]() -> void { + reader->cancelVibrate(fdp->ConsumeIntegral(), + fdp->ConsumeIntegral()); + }, + [&]() -> void { + reader->canDispatchToDisplay(fdp->ConsumeIntegral(), + fdp->ConsumeIntegral()); + }, + [&]() -> void { + reader->getKeyCodeForKeyLocation(fdp->ConsumeIntegral(), + fdp->ConsumeIntegral()); + }, + [&]() -> void { reader->getBatteryCapacity(fdp->ConsumeIntegral()); }, + [&]() -> void { reader->getBatteryStatus(fdp->ConsumeIntegral()); }, + [&]() -> void { reader->getBatteryDevicePath(fdp->ConsumeIntegral()); }, + [&]() -> void { reader->getLights(fdp->ConsumeIntegral()); }, + [&]() -> void { reader->getSensors(fdp->ConsumeIntegral()); }, + [&]() -> void { + reader->getLightPlayerId(fdp->ConsumeIntegral(), + fdp->ConsumeIntegral()); + }, + [&]() -> void { + reader->getLightColor(fdp->ConsumeIntegral(), + fdp->ConsumeIntegral()); + }, + [&]() -> void { + reader->setLightPlayerId(fdp->ConsumeIntegral(), + fdp->ConsumeIntegral(), + fdp->ConsumeIntegral()); + }, + [&]() -> void { + reader->setLightColor(fdp->ConsumeIntegral(), + fdp->ConsumeIntegral(), + fdp->ConsumeIntegral()); + }, + [&]() -> void { + reader->flushSensor(fdp->ConsumeIntegral(), + fdp->PickValueInArray( + kInputDeviceSensorType)); + }, + [&]() -> void { + reader->disableSensor(fdp->ConsumeIntegral(), + fdp->PickValueInArray( + kInputDeviceSensorType)); + }, + [&]() -> void { + reader->enableSensor(fdp->ConsumeIntegral(), + fdp->PickValueInArray( + kInputDeviceSensorType), + std::chrono::microseconds(fdp->ConsumeIntegral()), + std::chrono::microseconds(fdp->ConsumeIntegral())); + }, + [&]() -> void { reader->getBluetoothAddress(fdp->ConsumeIntegral()); }, + })(); + } + + reader->stop(); + return 0; +} + +} // namespace android diff --git a/services/inputflinger/tests/fuzzers/KeyboardInputFuzzer.cpp b/services/inputflinger/tests/fuzzers/KeyboardInputFuzzer.cpp new file mode 100644 index 0000000000000000000000000000000000000000..616e87081150098c138830968bc095f21792baa7 --- /dev/null +++ b/services/inputflinger/tests/fuzzers/KeyboardInputFuzzer.cpp @@ -0,0 +1,114 @@ +/* + * 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 + +namespace android { + +const int32_t kMaxKeycodes = 100; + +static void addProperty(FuzzContainer& fuzzer, std::shared_ptr fdp) { + // Pick a random property to set for the mapper to have set. + fdp->PickValueInArray>( + {[&]() -> void { fuzzer.addProperty("keyboard.orientationAware", "1"); }, + [&]() -> void { + fuzzer.addProperty("keyboard.orientationAware", + fdp->ConsumeRandomLengthString(100).data()); + }, + [&]() -> void { + fuzzer.addProperty("keyboard.doNotWakeByDefault", + fdp->ConsumeRandomLengthString(100).data()); + }, + [&]() -> void { + fuzzer.addProperty("keyboard.handlesKeyRepeat", + fdp->ConsumeRandomLengthString(100).data()); + }})(); +} + +extern "C" int LLVMFuzzerTestOneInput(uint8_t* data, size_t size) { + std::shared_ptr fdp = + std::make_shared(data, size); + FuzzContainer fuzzer(fdp); + + auto policyConfig = fuzzer.getPolicyConfig(); + KeyboardInputMapper& mapper = + fuzzer.getMapper(policyConfig, fdp->ConsumeIntegral(), + fdp->ConsumeIntegral()); + + // Loop through mapper operations until randomness is exhausted. + while (fdp->remaining_bytes() > 0) { + fdp->PickValueInArray>({ + [&]() -> void { addProperty(fuzzer, fdp); }, + [&]() -> void { + std::string dump; + mapper.dump(dump); + }, + [&]() -> void { + InputDeviceInfo info; + mapper.populateDeviceInfo(info); + }, + [&]() -> void { mapper.getSources(); }, + [&]() -> void { + std::list unused = + mapper.reconfigure(fdp->ConsumeIntegral(), policyConfig, + InputReaderConfiguration::Change( + fdp->ConsumeIntegral())); + }, + [&]() -> void { + std::list unused = mapper.reset(fdp->ConsumeIntegral()); + }, + [&]() -> void { + int32_t type, code; + type = fdp->ConsumeBool() ? fdp->PickValueInArray(kValidTypes) + : fdp->ConsumeIntegral(); + code = fdp->ConsumeBool() ? fdp->PickValueInArray(kValidCodes) + : fdp->ConsumeIntegral(); + RawEvent rawEvent{fdp->ConsumeIntegral(), + fdp->ConsumeIntegral(), + fdp->ConsumeIntegral(), + type, + code, + fdp->ConsumeIntegral()}; + std::list unused = mapper.process(&rawEvent); + }, + [&]() -> void { + mapper.getKeyCodeState(fdp->ConsumeIntegral(), + fdp->ConsumeIntegral()); + }, + [&]() -> void { + mapper.getScanCodeState(fdp->ConsumeIntegral(), + fdp->ConsumeIntegral()); + }, + [&]() -> void { + std::vector keyCodes; + int32_t numBytes = fdp->ConsumeIntegralInRange(0, kMaxKeycodes); + for (int32_t i = 0; i < numBytes; ++i) { + keyCodes.push_back(fdp->ConsumeIntegral()); + } + mapper.markSupportedKeyCodes(fdp->ConsumeIntegral(), keyCodes, + nullptr); + }, + [&]() -> void { mapper.getMetaState(); }, + [&]() -> void { mapper.updateMetaState(fdp->ConsumeIntegral()); }, + [&]() -> void { mapper.getAssociatedDisplayId(); }, + })(); + } + + return 0; +} + +} // namespace android diff --git a/services/inputflinger/tests/fuzzers/LatencyTrackerFuzzer.cpp b/services/inputflinger/tests/fuzzers/LatencyTrackerFuzzer.cpp index 4f066ad5136d2aca3e68149fc8907c4b62d160dd..72780fb363c66cc3e309809bfec3d89a07a14d4d 100644 --- a/services/inputflinger/tests/fuzzers/LatencyTrackerFuzzer.cpp +++ b/services/inputflinger/tests/fuzzers/LatencyTrackerFuzzer.cpp @@ -42,7 +42,7 @@ static sp getConnectionToken(FuzzedDataProvider& fdp, if (useExistingToken) { return tokens[fdp.ConsumeIntegralInRange(0ul, tokens.size() - 1)]; } - return new BBinder(); + return sp::make(); } extern "C" int LLVMFuzzerTestOneInput(uint8_t* data, size_t size) { @@ -54,7 +54,7 @@ extern "C" int LLVMFuzzerTestOneInput(uint8_t* data, size_t size) { // Make some pre-defined tokens to ensure that some timelines are complete. std::array /*token*/, 10> predefinedTokens; for (size_t i = 0; i < predefinedTokens.size(); i++) { - predefinedTokens[i] = new BBinder(); + predefinedTokens[i] = sp::make(); } // Randomly invoke LatencyTracker api's until randomness is exhausted. diff --git a/services/inputflinger/tests/fuzzers/MapperHelpers.h b/services/inputflinger/tests/fuzzers/MapperHelpers.h new file mode 100644 index 0000000000000000000000000000000000000000..1e44e0fba0452f11bbd70d67f4ae5eeec91b2476 --- /dev/null +++ b/services/inputflinger/tests/fuzzers/MapperHelpers.h @@ -0,0 +1,344 @@ +/* + * 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 +#include +#include + +constexpr size_t kValidTypes[] = {EV_SW, + EV_SYN, + SYN_REPORT, + EV_ABS, + EV_KEY, + EV_MSC, + EV_REL, + android::EventHubInterface::DEVICE_ADDED, + android::EventHubInterface::DEVICE_REMOVED, + android::EventHubInterface::FINISHED_DEVICE_SCAN}; + +constexpr size_t kValidCodes[] = { + SYN_REPORT, + ABS_MT_SLOT, + SYN_MT_REPORT, + ABS_MT_POSITION_X, + ABS_MT_POSITION_Y, + ABS_MT_TOUCH_MAJOR, + ABS_MT_TOUCH_MINOR, + ABS_MT_WIDTH_MAJOR, + ABS_MT_WIDTH_MINOR, + ABS_MT_ORIENTATION, + ABS_MT_TRACKING_ID, + ABS_MT_PRESSURE, + ABS_MT_DISTANCE, + ABS_MT_TOOL_TYPE, + SYN_MT_REPORT, + MSC_SCAN, + REL_X, + REL_Y, + REL_WHEEL, + REL_HWHEEL, + BTN_LEFT, + BTN_RIGHT, + BTN_MIDDLE, + BTN_BACK, + BTN_SIDE, + BTN_FORWARD, + BTN_EXTRA, + BTN_TASK, +}; + +constexpr size_t kMaxSize = 256; + +namespace android { + +template +ToolType getFuzzedToolType(Fdp& fdp) { + const int32_t toolType = fdp.template ConsumeIntegralInRange( + static_cast(ToolType::ftl_first), + static_cast(ToolType::ftl_last)); + return static_cast(toolType); +} + +class FuzzEventHub : public EventHubInterface { + InputDeviceIdentifier mIdentifier; + std::vector mVideoFrames; + PropertyMap mFuzzConfig; + std::shared_ptr mFdp; + +public: + FuzzEventHub(std::shared_ptr fdp) : mFdp(std::move(fdp)) {} + ~FuzzEventHub() {} + void addProperty(std::string key, std::string value) { mFuzzConfig.addProperty(key, value); } + + ftl::Flags getDeviceClasses(int32_t deviceId) const override { + return ftl::Flags(mFdp->ConsumeIntegral()); + } + InputDeviceIdentifier getDeviceIdentifier(int32_t deviceId) const override { + return mIdentifier; + } + int32_t getDeviceControllerNumber(int32_t deviceId) const override { + return mFdp->ConsumeIntegral(); + } + std::optional getConfiguration(int32_t deviceId) const override { + return mFuzzConfig; + } + status_t getAbsoluteAxisInfo(int32_t deviceId, int axis, + RawAbsoluteAxisInfo* outAxisInfo) const override { + return mFdp->ConsumeIntegral(); + } + bool hasRelativeAxis(int32_t deviceId, int axis) const override { return mFdp->ConsumeBool(); } + bool hasInputProperty(int32_t deviceId, int property) const override { + return mFdp->ConsumeBool(); + } + bool hasMscEvent(int32_t deviceId, int mscEvent) const override { return mFdp->ConsumeBool(); } + status_t mapKey(int32_t deviceId, int32_t scanCode, int32_t usageCode, int32_t metaState, + int32_t* outKeycode, int32_t* outMetaState, uint32_t* outFlags) const override { + return mFdp->ConsumeIntegral(); + } + status_t mapAxis(int32_t deviceId, int32_t scanCode, AxisInfo* outAxisInfo) const override { + return mFdp->ConsumeIntegral(); + } + void setExcludedDevices(const std::vector& devices) override {} + std::vector getEvents(int timeoutMillis) override { + std::vector events; + const size_t count = mFdp->ConsumeIntegralInRange(0, kMaxSize); + for (size_t i = 0; i < count; ++i) { + int32_t type = mFdp->ConsumeBool() ? mFdp->PickValueInArray(kValidTypes) + : mFdp->ConsumeIntegral(); + int32_t code = mFdp->ConsumeBool() ? mFdp->PickValueInArray(kValidCodes) + : mFdp->ConsumeIntegral(); + events.push_back({ + .when = mFdp->ConsumeIntegral(), + .readTime = mFdp->ConsumeIntegral(), + .deviceId = mFdp->ConsumeIntegral(), + .type = type, + .code = code, + .value = mFdp->ConsumeIntegral(), + }); + } + return events; + } + std::vector getVideoFrames(int32_t deviceId) override { return mVideoFrames; } + + base::Result> mapSensor( + int32_t deviceId, int32_t absCode) const override { + return base::ResultError("Fuzzer", UNKNOWN_ERROR); + }; + // Raw batteries are sysfs power_supply nodes we found from the EventHub device sysfs node, + // containing the raw info of the sysfs node structure. + std::vector getRawBatteryIds(int32_t deviceId) const override { return {}; } + std::optional getRawBatteryInfo(int32_t deviceId, + int32_t BatteryId) const override { + return std::nullopt; + }; + + std::vector getRawLightIds(int32_t deviceId) const override { return {}; }; + std::optional getRawLightInfo(int32_t deviceId, int32_t lightId) const override { + return std::nullopt; + }; + std::optional getLightBrightness(int32_t deviceId, int32_t lightId) const override { + return std::nullopt; + }; + void setLightBrightness(int32_t deviceId, int32_t lightId, int32_t brightness) override{}; + std::optional> getLightIntensities( + int32_t deviceId, int32_t lightId) const override { + return std::nullopt; + }; + void setLightIntensities(int32_t deviceId, int32_t lightId, + std::unordered_map intensities) override{}; + + std::optional getRawLayoutInfo(int32_t deviceId) const override { + return std::nullopt; + }; + + int32_t getScanCodeState(int32_t deviceId, int32_t scanCode) const override { + return mFdp->ConsumeIntegral(); + } + int32_t getKeyCodeState(int32_t deviceId, int32_t keyCode) const override { + return mFdp->ConsumeIntegral(); + } + int32_t getSwitchState(int32_t deviceId, int32_t sw) const override { + return mFdp->ConsumeIntegral(); + } + void addKeyRemapping(int32_t deviceId, int32_t fromKeyCode, int32_t toKeyCode) const override {} + int32_t getKeyCodeForKeyLocation(int32_t deviceId, int32_t locationKeyCode) const override { + return mFdp->ConsumeIntegral(); + } + status_t getAbsoluteAxisValue(int32_t deviceId, int32_t axis, + int32_t* outValue) const override { + return mFdp->ConsumeIntegral(); + } + bool markSupportedKeyCodes(int32_t deviceId, const std::vector& keyCodes, + uint8_t* outFlags) const override { + return mFdp->ConsumeBool(); + } + bool hasScanCode(int32_t deviceId, int32_t scanCode) const override { + return mFdp->ConsumeBool(); + } + bool hasKeyCode(int32_t deviceId, int32_t keyCode) const override { + return mFdp->ConsumeBool(); + } + bool hasLed(int32_t deviceId, int32_t led) const override { return mFdp->ConsumeBool(); } + void setLedState(int32_t deviceId, int32_t led, bool on) override {} + void getVirtualKeyDefinitions( + int32_t deviceId, std::vector& outVirtualKeys) const override {} + const std::shared_ptr getKeyCharacterMap(int32_t deviceId) const override { + return nullptr; + } + bool setKeyboardLayoutOverlay(int32_t deviceId, std::shared_ptr map) override { + return mFdp->ConsumeBool(); + } + void vibrate(int32_t deviceId, const VibrationElement& effect) override {} + void cancelVibrate(int32_t deviceId) override {} + + std::vector getVibratorIds(int32_t deviceId) const override { return {}; }; + + /* Query battery level. */ + std::optional getBatteryCapacity(int32_t deviceId, int32_t batteryId) const override { + return std::nullopt; + }; + + /* Query battery status. */ + std::optional getBatteryStatus(int32_t deviceId, int32_t batteryId) const override { + return std::nullopt; + }; + + void requestReopenDevices() override {} + void wake() override {} + void dump(std::string& dump) const override {} + void monitor() const override {} + bool isDeviceEnabled(int32_t deviceId) const override { return mFdp->ConsumeBool(); } + status_t enableDevice(int32_t deviceId) override { return mFdp->ConsumeIntegral(); } + status_t disableDevice(int32_t deviceId) override { return mFdp->ConsumeIntegral(); } + 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 {} +}; + +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); + } + 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) override { + return nullptr; + } + std::string getDeviceAlias(const InputDeviceIdentifier& identifier) { + return mFdp->ConsumeRandomLengthString(32); + } + TouchAffineTransformation getTouchAffineTransformation(const std::string& inputDeviceDescriptor, + ui::Rotation surfaceRotation) override { + return mTransform; + } + void setTouchAffineTransformation(const TouchAffineTransformation t) { mTransform = t; } + void notifyStylusGestureStarted(int32_t, nsecs_t) {} +}; + +class FuzzInputListener : public virtual InputListenerInterface { +public: + void notifyInputDevicesChanged(const NotifyInputDevicesChangedArgs& args) override {} + void notifyConfigurationChanged(const NotifyConfigurationChangedArgs& args) override {} + void notifyKey(const NotifyKeyArgs& args) override {} + void notifyMotion(const NotifyMotionArgs& args) override {} + void notifySwitch(const NotifySwitchArgs& args) override {} + void notifySensor(const NotifySensorArgs& args) override{}; + void notifyVibratorState(const NotifyVibratorStateArgs& args) override{}; + void notifyDeviceReset(const NotifyDeviceResetArgs& args) override {} + void notifyPointerCaptureChanged(const NotifyPointerCaptureChangedArgs& args) override{}; +}; + +class FuzzInputReaderContext : public InputReaderContext { + std::shared_ptr mEventHub; + sp mPolicy; + std::shared_ptr mFdp; + +public: + FuzzInputReaderContext(std::shared_ptr eventHub, + const sp& policy, + InputListenerInterface& listener, + std::shared_ptr mFdp) + : mEventHub(eventHub), mPolicy(policy), mFdp(mFdp) {} + ~FuzzInputReaderContext() {} + void updateGlobalMetaState() override {} + int32_t getGlobalMetaState() { return mFdp->ConsumeIntegral(); } + void disableVirtualKeysUntil(nsecs_t time) override {} + 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 {} + std::list dispatchExternalStylusState(const StylusState& outState) override { + return {}; + } + InputReaderPolicyInterface* getPolicy() override { return mPolicy.get(); } + EventHubInterface* getEventHub() override { return mEventHub.get(); } + int32_t getNextId() override { return mFdp->ConsumeIntegral(); } + + void updateLedMetaState(int32_t metaState) override{}; + int32_t getLedMetaState() override { return mFdp->ConsumeIntegral(); }; + void notifyStylusGestureStarted(int32_t, nsecs_t) {} +}; + +} // namespace android diff --git a/services/inputflinger/tests/fuzzers/MultiTouchInputFuzzer.cpp b/services/inputflinger/tests/fuzzers/MultiTouchInputFuzzer.cpp new file mode 100644 index 0000000000000000000000000000000000000000..212462d700688c071bfd4d697cb6f173dceb086f --- /dev/null +++ b/services/inputflinger/tests/fuzzers/MultiTouchInputFuzzer.cpp @@ -0,0 +1,141 @@ +/* + * 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 + +namespace android { + +const int32_t kMaxKeycodes = 100; + +static void addProperty(FuzzContainer& fuzzer, std::shared_ptr fdp) { + // Pick a random property to set for the mapper to have set. + fdp->PickValueInArray>( + {[&]() -> void { fuzzer.addProperty("touch.deviceType", "touchScreen"); }, + [&]() -> void { + fuzzer.addProperty("touch.deviceType", fdp->ConsumeRandomLengthString(8).data()); + }, + [&]() -> void { + fuzzer.addProperty("touch.size.scale", fdp->ConsumeRandomLengthString(8).data()); + }, + [&]() -> void { + fuzzer.addProperty("touch.size.bias", fdp->ConsumeRandomLengthString(8).data()); + }, + [&]() -> void { + fuzzer.addProperty("touch.size.isSummed", + fdp->ConsumeRandomLengthString(8).data()); + }, + [&]() -> void { + fuzzer.addProperty("touch.size.calibration", + fdp->ConsumeRandomLengthString(8).data()); + }, + [&]() -> void { + fuzzer.addProperty("touch.pressure.scale", + fdp->ConsumeRandomLengthString(8).data()); + }, + [&]() -> void { + fuzzer.addProperty("touch.size.calibration", + fdp->ConsumeBool() ? "diameter" : "area"); + }, + [&]() -> void { + fuzzer.addProperty("touch.pressure.calibration", + fdp->ConsumeRandomLengthString(8).data()); + }})(); +} + +extern "C" int LLVMFuzzerTestOneInput(uint8_t* data, size_t size) { + std::shared_ptr fdp = + std::make_shared(data, size); + FuzzContainer fuzzer(fdp); + + auto policyConfig = fuzzer.getPolicyConfig(); + MultiTouchInputMapper& mapper = fuzzer.getMapper(policyConfig); + + // Loop through mapper operations until randomness is exhausted. + while (fdp->remaining_bytes() > 0) { + fdp->PickValueInArray>({ + [&]() -> void { addProperty(fuzzer, fdp); }, + [&]() -> void { + std::string dump; + mapper.dump(dump); + }, + [&]() -> void { + InputDeviceInfo info; + mapper.populateDeviceInfo(info); + }, + [&]() -> void { mapper.getSources(); }, + [&]() -> void { + std::list unused = + mapper.reconfigure(fdp->ConsumeIntegral(), policyConfig, + InputReaderConfiguration::Change( + fdp->ConsumeIntegral())); + }, + [&]() -> void { + std::list unused = mapper.reset(fdp->ConsumeIntegral()); + }, + [&]() -> void { + int32_t type = fdp->ConsumeBool() ? fdp->PickValueInArray(kValidTypes) + : fdp->ConsumeIntegral(); + int32_t code = fdp->ConsumeBool() ? fdp->PickValueInArray(kValidCodes) + : fdp->ConsumeIntegral(); + RawEvent rawEvent{fdp->ConsumeIntegral(), + fdp->ConsumeIntegral(), + fdp->ConsumeIntegral(), + type, + code, + fdp->ConsumeIntegral()}; + std::list unused = mapper.process(&rawEvent); + }, + [&]() -> void { + mapper.getKeyCodeState(fdp->ConsumeIntegral(), + fdp->ConsumeIntegral()); + }, + [&]() -> void { + mapper.getScanCodeState(fdp->ConsumeIntegral(), + fdp->ConsumeIntegral()); + }, + [&]() -> void { + std::vector keyCodes; + int32_t numBytes = fdp->ConsumeIntegralInRange(0, kMaxKeycodes); + for (int32_t i = 0; i < numBytes; ++i) { + keyCodes.push_back(fdp->ConsumeIntegral()); + } + mapper.markSupportedKeyCodes(fdp->ConsumeIntegral(), keyCodes, + nullptr); + }, + [&]() -> void { + std::list unused = + mapper.cancelTouch(fdp->ConsumeIntegral(), + fdp->ConsumeIntegral()); + }, + [&]() -> void { + std::list unused = + mapper.timeoutExpired(fdp->ConsumeIntegral()); + }, + [&]() -> void { + StylusState state{fdp->ConsumeIntegral(), + fdp->ConsumeFloatingPoint(), + fdp->ConsumeIntegral(), getFuzzedToolType(*fdp)}; + std::list unused = mapper.updateExternalStylusState(state); + }, + [&]() -> void { mapper.getAssociatedDisplayId(); }, + })(); + } + + return 0; +} + +} // namespace android diff --git a/services/inputflinger/tests/fuzzers/SwitchInputFuzzer.cpp b/services/inputflinger/tests/fuzzers/SwitchInputFuzzer.cpp new file mode 100644 index 0000000000000000000000000000000000000000..590207ea229996de17254532d07e88e53ca6defc --- /dev/null +++ b/services/inputflinger/tests/fuzzers/SwitchInputFuzzer.cpp @@ -0,0 +1,61 @@ +/* + * 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 + +namespace android { + +extern "C" int LLVMFuzzerTestOneInput(uint8_t* data, size_t size) { + std::shared_ptr fdp = + std::make_shared(data, size); + FuzzContainer fuzzer(fdp); + + auto policyConfig = fuzzer.getPolicyConfig(); + SwitchInputMapper& mapper = fuzzer.getMapper(policyConfig); + + // Loop through mapper operations until randomness is exhausted. + while (fdp->remaining_bytes() > 0) { + fdp->PickValueInArray>({ + [&]() -> void { + std::string dump; + mapper.dump(dump); + }, + [&]() -> void { mapper.getSources(); }, + [&]() -> void { + int32_t type = fdp->ConsumeBool() ? fdp->PickValueInArray(kValidTypes) + : fdp->ConsumeIntegral(); + int32_t code = fdp->ConsumeBool() ? fdp->PickValueInArray(kValidCodes) + : fdp->ConsumeIntegral(); + RawEvent rawEvent{fdp->ConsumeIntegral(), + fdp->ConsumeIntegral(), + fdp->ConsumeIntegral(), + type, + code, + fdp->ConsumeIntegral()}; + std::list unused = mapper.process(&rawEvent); + }, + [&]() -> void { + mapper.getSwitchState(fdp->ConsumeIntegral(), + fdp->ConsumeIntegral()); + }, + })(); + } + + return 0; +} + +} // namespace android diff --git a/services/inputflinger/tests/fuzzers/ThreadSafeFuzzedDataProvider.h b/services/inputflinger/tests/fuzzers/ThreadSafeFuzzedDataProvider.h new file mode 100644 index 0000000000000000000000000000000000000000..2f76f181cb0e1540b79e75ce8c7448f0b2d782c4 --- /dev/null +++ b/services/inputflinger/tests/fuzzers/ThreadSafeFuzzedDataProvider.h @@ -0,0 +1,136 @@ +/* + * 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 + +/** + * A thread-safe interface to the FuzzedDataProvider + */ +class ThreadSafeFuzzedDataProvider : FuzzedDataProvider { +private: + std::mutex mLock; + +public: + ThreadSafeFuzzedDataProvider(const uint8_t* data, size_t size) + : FuzzedDataProvider(data, size) {} + + template + std::vector ConsumeBytes(size_t num_bytes) { + std::scoped_lock _l(mLock); + return FuzzedDataProvider::ConsumeBytes(num_bytes); + } + + template + std::vector ConsumeBytesWithTerminator(size_t num_bytes, T terminator) { + std::scoped_lock _l(mLock); + return FuzzedDataProvider::ConsumeBytesWithTerminator(num_bytes, terminator); + } + + template + std::vector ConsumeRemainingBytes() { + std::scoped_lock _l(mLock); + return FuzzedDataProvider::ConsumeRemainingBytes(); + } + + std::string ConsumeBytesAsString(size_t num_bytes) { + std::scoped_lock _l(mLock); + return FuzzedDataProvider::ConsumeBytesAsString(num_bytes); + } + + std::string ConsumeRandomLengthString(size_t max_length) { + std::scoped_lock _l(mLock); + return FuzzedDataProvider::ConsumeRandomLengthString(max_length); + } + + std::string ConsumeRandomLengthString() { + std::scoped_lock _l(mLock); + return FuzzedDataProvider::ConsumeRandomLengthString(); + } + + std::string ConsumeRemainingBytesAsString() { + std::scoped_lock _l(mLock); + return FuzzedDataProvider::ConsumeRemainingBytesAsString(); + } + + template + T ConsumeIntegral() { + std::scoped_lock _l(mLock); + return FuzzedDataProvider::ConsumeIntegral(); + } + + template + T ConsumeIntegralInRange(T min, T max) { + std::scoped_lock _l(mLock); + return FuzzedDataProvider::ConsumeIntegralInRange(min, max); + } + + template + T ConsumeFloatingPoint() { + std::scoped_lock _l(mLock); + return FuzzedDataProvider::ConsumeFloatingPoint(); + } + + template + T ConsumeFloatingPointInRange(T min, T max) { + std::scoped_lock _l(mLock); + return FuzzedDataProvider::ConsumeFloatingPointInRange(min, max); + } + + template + T ConsumeProbability() { + std::scoped_lock _l(mLock); + return FuzzedDataProvider::ConsumeProbability(); + } + + bool ConsumeBool() { + std::scoped_lock _l(mLock); + return FuzzedDataProvider::ConsumeBool(); + } + + template + T ConsumeEnum() { + std::scoped_lock _l(mLock); + return FuzzedDataProvider::ConsumeEnum(); + } + + template + T PickValueInArray(const T (&array)[size]) { + std::scoped_lock _l(mLock); + return FuzzedDataProvider::PickValueInArray(array); + } + + template + T PickValueInArray(const std::array& array) { + std::scoped_lock _l(mLock); + return FuzzedDataProvider::PickValueInArray(array); + } + + template + T PickValueInArray(std::initializer_list list) { + std::scoped_lock _l(mLock); + return FuzzedDataProvider::PickValueInArray(list); + } + + size_t ConsumeData(void* destination, size_t num_bytes) { + std::scoped_lock _l(mLock); + return FuzzedDataProvider::ConsumeData(destination, num_bytes); + } + + size_t remaining_bytes() { + std::scoped_lock _l(mLock); + return FuzzedDataProvider::remaining_bytes(); + } +}; diff --git a/services/powermanager/Android.bp b/services/powermanager/Android.bp index 6fbba3f568eafce82ad502eebbd6a873450d74ac..b34e54fd6bb0c52b135168b749f664048c1bc3ab 100644 --- a/services/powermanager/Android.bp +++ b/services/powermanager/Android.bp @@ -38,7 +38,17 @@ cc_library_shared { "libutils", "android.hardware.power@1.0", "android.hardware.power@1.1", - "android.hardware.power-V3-cpp", + "android.hardware.power@1.2", + "android.hardware.power@1.3", + "android.hardware.power-V4-cpp", + ], + + export_shared_lib_headers: [ + "android.hardware.power@1.0", + "android.hardware.power@1.1", + "android.hardware.power@1.2", + "android.hardware.power@1.3", + "android.hardware.power-V4-cpp", ], cflags: [ diff --git a/services/powermanager/PowerHalController.cpp b/services/powermanager/PowerHalController.cpp index 8c225d5d02319ab59db713dde975f98386f4dd01..f89035fd1c4cca709f17855a228aeff13e8637ab 100644 --- a/services/powermanager/PowerHalController.cpp +++ b/services/powermanager/PowerHalController.cpp @@ -33,16 +33,20 @@ namespace power { // ------------------------------------------------------------------------------------------------- std::unique_ptr HalConnector::connect() { - sp halAidl = PowerHalLoader::loadAidl(); - if (halAidl) { + if (sp halAidl = PowerHalLoader::loadAidl()) { return std::make_unique(halAidl); } - sp halHidlV1_0 = PowerHalLoader::loadHidlV1_0(); - sp halHidlV1_1 = PowerHalLoader::loadHidlV1_1(); - if (halHidlV1_1) { - return std::make_unique(halHidlV1_0, halHidlV1_1); - } - if (halHidlV1_0) { + // If V1_0 isn't defined, none of them are + if (sp halHidlV1_0 = PowerHalLoader::loadHidlV1_0()) { + if (sp halHidlV1_3 = PowerHalLoader::loadHidlV1_3()) { + return std::make_unique(halHidlV1_3); + } + if (sp halHidlV1_2 = PowerHalLoader::loadHidlV1_2()) { + return std::make_unique(halHidlV1_2); + } + if (sp halHidlV1_1 = PowerHalLoader::loadHidlV1_1()) { + return std::make_unique(halHidlV1_1); + } return std::make_unique(halHidlV1_0); } return nullptr; diff --git a/services/powermanager/PowerHalLoader.cpp b/services/powermanager/PowerHalLoader.cpp index 1f1b43a607bca27295c0135aa1e79dbd2d316ac6..6bd40f8ff2543845c2cd866aed51aa867f46ba1b 100644 --- a/services/powermanager/PowerHalLoader.cpp +++ b/services/powermanager/PowerHalLoader.cpp @@ -17,6 +17,8 @@ #define LOG_TAG "PowerHalLoader" #include +#include +#include #include #include #include @@ -55,12 +57,16 @@ std::mutex PowerHalLoader::gHalMutex; sp PowerHalLoader::gHalAidl = nullptr; sp PowerHalLoader::gHalHidlV1_0 = nullptr; sp PowerHalLoader::gHalHidlV1_1 = nullptr; +sp PowerHalLoader::gHalHidlV1_2 = nullptr; +sp PowerHalLoader::gHalHidlV1_3 = nullptr; void PowerHalLoader::unloadAll() { std::lock_guard lock(gHalMutex); gHalAidl = nullptr; gHalHidlV1_0 = nullptr; gHalHidlV1_1 = nullptr; + gHalHidlV1_2 = nullptr; + gHalHidlV1_3 = nullptr; } sp PowerHalLoader::loadAidl() { @@ -82,6 +88,20 @@ sp PowerHalLoader::loadHidlV1_1() { return loadHal(gHalExists, gHalHidlV1_1, loadFn, "HIDL v1.1"); } +sp PowerHalLoader::loadHidlV1_2() { + std::lock_guard lock(gHalMutex); + static bool gHalExists = true; + static auto loadFn = []() { return V1_2::IPower::castFrom(loadHidlV1_0Locked()); }; + return loadHal(gHalExists, gHalHidlV1_2, loadFn, "HIDL v1.2"); +} + +sp PowerHalLoader::loadHidlV1_3() { + std::lock_guard lock(gHalMutex); + static bool gHalExists = true; + static auto loadFn = []() { return V1_3::IPower::castFrom(loadHidlV1_0Locked()); }; + return loadHal(gHalExists, gHalHidlV1_3, loadFn, "HIDL v1.3"); +} + sp PowerHalLoader::loadHidlV1_0Locked() { static bool gHalExists = true; static auto loadFn = []() { return V1_0::IPower::getService(); }; diff --git a/services/powermanager/PowerHalWrapper.cpp b/services/powermanager/PowerHalWrapper.cpp index d74bd23a8d7d09177e9c45c56d88bc8b58deb581..9e7adf8e5cc42f48de93b68cadf50fca425fd9b5 100644 --- a/services/powermanager/PowerHalWrapper.cpp +++ b/services/powermanager/PowerHalWrapper.cpp @@ -24,8 +24,6 @@ #include using namespace android::hardware::power; -namespace V1_0 = android::hardware::power::V1_0; -namespace V1_1 = android::hardware::power::V1_1; namespace Aidl = android::hardware::power; namespace android { @@ -108,7 +106,7 @@ HalResult EmptyHalWrapper::getHintSessionPreferredRate() { HalResult HidlHalWrapperV1_0::setBoost(Boost boost, int32_t durationMs) { if (boost == Boost::INTERACTION) { - return sendPowerHint(V1_0::PowerHint::INTERACTION, durationMs); + return sendPowerHint(V1_3::PowerHint::INTERACTION, durationMs); } else { ALOGV("Skipped setBoost %s because Power HAL AIDL not available", toString(boost).c_str()); return HalResult::unsupported(); @@ -119,13 +117,13 @@ HalResult HidlHalWrapperV1_0::setMode(Mode mode, bool enabled) { uint32_t data = enabled ? 1 : 0; switch (mode) { case Mode::LAUNCH: - return sendPowerHint(V1_0::PowerHint::LAUNCH, data); + return sendPowerHint(V1_3::PowerHint::LAUNCH, data); case Mode::LOW_POWER: - return sendPowerHint(V1_0::PowerHint::LOW_POWER, data); + return sendPowerHint(V1_3::PowerHint::LOW_POWER, data); case Mode::SUSTAINED_PERFORMANCE: - return sendPowerHint(V1_0::PowerHint::SUSTAINED_PERFORMANCE, data); + return sendPowerHint(V1_3::PowerHint::SUSTAINED_PERFORMANCE, data); case Mode::VR: - return sendPowerHint(V1_0::PowerHint::VR_MODE, data); + return sendPowerHint(V1_3::PowerHint::VR_MODE, data); case Mode::INTERACTIVE: return setInteractive(enabled); case Mode::DOUBLE_TAP_TO_WAKE: @@ -137,8 +135,8 @@ HalResult HidlHalWrapperV1_0::setMode(Mode mode, bool enabled) { } } -HalResult HidlHalWrapperV1_0::sendPowerHint(V1_0::PowerHint hintId, uint32_t data) { - auto ret = mHandleV1_0->powerHint(hintId, data); +HalResult HidlHalWrapperV1_0::sendPowerHint(V1_3::PowerHint hintId, uint32_t data) { + auto ret = mHandleV1_0->powerHint(static_cast(hintId), data); return HalResult::fromReturn(ret); } @@ -152,7 +150,7 @@ HalResult HidlHalWrapperV1_0::setFeature(V1_0::Feature feature, bool enabl return HalResult::fromReturn(ret); } -HalResult> HidlHalWrapperV1_0::createHintSession( +HalResult> HidlHalWrapperV1_0::createHintSession( int32_t, int32_t, const std::vector& threadIds, int64_t) { ALOGV("Skipped createHintSession(task num=%zu) because Power HAL not available", threadIds.size()); @@ -166,8 +164,59 @@ HalResult HidlHalWrapperV1_0::getHintSessionPreferredRate() { // ------------------------------------------------------------------------------------------------- -HalResult HidlHalWrapperV1_1::sendPowerHint(V1_0::PowerHint hintId, uint32_t data) { - auto ret = mHandleV1_1->powerHintAsync(hintId, data); +HalResult HidlHalWrapperV1_1::sendPowerHint(V1_3::PowerHint hintId, uint32_t data) { + auto handle = static_cast(mHandleV1_0.get()); + auto ret = handle->powerHintAsync(static_cast(hintId), data); + return HalResult::fromReturn(ret); +} + +// ------------------------------------------------------------------------------------------------- + +HalResult HidlHalWrapperV1_2::sendPowerHint(V1_3::PowerHint hintId, uint32_t data) { + auto handle = static_cast(mHandleV1_0.get()); + auto ret = handle->powerHintAsync_1_2(static_cast(hintId), data); + return HalResult::fromReturn(ret); +} + +HalResult HidlHalWrapperV1_2::setBoost(Boost boost, int32_t durationMs) { + switch (boost) { + case Boost::CAMERA_SHOT: + return sendPowerHint(V1_3::PowerHint::CAMERA_SHOT, durationMs); + case Boost::CAMERA_LAUNCH: + return sendPowerHint(V1_3::PowerHint::CAMERA_LAUNCH, durationMs); + default: + return HidlHalWrapperV1_1::setBoost(boost, durationMs); + } +} + +HalResult HidlHalWrapperV1_2::setMode(Mode mode, bool enabled) { + uint32_t data = enabled ? 1 : 0; + switch (mode) { + case Mode::CAMERA_STREAMING_SECURE: + case Mode::CAMERA_STREAMING_LOW: + case Mode::CAMERA_STREAMING_MID: + case Mode::CAMERA_STREAMING_HIGH: + return sendPowerHint(V1_3::PowerHint::CAMERA_STREAMING, data); + case Mode::AUDIO_STREAMING_LOW_LATENCY: + return sendPowerHint(V1_3::PowerHint::AUDIO_LOW_LATENCY, data); + default: + return HidlHalWrapperV1_1::setMode(mode, enabled); + } +} + +// ------------------------------------------------------------------------------------------------- + +HalResult HidlHalWrapperV1_3::setMode(Mode mode, bool enabled) { + uint32_t data = enabled ? 1 : 0; + if (mode == Mode::EXPENSIVE_RENDERING) { + return sendPowerHint(V1_3::PowerHint::EXPENSIVE_RENDERING, data); + } + return HidlHalWrapperV1_2::setMode(mode, enabled); +} + +HalResult HidlHalWrapperV1_3::sendPowerHint(V1_3::PowerHint hintId, uint32_t data) { + auto handle = static_cast(mHandleV1_0.get()); + auto ret = handle->powerHintAsync_1_3(hintId, data); return HalResult::fromReturn(ret); } diff --git a/services/powermanager/benchmarks/Android.bp b/services/powermanager/benchmarks/Android.bp index fcb012fc7522469f42599aa988b689ba34eb7418..4343aec227e4349f73eb1e9632874fe45bfa5559 100644 --- a/services/powermanager/benchmarks/Android.bp +++ b/services/powermanager/benchmarks/Android.bp @@ -38,7 +38,9 @@ cc_benchmark { "libutils", "android.hardware.power@1.0", "android.hardware.power@1.1", - "android.hardware.power-V3-cpp", + "android.hardware.power@1.2", + "android.hardware.power@1.3", + "android.hardware.power-V4-cpp", ], static_libs: [ "libtestUtil", diff --git a/services/powermanager/tests/Android.bp b/services/powermanager/tests/Android.bp index 962784cbae64967e996c83be3d9f9c924d4158d1..54dffcf8171c390a286809416bcce1b2f34f430d 100644 --- a/services/powermanager/tests/Android.bp +++ b/services/powermanager/tests/Android.bp @@ -31,6 +31,8 @@ cc_test { "PowerHalWrapperAidlTest.cpp", "PowerHalWrapperHidlV1_0Test.cpp", "PowerHalWrapperHidlV1_1Test.cpp", + "PowerHalWrapperHidlV1_2Test.cpp", + "PowerHalWrapperHidlV1_3Test.cpp", "WorkSourceTest.cpp", ], cflags: [ @@ -47,7 +49,9 @@ cc_test { "libutils", "android.hardware.power@1.0", "android.hardware.power@1.1", - "android.hardware.power-V3-cpp", + "android.hardware.power@1.2", + "android.hardware.power@1.3", + "android.hardware.power-V4-cpp", ], static_libs: [ "libgmock", diff --git a/services/powermanager/tests/PowerHalLoaderTest.cpp b/services/powermanager/tests/PowerHalLoaderTest.cpp index 058e1b5ca8a4db6737d581916b3f3139a3c31b43..e36deed042aeaf20b86c34b9fd1c4c0619056bad 100644 --- a/services/powermanager/tests/PowerHalLoaderTest.cpp +++ b/services/powermanager/tests/PowerHalLoaderTest.cpp @@ -26,6 +26,8 @@ using IPowerV1_0 = android::hardware::power::V1_0::IPower; using IPowerV1_1 = android::hardware::power::V1_1::IPower; +using IPowerV1_2 = android::hardware::power::V1_2::IPower; +using IPowerV1_3 = android::hardware::power::V1_3::IPower; using IPowerAidl = android::hardware::power::IPower; using namespace android; @@ -52,6 +54,16 @@ sp loadHal() { return PowerHalLoader::loadHidlV1_1(); } +template <> +sp loadHal() { + return PowerHalLoader::loadHidlV1_2(); +} + +template <> +sp loadHal() { + return PowerHalLoader::loadHidlV1_3(); +} + // ------------------------------------------------------------------------------------------------- template @@ -63,7 +75,7 @@ public: // ------------------------------------------------------------------------------------------------- -typedef ::testing::Types PowerHalTypes; +typedef ::testing::Types PowerHalTypes; TYPED_TEST_SUITE(PowerHalLoaderTest, PowerHalTypes); TYPED_TEST(PowerHalLoaderTest, TestLoadsOnlyOnce) { diff --git a/services/powermanager/tests/PowerHalWrapperHidlV1_0Test.cpp b/services/powermanager/tests/PowerHalWrapperHidlV1_0Test.cpp index b54762cf6d17b2a6491c9bcb9a2d1eaa7b36c0fc..0cd2e22e7e06b1914c4ea67eeb0ac0e87f4b9445 100644 --- a/services/powermanager/tests/PowerHalWrapperHidlV1_0Test.cpp +++ b/services/powermanager/tests/PowerHalWrapperHidlV1_0Test.cpp @@ -87,18 +87,28 @@ TEST_F(PowerHalWrapperHidlV1_0Test, TestSetBoostFailed) { } TEST_F(PowerHalWrapperHidlV1_0Test, TestSetBoostUnsupported) { + EXPECT_CALL(*mMockHal.get(), powerHint(_, _)).Times(0); + EXPECT_CALL(*mMockHal.get(), setInteractive(_)).Times(0); + EXPECT_CALL(*mMockHal.get(), setFeature(_, _)).Times(0); + auto result = mWrapper->setBoost(Boost::CAMERA_LAUNCH, 10); ASSERT_TRUE(result.isUnsupported()); + result = mWrapper->setBoost(Boost::ML_ACC, 10); + ASSERT_TRUE(result.isUnsupported()); + result = mWrapper->setBoost(Boost::DISPLAY_UPDATE_IMMINENT, 10); + ASSERT_TRUE(result.isUnsupported()); } TEST_F(PowerHalWrapperHidlV1_0Test, TestSetModeSuccessful) { { InSequence seq; - EXPECT_CALL(*mMockHal.get(), powerHint(Eq(PowerHint::LAUNCH), Eq(1))).Times(Exactly(1)); - EXPECT_CALL(*mMockHal.get(), powerHint(Eq(PowerHint::LOW_POWER), Eq(0))).Times(Exactly(1)); - EXPECT_CALL(*mMockHal.get(), powerHint(Eq(PowerHint::SUSTAINED_PERFORMANCE), Eq(1))) + EXPECT_CALL(*mMockHal.get(), powerHint(Eq(PowerHint::LAUNCH), Eq(true))).Times(Exactly(1)); + EXPECT_CALL(*mMockHal.get(), powerHint(Eq(PowerHint::LOW_POWER), Eq(false))) + .Times(Exactly(1)); + EXPECT_CALL(*mMockHal.get(), powerHint(Eq(PowerHint::SUSTAINED_PERFORMANCE), Eq(true))) + .Times(Exactly(1)); + EXPECT_CALL(*mMockHal.get(), powerHint(Eq(PowerHint::VR_MODE), Eq(false))) .Times(Exactly(1)); - EXPECT_CALL(*mMockHal.get(), powerHint(Eq(PowerHint::VR_MODE), Eq(0))).Times(Exactly(1)); EXPECT_CALL(*mMockHal.get(), setInteractive(Eq(true))).Times(Exactly(1)); EXPECT_CALL(*mMockHal.get(), setFeature(Eq(Feature::POWER_FEATURE_DOUBLE_TAP_TO_WAKE), Eq(false))) @@ -131,6 +141,16 @@ TEST_F(PowerHalWrapperHidlV1_0Test, TestSetModeFailed) { } TEST_F(PowerHalWrapperHidlV1_0Test, TestSetModeIgnored) { + EXPECT_CALL(*mMockHal.get(), powerHint(_, _)).Times(0); + EXPECT_CALL(*mMockHal.get(), setInteractive(_)).Times(0); + EXPECT_CALL(*mMockHal.get(), setFeature(_, _)).Times(0); + auto result = mWrapper->setMode(Mode::CAMERA_STREAMING_HIGH, true); ASSERT_TRUE(result.isUnsupported()); + result = mWrapper->setMode(Mode::EXPENSIVE_RENDERING, false); + ASSERT_TRUE(result.isUnsupported()); + result = mWrapper->setMode(Mode::FIXED_PERFORMANCE, true); + ASSERT_TRUE(result.isUnsupported()); + result = mWrapper->setMode(Mode::GAME_LOADING, false); + ASSERT_TRUE(result.isUnsupported()); } diff --git a/services/powermanager/tests/PowerHalWrapperHidlV1_1Test.cpp b/services/powermanager/tests/PowerHalWrapperHidlV1_1Test.cpp index d30e8d2c496be94ce7416619bae642a8fd84a53e..32f84e20b6dd29d987f689b511edcfef04523717 100644 --- a/services/powermanager/tests/PowerHalWrapperHidlV1_1Test.cpp +++ b/services/powermanager/tests/PowerHalWrapperHidlV1_1Test.cpp @@ -31,7 +31,6 @@ using android::hardware::power::Mode; using android::hardware::power::V1_0::Feature; using android::hardware::power::V1_0::PowerHint; using IPowerV1_1 = android::hardware::power::V1_1::IPower; -using IPowerV1_0 = android::hardware::power::V1_0::IPower; using namespace android; using namespace android::power; @@ -40,15 +39,6 @@ using namespace testing; // ------------------------------------------------------------------------------------------------- -class MockIPowerV1_0 : public IPowerV1_0 { -public: - MOCK_METHOD(hardware::Return, setInteractive, (bool interactive), (override)); - MOCK_METHOD(hardware::Return, powerHint, (PowerHint hint, int32_t data), (override)); - MOCK_METHOD(hardware::Return, setFeature, (Feature feature, bool activate), (override)); - MOCK_METHOD(hardware::Return, getPlatformLowPowerStats, - (getPlatformLowPowerStats_cb _hidl_cb), (override)); -}; - class MockIPowerV1_1 : public IPowerV1_1 { public: MOCK_METHOD(hardware::Return, setInteractive, (bool interactive), (override)); @@ -69,23 +59,22 @@ public: protected: std::unique_ptr mWrapper = nullptr; - sp> mMockHalV1_0 = nullptr; - sp> mMockHalV1_1 = nullptr; + sp> mMockHal = nullptr; }; // ------------------------------------------------------------------------------------------------- void PowerHalWrapperHidlV1_1Test::SetUp() { - mMockHalV1_0 = new StrictMock(); - mMockHalV1_1 = new StrictMock(); - mWrapper = std::make_unique(mMockHalV1_0, mMockHalV1_1); + mMockHal = new StrictMock(); + mWrapper = std::make_unique(mMockHal); ASSERT_NE(mWrapper, nullptr); + EXPECT_CALL(*mMockHal.get(), powerHint(_, _)).Times(0); } // ------------------------------------------------------------------------------------------------- TEST_F(PowerHalWrapperHidlV1_1Test, TestSetBoostSuccessful) { - EXPECT_CALL(*mMockHalV1_1.get(), powerHintAsync(Eq(PowerHint::INTERACTION), Eq(1000))) + EXPECT_CALL(*mMockHal.get(), powerHintAsync(Eq(PowerHint::INTERACTION), Eq(1000))) .Times(Exactly(1)); auto result = mWrapper->setBoost(Boost::INTERACTION, 1000); @@ -93,7 +82,7 @@ TEST_F(PowerHalWrapperHidlV1_1Test, TestSetBoostSuccessful) { } TEST_F(PowerHalWrapperHidlV1_1Test, TestSetBoostFailed) { - EXPECT_CALL(*mMockHalV1_1.get(), powerHintAsync(Eq(PowerHint::INTERACTION), Eq(1000))) + EXPECT_CALL(*mMockHal.get(), powerHintAsync(Eq(PowerHint::INTERACTION), Eq(1000))) .Times(Exactly(1)) .WillRepeatedly([](PowerHint, int32_t) { return hardware::Return(hardware::Status::fromExceptionCode(-1)); @@ -104,24 +93,31 @@ TEST_F(PowerHalWrapperHidlV1_1Test, TestSetBoostFailed) { } TEST_F(PowerHalWrapperHidlV1_1Test, TestSetBoostUnsupported) { + EXPECT_CALL(*mMockHal.get(), powerHintAsync(_, _)).Times(0); + EXPECT_CALL(*mMockHal.get(), setInteractive(_)).Times(0); + EXPECT_CALL(*mMockHal.get(), setFeature(_, _)).Times(0); + auto result = mWrapper->setBoost(Boost::CAMERA_LAUNCH, 10); ASSERT_TRUE(result.isUnsupported()); + result = mWrapper->setBoost(Boost::ML_ACC, 10); + ASSERT_TRUE(result.isUnsupported()); + result = mWrapper->setBoost(Boost::DISPLAY_UPDATE_IMMINENT, 10); + ASSERT_TRUE(result.isUnsupported()); } TEST_F(PowerHalWrapperHidlV1_1Test, TestSetMode) { { InSequence seq; - EXPECT_CALL(*mMockHalV1_1.get(), powerHintAsync(Eq(PowerHint::LAUNCH), Eq(1))) + EXPECT_CALL(*mMockHal.get(), powerHintAsync(Eq(PowerHint::LAUNCH), Eq(true))) .Times(Exactly(1)); - EXPECT_CALL(*mMockHalV1_1.get(), powerHintAsync(Eq(PowerHint::LOW_POWER), Eq(0))) + EXPECT_CALL(*mMockHal.get(), powerHintAsync(Eq(PowerHint::LOW_POWER), Eq(false))) .Times(Exactly(1)); - EXPECT_CALL(*mMockHalV1_1.get(), - powerHintAsync(Eq(PowerHint::SUSTAINED_PERFORMANCE), Eq(1))) + EXPECT_CALL(*mMockHal.get(), powerHintAsync(Eq(PowerHint::SUSTAINED_PERFORMANCE), Eq(true))) .Times(Exactly(1)); - EXPECT_CALL(*mMockHalV1_1.get(), powerHintAsync(Eq(PowerHint::VR_MODE), Eq(0))) + EXPECT_CALL(*mMockHal.get(), powerHintAsync(Eq(PowerHint::VR_MODE), Eq(false))) .Times(Exactly(1)); - EXPECT_CALL(*mMockHalV1_0.get(), setInteractive(Eq(true))).Times(Exactly(1)); - EXPECT_CALL(*mMockHalV1_0.get(), + EXPECT_CALL(*mMockHal.get(), setInteractive(Eq(true))).Times(Exactly(1)); + EXPECT_CALL(*mMockHal.get(), setFeature(Eq(Feature::POWER_FEATURE_DOUBLE_TAP_TO_WAKE), Eq(false))) .Times(Exactly(1)); } @@ -141,7 +137,7 @@ TEST_F(PowerHalWrapperHidlV1_1Test, TestSetMode) { } TEST_F(PowerHalWrapperHidlV1_1Test, TestSetModeFailed) { - EXPECT_CALL(*mMockHalV1_1.get(), powerHintAsync(Eq(PowerHint::LAUNCH), Eq(1))) + EXPECT_CALL(*mMockHal.get(), powerHintAsync(Eq(PowerHint::LAUNCH), Eq(true))) .Times(Exactly(1)) .WillRepeatedly([](PowerHint, int32_t) { return hardware::Return(hardware::Status::fromExceptionCode(-1)); @@ -152,6 +148,16 @@ TEST_F(PowerHalWrapperHidlV1_1Test, TestSetModeFailed) { } TEST_F(PowerHalWrapperHidlV1_1Test, TestSetModeIgnored) { + EXPECT_CALL(*mMockHal.get(), powerHintAsync(_, _)).Times(0); + EXPECT_CALL(*mMockHal.get(), setInteractive(_)).Times(0); + EXPECT_CALL(*mMockHal.get(), setFeature(_, _)).Times(0); + auto result = mWrapper->setMode(Mode::CAMERA_STREAMING_HIGH, true); ASSERT_TRUE(result.isUnsupported()); + result = mWrapper->setMode(Mode::EXPENSIVE_RENDERING, false); + ASSERT_TRUE(result.isUnsupported()); + result = mWrapper->setMode(Mode::FIXED_PERFORMANCE, true); + ASSERT_TRUE(result.isUnsupported()); + result = mWrapper->setMode(Mode::GAME_LOADING, false); + ASSERT_TRUE(result.isUnsupported()); } diff --git a/services/powermanager/tests/PowerHalWrapperHidlV1_2Test.cpp b/services/powermanager/tests/PowerHalWrapperHidlV1_2Test.cpp new file mode 100644 index 0000000000000000000000000000000000000000..cf48409f5f8c6537d8bd0797ce621bce056da0a4 --- /dev/null +++ b/services/powermanager/tests/PowerHalWrapperHidlV1_2Test.cpp @@ -0,0 +1,200 @@ +/* + * 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. + */ + +#define LOG_TAG "PowerHalWrapperHidlV1_2Test" + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +using android::hardware::power::Boost; +using android::hardware::power::Mode; +using android::hardware::power::V1_0::Feature; +using PowerHintV1_0 = android::hardware::power::V1_0::PowerHint; +using PowerHintV1_2 = android::hardware::power::V1_2::PowerHint; + +using IPowerV1_2 = android::hardware::power::V1_2::IPower; + +using namespace android; +using namespace android::power; +using namespace std::chrono_literals; +using namespace testing; + +// ------------------------------------------------------------------------------------------------- + +class MockIPowerV1_2 : public IPowerV1_2 { +public: + MOCK_METHOD(hardware::Return, setInteractive, (bool interactive), (override)); + MOCK_METHOD(hardware::Return, powerHint, (PowerHintV1_0 hint, int32_t data), (override)); + MOCK_METHOD(hardware::Return, setFeature, (Feature feature, bool activate), (override)); + MOCK_METHOD(hardware::Return, getPlatformLowPowerStats, + (getPlatformLowPowerStats_cb _hidl_cb), (override)); + MOCK_METHOD(hardware::Return, powerHintAsync, (PowerHintV1_0 hint, int32_t data), + (override)); + MOCK_METHOD(hardware::Return, powerHintAsync_1_2, (PowerHintV1_2 hint, int32_t data), + (override)); + MOCK_METHOD(hardware::Return, getSubsystemLowPowerStats, + (getSubsystemLowPowerStats_cb _hidl_cb), (override)); +}; + +// ------------------------------------------------------------------------------------------------- + +class PowerHalWrapperHidlV1_2Test : public Test { +public: + void SetUp() override; + +protected: + std::unique_ptr mWrapper = nullptr; + sp> mMockHal = nullptr; +}; + +// ------------------------------------------------------------------------------------------------- + +void PowerHalWrapperHidlV1_2Test::SetUp() { + mMockHal = new StrictMock(); + mWrapper = std::make_unique(mMockHal); + ASSERT_NE(mWrapper, nullptr); + EXPECT_CALL(*mMockHal.get(), powerHint(_, _)).Times(0); + EXPECT_CALL(*mMockHal.get(), powerHintAsync(_, _)).Times(0); +} + +// ------------------------------------------------------------------------------------------------- + +TEST_F(PowerHalWrapperHidlV1_2Test, TestSetBoostSuccessful) { + { + InSequence seq; + EXPECT_CALL(*mMockHal.get(), powerHintAsync_1_2(Eq(PowerHintV1_2::INTERACTION), Eq(1000))) + .Times(Exactly(1)); + EXPECT_CALL(*mMockHal.get(), powerHintAsync_1_2(Eq(PowerHintV1_2::CAMERA_SHOT), Eq(500))) + .Times(Exactly(1)); + EXPECT_CALL(*mMockHal.get(), powerHintAsync_1_2(Eq(PowerHintV1_2::CAMERA_LAUNCH), Eq(300))) + .Times(Exactly(1)); + } + + auto result = mWrapper->setBoost(Boost::INTERACTION, 1000); + ASSERT_TRUE(result.isOk()); + result = mWrapper->setBoost(Boost::CAMERA_SHOT, 500); + ASSERT_TRUE(result.isOk()); + result = mWrapper->setBoost(Boost::CAMERA_LAUNCH, 300); + ASSERT_TRUE(result.isOk()); +} + +TEST_F(PowerHalWrapperHidlV1_2Test, TestSetBoostFailed) { + EXPECT_CALL(*mMockHal.get(), powerHintAsync_1_2(Eq(PowerHintV1_2::INTERACTION), Eq(1000))) + .Times(Exactly(1)) + .WillRepeatedly([](PowerHintV1_2, int32_t) { + return hardware::Return(hardware::Status::fromExceptionCode(-1)); + }); + + auto result = mWrapper->setBoost(Boost::INTERACTION, 1000); + ASSERT_TRUE(result.isFailed()); +} + +TEST_F(PowerHalWrapperHidlV1_2Test, TestSetBoostUnsupported) { + EXPECT_CALL(*mMockHal.get(), powerHintAsync_1_2(_, _)).Times(0); + EXPECT_CALL(*mMockHal.get(), setInteractive(_)).Times(0); + EXPECT_CALL(*mMockHal.get(), setFeature(_, _)).Times(0); + + auto result = mWrapper->setBoost(Boost::ML_ACC, 10); + ASSERT_TRUE(result.isUnsupported()); + result = mWrapper->setBoost(Boost::DISPLAY_UPDATE_IMMINENT, 10); + ASSERT_TRUE(result.isUnsupported()); + result = mWrapper->setBoost(Boost::AUDIO_LAUNCH, 10); + ASSERT_TRUE(result.isUnsupported()); +} + +TEST_F(PowerHalWrapperHidlV1_2Test, TestSetMode) { + { + InSequence seq; + EXPECT_CALL(*mMockHal.get(), powerHintAsync_1_2(Eq(PowerHintV1_2::LAUNCH), Eq(true))) + .Times(Exactly(1)); + EXPECT_CALL(*mMockHal.get(), powerHintAsync_1_2(Eq(PowerHintV1_2::LOW_POWER), Eq(false))) + .Times(Exactly(1)); + EXPECT_CALL(*mMockHal.get(), + powerHintAsync_1_2(Eq(PowerHintV1_2::SUSTAINED_PERFORMANCE), Eq(true))) + .Times(Exactly(1)); + EXPECT_CALL(*mMockHal.get(), powerHintAsync_1_2(Eq(PowerHintV1_2::VR_MODE), Eq(false))) + .Times(Exactly(1)); + EXPECT_CALL(*mMockHal.get(), setInteractive(Eq(true))).Times(Exactly(true)); + EXPECT_CALL(*mMockHal.get(), + setFeature(Eq(Feature::POWER_FEATURE_DOUBLE_TAP_TO_WAKE), Eq(false))) + .Times(Exactly(1)); + EXPECT_CALL(*mMockHal.get(), + powerHintAsync_1_2(Eq(PowerHintV1_2::CAMERA_STREAMING), Eq(true))) + .Times(Exactly(2)); + EXPECT_CALL(*mMockHal.get(), + powerHintAsync_1_2(Eq(PowerHintV1_2::CAMERA_STREAMING), Eq(false))) + .Times(Exactly(2)); + EXPECT_CALL(*mMockHal.get(), + powerHintAsync_1_2(Eq(PowerHintV1_2::AUDIO_LOW_LATENCY), Eq(true))) + .Times(Exactly(1)); + } + + auto result = mWrapper->setMode(Mode::LAUNCH, true); + ASSERT_TRUE(result.isOk()); + result = mWrapper->setMode(Mode::LOW_POWER, false); + ASSERT_TRUE(result.isOk()); + result = mWrapper->setMode(Mode::SUSTAINED_PERFORMANCE, true); + ASSERT_TRUE(result.isOk()); + result = mWrapper->setMode(Mode::VR, false); + ASSERT_TRUE(result.isOk()); + result = mWrapper->setMode(Mode::INTERACTIVE, true); + ASSERT_TRUE(result.isOk()); + result = mWrapper->setMode(Mode::DOUBLE_TAP_TO_WAKE, false); + ASSERT_TRUE(result.isOk()); + result = mWrapper->setMode(Mode::CAMERA_STREAMING_SECURE, true); + ASSERT_TRUE(result.isOk()); + result = mWrapper->setMode(Mode::CAMERA_STREAMING_LOW, true); + ASSERT_TRUE(result.isOk()); + result = mWrapper->setMode(Mode::CAMERA_STREAMING_MID, false); + ASSERT_TRUE(result.isOk()); + result = mWrapper->setMode(Mode::CAMERA_STREAMING_HIGH, false); + ASSERT_TRUE(result.isOk()); + result = mWrapper->setMode(Mode::AUDIO_STREAMING_LOW_LATENCY, true); + ASSERT_TRUE(result.isOk()); +} + +TEST_F(PowerHalWrapperHidlV1_2Test, TestSetModeFailed) { + EXPECT_CALL(*mMockHal.get(), powerHintAsync_1_2(Eq(PowerHintV1_2::LAUNCH), Eq(1))) + .Times(Exactly(1)) + .WillRepeatedly([](PowerHintV1_2, int32_t) { + return hardware::Return(hardware::Status::fromExceptionCode(-1)); + }); + + auto result = mWrapper->setMode(Mode::LAUNCH, 1); + ASSERT_TRUE(result.isFailed()); +} + +TEST_F(PowerHalWrapperHidlV1_2Test, TestSetModeIgnored) { + EXPECT_CALL(*mMockHal.get(), powerHintAsync_1_2(_, _)).Times(0); + EXPECT_CALL(*mMockHal.get(), setInteractive(_)).Times(0); + EXPECT_CALL(*mMockHal.get(), setFeature(_, _)).Times(0); + + auto result = mWrapper->setMode(Mode::EXPENSIVE_RENDERING, true); + ASSERT_TRUE(result.isUnsupported()); + result = mWrapper->setMode(Mode::DISPLAY_INACTIVE, true); + ASSERT_TRUE(result.isUnsupported()); + result = mWrapper->setMode(Mode::FIXED_PERFORMANCE, true); + ASSERT_TRUE(result.isUnsupported()); + result = mWrapper->setMode(Mode::GAME_LOADING, false); + ASSERT_TRUE(result.isUnsupported()); +} diff --git a/services/powermanager/tests/PowerHalWrapperHidlV1_3Test.cpp b/services/powermanager/tests/PowerHalWrapperHidlV1_3Test.cpp new file mode 100644 index 0000000000000000000000000000000000000000..2c48537edcaa4cfbc4d111ddbeb03e48a76324ad --- /dev/null +++ b/services/powermanager/tests/PowerHalWrapperHidlV1_3Test.cpp @@ -0,0 +1,210 @@ +/* + * 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. + */ + +#define LOG_TAG "PowerHalWrapperHidlV1_3Test" + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +using android::hardware::power::Boost; +using android::hardware::power::Mode; +using android::hardware::power::V1_0::Feature; +using PowerHintV1_0 = android::hardware::power::V1_0::PowerHint; +using PowerHintV1_2 = android::hardware::power::V1_2::PowerHint; +using PowerHintV1_3 = android::hardware::power::V1_3::PowerHint; + +using IPowerV1_3 = android::hardware::power::V1_3::IPower; + +using namespace android; +using namespace android::power; +using namespace std::chrono_literals; +using namespace testing; + +// ------------------------------------------------------------------------------------------------- + +class MockIPowerV1_3 : public IPowerV1_3 { +public: + MOCK_METHOD(hardware::Return, setInteractive, (bool interactive), (override)); + MOCK_METHOD(hardware::Return, powerHint, (PowerHintV1_0 hint, int32_t data), (override)); + MOCK_METHOD(hardware::Return, setFeature, (Feature feature, bool activate), (override)); + MOCK_METHOD(hardware::Return, getPlatformLowPowerStats, + (getPlatformLowPowerStats_cb _hidl_cb), (override)); + MOCK_METHOD(hardware::Return, powerHintAsync, (PowerHintV1_0 hint, int32_t data), + (override)); + MOCK_METHOD(hardware::Return, powerHintAsync_1_2, (PowerHintV1_2 hint, int32_t data), + (override)); + MOCK_METHOD(hardware::Return, powerHintAsync_1_3, (PowerHintV1_3 hint, int32_t data), + (override)); + + MOCK_METHOD(hardware::Return, getSubsystemLowPowerStats, + (getSubsystemLowPowerStats_cb _hidl_cb), (override)); +}; + +// ------------------------------------------------------------------------------------------------- + +class PowerHalWrapperHidlV1_3Test : public Test { +public: + void SetUp() override; + +protected: + std::unique_ptr mWrapper = nullptr; + sp> mMockHal = nullptr; +}; + +// ------------------------------------------------------------------------------------------------- + +void PowerHalWrapperHidlV1_3Test::SetUp() { + mMockHal = new StrictMock(); + mWrapper = std::make_unique(mMockHal); + ASSERT_NE(mWrapper, nullptr); + EXPECT_CALL(*mMockHal.get(), powerHint(_, _)).Times(0); + EXPECT_CALL(*mMockHal.get(), powerHintAsync(_, _)).Times(0); + EXPECT_CALL(*mMockHal.get(), powerHintAsync_1_2(_, _)).Times(0); +} + +// ------------------------------------------------------------------------------------------------- + +TEST_F(PowerHalWrapperHidlV1_3Test, TestSetBoostSuccessful) { + { + InSequence seq; + EXPECT_CALL(*mMockHal.get(), powerHintAsync_1_3(Eq(PowerHintV1_3::INTERACTION), Eq(1000))) + .Times(Exactly(1)); + EXPECT_CALL(*mMockHal.get(), powerHintAsync_1_3(Eq(PowerHintV1_3::CAMERA_SHOT), Eq(500))) + .Times(Exactly(1)); + EXPECT_CALL(*mMockHal.get(), powerHintAsync_1_3(Eq(PowerHintV1_3::CAMERA_LAUNCH), Eq(300))) + .Times(Exactly(1)); + } + + auto result = mWrapper->setBoost(Boost::INTERACTION, 1000); + ASSERT_TRUE(result.isOk()); + result = mWrapper->setBoost(Boost::CAMERA_SHOT, 500); + ASSERT_TRUE(result.isOk()); + result = mWrapper->setBoost(Boost::CAMERA_LAUNCH, 300); + ASSERT_TRUE(result.isOk()); +} + +TEST_F(PowerHalWrapperHidlV1_3Test, TestSetBoostFailed) { + EXPECT_CALL(*mMockHal.get(), powerHintAsync_1_3(Eq(PowerHintV1_3::INTERACTION), Eq(1000))) + .Times(Exactly(1)) + .WillRepeatedly([](PowerHintV1_3, int32_t) { + return hardware::Return(hardware::Status::fromExceptionCode(-1)); + }); + + auto result = mWrapper->setBoost(Boost::INTERACTION, 1000); + ASSERT_TRUE(result.isFailed()); +} + +TEST_F(PowerHalWrapperHidlV1_3Test, TestSetBoostUnsupported) { + EXPECT_CALL(*mMockHal.get(), powerHintAsync_1_3(_, _)).Times(0); + EXPECT_CALL(*mMockHal.get(), setInteractive(_)).Times(0); + EXPECT_CALL(*mMockHal.get(), setFeature(_, _)).Times(0); + + auto result = mWrapper->setBoost(Boost::ML_ACC, 10); + ASSERT_TRUE(result.isUnsupported()); + result = mWrapper->setBoost(Boost::DISPLAY_UPDATE_IMMINENT, 10); + ASSERT_TRUE(result.isUnsupported()); + result = mWrapper->setBoost(Boost::AUDIO_LAUNCH, 10); + ASSERT_TRUE(result.isUnsupported()); +} + +TEST_F(PowerHalWrapperHidlV1_3Test, TestSetMode) { + { + InSequence seq; + EXPECT_CALL(*mMockHal.get(), powerHintAsync_1_3(Eq(PowerHintV1_3::LAUNCH), Eq(true))) + .Times(Exactly(1)); + EXPECT_CALL(*mMockHal.get(), powerHintAsync_1_3(Eq(PowerHintV1_3::LOW_POWER), Eq(false))) + .Times(Exactly(1)); + EXPECT_CALL(*mMockHal.get(), + powerHintAsync_1_3(Eq(PowerHintV1_3::SUSTAINED_PERFORMANCE), Eq(true))) + .Times(Exactly(1)); + EXPECT_CALL(*mMockHal.get(), powerHintAsync_1_3(Eq(PowerHintV1_3::VR_MODE), Eq(false))) + .Times(Exactly(1)); + EXPECT_CALL(*mMockHal.get(), setInteractive(Eq(true))).Times(Exactly(true)); + EXPECT_CALL(*mMockHal.get(), + setFeature(Eq(Feature::POWER_FEATURE_DOUBLE_TAP_TO_WAKE), Eq(false))) + .Times(Exactly(1)); + EXPECT_CALL(*mMockHal.get(), + powerHintAsync_1_3(Eq(PowerHintV1_3::CAMERA_STREAMING), Eq(true))) + .Times(Exactly(2)); + EXPECT_CALL(*mMockHal.get(), + powerHintAsync_1_3(Eq(PowerHintV1_3::CAMERA_STREAMING), Eq(false))) + .Times(Exactly(2)); + EXPECT_CALL(*mMockHal.get(), + powerHintAsync_1_3(Eq(PowerHintV1_3::AUDIO_LOW_LATENCY), Eq(true))) + .Times(Exactly(1)); + EXPECT_CALL(*mMockHal.get(), + powerHintAsync_1_3(Eq(PowerHintV1_3::EXPENSIVE_RENDERING), Eq(false))) + .Times(Exactly(1)); + } + + auto result = mWrapper->setMode(Mode::LAUNCH, true); + ASSERT_TRUE(result.isOk()); + result = mWrapper->setMode(Mode::LOW_POWER, false); + ASSERT_TRUE(result.isOk()); + result = mWrapper->setMode(Mode::SUSTAINED_PERFORMANCE, true); + ASSERT_TRUE(result.isOk()); + result = mWrapper->setMode(Mode::VR, false); + ASSERT_TRUE(result.isOk()); + result = mWrapper->setMode(Mode::INTERACTIVE, true); + ASSERT_TRUE(result.isOk()); + result = mWrapper->setMode(Mode::DOUBLE_TAP_TO_WAKE, false); + ASSERT_TRUE(result.isOk()); + result = mWrapper->setMode(Mode::CAMERA_STREAMING_SECURE, true); + ASSERT_TRUE(result.isOk()); + result = mWrapper->setMode(Mode::CAMERA_STREAMING_LOW, true); + ASSERT_TRUE(result.isOk()); + result = mWrapper->setMode(Mode::CAMERA_STREAMING_MID, false); + ASSERT_TRUE(result.isOk()); + result = mWrapper->setMode(Mode::CAMERA_STREAMING_HIGH, false); + ASSERT_TRUE(result.isOk()); + result = mWrapper->setMode(Mode::AUDIO_STREAMING_LOW_LATENCY, true); + ASSERT_TRUE(result.isOk()); + result = mWrapper->setMode(Mode::EXPENSIVE_RENDERING, false); + ASSERT_TRUE(result.isOk()); +} + +TEST_F(PowerHalWrapperHidlV1_3Test, TestSetModeFailed) { + EXPECT_CALL(*mMockHal.get(), powerHintAsync_1_3(Eq(PowerHintV1_3::LAUNCH), Eq(1))) + .Times(Exactly(1)) + .WillRepeatedly([](PowerHintV1_3, int32_t) { + return hardware::Return(hardware::Status::fromExceptionCode(-1)); + }); + + auto result = mWrapper->setMode(Mode::LAUNCH, 1); + ASSERT_TRUE(result.isFailed()); +} + +TEST_F(PowerHalWrapperHidlV1_3Test, TestSetModeIgnored) { + EXPECT_CALL(*mMockHal.get(), powerHintAsync_1_3(_, _)).Times(0); + EXPECT_CALL(*mMockHal.get(), setInteractive(_)).Times(0); + EXPECT_CALL(*mMockHal.get(), setFeature(_, _)).Times(0); + + auto result = mWrapper->setMode(Mode::GAME, true); + ASSERT_TRUE(result.isUnsupported()); + result = mWrapper->setMode(Mode::DISPLAY_INACTIVE, true); + ASSERT_TRUE(result.isUnsupported()); + result = mWrapper->setMode(Mode::FIXED_PERFORMANCE, true); + ASSERT_TRUE(result.isUnsupported()); + result = mWrapper->setMode(Mode::GAME_LOADING, false); + ASSERT_TRUE(result.isUnsupported()); +} diff --git a/services/sensorservice/Android.bp b/services/sensorservice/Android.bp index a2042d6451feca6f251a0c44e175de847528b451..11c56a8c5684bcc7c28100401adad4da4947cdeb 100644 --- a/services/sensorservice/Android.bp +++ b/services/sensorservice/Android.bp @@ -7,7 +7,7 @@ package { default_applicable_licenses: ["frameworks_native_license"], } -cc_library_shared { +cc_library { name: "libsensorservice", srcs: [ @@ -76,7 +76,7 @@ cc_library_shared { "libaidlcommonsupport", "android.hardware.sensors@1.0-convert", "android.hardware.sensors-V1-convert", - "android.hardware.sensors-V1-ndk", + "android.hardware.sensors-V2-ndk", ], generated_headers: ["framework-cppstream-protos"], @@ -91,6 +91,12 @@ cc_library_shared { afdo: true, } +cc_library_headers { + name: "libsensorservice_headers", + export_include_dirs: ["."], + visibility: ["//frameworks/native/services/sensorservice/fuzzer"], +} + cc_binary { name: "sensorservice", diff --git a/services/sensorservice/BatteryService.cpp b/services/sensorservice/BatteryService.cpp index 94de55c124f38de7f4aaa7eedc53201e124a567b..0ea548c95821b76549c2ebc744118fc2b7543ca6 100644 --- a/services/sensorservice/BatteryService.cpp +++ b/services/sensorservice/BatteryService.cpp @@ -30,8 +30,8 @@ namespace android { // --------------------------------------------------------------------------- -BatteryService::BatteryService() : mBatteryStatService(nullptr) { -} +BatteryService::BatteryService() + : mBatteryStatService(nullptr), mLastWakeupSensorEventReportedMs(0) {} bool BatteryService::addSensor(uid_t uid, int handle) { Mutex::Autolock _l(mActivationsLock); @@ -74,6 +74,14 @@ void BatteryService::disableSensorImpl(uid_t uid, int handle) { } } +void BatteryService::noteWakeupSensorEventImpl(int64_t elapsedNanos, uid_t uid, int handle) { + if (checkService()) { + int64_t identity = IPCThreadState::self()->clearCallingIdentity(); + mBatteryStatService->noteWakeupSensorEvent(elapsedNanos, uid, handle); + IPCThreadState::self()->restoreCallingIdentity(identity); + } +} + bool BatteryService::checkService() { if (mBatteryStatService == nullptr) { const sp sm(defaultServiceManager()); diff --git a/services/sensorservice/BatteryService.h b/services/sensorservice/BatteryService.h index 13fc58aadb98560a131848139a51513bc7018342..60ef03f6859daee9b53ab1fdd09914f63fb9bf3e 100644 --- a/services/sensorservice/BatteryService.h +++ b/services/sensorservice/BatteryService.h @@ -19,11 +19,14 @@ #include #include +#include +#include namespace android { // --------------------------------------------------------------------------- class BatteryService : public Singleton { + static constexpr int64_t WAKEUP_SENSOR_EVENT_DEBOUNCE_MS = 1000; friend class Singleton; sp mBatteryStatService; @@ -32,6 +35,7 @@ class BatteryService : public Singleton { void enableSensorImpl(uid_t uid, int handle); void disableSensorImpl(uid_t uid, int handle); + void noteWakeupSensorEventImpl(int64_t elapsedNanos, uid_t uid, int handle); struct Info { uid_t uid; @@ -44,6 +48,7 @@ class BatteryService : public Singleton { } }; + int64_t mLastWakeupSensorEventReportedMs; Mutex mActivationsLock; SortedVector mActivations; bool addSensor(uid_t uid, int handle); @@ -57,6 +62,15 @@ public: static void disableSensor(uid_t uid, int handle) { BatteryService::getInstance().disableSensorImpl(uid, handle); } + static void noteWakeupSensorEvent(int64_t elapsed, uid_t uid, int handle) { + BatteryService& instance = BatteryService::getInstance(); + const int64_t nowElapsedMs = elapsedRealtime(); + if (nowElapsedMs >= (instance.mLastWakeupSensorEventReportedMs + + WAKEUP_SENSOR_EVENT_DEBOUNCE_MS)) { + instance.noteWakeupSensorEventImpl(elapsed, uid, handle); + instance.mLastWakeupSensorEventReportedMs = nowElapsedMs; + } + } }; // --------------------------------------------------------------------------- diff --git a/services/sensorservice/SensorDevice.cpp b/services/sensorservice/SensorDevice.cpp index 184fa7614d24696cd94fe0b9b3969bb69dd92bb6..3155b4ceaf0ff7decceb9325e7473d1a109eda1a 100644 --- a/services/sensorservice/SensorDevice.cpp +++ b/services/sensorservice/SensorDevice.cpp @@ -39,7 +39,6 @@ #include using namespace android::hardware::sensors; -using android::hardware::Return; using android::util::ProtoOutputStream; namespace android { diff --git a/services/sensorservice/SensorDirectConnection.cpp b/services/sensorservice/SensorDirectConnection.cpp index 7548742e28c3d1614752b25228b904e91082d2cc..555b80aed3c975ee6a477c05b38bf8825aa4a105 100644 --- a/services/sensorservice/SensorDirectConnection.cpp +++ b/services/sensorservice/SensorDirectConnection.cpp @@ -14,11 +14,11 @@ * limitations under the License. */ -#include "SensorDevice.h" #include "SensorDirectConnection.h" #include #include #include +#include "SensorDevice.h" #define UNUSED(x) (void)(x) @@ -28,10 +28,10 @@ using util::ProtoOutputStream; SensorService::SensorDirectConnection::SensorDirectConnection(const sp& service, uid_t uid, const sensors_direct_mem_t *mem, int32_t halChannelHandle, - const String16& opPackageName) + const String16& opPackageName, int deviceId) : mService(service), mUid(uid), mMem(*mem), mHalChannelHandle(halChannelHandle), - mOpPackageName(opPackageName), mDestroyed(false) { + mOpPackageName(opPackageName), mDeviceId(deviceId), mDestroyed(false) { mUserId = multiuser_get_user_id(mUid); ALOGD_IF(DEBUG_CONNECTIONS, "Created SensorDirectConnection"); } @@ -51,7 +51,7 @@ void SensorService::SensorDirectConnection::destroy() { stopAll(); mService->cleanupConnection(this); if (mMem.handle != nullptr) { - native_handle_close(mMem.handle); + native_handle_close_with_tag(mMem.handle); native_handle_delete(const_cast(mMem.handle)); } mDestroyed = true; @@ -151,7 +151,7 @@ int32_t SensorService::SensorDirectConnection::configureChannel(int handle, int return PERMISSION_DENIED; } - sp si = mService->getSensorInterfaceFromHandle(handle); + std::shared_ptr si = mService->getSensorInterfaceFromHandle(handle); if (si == nullptr) { return NAME_NOT_FOUND; } @@ -180,8 +180,7 @@ int32_t SensorService::SensorDirectConnection::configureChannel(int handle, int }; Mutex::Autolock _l(mConnectionLock); - SensorDevice& dev(SensorDevice::getInstance()); - int ret = dev.configureDirectChannel(handle, getHalChannelHandle(), &config); + int ret = configure(handle, &config); if (rateLevel == SENSOR_DIRECT_RATE_STOP) { if (ret == NO_ERROR) { @@ -224,11 +223,10 @@ void SensorService::SensorDirectConnection::capRates() { std::unordered_map& existingConnections = (!temporarilyStopped) ? mActivated : mActivatedBackup; - SensorDevice& dev(SensorDevice::getInstance()); for (auto &i : existingConnections) { int handle = i.first; int rateLevel = i.second; - sp si = mService->getSensorInterfaceFromHandle(handle); + std::shared_ptr si = mService->getSensorInterfaceFromHandle(handle); if (si != nullptr) { const Sensor& s = si->getSensor(); if (mService->isSensorInCappedSet(s.getType()) && @@ -239,8 +237,8 @@ void SensorService::SensorDirectConnection::capRates() { // Only reconfigure the channel if it's ongoing if (!temporarilyStopped) { // Stopping before reconfiguring is the well-tested path in CTS - dev.configureDirectChannel(handle, getHalChannelHandle(), &stopConfig); - dev.configureDirectChannel(handle, getHalChannelHandle(), &capConfig); + configure(handle, &stopConfig); + configure(handle, &capConfig); } } } @@ -258,7 +256,6 @@ void SensorService::SensorDirectConnection::uncapRates() { const struct sensors_direct_cfg_t stopConfig = { .rate_level = SENSOR_DIRECT_RATE_STOP }; - SensorDevice& dev(SensorDevice::getInstance()); for (auto &i : mMicRateBackup) { int handle = i.first; int rateLevel = i.second; @@ -273,13 +270,23 @@ void SensorService::SensorDirectConnection::uncapRates() { // Only reconfigure the channel if it's ongoing if (!temporarilyStopped) { // Stopping before reconfiguring is the well-tested path in CTS - dev.configureDirectChannel(handle, getHalChannelHandle(), &stopConfig); - dev.configureDirectChannel(handle, getHalChannelHandle(), &config); + configure(handle, &stopConfig); + configure(handle, &config); } } mMicRateBackup.clear(); } +int SensorService::SensorDirectConnection::configure( + int handle, const sensors_direct_cfg_t* config) { + if (mDeviceId == RuntimeSensor::DEFAULT_DEVICE_ID) { + SensorDevice& dev(SensorDevice::getInstance()); + return dev.configureDirectChannel(handle, getHalChannelHandle(), config); + } else { + return mService->configureRuntimeSensorDirectChannel(handle, this, config); + } +} + void SensorService::SensorDirectConnection::stopAll(bool backupRecord) { Mutex::Autolock _l(mConnectionLock); stopAllLocked(backupRecord); @@ -290,9 +297,8 @@ void SensorService::SensorDirectConnection::stopAllLocked(bool backupRecord) { .rate_level = SENSOR_DIRECT_RATE_STOP }; - SensorDevice& dev(SensorDevice::getInstance()); for (auto &i : mActivated) { - dev.configureDirectChannel(i.first, getHalChannelHandle(), &config); + configure(i.first, &config); } if (backupRecord && mActivatedBackup.empty()) { @@ -306,8 +312,6 @@ void SensorService::SensorDirectConnection::recoverAll() { if (!mActivatedBackup.empty()) { stopAllLocked(false); - SensorDevice& dev(SensorDevice::getInstance()); - // recover list of report from backup ALOG_ASSERT(mActivated.empty(), "mActivated must be empty if mActivatedBackup was non-empty"); @@ -319,7 +323,7 @@ void SensorService::SensorDirectConnection::recoverAll() { struct sensors_direct_cfg_t config = { .rate_level = i.second }; - dev.configureDirectChannel(i.first, getHalChannelHandle(), &config); + configure(i.first, &config); } } } diff --git a/services/sensorservice/SensorDirectConnection.h b/services/sensorservice/SensorDirectConnection.h index d39a073f98069a117f0196e2faf40596abbf32ec..bfaf8113301d06131f175c015bf5182e37b59d1c 100644 --- a/services/sensorservice/SensorDirectConnection.h +++ b/services/sensorservice/SensorDirectConnection.h @@ -39,7 +39,7 @@ class SensorService::SensorDirectConnection: public BnSensorEventConnection { public: SensorDirectConnection(const sp& service, uid_t uid, const sensors_direct_mem_t *mem, int32_t halChannelHandle, - const String16& opPackageName); + const String16& opPackageName, int deviceId); void dump(String8& result) const; void dump(util::ProtoOutputStream* proto) const; uid_t getUid() const { return mUid; } @@ -53,6 +53,7 @@ public: void onSensorAccessChanged(bool hasAccess); void onMicSensorAccessChanged(bool isMicToggleOn); userid_t getUserId() const { return mUserId; } + int getDeviceId() const { return mDeviceId; } protected: virtual ~SensorDirectConnection(); @@ -68,6 +69,9 @@ protected: private: bool hasSensorAccess() const; + // Sends the configuration to the relevant sensor device. + int configure(int handle, const sensors_direct_cfg_t* config); + // Stops all active sensor direct report requests. // // If backupRecord is true, stopped requests can be recovered @@ -95,6 +99,7 @@ private: const sensors_direct_mem_t mMem; const int32_t mHalChannelHandle; const String16 mOpPackageName; + const int mDeviceId; mutable Mutex mConnectionLock; std::unordered_map mActivated; diff --git a/services/sensorservice/SensorEventConnection.cpp b/services/sensorservice/SensorEventConnection.cpp index aa9637719f08c9a354c66e70ca9da92c29658ef8..dc8657759c039b8b7839008e136093ab3cf866c7 100644 --- a/services/sensorservice/SensorEventConnection.cpp +++ b/services/sensorservice/SensorEventConnection.cpp @@ -23,6 +23,7 @@ #include #include "vec.h" +#include "BatteryService.h" #include "SensorEventConnection.h" #include "SensorDevice.h" @@ -55,14 +56,13 @@ SensorService::SensorEventConnection::SensorEventConnection( SensorService::SensorEventConnection::~SensorEventConnection() { ALOGD_IF(DEBUG_CONNECTIONS, "~SensorEventConnection(%p)", this); destroy(); - mService->cleanupConnection(this); - if (mEventCache != nullptr) { - delete[] mEventCache; - } + delete[] mEventCache; } void SensorService::SensorEventConnection::destroy() { - mDestroyed = true; + if (!mDestroyed.exchange(true)) { + mService->cleanupConnection(this); + } } void SensorService::SensorEventConnection::onFirstRef() { @@ -82,7 +82,7 @@ void SensorService::SensorEventConnection::resetWakeLockRefCount() { void SensorService::SensorEventConnection::dump(String8& result) { Mutex::Autolock _l(mConnectionLock); result.appendFormat("\tOperating Mode: "); - if (!mService->isWhiteListedPackage(getPackageName())) { + if (!mService->isAllowListedPackage(getPackageName())) { result.append("RESTRICTED\n"); } else if (mDataInjectionMode) { result.append("DATA_INJECTION\n"); @@ -124,7 +124,7 @@ void SensorService::SensorEventConnection::dump(util::ProtoOutputStream* proto) using namespace service::SensorEventConnectionProto; Mutex::Autolock _l(mConnectionLock); - if (!mService->isWhiteListedPackage(getPackageName())) { + if (!mService->isAllowListedPackage(getPackageName())) { proto->write(OPERATING_MODE, OP_MODE_RESTRICTED); } else if (mDataInjectionMode) { proto->write(OPERATING_MODE, OP_MODE_DATA_INJECTION); @@ -160,7 +160,7 @@ void SensorService::SensorEventConnection::dump(util::ProtoOutputStream* proto) bool SensorService::SensorEventConnection::addSensor(int32_t handle) { Mutex::Autolock _l(mConnectionLock); - sp si = mService->getSensorInterfaceFromHandle(handle); + std::shared_ptr si = mService->getSensorInterfaceFromHandle(handle); if (si == nullptr || !mService->canAccessSensor(si->getSensor(), "Add to SensorEventConnection: ", mOpPackageName) || @@ -202,7 +202,7 @@ bool SensorService::SensorEventConnection::hasOneShotSensors() const { Mutex::Autolock _l(mConnectionLock); for (auto &it : mSensorInfo) { const int handle = it.first; - sp si = mService->getSensorInterfaceFromHandle(handle); + std::shared_ptr si = mService->getSensorInterfaceFromHandle(handle); if (si != nullptr && si->getSensor().getReportingMode() == AREPORTING_MODE_ONE_SHOT) { return true; } @@ -245,7 +245,7 @@ void SensorService::SensorEventConnection::updateLooperRegistrationLocked( if (mDataInjectionMode) looper_flags |= ALOOPER_EVENT_INPUT; for (auto& it : mSensorInfo) { const int handle = it.first; - sp si = mService->getSensorInterfaceFromHandle(handle); + std::shared_ptr si = mService->getSensorInterfaceFromHandle(handle); if (si != nullptr && si->getSensor().isWakeUpSensor()) { looper_flags |= ALOOPER_EVENT_INPUT; } @@ -392,6 +392,8 @@ status_t SensorService::SensorEventConnection::sendEvents( if (hasSensorAccess()) { index_wake_up_event = findWakeUpSensorEventLocked(scratch, count); if (index_wake_up_event >= 0) { + BatteryService::noteWakeupSensorEvent(scratch[index_wake_up_event].timestamp, + mUid, scratch[index_wake_up_event].sensor); scratch[index_wake_up_event].flags |= WAKE_UP_SENSOR_EVENT_NEEDS_ACK; ++mWakeLockRefCount; #if DEBUG_CONNECTIONS @@ -555,7 +557,7 @@ void SensorService::SensorEventConnection::sendPendingFlushEventsLocked() { // flush complete events to be sent. for (auto& it : mSensorInfo) { const int handle = it.first; - sp si = mService->getSensorInterfaceFromHandle(handle); + std::shared_ptr si = mService->getSensorInterfaceFromHandle(handle); if (si == nullptr) { continue; } @@ -689,7 +691,7 @@ status_t SensorService::SensorEventConnection::enableDisable( if (enabled) { nsecs_t requestedSamplingPeriodNs = samplingPeriodNs; bool isSensorCapped = false; - sp si = mService->getSensorInterfaceFromHandle(handle); + std::shared_ptr si = mService->getSensorInterfaceFromHandle(handle); if (si != nullptr) { const Sensor& s = si->getSensor(); if (mService->isSensorInCappedSet(s.getType())) { @@ -729,7 +731,7 @@ status_t SensorService::SensorEventConnection::setEventRate(int handle, nsecs_t nsecs_t requestedSamplingPeriodNs = samplingPeriodNs; bool isSensorCapped = false; - sp si = mService->getSensorInterfaceFromHandle(handle); + std::shared_ptr si = mService->getSensorInterfaceFromHandle(handle); if (si != nullptr) { const Sensor& s = si->getSensor(); if (mService->isSensorInCappedSet(s.getType())) { @@ -850,9 +852,14 @@ int SensorService::SensorEventConnection::handleEvent(int fd, int events, void* // Unregister call backs. return 0; } + if (!mService->isAllowListedPackage(mPackageName)) { + ALOGE("App not allowed to inject data, dropping event" + "package=%s uid=%d", mPackageName.c_str(), mUid); + return 0; + } sensors_event_t sensor_event; memcpy(&sensor_event, buf, sizeof(sensors_event_t)); - sp si = + std::shared_ptr si = mService->getSensorInterfaceFromHandle(sensor_event.sensor); if (si == nullptr) { return 1; @@ -903,7 +910,7 @@ int SensorService::SensorEventConnection::computeMaxCacheSizeLocked() const { size_t fifoWakeUpSensors = 0; size_t fifoNonWakeUpSensors = 0; for (auto& it : mSensorInfo) { - sp si = mService->getSensorInterfaceFromHandle(it.first); + std::shared_ptr si = mService->getSensorInterfaceFromHandle(it.first); if (si == nullptr) { continue; } diff --git a/services/sensorservice/SensorInterface.cpp b/services/sensorservice/SensorInterface.cpp index 46f00e83293d9013876db551339a6a1741bb74b6..e9c83359630bd8b6990fafcd16aecbfa38f6c358 100644 --- a/services/sensorservice/SensorInterface.cpp +++ b/services/sensorservice/SensorInterface.cpp @@ -87,6 +87,45 @@ VirtualSensor::VirtualSensor() : // --------------------------------------------------------------------------- +RuntimeSensor::RuntimeSensor(const sensor_t& sensor, sp callback) + : BaseSensor(sensor), mCallback(std::move(callback)) { +} + +status_t RuntimeSensor::activate(void*, bool enabled) { + if (enabled != mEnabled) { + mEnabled = enabled; + return mCallback->onConfigurationChanged(mSensor.getHandle(), mEnabled, mSamplingPeriodNs, + mBatchReportLatencyNs); + } + return OK; +} + +status_t RuntimeSensor::batch(void*, int, int, int64_t samplingPeriodNs, + int64_t maxBatchReportLatencyNs) { + if (mSamplingPeriodNs != samplingPeriodNs || mBatchReportLatencyNs != maxBatchReportLatencyNs) { + mSamplingPeriodNs = samplingPeriodNs; + mBatchReportLatencyNs = maxBatchReportLatencyNs; + if (mEnabled) { + return mCallback->onConfigurationChanged(mSensor.getHandle(), mEnabled, + mSamplingPeriodNs, mBatchReportLatencyNs); + } + } + return OK; +} + +status_t RuntimeSensor::setDelay(void*, int, int64_t ns) { + if (mSamplingPeriodNs != ns) { + mSamplingPeriodNs = ns; + if (mEnabled) { + return mCallback->onConfigurationChanged(mSensor.getHandle(), mEnabled, + mSamplingPeriodNs, mBatchReportLatencyNs); + } + } + return OK; +} + +// --------------------------------------------------------------------------- + ProximitySensor::ProximitySensor(const sensor_t& sensor, SensorService& service) : HardwareSensor(sensor), mSensorService(service) { } diff --git a/services/sensorservice/SensorInterface.h b/services/sensorservice/SensorInterface.h index 57043592c5a11bd157caa0905c6d2baf0800d666..c446d61f89493eef7e48b442cdbebb4f0fc443ea 100644 --- a/services/sensorservice/SensorInterface.h +++ b/services/sensorservice/SensorInterface.h @@ -104,6 +104,32 @@ protected: // --------------------------------------------------------------------------- +class RuntimeSensor : public BaseSensor { +public: + static constexpr int DEFAULT_DEVICE_ID = 0; + + class SensorCallback : public virtual RefBase { + public: + virtual status_t onConfigurationChanged(int handle, bool enabled, int64_t samplingPeriodNs, + int64_t batchReportLatencyNs) = 0; + }; + RuntimeSensor(const sensor_t& sensor, sp callback); + virtual status_t activate(void* ident, bool enabled) override; + virtual status_t batch(void* ident, int handle, int flags, int64_t samplingPeriodNs, + int64_t maxBatchReportLatencyNs) override; + virtual status_t setDelay(void* ident, int handle, int64_t ns) override; + virtual bool process(sensors_event_t*, const sensors_event_t&) { return false; } + virtual bool isVirtual() const override { return false; } + +private: + bool mEnabled = false; + int64_t mSamplingPeriodNs = 0; + int64_t mBatchReportLatencyNs = 0; + sp mCallback; +}; + +// --------------------------------------------------------------------------- + class ProximitySensor : public HardwareSensor { public: explicit ProximitySensor(const sensor_t& sensor, SensorService& service); diff --git a/services/sensorservice/SensorList.cpp b/services/sensorservice/SensorList.cpp index 02f22c5c4aa2bb36805e1de292efe8254aa9cde6..7e302060c5780583544590abb696be24f0cd1ffa 100644 --- a/services/sensorservice/SensorList.cpp +++ b/services/sensorservice/SensorList.cpp @@ -28,13 +28,13 @@ namespace SensorServiceUtil { const Sensor SensorList::mNonSensor = Sensor("unknown"); -bool SensorList::add( - int handle, SensorInterface* si, bool isForDebug, bool isVirtual) { +bool SensorList::add(int handle, std::shared_ptr si, bool isForDebug, + bool isVirtual, int deviceId) { std::lock_guard lk(mLock); if (handle == si->getSensor().getHandle() && mUsedHandle.insert(handle).second) { // will succeed as the mUsedHandle does not have this handle - mHandleMap.emplace(handle, Entry(si, isForDebug, isVirtual)); + mHandleMap.emplace(handle, Entry(std::move(si), isForDebug, isVirtual, deviceId)); return true; } // handle exist already or handle mismatch @@ -63,12 +63,12 @@ String8 SensorList::getStringType(int handle) const { mNonSensor.getStringType()); } -sp SensorList::getInterface(int handle) const { - return getOne>( - handle, [] (const Entry& e) -> sp {return e.si;}, nullptr); +std::shared_ptr SensorList::getInterface(int handle) const { + return getOne>( + handle, [] (const Entry& e) -> std::shared_ptr {return e.si;}, + nullptr); } - bool SensorList::isNewHandle(int handle) const { std::lock_guard lk(mLock); return mUsedHandle.find(handle) == mUsedHandle.end(); @@ -79,7 +79,8 @@ const Vector SensorList::getUserSensors() const { Vector sensors; forEachEntry( [&sensors] (const Entry& e) -> bool { - if (!e.isForDebug && !e.si->getSensor().isDynamicSensor()) { + if (!e.isForDebug && !e.si->getSensor().isDynamicSensor() + && e.deviceId == RuntimeSensor::DEFAULT_DEVICE_ID) { sensors.add(e.si->getSensor()); } return true; @@ -92,7 +93,8 @@ const Vector SensorList::getUserDebugSensors() const { Vector sensors; forEachEntry( [&sensors] (const Entry& e) -> bool { - if (!e.si->getSensor().isDynamicSensor()) { + if (!e.si->getSensor().isDynamicSensor() + && e.deviceId == RuntimeSensor::DEFAULT_DEVICE_ID) { sensors.add(e.si->getSensor()); } return true; @@ -105,7 +107,8 @@ const Vector SensorList::getDynamicSensors() const { Vector sensors; forEachEntry( [&sensors] (const Entry& e) -> bool { - if (!e.isForDebug && e.si->getSensor().isDynamicSensor()) { + if (!e.isForDebug && e.si->getSensor().isDynamicSensor() + && e.deviceId == RuntimeSensor::DEFAULT_DEVICE_ID) { sensors.add(e.si->getSensor()); } return true; @@ -118,7 +121,20 @@ const Vector SensorList::getVirtualSensors() const { Vector sensors; forEachEntry( [&sensors] (const Entry& e) -> bool { - if (e.isVirtual) { + if (e.isVirtual && e.deviceId == RuntimeSensor::DEFAULT_DEVICE_ID) { + sensors.add(e.si->getSensor()); + } + return true; + }); + return sensors; +} + +const Vector SensorList::getRuntimeSensors(int deviceId) const { + // lock in forEachEntry + Vector sensors; + forEachEntry( + [&sensors, deviceId] (const Entry& e) -> bool { + if (!e.isForDebug && e.deviceId == deviceId) { sensors.add(e.si->getSensor()); } return true; diff --git a/services/sensorservice/SensorList.h b/services/sensorservice/SensorList.h index 049ae7c855e739394ba831db6c797e62b83a7a5e..ad5b21f37016ce6e591e7319d02aba6a0de98ee6 100644 --- a/services/sensorservice/SensorList.h +++ b/services/sensorservice/SensorList.h @@ -37,20 +37,18 @@ namespace SensorServiceUtil { class SensorList : public Dumpable { public: struct Entry { - sp si; + std::shared_ptr si; const bool isForDebug; const bool isVirtual; - Entry(SensorInterface* si_, bool debug_, bool virtual_) : - si(si_), isForDebug(debug_), isVirtual(virtual_) { + const int deviceId; + Entry(std::shared_ptr si_, bool debug_, bool virtual_, int deviceId_) : + si(std::move(si_)), isForDebug(debug_), isVirtual(virtual_), deviceId(deviceId_) { } }; - // After SensorInterface * is added into SensorList, it can be assumed that SensorList own the - // object it pointed to and the object should not be released elsewhere. - bool add(int handle, SensorInterface* si, bool isForDebug = false, bool isVirtual = false); - - // After a handle is removed, the object that SensorInterface * pointing to may get deleted if - // no more sp<> of the same object exist. + // SensorList owns the SensorInterface pointer. + bool add(int handle, std::shared_ptr si, bool isForDebug = false, + bool isVirtual = false, int deviceId = RuntimeSensor::DEFAULT_DEVICE_ID); bool remove(int handle); inline bool hasAnySensor() const { return mHandleMap.size() > 0;} @@ -60,11 +58,12 @@ public: const Vector getUserDebugSensors() const; const Vector getDynamicSensors() const; const Vector getVirtualSensors() const; + const Vector getRuntimeSensors(int deviceId) const; String8 getName(int handle) const; String8 getStringType(int handle) const; - sp getInterface(int handle) const; + std::shared_ptr getInterface(int handle) const; bool isNewHandle(int handle) const; // Iterate through Sensor in sensor list and perform operation f on each Sensor object. diff --git a/services/sensorservice/SensorService.cpp b/services/sensorservice/SensorService.cpp index 3d613596f55382748043edae69f9a9d147c0376e..193847bb9f0507a03900b20ab36caa1ad6051471 100644 --- a/services/sensorservice/SensorService.cpp +++ b/services/sensorservice/SensorService.cpp @@ -13,10 +13,10 @@ * See the License for the specific language governing permissions and * limitations under the License. */ +#include #include #include #include -#include #include #include #include @@ -25,6 +25,7 @@ #include #include #include +#include #include #include #include @@ -51,6 +52,7 @@ #include "SensorEventConnection.h" #include "SensorRecord.h" #include "SensorRegistrationInfo.h" +#include "SensorServiceUtils.h" #include #include @@ -63,6 +65,7 @@ #include #include +#include #include @@ -102,6 +105,32 @@ static const String16 sDumpPermission("android.permission.DUMP"); static const String16 sLocationHardwarePermission("android.permission.LOCATION_HARDWARE"); static const String16 sManageSensorsPermission("android.permission.MANAGE_SENSORS"); +namespace { + +int32_t nextRuntimeSensorHandle() { + using ::aidl::android::hardware::sensors::ISensors; + static int32_t nextHandle = ISensors::RUNTIME_SENSORS_HANDLE_BASE; + if (nextHandle == ISensors::RUNTIME_SENSORS_HANDLE_END) { + return -1; + } + return nextHandle++; +} + +class RuntimeSensorCallbackProxy : public RuntimeSensor::SensorCallback { + public: + RuntimeSensorCallbackProxy(sp callback) + : mCallback(std::move(callback)) {} + status_t onConfigurationChanged(int handle, bool enabled, int64_t samplingPeriodNs, + int64_t batchReportLatencyNs) override { + return mCallback->onConfigurationChanged(handle, enabled, samplingPeriodNs, + batchReportLatencyNs); + } + private: + sp mCallback; +}; + +} // namespace + static bool isAutomotive() { sp serviceManager = defaultServiceManager(); if (serviceManager.get() == nullptr) { @@ -137,6 +166,77 @@ SensorService::SensorService() mMicSensorPrivacyPolicy = new MicrophonePrivacyPolicy(this); } +int SensorService::registerRuntimeSensor( + const sensor_t& sensor, int deviceId, sp callback) { + int handle = 0; + while (handle == 0 || !mSensors.isNewHandle(handle)) { + handle = nextRuntimeSensorHandle(); + if (handle < 0) { + // Ran out of the dedicated range for runtime sensors. + return handle; + } + } + + ALOGI("Registering runtime sensor handle 0x%x, type %d, name %s", + handle, sensor.type, sensor.name); + + sp runtimeSensorCallback( + new RuntimeSensorCallbackProxy(callback)); + sensor_t runtimeSensor = sensor; + // force the handle to be consistent + runtimeSensor.handle = handle; + auto si = std::make_shared(runtimeSensor, std::move(runtimeSensorCallback)); + + Mutex::Autolock _l(mLock); + if (!registerSensor(std::move(si), /* isDebug= */ false, /* isVirtual= */ false, deviceId)) { + // The registration was unsuccessful. + return mSensors.getNonSensor().getHandle(); + } + + if (mRuntimeSensorCallbacks.find(deviceId) == mRuntimeSensorCallbacks.end()) { + mRuntimeSensorCallbacks.emplace(deviceId, callback); + } + return handle; +} + +status_t SensorService::unregisterRuntimeSensor(int handle) { + ALOGI("Unregistering runtime sensor handle 0x%x disconnected", handle); + int deviceId = getDeviceIdFromHandle(handle); + { + Mutex::Autolock _l(mLock); + if (!unregisterDynamicSensorLocked(handle)) { + ALOGE("Runtime sensor release error."); + return UNKNOWN_ERROR; + } + } + + ConnectionSafeAutolock connLock = mConnectionHolder.lock(mLock); + for (const sp& connection : connLock.getActiveConnections()) { + connection->removeSensor(handle); + } + + // If this was the last sensor for this device, remove its callback. + bool deviceHasSensors = false; + mSensors.forEachEntry( + [&deviceId, &deviceHasSensors] (const SensorServiceUtil::SensorList::Entry& e) -> bool { + if (e.deviceId == deviceId) { + deviceHasSensors = true; + return false; // stop iterating + } + return true; + }); + if (!deviceHasSensors) { + mRuntimeSensorCallbacks.erase(deviceId); + } + return OK; +} + +status_t SensorService::sendRuntimeSensorEvent(const sensors_event_t& event) { + Mutex::Autolock _l(mLock); + mRuntimeSensorEventQueue.push(event); + return OK; +} + bool SensorService::initializeHmacKey() { int fd = open(SENSOR_SERVICE_HMAC_KEY_FILE, O_RDONLY|O_CLOEXEC); if (fd != -1) { @@ -238,11 +338,13 @@ void SensorService::onFirstRef() { } if (useThisSensor) { if (list[i].type == SENSOR_TYPE_PROXIMITY) { - SensorInterface* s = new ProximitySensor(list[i], *this); - registerSensor(s); - mProxSensorHandles.push_back(s->getSensor().getHandle()); + auto s = std::make_shared(list[i], *this); + const int handle = s->getSensor().getHandle(); + if (registerSensor(std::move(s))) { + mProxSensorHandles.push_back(handle); + } } else { - registerSensor(new HardwareSensor(list[i])); + registerSensor(std::make_shared(list[i])); } } } @@ -257,56 +359,63 @@ void SensorService::onFirstRef() { // available in the HAL bool needRotationVector = (virtualSensorsNeeds & (1<(), + /* isDebug= */ !needRotationVector); + registerVirtualSensor(std::make_shared(), + /* isDebug= */ !needRotationVector); // virtual debugging sensors are not for user - registerSensor( new CorrectedGyroSensor(list, count), true, true); - registerSensor( new GyroDriftSensor(), true, true); + registerVirtualSensor(std::make_shared(list, count), + /* isDebug= */ true); + registerVirtualSensor(std::make_shared(), /* isDebug= */ true); } if (hasAccel && (hasGyro || hasGyroUncalibrated)) { bool needGravitySensor = (virtualSensorsNeeds & (1<(list, count), + /* isDebug= */ !needGravitySensor); bool needLinearAcceleration = (virtualSensorsNeeds & (1<(list, count), + /* isDebug= */ !needLinearAcceleration); bool needGameRotationVector = (virtualSensorsNeeds & (1<(), + /* isDebug= */ !needGameRotationVector); } if (hasAccel && hasMag) { bool needGeoMagRotationVector = (virtualSensorsNeeds & (1<(), + /* isDebug= */ !needGeoMagRotationVector); } if (isAutomotive()) { if (hasAccel) { - registerSensor(new LimitedAxesImuSensor(list, count, SENSOR_TYPE_ACCELEROMETER), - /*isDebug=*/false, /*isVirtual=*/true); + registerVirtualSensor( + std::make_shared( + list, count, SENSOR_TYPE_ACCELEROMETER)); } if (hasGyro) { - registerSensor(new LimitedAxesImuSensor(list, count, SENSOR_TYPE_GYROSCOPE), - /*isDebug=*/false, /*isVirtual=*/true); + registerVirtualSensor( + std::make_shared( + list, count, SENSOR_TYPE_GYROSCOPE)); } if (hasAccelUncalibrated) { - registerSensor(new LimitedAxesImuSensor(list, count, - SENSOR_TYPE_ACCELEROMETER_UNCALIBRATED), - /*isDebug=*/false, /*isVirtual=*/true); + registerVirtualSensor( + std::make_shared( + list, count, SENSOR_TYPE_ACCELEROMETER_UNCALIBRATED)); } if (hasGyroUncalibrated) { - registerSensor(new LimitedAxesImuSensor(list, count, - SENSOR_TYPE_GYROSCOPE_UNCALIBRATED), - /*isDebug=*/false, /*isVirtual=*/true); + registerVirtualSensor( + std::make_shared( + list, count, SENSOR_TYPE_GYROSCOPE_UNCALIBRATED)); } } @@ -407,19 +516,21 @@ bool SensorService::hasSensorAccessLocked(uid_t uid, const String16& opPackageNa && isUidActive(uid) && !isOperationRestrictedLocked(opPackageName); } -const Sensor& SensorService::registerSensor(SensorInterface* s, bool isDebug, bool isVirtual) { - int handle = s->getSensor().getHandle(); - int type = s->getSensor().getType(); - if (mSensors.add(handle, s, isDebug, isVirtual)){ +bool SensorService::registerSensor(std::shared_ptr s, bool isDebug, bool isVirtual, + int deviceId) { + const int handle = s->getSensor().getHandle(); + const int type = s->getSensor().getType(); + if (mSensors.add(handle, std::move(s), isDebug, isVirtual, deviceId)) { mRecentEvent.emplace(handle, new SensorServiceUtil::RecentEventLogger(type)); - return s->getSensor(); + return true; } else { - return mSensors.getNonSensor(); + LOG_FATAL("Failed to register sensor with handle %d", handle); + return false; } } -const Sensor& SensorService::registerDynamicSensorLocked(SensorInterface* s, bool isDebug) { - return registerSensor(s, isDebug); +bool SensorService::registerDynamicSensorLocked(std::shared_ptr s, bool isDebug) { + return registerSensor(std::move(s), isDebug); } bool SensorService::unregisterDynamicSensorLocked(int handle) { @@ -433,8 +544,8 @@ bool SensorService::unregisterDynamicSensorLocked(int handle) { return ret; } -const Sensor& SensorService::registerVirtualSensor(SensorInterface* s, bool isDebug) { - return registerSensor(s, isDebug, true); +bool SensorService::registerVirtualSensor(std::shared_ptr s, bool isDebug) { + return registerSensor(std::move(s), isDebug, true); } SensorService::~SensorService() { @@ -457,55 +568,22 @@ status_t SensorService::dump(int fd, const Vector& args) { if (args.size() > 2) { return INVALID_OPERATION; } - ConnectionSafeAutolock connLock = mConnectionHolder.lock(mLock); - SensorDevice& dev(SensorDevice::getInstance()); - if (args.size() == 2 && args[0] == String16("restrict")) { - // If already in restricted mode. Ignore. - if (mCurrentOperatingMode == RESTRICTED) { - return status_t(NO_ERROR); - } - // If in any mode other than normal, ignore. - if (mCurrentOperatingMode != NORMAL) { - return INVALID_OPERATION; + if (args.size() > 0) { + Mode targetOperatingMode = NORMAL; + std::string inputStringMode = String8(args[0]).c_str(); + if (getTargetOperatingMode(inputStringMode, &targetOperatingMode)) { + status_t error = changeOperatingMode(args, targetOperatingMode); + // Dump the latest state only if no error was encountered. + if (error != NO_ERROR) { + return error; + } } + } - mCurrentOperatingMode = RESTRICTED; - // temporarily stop all sensor direct report and disable sensors - disableAllSensorsLocked(&connLock); - mWhiteListedPackage = String8(args[1]); - return status_t(NO_ERROR); - } else if (args.size() == 1 && args[0] == String16("enable")) { - // If currently in restricted mode, reset back to NORMAL mode else ignore. - if (mCurrentOperatingMode == RESTRICTED) { - mCurrentOperatingMode = NORMAL; - // enable sensors and recover all sensor direct report - enableAllSensorsLocked(&connLock); - } - if (mCurrentOperatingMode == DATA_INJECTION) { - resetToNormalModeLocked(); - } - mWhiteListedPackage.clear(); - return status_t(NO_ERROR); - } else if (args.size() == 2 && args[0] == String16("data_injection")) { - if (mCurrentOperatingMode == NORMAL) { - dev.disableAllSensors(); - status_t err = dev.setMode(DATA_INJECTION); - if (err == NO_ERROR) { - mCurrentOperatingMode = DATA_INJECTION; - } else { - // Re-enable sensors. - dev.enableAllSensors(); - } - mWhiteListedPackage = String8(args[1]); - return NO_ERROR; - } else if (mCurrentOperatingMode == DATA_INJECTION) { - // Already in DATA_INJECTION mode. Treat this as a no_op. - return NO_ERROR; - } else { - // Transition to data injection mode supported only from NORMAL mode. - return INVALID_OPERATION; - } - } else if (args.size() == 1 && args[0] == String16("--proto")) { + ConnectionSafeAutolock connLock = mConnectionHolder.lock(mLock); + // Run the following logic if a transition isn't requested above based on the input + // argument parsing. + if (args.size() == 1 && args[0] == String16("--proto")) { return dumpProtoLocked(fd, &connLock); } else if (!mSensors.hasAnySensor()) { result.append("No Sensors on the device\n"); @@ -529,8 +607,8 @@ status_t SensorService::dump(int fd, const Vector& args) { result.append("Recent Sensor events:\n"); for (auto&& i : mRecentEvent) { - sp s = mSensors.getInterface(i.first); - if (!i.second->isEmpty()) { + std::shared_ptr s = getSensorInterfaceFromHandle(i.first); + if (!i.second->isEmpty() && s != nullptr) { if (privileged || s->getSensor().getRequiredPermission().empty()) { i.second->setFormat("normal"); } else { @@ -564,10 +642,18 @@ status_t SensorService::dump(int fd, const Vector& args) { result.appendFormat(" NORMAL\n"); break; case RESTRICTED: - result.appendFormat(" RESTRICTED : %s\n", mWhiteListedPackage.c_str()); + result.appendFormat(" RESTRICTED : %s\n", mAllowListedPackage.c_str()); break; case DATA_INJECTION: - result.appendFormat(" DATA_INJECTION : %s\n", mWhiteListedPackage.c_str()); + result.appendFormat(" DATA_INJECTION : %s\n", mAllowListedPackage.c_str()); + break; + case REPLAY_DATA_INJECTION: + result.appendFormat(" REPLAY_DATA_INJECTION : %s\n", + mAllowListedPackage.c_str()); + break; + default: + result.appendFormat(" UNKNOWN\n"); + break; } result.appendFormat("Sensor Privacy: %s\n", mSensorPrivacyPolicy->isSensorPrivacyEnabled() ? "enabled" : "disabled"); @@ -647,8 +733,8 @@ status_t SensorService::dumpProtoLocked(int fd, ConnectionSafeAutolock* connLock // Write SensorEventsProto token = proto.start(SENSOR_EVENTS); for (auto&& i : mRecentEvent) { - sp s = mSensors.getInterface(i.first); - if (!i.second->isEmpty()) { + std::shared_ptr s = getSensorInterfaceFromHandle(i.first); + if (!i.second->isEmpty() && s != nullptr) { i.second->setFormat(privileged || s->getSensor().getRequiredPermission().empty() ? "normal" : "mask_data"); const uint64_t mToken = proto.start(service::SensorEventsProto::RECENT_EVENTS_LOGS); @@ -685,11 +771,11 @@ status_t SensorService::dumpProtoLocked(int fd, ConnectionSafeAutolock* connLock break; case RESTRICTED: proto.write(OPERATING_MODE, OP_MODE_RESTRICTED); - proto.write(WHITELISTED_PACKAGE, std::string(mWhiteListedPackage.c_str())); + proto.write(WHITELISTED_PACKAGE, std::string(mAllowListedPackage.c_str())); break; case DATA_INJECTION: proto.write(OPERATING_MODE, OP_MODE_DATA_INJECTION); - proto.write(WHITELISTED_PACKAGE, std::string(mWhiteListedPackage.c_str())); + proto.write(WHITELISTED_PACKAGE, std::string(mAllowListedPackage.c_str())); break; default: proto.write(OPERATING_MODE, OP_MODE_UNKNOWN); @@ -938,7 +1024,7 @@ void SensorService::cleanupAutoDisabledSensorLocked(const sphasSensor(handle)) { - sp si = getSensorInterfaceFromHandle(handle); + std::shared_ptr si = getSensorInterfaceFromHandle(handle); // If this buffer has an event from a one_shot sensor and this connection is registered // for this particular one_shot sensor, try cleaning up the connection. if (si != nullptr && @@ -969,7 +1055,12 @@ bool SensorService::threadLoop() { if (count < 0) { if(count == DEAD_OBJECT && device.isReconnecting()) { device.reconnect(); - continue; + // There are no "real" events at this point, but do not skip the rest of the loop + // if there are pending runtime events. + Mutex::Autolock _l(&mLock); + if (mRuntimeSensorEventQueue.empty()) { + continue; + } } else { ALOGE("sensor poll failed (%s)", strerror(-count)); break; @@ -1003,6 +1094,7 @@ bool SensorService::threadLoop() { recordLastValueLocked(mSensorEventBuffer, count); // handle virtual sensors + bool bufferNeedsSorting = false; if (count && vcount) { sensors_event_t const * const event = mSensorEventBuffer; if (!mActiveVirtualSensors.empty()) { @@ -1022,7 +1114,7 @@ bool SensorService::threadLoop() { break; } sensors_event_t out; - sp si = mSensors.getInterface(handle); + std::shared_ptr si = getSensorInterfaceFromHandle(handle); if (si == nullptr) { ALOGE("handle %d is not an valid virtual sensor", handle); continue; @@ -1038,12 +1130,37 @@ bool SensorService::threadLoop() { // record the last synthesized values recordLastValueLocked(&mSensorEventBuffer[count], k); count += k; - // sort the buffer by time-stamps - sortEventBuffer(mSensorEventBuffer, count); + bufferNeedsSorting = true; } } } + // handle runtime sensors + { + size_t k = 0; + while (!mRuntimeSensorEventQueue.empty()) { + if (count + k >= minBufferSize) { + ALOGE("buffer too small to hold all events: count=%zd, k=%zu, size=%zu", + count, k, minBufferSize); + break; + } + mSensorEventBuffer[count + k] = mRuntimeSensorEventQueue.front(); + mRuntimeSensorEventQueue.pop(); + k++; + } + if (k) { + // record the last synthesized values + recordLastValueLocked(&mSensorEventBuffer[count], k); + count += k; + bufferNeedsSorting = true; + } + } + + if (bufferNeedsSorting) { + // sort the buffer by time-stamps + sortEventBuffer(mSensorEventBuffer, count); + } + // handle backward compatibility for RotationVector sensor if (halVersion < SENSORS_DEVICE_API_VERSION_1_0) { for (int i = 0; i < count; i++) { @@ -1092,12 +1209,12 @@ bool SensorService::threadLoop() { // force the handle to be consistent s.handle = handle; - SensorInterface *si = new HardwareSensor(s, uuid); + auto si = std::make_shared(s, uuid); // This will release hold on dynamic sensor meta, so it should be called // after Sensor object is created. device.handleDynamicSensorConnection(handle, true /*connected*/); - registerDynamicSensorLocked(si); + registerDynamicSensorLocked(std::move(si)); } else { ALOGE("Handle %d has been used, cannot use again before reboot.", handle); } @@ -1224,7 +1341,7 @@ String8 SensorService::getSensorStringType(int handle) const { } bool SensorService::isVirtualSensor(int handle) const { - sp sensor = getSensorInterfaceFromHandle(handle); + std::shared_ptr sensor = getSensorInterfaceFromHandle(handle); return sensor != nullptr && sensor->isVirtual(); } @@ -1233,7 +1350,7 @@ bool SensorService::isWakeUpSensorEvent(const sensors_event_t& event) const { if (event.type == SENSOR_TYPE_META_DATA) { handle = event.meta_data.sensor; } - sp sensor = getSensorInterfaceFromHandle(handle); + std::shared_ptr sensor = getSensorInterfaceFromHandle(handle); return sensor != nullptr && sensor->getSensor().isWakeUpSensor(); } @@ -1342,19 +1459,37 @@ Vector SensorService::getSensorList(const String16& opPackageName) { return accessibleSensorList; } +void SensorService::addSensorIfAccessible(const String16& opPackageName, const Sensor& sensor, + Vector& accessibleSensorList) { + if (canAccessSensor(sensor, "can't see", opPackageName)) { + accessibleSensorList.add(sensor); + } else if (sensor.getType() != SENSOR_TYPE_HEAD_TRACKER) { + ALOGI("Skipped sensor %s because it requires permission %s and app op %" PRId32, + sensor.getName().c_str(), sensor.getRequiredPermission().c_str(), + sensor.getRequiredAppOp()); + } +} + Vector SensorService::getDynamicSensorList(const String16& opPackageName) { Vector accessibleSensorList; mSensors.forEachSensor( [this, &opPackageName, &accessibleSensorList] (const Sensor& sensor) -> bool { if (sensor.isDynamicSensor()) { - if (canAccessSensor(sensor, "can't see", opPackageName)) { - accessibleSensorList.add(sensor); - } else if (sensor.getType() != SENSOR_TYPE_HEAD_TRACKER) { - ALOGI("Skipped sensor %s because it requires permission %s and app op %" PRId32, - sensor.getName().c_str(), - sensor.getRequiredPermission().c_str(), - sensor.getRequiredAppOp()); - } + addSensorIfAccessible(opPackageName, sensor, accessibleSensorList); + } + return true; + }); + makeUuidsIntoIdsForSensorList(accessibleSensorList); + return accessibleSensorList; +} + +Vector SensorService::getRuntimeSensorList(const String16& opPackageName, int deviceId) { + Vector accessibleSensorList; + mSensors.forEachEntry( + [this, &opPackageName, deviceId, &accessibleSensorList] ( + const SensorServiceUtil::SensorList::Entry& e) -> bool { + if (e.deviceId == deviceId) { + addSensorIfAccessible(opPackageName, e.si->getSensor(), accessibleSensorList); } return true; }); @@ -1364,8 +1499,10 @@ Vector SensorService::getDynamicSensorList(const String16& opPackageName sp SensorService::createSensorEventConnection(const String8& packageName, int requestedMode, const String16& opPackageName, const String16& attributionTag) { - // Only 2 modes supported for a SensorEventConnection ... NORMAL and DATA_INJECTION. - if (requestedMode != NORMAL && requestedMode != DATA_INJECTION) { + // Only 3 modes supported for a SensorEventConnection ... NORMAL, DATA_INJECTION and + // REPLAY_DATA_INJECTION. + if (requestedMode != NORMAL && requestedMode != DATA_INJECTION && + requestedMode != REPLAY_DATA_INJECTION) { return nullptr; } resetTargetSdkVersionCache(opPackageName); @@ -1375,7 +1512,7 @@ sp SensorService::createSensorEventConnection(const Stri // operating in DI mode. if (requestedMode == DATA_INJECTION) { if (mCurrentOperatingMode != DATA_INJECTION) return nullptr; - if (!isWhiteListedPackage(packageName)) return nullptr; + if (!isAllowListedPackage(packageName)) return nullptr; } uid_t uid = IPCThreadState::self()->getCallingUid(); @@ -1386,8 +1523,9 @@ sp SensorService::createSensorEventConnection(const Stri String16 connOpPackageName = (opPackageName == String16("")) ? String16(connPackageName) : opPackageName; sp result(new SensorEventConnection(this, uid, connPackageName, - requestedMode == DATA_INJECTION, connOpPackageName, attributionTag)); - if (requestedMode == DATA_INJECTION) { + requestedMode == DATA_INJECTION || requestedMode == REPLAY_DATA_INJECTION, + connOpPackageName, attributionTag)); + if (requestedMode == DATA_INJECTION || requestedMode == REPLAY_DATA_INJECTION) { mConnectionHolder.addEventConnectionIfNotPresent(result); // Add the associated file descriptor to the Looper for polling whenever there is data to // be injected. @@ -1402,7 +1540,7 @@ int SensorService::isDataInjectionEnabled() { } sp SensorService::createSensorDirectConnection( - const String16& opPackageName, uint32_t size, int32_t type, int32_t format, + const String16& opPackageName, int deviceId, uint32_t size, int32_t type, int32_t format, const native_handle *resource) { resetTargetSdkVersionCache(opPackageName); ConnectionSafeAutolock connLock = mConnectionHolder.lock(mLock); @@ -1475,20 +1613,32 @@ sp SensorService::createSensorDirectConnection( if (!clone) { return nullptr; } + native_handle_set_fdsan_tag(clone); sp conn; - SensorDevice& dev(SensorDevice::getInstance()); - int channelHandle = dev.registerDirectChannel(&mem); + int channelHandle = 0; + if (deviceId == RuntimeSensor::DEFAULT_DEVICE_ID) { + SensorDevice& dev(SensorDevice::getInstance()); + channelHandle = dev.registerDirectChannel(&mem); + } else { + auto runtimeSensorCallback = mRuntimeSensorCallbacks.find(deviceId); + if (runtimeSensorCallback == mRuntimeSensorCallbacks.end()) { + ALOGE("Runtime sensor callback for deviceId %d not found", deviceId); + } else { + int fd = dup(clone->data[0]); + channelHandle = runtimeSensorCallback->second->onDirectChannelCreated(fd); + } + } if (channelHandle <= 0) { ALOGE("SensorDevice::registerDirectChannel returns %d", channelHandle); } else { mem.handle = clone; - conn = new SensorDirectConnection(this, uid, &mem, channelHandle, opPackageName); + conn = new SensorDirectConnection(this, uid, &mem, channelHandle, opPackageName, deviceId); } if (conn == nullptr) { - native_handle_close(clone); + native_handle_close_with_tag(clone); native_handle_delete(clone); } else { // add to list of direct connections @@ -1498,6 +1648,24 @@ sp SensorService::createSensorDirectConnection( return conn; } +int SensorService::configureRuntimeSensorDirectChannel( + int sensorHandle, const SensorDirectConnection* c, const sensors_direct_cfg_t* config) { + int deviceId = c->getDeviceId(); + int sensorDeviceId = getDeviceIdFromHandle(sensorHandle); + if (sensorDeviceId != c->getDeviceId()) { + ALOGE("Cannot configure direct channel created for device %d with a sensor that belongs " + "to device %d", c->getDeviceId(), sensorDeviceId); + return BAD_VALUE; + } + auto runtimeSensorCallback = mRuntimeSensorCallbacks.find(deviceId); + if (runtimeSensorCallback == mRuntimeSensorCallbacks.end()) { + ALOGE("Runtime sensor callback for deviceId %d not found", deviceId); + return BAD_VALUE; + } + return runtimeSensorCallback->second->onDirectChannelConfigured( + c->getHalChannelHandle(), sensorHandle, config->rate_level); +} + int SensorService::setOperationParameter( int32_t handle, int32_t type, const Vector &floats, const Vector &ints) { @@ -1614,7 +1782,7 @@ void SensorService::cleanupConnection(SensorEventConnection* c) { int handle = mActiveSensors.keyAt(i); if (c->hasSensor(handle)) { ALOGD_IF(DEBUG_CONNECTIONS, "%zu: disabling handle=0x%08x", i, handle); - sp sensor = getSensorInterfaceFromHandle(handle); + std::shared_ptr sensor = getSensorInterfaceFromHandle(handle); if (sensor != nullptr) { sensor->activate(c, false); } else { @@ -1653,8 +1821,18 @@ void SensorService::cleanupConnection(SensorEventConnection* c) { void SensorService::cleanupConnection(SensorDirectConnection* c) { Mutex::Autolock _l(mLock); - SensorDevice& dev(SensorDevice::getInstance()); - dev.unregisterDirectChannel(c->getHalChannelHandle()); + int deviceId = c->getDeviceId(); + if (deviceId == RuntimeSensor::DEFAULT_DEVICE_ID) { + SensorDevice& dev(SensorDevice::getInstance()); + dev.unregisterDirectChannel(c->getHalChannelHandle()); + } else { + auto runtimeSensorCallback = mRuntimeSensorCallbacks.find(deviceId); + if (runtimeSensorCallback != mRuntimeSensorCallbacks.end()) { + runtimeSensorCallback->second->onDirectChannelDestroyed(c->getHalChannelHandle()); + } else { + ALOGE("Runtime sensor callback for deviceId %d not found", deviceId); + } + } mConnectionHolder.removeDirectConnection(c); } @@ -1728,25 +1906,38 @@ status_t SensorService::removeProximityActiveListener( return NAME_NOT_FOUND; } -sp SensorService::getSensorInterfaceFromHandle(int handle) const { +std::shared_ptr SensorService::getSensorInterfaceFromHandle(int handle) const { return mSensors.getInterface(handle); } +int SensorService::getDeviceIdFromHandle(int handle) const { + int deviceId = RuntimeSensor::DEFAULT_DEVICE_ID; + mSensors.forEachEntry( + [&deviceId, handle] (const SensorServiceUtil::SensorList::Entry& e) -> bool { + if (e.si->getSensor().getHandle() == handle) { + deviceId = e.deviceId; + return false; // stop iterating + } + return true; + }); + return deviceId; +} + status_t SensorService::enable(const sp& connection, int handle, nsecs_t samplingPeriodNs, nsecs_t maxBatchReportLatencyNs, int reservedFlags, const String16& opPackageName) { if (mInitCheck != NO_ERROR) return mInitCheck; - sp sensor = getSensorInterfaceFromHandle(handle); + std::shared_ptr sensor = getSensorInterfaceFromHandle(handle); if (sensor == nullptr || !canAccessSensor(sensor->getSensor(), "Tried enabling", opPackageName)) { return BAD_VALUE; } ConnectionSafeAutolock connLock = mConnectionHolder.lock(mLock); - if (mCurrentOperatingMode != NORMAL - && !isWhiteListedPackage(connection->getPackageName())) { + if (mCurrentOperatingMode != NORMAL && mCurrentOperatingMode != REPLAY_DATA_INJECTION && + !isAllowListedPackage(connection->getPackageName())) { return INVALID_OPERATION; } @@ -1884,7 +2075,7 @@ status_t SensorService::disable(const sp& connection, int Mutex::Autolock _l(mLock); status_t err = cleanupWithoutDisableLocked(connection, handle); if (err == NO_ERROR) { - sp sensor = getSensorInterfaceFromHandle(handle); + std::shared_ptr sensor = getSensorInterfaceFromHandle(handle); err = sensor != nullptr ? sensor->activate(connection.get(), false) : status_t(BAD_VALUE); } @@ -1930,7 +2121,7 @@ status_t SensorService::setEventRate(const sp& connection if (mInitCheck != NO_ERROR) return mInitCheck; - sp sensor = getSensorInterfaceFromHandle(handle); + std::shared_ptr sensor = getSensorInterfaceFromHandle(handle); if (sensor == nullptr || !canAccessSensor(sensor->getSensor(), "Tried configuring", opPackageName)) { return BAD_VALUE; @@ -1956,7 +2147,7 @@ status_t SensorService::flushSensor(const sp& connection, Mutex::Autolock _l(mLock); // Loop through all sensors for this connection and call flush on each of them. for (int handle : connection->getActiveSensorHandles()) { - sp sensor = getSensorInterfaceFromHandle(handle); + std::shared_ptr sensor = getSensorInterfaceFromHandle(handle); if (sensor == nullptr) { continue; } @@ -2094,6 +2285,95 @@ void SensorService::resetTargetSdkVersionCache(const String16& opPackageName) { } } +bool SensorService::getTargetOperatingMode(const std::string &inputString, Mode *targetModeOut) { + if (inputString == std::string("restrict")) { + *targetModeOut = RESTRICTED; + return true; + } + if (inputString == std::string("enable")) { + *targetModeOut = NORMAL; + return true; + } + if (inputString == std::string("data_injection")) { + *targetModeOut = DATA_INJECTION; + return true; + } + if (inputString == std::string("replay_data_injection")) { + *targetModeOut = REPLAY_DATA_INJECTION; + return true; + } + return false; +} + +status_t SensorService::changeOperatingMode(const Vector& args, + Mode targetOperatingMode) { + ConnectionSafeAutolock connLock = mConnectionHolder.lock(mLock); + SensorDevice& dev(SensorDevice::getInstance()); + if (mCurrentOperatingMode == targetOperatingMode) { + return NO_ERROR; + } + if (targetOperatingMode != NORMAL && args.size() < 2) { + return INVALID_OPERATION; + } + switch (targetOperatingMode) { + case NORMAL: + // If currently in restricted mode, reset back to NORMAL mode else ignore. + if (mCurrentOperatingMode == RESTRICTED) { + mCurrentOperatingMode = NORMAL; + // enable sensors and recover all sensor direct report + enableAllSensorsLocked(&connLock); + } + if (mCurrentOperatingMode == REPLAY_DATA_INJECTION) { + dev.disableAllSensors(); + } + if (mCurrentOperatingMode == DATA_INJECTION || + mCurrentOperatingMode == REPLAY_DATA_INJECTION) { + resetToNormalModeLocked(); + } + mAllowListedPackage.clear(); + return status_t(NO_ERROR); + case RESTRICTED: + // If in any mode other than normal, ignore. + if (mCurrentOperatingMode != NORMAL) { + return INVALID_OPERATION; + } + + mCurrentOperatingMode = RESTRICTED; + // temporarily stop all sensor direct report and disable sensors + disableAllSensorsLocked(&connLock); + mAllowListedPackage = String8(args[1]); + return status_t(NO_ERROR); + case REPLAY_DATA_INJECTION: + if (SensorServiceUtil::isUserBuild()) { + return INVALID_OPERATION; + } + FALLTHROUGH_INTENDED; + case DATA_INJECTION: + if (mCurrentOperatingMode == NORMAL) { + dev.disableAllSensors(); + // Always use DATA_INJECTION here since this value goes to the HAL and the HAL + // doesn't have an understanding of replay vs. normal data injection. + status_t err = dev.setMode(DATA_INJECTION); + if (err == NO_ERROR) { + mCurrentOperatingMode = targetOperatingMode; + } + if (err != NO_ERROR || targetOperatingMode == REPLAY_DATA_INJECTION) { + // Re-enable sensors. + dev.enableAllSensors(); + } + mAllowListedPackage = String8(args[1]); + return NO_ERROR; + } else { + // Transition to data injection mode supported only from NORMAL mode. + return INVALID_OPERATION; + } + break; + default: + break; + } + return NO_ERROR; +} + void SensorService::checkWakeLockState() { ConnectionSafeAutolock connLock = mConnectionHolder.lock(mLock); checkWakeLockStateLocked(&connLock); @@ -2123,14 +2403,14 @@ void SensorService::sendEventsFromCache(const sp& connect } } -bool SensorService::isWhiteListedPackage(const String8& packageName) { - return (packageName.contains(mWhiteListedPackage.c_str())); +bool SensorService::isAllowListedPackage(const String8& packageName) { + return (packageName.contains(mAllowListedPackage.c_str())); } bool SensorService::isOperationRestrictedLocked(const String16& opPackageName) { if (mCurrentOperatingMode == RESTRICTED) { String8 package(opPackageName); - return !isWhiteListedPackage(package); + return !isAllowListedPackage(package); } return false; } diff --git a/services/sensorservice/SensorService.h b/services/sensorservice/SensorService.h index 4ba3c519852f8eac0165c4620f03c8c83aba9b5d..0aa1bcbd720ac9647f0b9c56e8f33f4467e8607d 100644 --- a/services/sensorservice/SensorService.h +++ b/services/sensorservice/SensorService.h @@ -42,6 +42,7 @@ #include #include +#include #include #include #include @@ -101,8 +102,7 @@ public: // Step Detector etc. Typically in this mode, there will be a client (a // SensorEventConnection) which will be injecting sensor data into the HAL. Normal apps can // unregister and register for any sensor that supports injection. Registering to sensors - // that do not support injection will give an error. TODO: Allow exactly one - // client to inject sensor data at a time. + // that do not support injection will give an error. DATA_INJECTION = 1, // This mode is used only for testing sensors. Each sensor can be tested in isolation with // the required sampling_rate and maxReportLatency parameters without having to think about @@ -115,10 +115,14 @@ public: // corresponding parameters if the application hasn't unregistered for sensors in the mean // time. NOTE: Non allowlisted app whose sensors were previously deactivated may still // receive events if a allowlisted app requests data from the same sensor. - RESTRICTED = 2 + RESTRICTED = 2, + // Mostly equivalent to DATA_INJECTION with the difference being that the injected data is + // delivered to all requesting apps rather than just the package allowed to inject data. + // This mode is only allowed to be used on development builds. + REPLAY_DATA_INJECTION = 3, // State Transitions supported. - // RESTRICTED <--- NORMAL ---> DATA_INJECTION + // RESTRICTED <--- NORMAL ---> DATA_INJECTION/REPLAY_DATA_INJECTION // ---> <--- // Shell commands to switch modes in SensorService. @@ -143,6 +147,19 @@ public: virtual void onProximityActive(bool isActive) = 0; }; + class RuntimeSensorCallback : public virtual RefBase { + public: + // Note that the callback is invoked from an async thread and can interact with the + // SensorService directly. + virtual status_t onConfigurationChanged(int handle, bool enabled, + int64_t samplingPeriodNanos, + int64_t batchReportLatencyNanos) = 0; + virtual int onDirectChannelCreated(int fd) = 0; + virtual void onDirectChannelDestroyed(int channelHandle) = 0; + virtual int onDirectChannelConfigured(int channelHandle, int sensorHandle, + int rateLevel) = 0; + }; + static char const* getServiceName() ANDROID_API { return "sensorservice"; } SensorService() ANDROID_API; @@ -169,6 +186,14 @@ public: status_t addProximityActiveListener(const sp& callback) ANDROID_API; status_t removeProximityActiveListener(const sp& callback) ANDROID_API; + int registerRuntimeSensor(const sensor_t& sensor, int deviceId, + sp callback) ANDROID_API; + status_t unregisterRuntimeSensor(int handle) ANDROID_API; + status_t sendRuntimeSensorEvent(const sensors_event_t& event) ANDROID_API; + + int configureRuntimeSensorDirectChannel(int sensorHandle, const SensorDirectConnection* c, + const sensors_direct_cfg_t* config); + // Returns true if a sensor should be throttled according to our rate-throttling rules. static bool isSensorInCappedSet(int sensorType); @@ -263,7 +288,7 @@ private: void onUidStateChanged(uid_t uid __unused, int32_t procState __unused, int64_t procStateSeq __unused, int32_t capability __unused) override {} - void onUidProcAdjChanged(uid_t uid __unused) override {} + void onUidProcAdjChanged(uid_t uid __unused, int32_t adj __unused) override {} void addOverrideUid(uid_t uid, bool active); void removeOverrideUid(uid_t uid); @@ -346,12 +371,14 @@ private: // ISensorServer interface virtual Vector getSensorList(const String16& opPackageName); virtual Vector getDynamicSensorList(const String16& opPackageName); + virtual Vector getRuntimeSensorList(const String16& opPackageName, int deviceId); virtual sp createSensorEventConnection( const String8& packageName, int requestedMode, const String16& opPackageName, const String16& attributionTag); virtual int isDataInjectionEnabled(); virtual sp createSensorDirectConnection(const String16& opPackageName, - uint32_t size, int32_t type, int32_t format, const native_handle *resource); + int deviceId, uint32_t size, int32_t type, int32_t format, + const native_handle *resource); virtual int setOperationParameter( int32_t handle, int32_t type, const Vector &floats, const Vector &ints); virtual status_t dump(int fd, const Vector& args); @@ -360,14 +387,15 @@ private: String8 getSensorName(int handle) const; String8 getSensorStringType(int handle) const; bool isVirtualSensor(int handle) const; - sp getSensorInterfaceFromHandle(int handle) const; + std::shared_ptr getSensorInterfaceFromHandle(int handle) const; + int getDeviceIdFromHandle(int handle) const; bool isWakeUpSensor(int type) const; void recordLastValueLocked(sensors_event_t const* buffer, size_t count); static void sortEventBuffer(sensors_event_t* buffer, size_t count); - const Sensor& registerSensor(SensorInterface* sensor, - bool isDebug = false, bool isVirtual = false); - const Sensor& registerVirtualSensor(SensorInterface* sensor, bool isDebug = false); - const Sensor& registerDynamicSensorLocked(SensorInterface* sensor, bool isDebug = false); + bool registerSensor(std::shared_ptr sensor, bool isDebug = false, + bool isVirtual = false, int deviceId = RuntimeSensor::DEFAULT_DEVICE_ID); + bool registerVirtualSensor(std::shared_ptr sensor, bool isDebug = false); + bool registerDynamicSensorLocked(std::shared_ptr sensor, bool isDebug = false); bool unregisterDynamicSensorLocked(int handle); status_t cleanupWithoutDisable(const sp& connection, int handle); status_t cleanupWithoutDisableLocked(const sp& connection, int handle); @@ -375,9 +403,14 @@ private: sensors_event_t const* buffer, const int count); bool canAccessSensor(const Sensor& sensor, const char* operation, const String16& opPackageName); + void addSensorIfAccessible(const String16& opPackageName, const Sensor& sensor, + Vector& accessibleSensorList); static bool hasPermissionForSensor(const Sensor& sensor); static int getTargetSdkVersion(const String16& opPackageName); static void resetTargetSdkVersionCache(const String16& opPackageName); + // Checks if the provided target operating mode is valid and returns the enum if it is. + static bool getTargetOperatingMode(const std::string &inputString, Mode *targetModeOut); + status_t changeOperatingMode(const Vector& args, Mode targetOperatingMode); // SensorService acquires a partial wakelock for delivering events from wake up sensors. This // method checks whether all the events from these wake up sensors have been delivered to the // corresponding applications, if yes the wakelock is released. @@ -403,7 +436,7 @@ private: // If SensorService is operating in RESTRICTED mode, only select whitelisted packages are // allowed to register for or call flush on sensors. Typically only cts test packages are // allowed. - bool isWhiteListedPackage(const String8& packageName); + bool isAllowListedPackage(const String8& packageName); // Returns true if a connection with the specified opPackageName has no access to sensors // in the RESTRICTED mode (i.e. the service is in RESTRICTED mode, and the package is not @@ -492,6 +525,8 @@ private: wp * mMapFlushEventsToConnections; std::unordered_map mRecentEvent; Mode mCurrentOperatingMode; + std::queue mRuntimeSensorEventQueue; + std::unordered_map> mRuntimeSensorCallbacks; // true if the head tracker sensor type is currently restricted to system usage only // (can only be unrestricted for testing, via shell cmd) @@ -501,7 +536,7 @@ private: // applications with this packageName are allowed to activate/deactivate or call flush on // sensors. To run CTS this is can be set to ".cts." and only CTS tests will get access to // sensors. - String8 mWhiteListedPackage; + String8 mAllowListedPackage; int mNextSensorRegIndex; Vector mLastNSensorRegistrations; diff --git a/services/sensorservice/SensorServiceUtils.cpp b/services/sensorservice/SensorServiceUtils.cpp index 6bad962521d0e2c7458c02ce701e894b6a340514..46b4b5bff63650499d2e94b8d6ca249470e5d568 100644 --- a/services/sensorservice/SensorServiceUtils.cpp +++ b/services/sensorservice/SensorServiceUtils.cpp @@ -16,6 +16,7 @@ #include "SensorServiceUtils.h" +#include #include namespace android { @@ -76,5 +77,10 @@ size_t eventSizeBySensorType(int type) { } } +bool isUserBuild() { + std::string buildType = android::base::GetProperty("ro.build.type", "user"); + return "user" == buildType; +} + } // namespace SensorServiceUtil } // namespace android; diff --git a/services/sensorservice/SensorServiceUtils.h b/services/sensorservice/SensorServiceUtils.h index 49457cf287d858c01336a8e35b1e18d1244b7f2f..a6e0d6b401bd498a705d4a793c8318749085d177 100644 --- a/services/sensorservice/SensorServiceUtils.h +++ b/services/sensorservice/SensorServiceUtils.h @@ -38,6 +38,11 @@ public: size_t eventSizeBySensorType(int type); +/** + * Returns true if on a user (production) build. + */ +bool isUserBuild(); + } // namespace SensorServiceUtil } // namespace android; diff --git a/services/sensorservice/aidl/Android.bp b/services/sensorservice/aidl/Android.bp index 34d1de72f98ab511e3551cbf535cbf0719399638..542fcaee8dc6e4ae127c2058a418c02373413ee8 100644 --- a/services/sensorservice/aidl/Android.bp +++ b/services/sensorservice/aidl/Android.bp @@ -28,7 +28,7 @@ cc_library { "libbinder_ndk", "libsensor", "android.frameworks.sensorservice-V1-ndk", - "android.hardware.sensors-V1-ndk", + "android.hardware.sensors-V2-ndk", ], export_include_dirs: [ "include/", diff --git a/services/sensorservice/aidl/DirectReportChannel.cpp b/services/sensorservice/aidl/DirectReportChannel.cpp index cab53c17f59d0c8a47dbbdb690c9d2bb64d383a9..9ef4ca8d0845d3d5dcf2fdf9fed21f9b5458bc82 100644 --- a/services/sensorservice/aidl/DirectReportChannel.cpp +++ b/services/sensorservice/aidl/DirectReportChannel.cpp @@ -32,7 +32,7 @@ ndk::ScopedAStatus DirectReportChannel::configure( int32_t sensorHandle, ::aidl::android::hardware::sensors::ISensors::RateLevel rate, int32_t* _aidl_return) { int token = mManager.configureDirectChannel(mId, sensorHandle, static_cast(rate)); - if (token <= 0) { + if (token < 0) { return ndk::ScopedAStatus::fromServiceSpecificError(token); } *_aidl_return = token; diff --git a/services/sensorservice/aidl/SensorManager.cpp b/services/sensorservice/aidl/SensorManager.cpp index ee4c5f819c482a8e126204f12b410b45802b0efb..2b6ea7cef1947eee359fba558159863363977ace 100644 --- a/services/sensorservice/aidl/SensorManager.cpp +++ b/services/sensorservice/aidl/SensorManager.cpp @@ -59,6 +59,9 @@ SensorManagerAidl::~SensorManagerAidl() { if (mPollThread.joinable()) { mPollThread.join(); } + + ::android::SensorManager::removeInstanceForPackage( + String16(ISensorManager::descriptor)); } ndk::ScopedAStatus createDirectChannel(::android::SensorManager& manager, size_t size, int type, @@ -185,12 +188,8 @@ ndk::ScopedAStatus SensorManagerAidl::getSensorList(std::vector* _ai } ::android::SensorManager& SensorManagerAidl::getInternalManager() { - std::lock_guard lock(mInternalManagerMutex); - if (mInternalManager == nullptr) { - mInternalManager = &::android::SensorManager::getInstanceForPackage( - String16(ISensorManager::descriptor)); - } - return *mInternalManager; + return ::android::SensorManager::getInstanceForPackage( + String16(ISensorManager::descriptor)); } /* One global looper for all event queues created from this SensorManager. */ diff --git a/services/sensorservice/aidl/fuzzer/Android.bp b/services/sensorservice/aidl/fuzzer/Android.bp index cb586c6b693dbc8e2555398c0c9acf0c12c937ac..ed4829abe3eb956a36d020c2946b7dc719e823c6 100644 --- a/services/sensorservice/aidl/fuzzer/Android.bp +++ b/services/sensorservice/aidl/fuzzer/Android.bp @@ -19,7 +19,7 @@ cc_fuzz { "libpermission", "android.frameworks.sensorservice-V1-ndk", "android.hardware.sensors-V1-convert", - "android.hardware.sensors-V1-ndk", + "android.hardware.sensors-V2-ndk", "android.hardware.common-V2-ndk", "libsensor", "libfakeservicemanager", diff --git a/services/sensorservice/aidl/include/sensorserviceaidl/SensorManagerAidl.h b/services/sensorservice/aidl/include/sensorserviceaidl/SensorManagerAidl.h index c77ee880dde1d2574863093718daefedfff63bab..83496f601225146766e378515e31e87044f8526b 100644 --- a/services/sensorservice/aidl/include/sensorserviceaidl/SensorManagerAidl.h +++ b/services/sensorservice/aidl/include/sensorserviceaidl/SensorManagerAidl.h @@ -57,8 +57,6 @@ private: ::android::SensorManager& getInternalManager(); sp getLooper(); - std::mutex mInternalManagerMutex; - ::android::SensorManager* mInternalManager = nullptr; // does not own sp mLooper; volatile bool mStopThread; diff --git a/services/sensorservice/hidl/SensorManager.cpp b/services/sensorservice/hidl/SensorManager.cpp index 0a4e68412db66669074b5d7e59cede8e3cd4f6e7..3d148e103a44e4df6e16212f69201822ba724769 100644 --- a/services/sensorservice/hidl/SensorManager.cpp +++ b/services/sensorservice/hidl/SensorManager.cpp @@ -196,12 +196,8 @@ sp SensorManager::getLooper() { } ::android::SensorManager& SensorManager::getInternalManager() { - std::lock_guard lock(mInternalManagerMutex); - if (mInternalManager == nullptr) { - mInternalManager = &::android::SensorManager::getInstanceForPackage( - String16(ISensorManager::descriptor)); - } - return *mInternalManager; + return ::android::SensorManager::getInstanceForPackage( + String16(ISensorManager::descriptor)); } Return SensorManager::createEventQueue( diff --git a/services/sensorservice/hidl/include/sensorservicehidl/SensorManager.h b/services/sensorservice/hidl/include/sensorservicehidl/SensorManager.h index 8d7a05b5e22ca0f27197c73df8bb7a498b5d007a..1b085acfca14d480cd06ee063f07af4c2a369037 100644 --- a/services/sensorservice/hidl/include/sensorservicehidl/SensorManager.h +++ b/services/sensorservice/hidl/include/sensorservicehidl/SensorManager.h @@ -58,8 +58,6 @@ private: ::android::SensorManager& getInternalManager(); sp getLooper(); - std::mutex mInternalManagerMutex; - ::android::SensorManager* mInternalManager = nullptr; // does not own sp mLooper; volatile bool mStopThread; diff --git a/services/sensorservice/tests/sensorservicetest.cpp b/services/sensorservice/tests/sensorservicetest.cpp index 8b4b3f61b07a364a01d28fdc95412c159c394b3a..88521f118533519c1d2bab5d0258ca0921a39343 100644 --- a/services/sensorservice/tests/sensorservicetest.cpp +++ b/services/sensorservice/tests/sensorservicetest.cpp @@ -89,6 +89,17 @@ void testInvalidSharedMem_NoCrash(SensorManager &mgr) { // Should print -22 (BAD_VALUE) and the device runtime shouldn't restart printf("createInvalidDirectChannel=%d\n", ret); + + // Secondary test: correct channel creation & destruction (should print 0) + ret = mgr.createDirectChannel(kMemSize, ASENSOR_DIRECT_CHANNEL_TYPE_HARDWARE_BUFFER, + resourceHandle); + printf("createValidDirectChannel=%d\n", ret); + + // Third test: double-destroy (should not crash) + mgr.destroyDirectChannel(ret); + AHardwareBuffer_release(hardwareBuffer); + printf("duplicate destroyDirectChannel...\n"); + mgr.destroyDirectChannel(ret); } int main() { diff --git a/services/surfaceflinger/Android.bp b/services/surfaceflinger/Android.bp index cbb95f963c9857d2f9248727f7044c6fa7f0fe0c..5683a9280f03edbaa894125066f66e7aed995eeb 100644 --- a/services/surfaceflinger/Android.bp +++ b/services/surfaceflinger/Android.bp @@ -18,14 +18,16 @@ cc_defaults { "-Wunused", "-Wunreachable-code", "-Wconversion", + "-DANDROID_UTILS_REF_BASE_DISABLE_IMPLICIT_CONSTRUCTION", ], } cc_defaults { name: "libsurfaceflinger_defaults", defaults: [ + "android.hardware.graphics.composer3-ndk_shared", + "librenderengine_deps", "surfaceflinger_defaults", - "skia_renderengine_deps", ], cflags: [ "-DLOG_TAG=\"SurfaceFlinger\"", @@ -45,10 +47,7 @@ cc_defaults { "android.hardware.graphics.composer@2.2", "android.hardware.graphics.composer@2.3", "android.hardware.graphics.composer@2.4", - "android.hardware.graphics.composer3-V1-ndk", - "android.hardware.power@1.0", - "android.hardware.power@1.3", - "android.hardware.power-V2-cpp", + "android.hardware.power-V4-cpp", "libbase", "libbinder", "libbinder_ndk", @@ -62,6 +61,7 @@ cc_defaults { "liblayers_proto", "liblog", "libnativewindow", + "libpowermanager", "libprocessgroup", "libprotobuf-cpp-lite", "libsync", @@ -83,7 +83,6 @@ cc_defaults { "libserviceutils", "libshaders", "libtonemap", - "libtrace_proto", ], header_libs: [ "android.hardware.graphics.composer@2.1-command-buffer", @@ -105,8 +104,7 @@ cc_defaults { "android.hardware.graphics.composer@2.2", "android.hardware.graphics.composer@2.3", "android.hardware.graphics.composer@2.4", - "android.hardware.graphics.composer3-V1-ndk", - "android.hardware.power@1.3", + "libpowermanager", "libhidlbase", "libtimestats", ], @@ -141,26 +139,29 @@ filegroup { name: "libsurfaceflinger_sources", srcs: [ "BackgroundExecutor.cpp", - "BufferLayer.cpp", - "BufferLayerConsumer.cpp", - "BufferQueueLayer.cpp", - "BufferStateLayer.cpp", - "ClientCache.cpp", "Client.cpp", - "EffectLayer.cpp", - "ContainerLayer.cpp", + "ClientCache.cpp", + "Display/DisplaySnapshot.cpp", "DisplayDevice.cpp", "DisplayHardware/AidlComposerHal.cpp", - "DisplayHardware/HidlComposerHal.cpp", "DisplayHardware/ComposerHal.cpp", "DisplayHardware/FramebufferSurface.cpp", "DisplayHardware/HWC2.cpp", "DisplayHardware/HWComposer.cpp", + "DisplayHardware/HidlComposerHal.cpp", "DisplayHardware/PowerAdvisor.cpp", "DisplayHardware/VirtualDisplaySurface.cpp", "DisplayRenderArea.cpp", "Effects/Daltonizer.cpp", "EventLog/EventLog.cpp", + "FrontEnd/LayerCreationArgs.cpp", + "FrontEnd/LayerHandle.cpp", + "FrontEnd/LayerSnapshot.cpp", + "FrontEnd/LayerSnapshotBuilder.cpp", + "FrontEnd/LayerHierarchy.cpp", + "FrontEnd/LayerLifecycleManager.cpp", + "FrontEnd/RequestedLayerState.cpp", + "FrontEnd/TransactionHandler.cpp", "FlagManager.cpp", "FpsReporter.cpp", "FrameTracer/FrameTracer.cpp", @@ -168,23 +169,21 @@ filegroup { "HdrLayerInfoReporter.cpp", "WindowInfosListenerInvoker.cpp", "Layer.cpp", + "LayerFE.cpp", "LayerProtoHelper.cpp", - "LayerRejecter.cpp", "LayerRenderArea.cpp", "LayerVector.cpp", - "MonitoredProducer.cpp", "NativeWindowSurface.cpp", "RefreshRateOverlay.cpp", "RegionSamplingThread.cpp", "RenderArea.cpp", - "Scheduler/DispSyncSource.cpp", "Scheduler/EventThread.cpp", "Scheduler/FrameRateOverrideMappings.cpp", "Scheduler/OneShotTimer.cpp", "Scheduler/LayerHistory.cpp", "Scheduler/LayerInfo.cpp", "Scheduler/MessageQueue.cpp", - "Scheduler/RefreshRateConfigs.cpp", + "Scheduler/RefreshRateSelector.cpp", "Scheduler/Scheduler.cpp", "Scheduler/VSyncDispatchTimerQueue.cpp", "Scheduler/VSyncPredictor.cpp", @@ -192,10 +191,10 @@ filegroup { "Scheduler/VsyncConfiguration.cpp", "Scheduler/VsyncModulator.cpp", "Scheduler/VsyncSchedule.cpp", + "ScreenCaptureOutput.cpp", "StartPropertySetThread.cpp", "SurfaceFlinger.cpp", "SurfaceFlingerDefaultFactory.cpp", - "SurfaceInterceptor.cpp", "Tracing/LayerTracing.cpp", "Tracing/TransactionTracing.cpp", "Tracing/TransactionProtoParser.cpp", @@ -230,7 +229,6 @@ cc_defaults { ], static_libs: [ "libserviceutils", - "libtrace_proto", ], } diff --git a/services/surfaceflinger/BackgroundExecutor.cpp b/services/surfaceflinger/BackgroundExecutor.cpp index a15de2b1839e7658c0cacf9f979700b31bbc6bd0..5a1ec6f50162505a6e780cfc9b8fbbc41c00fab9 100644 --- a/services/surfaceflinger/BackgroundExecutor.cpp +++ b/services/surfaceflinger/BackgroundExecutor.cpp @@ -20,6 +20,7 @@ #define ATRACE_TAG ATRACE_TAG_GRAPHICS #include +#include #include "BackgroundExecutor.h" @@ -28,29 +29,19 @@ namespace android { ANDROID_SINGLETON_STATIC_INSTANCE(BackgroundExecutor); BackgroundExecutor::BackgroundExecutor() : Singleton() { + // mSemaphore must be initialized before any calls to + // BackgroundExecutor::sendCallbacks. For this reason, we initialize it + // within the constructor instead of within mThread. + LOG_ALWAYS_FATAL_IF(sem_init(&mSemaphore, 0, 0), "sem_init failed"); mThread = std::thread([&]() { - LOG_ALWAYS_FATAL_IF(sem_init(&mSemaphore, 0, 0), "sem_init failed"); while (!mDone) { LOG_ALWAYS_FATAL_IF(sem_wait(&mSemaphore), "sem_wait failed (%d)", errno); - - ftl::SmallVector workItems; - - Work* work = mWorks.pop(); - while (work) { - workItems.push_back(work); - work = mWorks.pop(); + auto callbacks = mCallbacksQueue.pop(); + if (!callbacks) { + continue; } - - // Sequence numbers are guaranteed to be in intended order, as we assume a single - // producer and single consumer. - std::stable_sort(workItems.begin(), workItems.end(), [](Work* left, Work* right) { - return left->sequence < right->sequence; - }); - for (Work* work : workItems) { - for (auto& task : work->tasks) { - task(); - } - delete work; + for (auto& callback : *callbacks) { + callback(); } } }); @@ -66,12 +57,21 @@ BackgroundExecutor::~BackgroundExecutor() { } void BackgroundExecutor::sendCallbacks(Callbacks&& tasks) { - Work* work = new Work(); - work->sequence = mSequence; - work->tasks = std::move(tasks); - mWorks.push(work); - mSequence++; + mCallbacksQueue.push(std::move(tasks)); LOG_ALWAYS_FATAL_IF(sem_post(&mSemaphore), "sem_post failed"); } -} // namespace android \ No newline at end of file +void BackgroundExecutor::flushQueue() { + std::mutex mutex; + std::condition_variable cv; + bool flushComplete = false; + sendCallbacks({[&]() { + std::scoped_lock lock{mutex}; + flushComplete = true; + cv.notify_one(); + }}); + std::unique_lock lock{mutex}; + cv.wait(lock, [&]() { return flushComplete; }); +} + +} // namespace android diff --git a/services/surfaceflinger/BackgroundExecutor.h b/services/surfaceflinger/BackgroundExecutor.h index eeaf3bdd2e04ccfb81448fe0a225d7792bad01d2..66b7d7a1fc865b180f94bb60b777511efc98fa2a 100644 --- a/services/surfaceflinger/BackgroundExecutor.h +++ b/services/surfaceflinger/BackgroundExecutor.h @@ -16,15 +16,13 @@ #pragma once -#include -#include #include #include #include -#include -#include #include +#include "LocklessQueue.h" + namespace android { // Executes tasks off the main thread. @@ -34,24 +32,15 @@ public: ~BackgroundExecutor(); using Callbacks = ftl::SmallVector, 10>; // Queues callbacks onto a work queue to be executed by a background thread. - // Note that this is not thread-safe - a single producer is assumed. + // This is safe to call from multiple threads. void sendCallbacks(Callbacks&& tasks); + void flushQueue(); private: sem_t mSemaphore; std::atomic_bool mDone = false; - // Sequence number for work items. - // Work items are batched by sequence number. Work items for earlier sequence numbers are - // executed first. Work items with the same sequence number are executed in the same order they - // were added to the stack (meaning the stack must reverse the order after popping from the - // queue) - int32_t mSequence = 0; - struct Work { - int32_t sequence = 0; - Callbacks tasks; - }; - LocklessStack mWorks; + LocklessQueue mCallbacksQueue; std::thread mThread; }; diff --git a/services/surfaceflinger/BufferLayer.cpp b/services/surfaceflinger/BufferLayer.cpp deleted file mode 100644 index d9c89cd821bbb674fce210de9563a1b7f63613ec..0000000000000000000000000000000000000000 --- a/services/surfaceflinger/BufferLayer.cpp +++ /dev/null @@ -1,814 +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. - */ - -// TODO(b/129481165): remove the #pragma below and fix conversion issues -#pragma clang diagnostic push -#pragma clang diagnostic ignored "-Wconversion" - -//#define LOG_NDEBUG 0 -#undef LOG_TAG -#define LOG_TAG "BufferLayer" -#define ATRACE_TAG ATRACE_TAG_GRAPHICS - -#include "BufferLayer.h" - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include -#include -#include -#include - -#include "Colorizer.h" -#include "DisplayDevice.h" -#include "FrameTracer/FrameTracer.h" -#include "LayerRejecter.h" -#include "TimeStats/TimeStats.h" - -namespace android { - -using gui::WindowInfo; - -static constexpr float defaultMaxLuminance = 1000.0; - -BufferLayer::BufferLayer(const LayerCreationArgs& args) - : Layer(args), - mTextureName(args.textureName), - mCompositionState{mFlinger->getCompositionEngine().createLayerFECompositionState()} { - ALOGV("Creating Layer %s", getDebugName()); - - mPremultipliedAlpha = !(args.flags & ISurfaceComposerClient::eNonPremultiplied); - - mPotentialCursor = args.flags & ISurfaceComposerClient::eCursorWindow; - mProtectedByApp = args.flags & ISurfaceComposerClient::eProtectedByApp; -} - -BufferLayer::~BufferLayer() { - if (!isClone()) { - // The original layer and the clone layer share the same texture. Therefore, only one of - // the layers, in this case the original layer, needs to handle the deletion. The original - // layer and the clone should be removed at the same time so there shouldn't be any issue - // with the clone layer trying to use the deleted texture. - mFlinger->deleteTextureAsync(mTextureName); - } - const int32_t layerId = getSequence(); - mFlinger->mTimeStats->onDestroy(layerId); - mFlinger->mFrameTracer->onDestroy(layerId); -} - -void BufferLayer::useSurfaceDamage() { - if (mFlinger->mForceFullDamage) { - surfaceDamageRegion = Region::INVALID_REGION; - } else { - surfaceDamageRegion = mBufferInfo.mSurfaceDamage; - } -} - -void BufferLayer::useEmptyDamage() { - surfaceDamageRegion.clear(); -} - -bool BufferLayer::isOpaque(const Layer::State& s) const { - // if we don't have a buffer or sidebandStream yet, we're translucent regardless of the - // layer's opaque flag. - if ((mSidebandStream == nullptr) && (mBufferInfo.mBuffer == nullptr)) { - return false; - } - - // if the layer has the opaque flag, then we're always opaque, - // otherwise we use the current buffer's format. - return ((s.flags & layer_state_t::eLayerOpaque) != 0) || getOpacityForFormat(getPixelFormat()); -} - -bool BufferLayer::canReceiveInput() const { - return !isHiddenByPolicy() && (mBufferInfo.mBuffer == nullptr || getAlpha() > 0.0f); -} - -bool BufferLayer::isVisible() const { - return !isHiddenByPolicy() && getAlpha() > 0.0f && - (mBufferInfo.mBuffer != nullptr || mSidebandStream != nullptr); -} - -bool BufferLayer::isFixedSize() const { - return getEffectiveScalingMode() != NATIVE_WINDOW_SCALING_MODE_FREEZE; -} - -bool BufferLayer::usesSourceCrop() const { - return true; -} - -static constexpr mat4 inverseOrientation(uint32_t transform) { - const mat4 flipH(-1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 1, 0, 0, 1); - const mat4 flipV(1, 0, 0, 0, 0, -1, 0, 0, 0, 0, 1, 0, 0, 1, 0, 1); - const mat4 rot90(0, 1, 0, 0, -1, 0, 0, 0, 0, 0, 1, 0, 1, 0, 0, 1); - mat4 tr; - - if (transform & NATIVE_WINDOW_TRANSFORM_ROT_90) { - tr = tr * rot90; - } - if (transform & NATIVE_WINDOW_TRANSFORM_FLIP_H) { - tr = tr * flipH; - } - if (transform & NATIVE_WINDOW_TRANSFORM_FLIP_V) { - tr = tr * flipV; - } - return inverse(tr); -} - -std::optional BufferLayer::prepareClientComposition( - compositionengine::LayerFE::ClientCompositionTargetSettings& targetSettings) { - ATRACE_CALL(); - - std::optional result = - Layer::prepareClientComposition(targetSettings); - if (!result) { - return result; - } - - if (CC_UNLIKELY(mBufferInfo.mBuffer == 0) && mSidebandStream != nullptr) { - // For surfaceview of tv sideband, there is no activeBuffer - // in bufferqueue, we need return LayerSettings. - return result; - } - const bool blackOutLayer = (isProtected() && !targetSettings.supportsProtectedContent) || - ((isSecure() || isProtected()) && !targetSettings.isSecure); - const bool bufferCanBeUsedAsHwTexture = - mBufferInfo.mBuffer->getUsage() & GraphicBuffer::USAGE_HW_TEXTURE; - compositionengine::LayerFE::LayerSettings& layer = *result; - if (blackOutLayer || !bufferCanBeUsedAsHwTexture) { - ALOGE_IF(!bufferCanBeUsedAsHwTexture, "%s is blacked out as buffer is not gpu readable", - mName.c_str()); - prepareClearClientComposition(layer, true /* blackout */); - return layer; - } - - const State& s(getDrawingState()); - layer.source.buffer.buffer = mBufferInfo.mBuffer; - layer.source.buffer.isOpaque = isOpaque(s); - layer.source.buffer.fence = mBufferInfo.mFence; - layer.source.buffer.textureName = mTextureName; - layer.source.buffer.usePremultipliedAlpha = getPremultipledAlpha(); - layer.source.buffer.isY410BT2020 = isHdrY410(); - bool hasSmpte2086 = mBufferInfo.mHdrMetadata.validTypes & HdrMetadata::SMPTE2086; - bool hasCta861_3 = mBufferInfo.mHdrMetadata.validTypes & HdrMetadata::CTA861_3; - float maxLuminance = 0.f; - if (hasSmpte2086 && hasCta861_3) { - maxLuminance = std::min(mBufferInfo.mHdrMetadata.smpte2086.maxLuminance, - mBufferInfo.mHdrMetadata.cta8613.maxContentLightLevel); - } else if (hasSmpte2086) { - maxLuminance = mBufferInfo.mHdrMetadata.smpte2086.maxLuminance; - } else if (hasCta861_3) { - maxLuminance = mBufferInfo.mHdrMetadata.cta8613.maxContentLightLevel; - } else { - switch (layer.sourceDataspace & HAL_DATASPACE_TRANSFER_MASK) { - case HAL_DATASPACE_TRANSFER_ST2084: - case HAL_DATASPACE_TRANSFER_HLG: - // Behavior-match previous releases for HDR content - maxLuminance = defaultMaxLuminance; - break; - } - } - layer.source.buffer.maxLuminanceNits = maxLuminance; - layer.frameNumber = mCurrentFrameNumber; - layer.bufferId = mBufferInfo.mBuffer ? mBufferInfo.mBuffer->getId() : 0; - - const bool useFiltering = - targetSettings.needsFiltering || mNeedsFiltering || bufferNeedsFiltering(); - - // Query the texture matrix given our current filtering mode. - float textureMatrix[16]; - getDrawingTransformMatrix(useFiltering, textureMatrix); - - if (getTransformToDisplayInverse()) { - /* - * the code below applies the primary display's inverse transform to - * the texture transform - */ - uint32_t transform = DisplayDevice::getPrimaryDisplayRotationFlags(); - mat4 tr = inverseOrientation(transform); - - /** - * TODO(b/36727915): This is basically a hack. - * - * Ensure that regardless of the parent transformation, - * this buffer is always transformed from native display - * orientation to display orientation. For example, in the case - * of a camera where the buffer remains in native orientation, - * we want the pixels to always be upright. - */ - sp p = mDrawingParent.promote(); - if (p != nullptr) { - const auto parentTransform = p->getTransform(); - tr = tr * inverseOrientation(parentTransform.getOrientation()); - } - - // and finally apply it to the original texture matrix - const mat4 texTransform(mat4(static_cast(textureMatrix)) * tr); - memcpy(textureMatrix, texTransform.asArray(), sizeof(textureMatrix)); - } - - const Rect win{getBounds()}; - float bufferWidth = getBufferSize(s).getWidth(); - float bufferHeight = getBufferSize(s).getHeight(); - - // BufferStateLayers can have a "buffer size" of [0, 0, -1, -1] when no display frame has - // been set and there is no parent layer bounds. In that case, the scale is meaningless so - // ignore them. - if (!getBufferSize(s).isValid()) { - bufferWidth = float(win.right) - float(win.left); - bufferHeight = float(win.bottom) - float(win.top); - } - - const float scaleHeight = (float(win.bottom) - float(win.top)) / bufferHeight; - const float scaleWidth = (float(win.right) - float(win.left)) / bufferWidth; - const float translateY = float(win.top) / bufferHeight; - const float translateX = float(win.left) / bufferWidth; - - // Flip y-coordinates because GLConsumer expects OpenGL convention. - mat4 tr = mat4::translate(vec4(.5, .5, 0, 1)) * mat4::scale(vec4(1, -1, 1, 1)) * - mat4::translate(vec4(-.5, -.5, 0, 1)) * - mat4::translate(vec4(translateX, translateY, 0, 1)) * - mat4::scale(vec4(scaleWidth, scaleHeight, 1.0, 1.0)); - - layer.source.buffer.useTextureFiltering = useFiltering; - layer.source.buffer.textureTransform = mat4(static_cast(textureMatrix)) * tr; - - return layer; -} - -bool BufferLayer::isHdrY410() const { - // pixel format is HDR Y410 masquerading as RGBA_1010102 - return (mBufferInfo.mDataspace == ui::Dataspace::BT2020_ITU_PQ && - mBufferInfo.mApi == NATIVE_WINDOW_API_MEDIA && - mBufferInfo.mPixelFormat == HAL_PIXEL_FORMAT_RGBA_1010102); -} - -sp BufferLayer::getCompositionEngineLayerFE() const { - return asLayerFE(); -} - -compositionengine::LayerFECompositionState* BufferLayer::editCompositionState() { - return mCompositionState.get(); -} - -const compositionengine::LayerFECompositionState* BufferLayer::getCompositionState() const { - return mCompositionState.get(); -} - -void BufferLayer::preparePerFrameCompositionState() { - Layer::preparePerFrameCompositionState(); - - // Sideband layers - auto* compositionState = editCompositionState(); - if (compositionState->sidebandStream.get() && !compositionState->sidebandStreamHasFrame) { - compositionState->compositionType = - aidl::android::hardware::graphics::composer3::Composition::SIDEBAND; - return; - } else if ((mDrawingState.flags & layer_state_t::eLayerIsDisplayDecoration) != 0) { - compositionState->compositionType = - aidl::android::hardware::graphics::composer3::Composition::DISPLAY_DECORATION; - } else { - // Normal buffer layers - compositionState->hdrMetadata = mBufferInfo.mHdrMetadata; - compositionState->compositionType = mPotentialCursor - ? aidl::android::hardware::graphics::composer3::Composition::CURSOR - : aidl::android::hardware::graphics::composer3::Composition::DEVICE; - } - - compositionState->buffer = getBuffer(); - compositionState->bufferSlot = (mBufferInfo.mBufferSlot == BufferQueue::INVALID_BUFFER_SLOT) - ? 0 - : mBufferInfo.mBufferSlot; - compositionState->acquireFence = mBufferInfo.mFence; - compositionState->frameNumber = mBufferInfo.mFrameNumber; - compositionState->sidebandStreamHasFrame = false; -} - -bool BufferLayer::onPreComposition(nsecs_t) { - return hasReadyFrame(); -} -namespace { -TimeStats::SetFrameRateVote frameRateToSetFrameRateVotePayload(Layer::FrameRate frameRate) { - using FrameRateCompatibility = TimeStats::SetFrameRateVote::FrameRateCompatibility; - using Seamlessness = TimeStats::SetFrameRateVote::Seamlessness; - const auto frameRateCompatibility = [frameRate] { - switch (frameRate.type) { - case Layer::FrameRateCompatibility::Default: - return FrameRateCompatibility::Default; - case Layer::FrameRateCompatibility::ExactOrMultiple: - return FrameRateCompatibility::ExactOrMultiple; - default: - return FrameRateCompatibility::Undefined; - } - }(); - - const auto seamlessness = [frameRate] { - switch (frameRate.seamlessness) { - case scheduler::Seamlessness::OnlySeamless: - return Seamlessness::ShouldBeSeamless; - case scheduler::Seamlessness::SeamedAndSeamless: - return Seamlessness::NotRequired; - default: - return Seamlessness::Undefined; - } - }(); - - return TimeStats::SetFrameRateVote{.frameRate = frameRate.rate.getValue(), - .frameRateCompatibility = frameRateCompatibility, - .seamlessness = seamlessness}; -} -} // namespace - -void BufferLayer::onPostComposition(const DisplayDevice* display, - const std::shared_ptr& glDoneFence, - const std::shared_ptr& presentFence, - const CompositorTiming& compositorTiming) { - // mFrameLatencyNeeded is true when a new frame was latched for the - // composition. - if (!mBufferInfo.mFrameLatencyNeeded) return; - - // Update mFrameEventHistory. - finalizeFrameEventHistory(glDoneFence, compositorTiming); - - // Update mFrameTracker. - nsecs_t desiredPresentTime = mBufferInfo.mDesiredPresentTime; - mFrameTracker.setDesiredPresentTime(desiredPresentTime); - - const int32_t layerId = getSequence(); - mFlinger->mTimeStats->setDesiredTime(layerId, mCurrentFrameNumber, desiredPresentTime); - - const auto outputLayer = findOutputLayerForDisplay(display); - if (outputLayer && outputLayer->requiresClientComposition()) { - nsecs_t clientCompositionTimestamp = outputLayer->getState().clientCompositionTimestamp; - mFlinger->mFrameTracer->traceTimestamp(layerId, getCurrentBufferId(), mCurrentFrameNumber, - clientCompositionTimestamp, - FrameTracer::FrameEvent::FALLBACK_COMPOSITION); - // Update the SurfaceFrames in the drawing state - if (mDrawingState.bufferSurfaceFrameTX) { - mDrawingState.bufferSurfaceFrameTX->setGpuComposition(); - } - for (auto& [token, surfaceFrame] : mDrawingState.bufferlessSurfaceFramesTX) { - surfaceFrame->setGpuComposition(); - } - } - - std::shared_ptr frameReadyFence = mBufferInfo.mFenceTime; - if (frameReadyFence->isValid()) { - mFrameTracker.setFrameReadyFence(std::move(frameReadyFence)); - } else { - // There was no fence for this frame, so assume that it was ready - // to be presented at the desired present time. - mFrameTracker.setFrameReadyTime(desiredPresentTime); - } - - if (display) { - const Fps refreshRate = display->refreshRateConfigs().getActiveMode()->getFps(); - const std::optional renderRate = - mFlinger->mScheduler->getFrameRateOverride(getOwnerUid()); - - const auto vote = frameRateToSetFrameRateVotePayload(mDrawingState.frameRate); - const auto gameMode = getGameMode(); - - if (presentFence->isValid()) { - mFlinger->mTimeStats->setPresentFence(layerId, mCurrentFrameNumber, presentFence, - refreshRate, renderRate, vote, gameMode); - mFlinger->mFrameTracer->traceFence(layerId, getCurrentBufferId(), mCurrentFrameNumber, - presentFence, - FrameTracer::FrameEvent::PRESENT_FENCE); - mFrameTracker.setActualPresentFence(std::shared_ptr(presentFence)); - } else if (const auto displayId = PhysicalDisplayId::tryCast(display->getId()); - displayId && mFlinger->getHwComposer().isConnected(*displayId)) { - // The HWC doesn't support present fences, so use the refresh - // timestamp instead. - const nsecs_t actualPresentTime = display->getRefreshTimestamp(); - mFlinger->mTimeStats->setPresentTime(layerId, mCurrentFrameNumber, actualPresentTime, - refreshRate, renderRate, vote, gameMode); - mFlinger->mFrameTracer->traceTimestamp(layerId, getCurrentBufferId(), - mCurrentFrameNumber, actualPresentTime, - FrameTracer::FrameEvent::PRESENT_FENCE); - mFrameTracker.setActualPresentTime(actualPresentTime); - } - } - - mFrameTracker.advanceFrame(); - mBufferInfo.mFrameLatencyNeeded = false; -} - -void BufferLayer::gatherBufferInfo() { - mBufferInfo.mPixelFormat = - !mBufferInfo.mBuffer ? PIXEL_FORMAT_NONE : mBufferInfo.mBuffer->getPixelFormat(); - mBufferInfo.mFrameLatencyNeeded = true; -} - -bool BufferLayer::shouldPresentNow(nsecs_t expectedPresentTime) const { - // If this is not a valid vsync for the layer's uid, return and try again later - const bool isVsyncValidForUid = - mFlinger->mScheduler->isVsyncValid(expectedPresentTime, mOwnerUid); - if (!isVsyncValidForUid) { - ATRACE_NAME("!isVsyncValidForUid"); - return false; - } - - // AutoRefresh layers and sideband streams should always be presented - if (getSidebandStreamChanged() || getAutoRefresh()) { - return true; - } - - // If this layer doesn't have a frame is shouldn't be presented - if (!hasFrameUpdate()) { - return false; - } - - // Defer to the derived class to decide whether the next buffer is due for - // presentation. - return isBufferDue(expectedPresentTime); -} - -bool BufferLayer::latchBuffer(bool& recomputeVisibleRegions, nsecs_t latchTime, - nsecs_t expectedPresentTime) { - ATRACE_CALL(); - - bool refreshRequired = latchSidebandStream(recomputeVisibleRegions); - - if (refreshRequired) { - return refreshRequired; - } - - // If the head buffer's acquire fence hasn't signaled yet, return and - // try again later - if (!fenceHasSignaled()) { - ATRACE_NAME("!fenceHasSignaled()"); - mFlinger->onLayerUpdate(); - return false; - } - - // Capture the old state of the layer for comparisons later - const State& s(getDrawingState()); - const bool oldOpacity = isOpaque(s); - - BufferInfo oldBufferInfo = mBufferInfo; - - status_t err = updateTexImage(recomputeVisibleRegions, latchTime, expectedPresentTime); - if (err != NO_ERROR) { - return false; - } - - err = updateActiveBuffer(); - if (err != NO_ERROR) { - return false; - } - - err = updateFrameNumber(); - if (err != NO_ERROR) { - return false; - } - - gatherBufferInfo(); - - if (oldBufferInfo.mBuffer == nullptr) { - // the first time we receive a buffer, we need to trigger a - // geometry invalidation. - recomputeVisibleRegions = true; - } - - if ((mBufferInfo.mCrop != oldBufferInfo.mCrop) || - (mBufferInfo.mTransform != oldBufferInfo.mTransform) || - (mBufferInfo.mScaleMode != oldBufferInfo.mScaleMode) || - (mBufferInfo.mTransformToDisplayInverse != oldBufferInfo.mTransformToDisplayInverse)) { - recomputeVisibleRegions = true; - } - - if (oldBufferInfo.mBuffer != nullptr) { - uint32_t bufWidth = mBufferInfo.mBuffer->getWidth(); - uint32_t bufHeight = mBufferInfo.mBuffer->getHeight(); - if (bufWidth != oldBufferInfo.mBuffer->getWidth() || - bufHeight != oldBufferInfo.mBuffer->getHeight()) { - recomputeVisibleRegions = true; - } - } - - if (oldOpacity != isOpaque(s)) { - recomputeVisibleRegions = true; - } - - return true; -} - -bool BufferLayer::hasReadyFrame() const { - return hasFrameUpdate() || getSidebandStreamChanged() || getAutoRefresh(); -} - -uint32_t BufferLayer::getEffectiveScalingMode() const { - return mBufferInfo.mScaleMode; -} - -bool BufferLayer::isProtected() const { - return (mBufferInfo.mBuffer != nullptr) && - (mBufferInfo.mBuffer->getUsage() & GRALLOC_USAGE_PROTECTED); -} - -// As documented in libhardware header, formats in the range -// 0x100 - 0x1FF are specific to the HAL implementation, and -// are known to have no alpha channel -// TODO: move definition for device-specific range into -// hardware.h, instead of using hard-coded values here. -#define HARDWARE_IS_DEVICE_FORMAT(f) ((f) >= 0x100 && (f) <= 0x1FF) - -bool BufferLayer::getOpacityForFormat(PixelFormat format) { - if (HARDWARE_IS_DEVICE_FORMAT(format)) { - return true; - } - switch (format) { - case PIXEL_FORMAT_RGBA_8888: - case PIXEL_FORMAT_BGRA_8888: - case PIXEL_FORMAT_RGBA_FP16: - case PIXEL_FORMAT_RGBA_1010102: - case PIXEL_FORMAT_R_8: - return false; - } - // in all other case, we have no blending (also for unknown formats) - return true; -} - -bool BufferLayer::needsFiltering(const DisplayDevice* display) const { - const auto outputLayer = findOutputLayerForDisplay(display); - if (outputLayer == nullptr) { - return false; - } - - // We need filtering if the sourceCrop rectangle size does not match the - // displayframe rectangle size (not a 1:1 render) - const auto& compositionState = outputLayer->getState(); - const auto displayFrame = compositionState.displayFrame; - const auto sourceCrop = compositionState.sourceCrop; - return sourceCrop.getHeight() != displayFrame.getHeight() || - sourceCrop.getWidth() != displayFrame.getWidth(); -} - -bool BufferLayer::needsFilteringForScreenshots(const DisplayDevice* display, - const ui::Transform& inverseParentTransform) const { - const auto outputLayer = findOutputLayerForDisplay(display); - if (outputLayer == nullptr) { - return false; - } - - // We need filtering if the sourceCrop rectangle size does not match the - // viewport rectangle size (not a 1:1 render) - const auto& compositionState = outputLayer->getState(); - const ui::Transform& displayTransform = display->getTransform(); - const ui::Transform inverseTransform = inverseParentTransform * displayTransform.inverse(); - // Undo the transformation of the displayFrame so that we're back into - // layer-stack space. - const Rect frame = inverseTransform.transform(compositionState.displayFrame); - const FloatRect sourceCrop = compositionState.sourceCrop; - - int32_t frameHeight = frame.getHeight(); - int32_t frameWidth = frame.getWidth(); - // If the display transform had a rotational component then undo the - // rotation so that the orientation matches the source crop. - if (displayTransform.getOrientation() & ui::Transform::ROT_90) { - std::swap(frameHeight, frameWidth); - } - return sourceCrop.getHeight() != frameHeight || sourceCrop.getWidth() != frameWidth; -} - -Rect BufferLayer::getBufferSize(const State& s) const { - // If we have a sideband stream, or we are scaling the buffer then return the layer size since - // we cannot determine the buffer size. - if ((s.sidebandStream != nullptr) || - (getEffectiveScalingMode() != NATIVE_WINDOW_SCALING_MODE_FREEZE)) { - return Rect(getActiveWidth(s), getActiveHeight(s)); - } - - if (mBufferInfo.mBuffer == nullptr) { - return Rect::INVALID_RECT; - } - - uint32_t bufWidth = mBufferInfo.mBuffer->getWidth(); - uint32_t bufHeight = mBufferInfo.mBuffer->getHeight(); - - // Undo any transformations on the buffer and return the result. - if (mBufferInfo.mTransform & ui::Transform::ROT_90) { - std::swap(bufWidth, bufHeight); - } - - if (getTransformToDisplayInverse()) { - uint32_t invTransform = DisplayDevice::getPrimaryDisplayRotationFlags(); - if (invTransform & ui::Transform::ROT_90) { - std::swap(bufWidth, bufHeight); - } - } - - return Rect(bufWidth, bufHeight); -} - -FloatRect BufferLayer::computeSourceBounds(const FloatRect& parentBounds) const { - const State& s(getDrawingState()); - - // If we have a sideband stream, or we are scaling the buffer then return the layer size since - // we cannot determine the buffer size. - if ((s.sidebandStream != nullptr) || - (getEffectiveScalingMode() != NATIVE_WINDOW_SCALING_MODE_FREEZE)) { - return FloatRect(0, 0, getActiveWidth(s), getActiveHeight(s)); - } - - if (mBufferInfo.mBuffer == nullptr) { - return parentBounds; - } - - uint32_t bufWidth = mBufferInfo.mBuffer->getWidth(); - uint32_t bufHeight = mBufferInfo.mBuffer->getHeight(); - - // Undo any transformations on the buffer and return the result. - if (mBufferInfo.mTransform & ui::Transform::ROT_90) { - std::swap(bufWidth, bufHeight); - } - - if (getTransformToDisplayInverse()) { - uint32_t invTransform = DisplayDevice::getPrimaryDisplayRotationFlags(); - if (invTransform & ui::Transform::ROT_90) { - std::swap(bufWidth, bufHeight); - } - } - - return FloatRect(0, 0, bufWidth, bufHeight); -} - -void BufferLayer::latchAndReleaseBuffer() { - if (hasReadyFrame()) { - bool ignored = false; - latchBuffer(ignored, systemTime(), 0 /* expectedPresentTime */); - } - releasePendingBuffer(systemTime()); -} - -PixelFormat BufferLayer::getPixelFormat() const { - return mBufferInfo.mPixelFormat; -} - -bool BufferLayer::getTransformToDisplayInverse() const { - return mBufferInfo.mTransformToDisplayInverse; -} - -Rect BufferLayer::getBufferCrop() const { - // this is the crop rectangle that applies to the buffer - // itself (as opposed to the window) - if (!mBufferInfo.mCrop.isEmpty()) { - // if the buffer crop is defined, we use that - return mBufferInfo.mCrop; - } else if (mBufferInfo.mBuffer != nullptr) { - // otherwise we use the whole buffer - return mBufferInfo.mBuffer->getBounds(); - } else { - // if we don't have a buffer yet, we use an empty/invalid crop - return Rect(); - } -} - -uint32_t BufferLayer::getBufferTransform() const { - return mBufferInfo.mTransform; -} - -ui::Dataspace BufferLayer::getDataSpace() const { - return mBufferInfo.mDataspace; -} - -ui::Dataspace BufferLayer::translateDataspace(ui::Dataspace dataspace) { - ui::Dataspace updatedDataspace = dataspace; - // translate legacy dataspaces to modern dataspaces - switch (dataspace) { - case ui::Dataspace::SRGB: - updatedDataspace = ui::Dataspace::V0_SRGB; - break; - case ui::Dataspace::SRGB_LINEAR: - updatedDataspace = ui::Dataspace::V0_SRGB_LINEAR; - break; - case ui::Dataspace::JFIF: - updatedDataspace = ui::Dataspace::V0_JFIF; - break; - case ui::Dataspace::BT601_625: - updatedDataspace = ui::Dataspace::V0_BT601_625; - break; - case ui::Dataspace::BT601_525: - updatedDataspace = ui::Dataspace::V0_BT601_525; - break; - case ui::Dataspace::BT709: - updatedDataspace = ui::Dataspace::V0_BT709; - break; - default: - break; - } - - return updatedDataspace; -} - -sp BufferLayer::getBuffer() const { - return mBufferInfo.mBuffer ? mBufferInfo.mBuffer->getBuffer() : nullptr; -} - -void BufferLayer::getDrawingTransformMatrix(bool filteringEnabled, float outMatrix[16]) { - GLConsumer::computeTransformMatrix(outMatrix, - mBufferInfo.mBuffer ? mBufferInfo.mBuffer->getBuffer() - : nullptr, - mBufferInfo.mCrop, mBufferInfo.mTransform, filteringEnabled); -} - -void BufferLayer::setInitialValuesForClone(const sp& clonedFrom) { - Layer::setInitialValuesForClone(clonedFrom); - - sp bufferClonedFrom = static_cast(clonedFrom.get()); - mPremultipliedAlpha = bufferClonedFrom->mPremultipliedAlpha; - mPotentialCursor = bufferClonedFrom->mPotentialCursor; - mProtectedByApp = bufferClonedFrom->mProtectedByApp; - - updateCloneBufferInfo(); -} - -void BufferLayer::updateCloneBufferInfo() { - if (!isClone() || !isClonedFromAlive()) { - return; - } - - sp clonedFrom = static_cast(getClonedFrom().get()); - mBufferInfo = clonedFrom->mBufferInfo; - mSidebandStream = clonedFrom->mSidebandStream; - surfaceDamageRegion = clonedFrom->surfaceDamageRegion; - mCurrentFrameNumber = clonedFrom->mCurrentFrameNumber.load(); - mPreviousFrameNumber = clonedFrom->mPreviousFrameNumber; - - // After buffer info is updated, the drawingState from the real layer needs to be copied into - // the cloned. This is because some properties of drawingState can change when latchBuffer is - // called. However, copying the drawingState would also overwrite the cloned layer's relatives - // and touchableRegionCrop. Therefore, temporarily store the relatives so they can be set in - // the cloned drawingState again. - wp tmpZOrderRelativeOf = mDrawingState.zOrderRelativeOf; - SortedVector> tmpZOrderRelatives = mDrawingState.zOrderRelatives; - wp tmpTouchableRegionCrop = mDrawingState.touchableRegionCrop; - WindowInfo tmpInputInfo = mDrawingState.inputInfo; - - cloneDrawingState(clonedFrom.get()); - - mDrawingState.touchableRegionCrop = tmpTouchableRegionCrop; - mDrawingState.zOrderRelativeOf = tmpZOrderRelativeOf; - mDrawingState.zOrderRelatives = tmpZOrderRelatives; - mDrawingState.inputInfo = tmpInputInfo; -} - -void BufferLayer::setTransformHint(ui::Transform::RotationFlags displayTransformHint) { - mTransformHint = getFixedTransformHint(); - if (mTransformHint == ui::Transform::ROT_INVALID) { - mTransformHint = displayTransformHint; - } -} - -bool BufferLayer::bufferNeedsFiltering() const { - return isFixedSize(); -} - -const std::shared_ptr& BufferLayer::getExternalTexture() const { - return mBufferInfo.mBuffer; -} - -} // namespace android - -#if defined(__gl_h_) -#error "don't include gl/gl.h in this file" -#endif - -#if defined(__gl2_h_) -#error "don't include gl2/gl2.h in this file" -#endif - -// TODO(b/129481165): remove the #pragma below and fix conversion issues -#pragma clang diagnostic pop // ignored "-Wconversion" diff --git a/services/surfaceflinger/BufferLayer.h b/services/surfaceflinger/BufferLayer.h deleted file mode 100644 index 4c70eb58bff8d0b8a96583415095f219ffca68e8..0000000000000000000000000000000000000000 --- a/services/surfaceflinger/BufferLayer.h +++ /dev/null @@ -1,222 +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. - */ - -#pragma once - -#include -#include -#include - -#include -#include -#include -#include -#include -#include // For NATIVE_WINDOW_SCALING_MODE_FREEZE -#include -#include -#include -#include -#include -#include -#include - -#include "BufferLayerConsumer.h" -#include "Client.h" -#include "DisplayHardware/HWComposer.h" -#include "FrameTimeline.h" -#include "FrameTracker.h" -#include "Layer.h" -#include "LayerVector.h" -#include "MonitoredProducer.h" -#include "SurfaceFlinger.h" - -namespace android { - -class BufferLayer : public Layer { -public: - explicit BufferLayer(const LayerCreationArgs& args); - virtual ~BufferLayer() override; - - // Implements Layer. - sp getCompositionEngineLayerFE() const override; - compositionengine::LayerFECompositionState* editCompositionState() override; - - // If we have received a new buffer this frame, we will pass its surface - // damage down to hardware composer. Otherwise, we must send a region with - // one empty rect. - void useSurfaceDamage() override; - void useEmptyDamage() override; - - bool isOpaque(const Layer::State& s) const override; - bool canReceiveInput() const override; - - // isVisible - true if this layer is visible, false otherwise - bool isVisible() const override; - - // isProtected - true if the layer may contain protected content in the - // GRALLOC_USAGE_PROTECTED sense. - bool isProtected() const override; - - // isFixedSize - true if content has a fixed size - bool isFixedSize() const override; - - bool usesSourceCrop() const override; - - bool isHdrY410() const override; - - void onPostComposition(const DisplayDevice*, const std::shared_ptr& glDoneFence, - const std::shared_ptr& presentFence, - const CompositorTiming&) override; - - // latchBuffer - called each time the screen is redrawn and returns whether - // the visible regions need to be recomputed (this is a fairly heavy - // operation, so this should be set only if needed). Typically this is used - // to figure out if the content or size of a surface has changed. - bool latchBuffer(bool& recomputeVisibleRegions, nsecs_t latchTime, - nsecs_t expectedPresentTime) override; - bool hasReadyFrame() const override; - - // Returns the current scaling mode - uint32_t getEffectiveScalingMode() const override; - - // Calls latchBuffer if the buffer has a frame queued and then releases the buffer. - // This is used if the buffer is just latched and releases to free up the buffer - // and will not be shown on screen. - // Should only be called on the main thread. - void latchAndReleaseBuffer() override; - - bool getTransformToDisplayInverse() const override; - - Rect getBufferCrop() const override; - - uint32_t getBufferTransform() const override; - - ui::Dataspace getDataSpace() const override; - - sp getBuffer() const override; - const std::shared_ptr& getExternalTexture() const override; - - ui::Transform::RotationFlags getTransformHint() const override { return mTransformHint; } - - // Returns true if the transformed buffer size does not match the layer size and we need - // to apply filtering. - virtual bool bufferNeedsFiltering() const; - -protected: - struct BufferInfo { - nsecs_t mDesiredPresentTime; - std::shared_ptr mFenceTime; - sp mFence; - uint32_t mTransform{0}; - ui::Dataspace mDataspace{ui::Dataspace::UNKNOWN}; - Rect mCrop; - uint32_t mScaleMode{NATIVE_WINDOW_SCALING_MODE_FREEZE}; - Region mSurfaceDamage; - HdrMetadata mHdrMetadata; - int mApi; - PixelFormat mPixelFormat{PIXEL_FORMAT_NONE}; - bool mTransformToDisplayInverse{false}; - - std::shared_ptr mBuffer; - uint64_t mFrameNumber; - int mBufferSlot{BufferQueue::INVALID_BUFFER_SLOT}; - - bool mFrameLatencyNeeded{false}; - }; - - BufferInfo mBufferInfo; - virtual void gatherBufferInfo() = 0; - - std::optional prepareClientComposition( - compositionengine::LayerFE::ClientCompositionTargetSettings&) override; - - /* - * compositionengine::LayerFE overrides - */ - const compositionengine::LayerFECompositionState* getCompositionState() const override; - bool onPreComposition(nsecs_t) override; - void preparePerFrameCompositionState() override; - - static bool getOpacityForFormat(PixelFormat format); - - // from graphics API - const uint32_t mTextureName; - ui::Dataspace translateDataspace(ui::Dataspace dataspace); - void setInitialValuesForClone(const sp& clonedFrom); - void updateCloneBufferInfo() override; - uint64_t mPreviousFrameNumber = 0; - - void setTransformHint(ui::Transform::RotationFlags displayTransformHint) override; - - // Transform hint provided to the producer. This must be accessed holding - // the mStateLock. - ui::Transform::RotationFlags mTransformHint = ui::Transform::ROT_0; - - bool getAutoRefresh() const { return mDrawingState.autoRefresh; } - bool getSidebandStreamChanged() const { return mSidebandStreamChanged; } - - // Returns true if the next buffer should be presented at the expected present time - bool shouldPresentNow(nsecs_t expectedPresentTime) const; - - // Returns true if the next buffer should be presented at the expected present time, - // overridden by BufferStateLayer and BufferQueueLayer for implementation - // specific logic - virtual bool isBufferDue(nsecs_t /*expectedPresentTime*/) const = 0; - - std::atomic mSidebandStreamChanged{false}; - -private: - virtual bool fenceHasSignaled() const = 0; - virtual bool framePresentTimeIsCurrent(nsecs_t expectedPresentTime) const = 0; - - // Latch sideband stream and returns true if the dirty region should be updated. - virtual bool latchSidebandStream(bool& recomputeVisibleRegions) = 0; - - virtual bool hasFrameUpdate() const = 0; - - virtual status_t updateTexImage(bool& recomputeVisibleRegions, nsecs_t latchTime, - nsecs_t expectedPresentTime) = 0; - - virtual status_t updateActiveBuffer() = 0; - virtual status_t updateFrameNumber() = 0; - - // We generate InputWindowHandles for all non-cursor buffered layers regardless of whether they - // have an InputChannel. This is to enable the InputDispatcher to do PID based occlusion - // detection. - bool needsInputInfo() const override { return !mPotentialCursor; } - - // Returns true if this layer requires filtering - bool needsFiltering(const DisplayDevice*) const override; - bool needsFilteringForScreenshots(const DisplayDevice*, - const ui::Transform& inverseParentTransform) const override; - - // BufferStateLayers can return Rect::INVALID_RECT if the layer does not have a display frame - // and its parent layer is not bounded - Rect getBufferSize(const State& s) const override; - - PixelFormat getPixelFormat() const; - - // Computes the transform matrix using the setFilteringEnabled to determine whether the - // transform matrix should be computed for use with bilinear filtering. - void getDrawingTransformMatrix(bool filteringEnabled, float outMatrix[16]); - - std::unique_ptr mCompositionState; - - FloatRect computeSourceBounds(const FloatRect& parentBounds) const override; -}; - -} // namespace android diff --git a/services/surfaceflinger/BufferLayerConsumer.cpp b/services/surfaceflinger/BufferLayerConsumer.cpp deleted file mode 100644 index a1035f01bfbad347072cf061d1d9ee498393a9c3..0000000000000000000000000000000000000000 --- a/services/surfaceflinger/BufferLayerConsumer.cpp +++ /dev/null @@ -1,500 +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. - */ - -// TODO(b/129481165): remove the #pragma below and fix conversion issues -#pragma clang diagnostic push -#pragma clang diagnostic ignored "-Wconversion" - -#undef LOG_TAG -#define LOG_TAG "BufferLayerConsumer" -#define ATRACE_TAG ATRACE_TAG_GRAPHICS -//#define LOG_NDEBUG 0 - -#include "BufferLayerConsumer.h" -#include "Layer.h" -#include "Scheduler/VsyncController.h" - -#include - -#include - -#include - -#include - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -namespace android { - -// Macros for including the BufferLayerConsumer name in log messages -#define BLC_LOGV(x, ...) ALOGV("[%s] " x, mName.c_str(), ##__VA_ARGS__) -#define BLC_LOGD(x, ...) ALOGD("[%s] " x, mName.c_str(), ##__VA_ARGS__) -// #define BLC_LOGI(x, ...) ALOGI("[%s] " x, mName.c_str(), ##__VA_ARGS__) -#define BLC_LOGW(x, ...) ALOGW("[%s] " x, mName.c_str(), ##__VA_ARGS__) -#define BLC_LOGE(x, ...) ALOGE("[%s] " x, mName.c_str(), ##__VA_ARGS__) - -static const mat4 mtxIdentity; - -BufferLayerConsumer::BufferLayerConsumer(const sp& bq, - renderengine::RenderEngine& engine, uint32_t tex, - Layer* layer) - : ConsumerBase(bq, false), - mCurrentCrop(Rect::EMPTY_RECT), - mCurrentTransform(0), - mCurrentScalingMode(NATIVE_WINDOW_SCALING_MODE_FREEZE), - mCurrentFence(Fence::NO_FENCE), - mCurrentTimestamp(0), - mCurrentDataSpace(ui::Dataspace::UNKNOWN), - mCurrentFrameNumber(0), - mCurrentTransformToDisplayInverse(false), - mCurrentSurfaceDamage(), - mCurrentApi(0), - mDefaultWidth(1), - mDefaultHeight(1), - mFilteringEnabled(true), - mRE(engine), - mTexName(tex), - mLayer(layer), - mCurrentTexture(BufferQueue::INVALID_BUFFER_SLOT) { - BLC_LOGV("BufferLayerConsumer"); - - memcpy(mCurrentTransformMatrix, mtxIdentity.asArray(), sizeof(mCurrentTransformMatrix)); - - mConsumer->setConsumerUsageBits(DEFAULT_USAGE_FLAGS); -} - -status_t BufferLayerConsumer::setDefaultBufferSize(uint32_t w, uint32_t h) { - Mutex::Autolock lock(mMutex); - if (mAbandoned) { - BLC_LOGE("setDefaultBufferSize: BufferLayerConsumer is abandoned!"); - return NO_INIT; - } - mDefaultWidth = w; - mDefaultHeight = h; - return mConsumer->setDefaultBufferSize(w, h); -} - -void BufferLayerConsumer::setContentsChangedListener(const wp& listener) { - setFrameAvailableListener(listener); - Mutex::Autolock lock(mMutex); - mContentsChangedListener = listener; -} - -status_t BufferLayerConsumer::updateTexImage(BufferRejecter* rejecter, nsecs_t expectedPresentTime, - bool* autoRefresh, bool* queuedBuffer, - uint64_t maxFrameNumber) { - ATRACE_CALL(); - BLC_LOGV("updateTexImage"); - Mutex::Autolock lock(mMutex); - - if (mAbandoned) { - BLC_LOGE("updateTexImage: BufferLayerConsumer is abandoned!"); - return NO_INIT; - } - - BufferItem item; - - // Acquire the next buffer. - // In asynchronous mode the list is guaranteed to be one buffer - // deep, while in synchronous mode we use the oldest buffer. - status_t err = acquireBufferLocked(&item, expectedPresentTime, maxFrameNumber); - if (err != NO_ERROR) { - if (err == BufferQueue::NO_BUFFER_AVAILABLE) { - err = NO_ERROR; - } else if (err == BufferQueue::PRESENT_LATER) { - // return the error, without logging - } else { - BLC_LOGE("updateTexImage: acquire failed: %s (%d)", strerror(-err), err); - } - return err; - } - - if (autoRefresh) { - *autoRefresh = item.mAutoRefresh; - } - - if (queuedBuffer) { - *queuedBuffer = item.mQueuedBuffer; - } - - // We call the rejecter here, in case the caller has a reason to - // not accept this buffer. This is used by SurfaceFlinger to - // reject buffers which have the wrong size - int slot = item.mSlot; - if (rejecter && rejecter->reject(mSlots[slot].mGraphicBuffer, item)) { - releaseBufferLocked(slot, mSlots[slot].mGraphicBuffer); - return BUFFER_REJECTED; - } - - // Release the previous buffer. - err = updateAndReleaseLocked(item, &mPendingRelease); - if (err != NO_ERROR) { - return err; - } - return err; -} - -void BufferLayerConsumer::setReleaseFence(const sp& fence) { - if (!fence->isValid()) { - return; - } - - auto slot = mPendingRelease.isPending ? mPendingRelease.currentTexture : mCurrentTexture; - if (slot == BufferQueue::INVALID_BUFFER_SLOT) { - return; - } - - auto buffer = mPendingRelease.isPending ? mPendingRelease.graphicBuffer - : mCurrentTextureBuffer->getBuffer(); - auto err = addReleaseFence(slot, buffer, fence); - if (err != OK) { - BLC_LOGE("setReleaseFence: failed to add the fence: %s (%d)", strerror(-err), err); - } -} - -bool BufferLayerConsumer::releasePendingBuffer() { - if (!mPendingRelease.isPending) { - BLC_LOGV("Pending buffer already released"); - return false; - } - BLC_LOGV("Releasing pending buffer"); - Mutex::Autolock lock(mMutex); - status_t result = - releaseBufferLocked(mPendingRelease.currentTexture, mPendingRelease.graphicBuffer); - if (result < NO_ERROR) { - BLC_LOGE("releasePendingBuffer failed: %s (%d)", strerror(-result), result); - } - mPendingRelease = PendingRelease(); - return true; -} - -sp BufferLayerConsumer::getPrevFinalReleaseFence() const { - Mutex::Autolock lock(mMutex); - return ConsumerBase::mPrevFinalReleaseFence; -} - -status_t BufferLayerConsumer::acquireBufferLocked(BufferItem* item, nsecs_t presentWhen, - uint64_t maxFrameNumber) { - status_t err = ConsumerBase::acquireBufferLocked(item, presentWhen, maxFrameNumber); - if (err != NO_ERROR) { - return err; - } - - // If item->mGraphicBuffer is not null, this buffer has not been acquired - // before, so we need to clean up old references. - if (item->mGraphicBuffer != nullptr) { - std::lock_guard lock(mImagesMutex); - if (mImages[item->mSlot] == nullptr || mImages[item->mSlot]->getBuffer() == nullptr || - mImages[item->mSlot]->getBuffer()->getId() != item->mGraphicBuffer->getId()) { - mImages[item->mSlot] = std::make_shared< - renderengine::impl::ExternalTexture>(item->mGraphicBuffer, mRE, - renderengine::impl::ExternalTexture:: - Usage::READABLE); - } - } - - return NO_ERROR; -} - -status_t BufferLayerConsumer::updateAndReleaseLocked(const BufferItem& item, - PendingRelease* pendingRelease) { - status_t err = NO_ERROR; - - int slot = item.mSlot; - - BLC_LOGV("updateAndRelease: (slot=%d buf=%p) -> (slot=%d buf=%p)", mCurrentTexture, - (mCurrentTextureBuffer != nullptr && mCurrentTextureBuffer->getBuffer() != nullptr) - ? mCurrentTextureBuffer->getBuffer()->handle - : 0, - slot, mSlots[slot].mGraphicBuffer->handle); - - // Hang onto the pointer so that it isn't freed in the call to - // releaseBufferLocked() if we're in shared buffer mode and both buffers are - // the same. - - std::shared_ptr nextTextureBuffer; - { - std::lock_guard lock(mImagesMutex); - nextTextureBuffer = mImages[slot]; - } - - // release old buffer - if (mCurrentTexture != BufferQueue::INVALID_BUFFER_SLOT) { - if (pendingRelease == nullptr) { - status_t status = - releaseBufferLocked(mCurrentTexture, mCurrentTextureBuffer->getBuffer()); - if (status < NO_ERROR) { - BLC_LOGE("updateAndRelease: failed to release buffer: %s (%d)", strerror(-status), - status); - err = status; - // keep going, with error raised [?] - } - } else { - pendingRelease->currentTexture = mCurrentTexture; - pendingRelease->graphicBuffer = mCurrentTextureBuffer->getBuffer(); - pendingRelease->isPending = true; - } - } - - // Update the BufferLayerConsumer state. - mCurrentTexture = slot; - mCurrentTextureBuffer = nextTextureBuffer; - mCurrentCrop = item.mCrop; - mCurrentTransform = item.mTransform; - mCurrentScalingMode = item.mScalingMode; - mCurrentTimestamp = item.mTimestamp; - mCurrentDataSpace = static_cast(item.mDataSpace); - mCurrentHdrMetadata = item.mHdrMetadata; - mCurrentFence = item.mFence; - mCurrentFenceTime = item.mFenceTime; - mCurrentFrameNumber = item.mFrameNumber; - mCurrentTransformToDisplayInverse = item.mTransformToDisplayInverse; - mCurrentSurfaceDamage = item.mSurfaceDamage; - mCurrentApi = item.mApi; - - computeCurrentTransformMatrixLocked(); - - return err; -} - -void BufferLayerConsumer::getTransformMatrix(float mtx[16]) { - Mutex::Autolock lock(mMutex); - memcpy(mtx, mCurrentTransformMatrix, sizeof(mCurrentTransformMatrix)); -} - -void BufferLayerConsumer::setFilteringEnabled(bool enabled) { - Mutex::Autolock lock(mMutex); - if (mAbandoned) { - BLC_LOGE("setFilteringEnabled: BufferLayerConsumer is abandoned!"); - return; - } - bool needsRecompute = mFilteringEnabled != enabled; - mFilteringEnabled = enabled; - - if (needsRecompute && mCurrentTextureBuffer == nullptr) { - BLC_LOGD("setFilteringEnabled called with mCurrentTextureBuffer == nullptr"); - } - - if (needsRecompute && mCurrentTextureBuffer != nullptr) { - computeCurrentTransformMatrixLocked(); - } -} - -void BufferLayerConsumer::computeCurrentTransformMatrixLocked() { - BLC_LOGV("computeCurrentTransformMatrixLocked"); - if (mCurrentTextureBuffer == nullptr || mCurrentTextureBuffer->getBuffer() == nullptr) { - BLC_LOGD("computeCurrentTransformMatrixLocked: " - "mCurrentTextureBuffer is nullptr"); - } - GLConsumer::computeTransformMatrix(mCurrentTransformMatrix, - mCurrentTextureBuffer == nullptr - ? nullptr - : mCurrentTextureBuffer->getBuffer(), - getCurrentCropLocked(), mCurrentTransform, - mFilteringEnabled); -} - -nsecs_t BufferLayerConsumer::getTimestamp() { - BLC_LOGV("getTimestamp"); - Mutex::Autolock lock(mMutex); - return mCurrentTimestamp; -} - -ui::Dataspace BufferLayerConsumer::getCurrentDataSpace() { - BLC_LOGV("getCurrentDataSpace"); - Mutex::Autolock lock(mMutex); - return mCurrentDataSpace; -} - -const HdrMetadata& BufferLayerConsumer::getCurrentHdrMetadata() const { - BLC_LOGV("getCurrentHdrMetadata"); - Mutex::Autolock lock(mMutex); - return mCurrentHdrMetadata; -} - -uint64_t BufferLayerConsumer::getFrameNumber() { - BLC_LOGV("getFrameNumber"); - Mutex::Autolock lock(mMutex); - return mCurrentFrameNumber; -} - -bool BufferLayerConsumer::getTransformToDisplayInverse() const { - Mutex::Autolock lock(mMutex); - return mCurrentTransformToDisplayInverse; -} - -const Region& BufferLayerConsumer::getSurfaceDamage() const { - return mCurrentSurfaceDamage; -} - -void BufferLayerConsumer::mergeSurfaceDamage(const Region& damage) { - if (damage.bounds() == Rect::INVALID_RECT || - mCurrentSurfaceDamage.bounds() == Rect::INVALID_RECT) { - mCurrentSurfaceDamage = Region::INVALID_REGION; - } else { - mCurrentSurfaceDamage |= damage; - } -} - -int BufferLayerConsumer::getCurrentApi() const { - Mutex::Autolock lock(mMutex); - return mCurrentApi; -} - -std::shared_ptr BufferLayerConsumer::getCurrentBuffer( - int* outSlot, sp* outFence) const { - Mutex::Autolock lock(mMutex); - - if (outSlot != nullptr) { - *outSlot = mCurrentTexture; - } - - if (outFence != nullptr) { - *outFence = mCurrentFence; - } - - return mCurrentTextureBuffer == nullptr ? nullptr : mCurrentTextureBuffer; -} - -Rect BufferLayerConsumer::getCurrentCrop() const { - Mutex::Autolock lock(mMutex); - return getCurrentCropLocked(); -} - -Rect BufferLayerConsumer::getCurrentCropLocked() const { - uint32_t width = mDefaultWidth; - uint32_t height = mDefaultHeight; - // If the buffer comes with a rotated bit for 90 (or 270) degrees, switch width/height in order - // to scale and crop correctly. - if (mCurrentTransform & NATIVE_WINDOW_TRANSFORM_ROT_90) { - width = mDefaultHeight; - height = mDefaultWidth; - } - - return (mCurrentScalingMode == NATIVE_WINDOW_SCALING_MODE_SCALE_CROP) - ? GLConsumer::scaleDownCrop(mCurrentCrop, width, height) - : mCurrentCrop; -} - -uint32_t BufferLayerConsumer::getCurrentTransform() const { - Mutex::Autolock lock(mMutex); - return mCurrentTransform; -} - -uint32_t BufferLayerConsumer::getCurrentScalingMode() const { - Mutex::Autolock lock(mMutex); - return mCurrentScalingMode; -} - -sp BufferLayerConsumer::getCurrentFence() const { - Mutex::Autolock lock(mMutex); - return mCurrentFence; -} - -std::shared_ptr BufferLayerConsumer::getCurrentFenceTime() const { - Mutex::Autolock lock(mMutex); - return mCurrentFenceTime; -} - -void BufferLayerConsumer::freeBufferLocked(int slotIndex) { - BLC_LOGV("freeBufferLocked: slotIndex=%d", slotIndex); - std::lock_guard lock(mImagesMutex); - if (slotIndex == mCurrentTexture) { - mCurrentTexture = BufferQueue::INVALID_BUFFER_SLOT; - } - mImages[slotIndex] = nullptr; - ConsumerBase::freeBufferLocked(slotIndex); -} - -void BufferLayerConsumer::onDisconnect() { - Mutex::Autolock lock(mMutex); - - if (mAbandoned) { - // Nothing to do if we're already abandoned. - return; - } - - mLayer->onDisconnect(); -} - -void BufferLayerConsumer::onSidebandStreamChanged() { - [[maybe_unused]] FrameAvailableListener* unsafeFrameAvailableListener = nullptr; - { - Mutex::Autolock lock(mFrameAvailableMutex); - unsafeFrameAvailableListener = mFrameAvailableListener.unsafe_get(); - } - sp listener; - { // scope for the lock - Mutex::Autolock lock(mMutex); - ALOG_ASSERT(unsafeFrameAvailableListener == mContentsChangedListener.unsafe_get()); - listener = mContentsChangedListener.promote(); - } - - if (listener != nullptr) { - listener->onSidebandStreamChanged(); - } -} - -void BufferLayerConsumer::onBufferAvailable(const BufferItem& item) { - if (item.mGraphicBuffer != nullptr && item.mSlot != BufferQueue::INVALID_BUFFER_SLOT) { - std::lock_guard lock(mImagesMutex); - const std::shared_ptr& oldImage = mImages[item.mSlot]; - if (oldImage == nullptr || oldImage->getBuffer() == nullptr || - oldImage->getBuffer()->getId() != item.mGraphicBuffer->getId()) { - mImages[item.mSlot] = std::make_shared< - renderengine::impl::ExternalTexture>(item.mGraphicBuffer, mRE, - renderengine::impl::ExternalTexture:: - Usage::READABLE); - } - } -} - -void BufferLayerConsumer::abandonLocked() { - BLC_LOGV("abandonLocked"); - mCurrentTextureBuffer = nullptr; - for (int i = 0; i < BufferQueue::NUM_BUFFER_SLOTS; i++) { - std::lock_guard lock(mImagesMutex); - mImages[i] = nullptr; - } - ConsumerBase::abandonLocked(); -} - -status_t BufferLayerConsumer::setConsumerUsageBits(uint64_t usage) { - return ConsumerBase::setConsumerUsageBits(usage | DEFAULT_USAGE_FLAGS); -} - -void BufferLayerConsumer::dumpLocked(String8& result, const char* prefix) const { - result.appendFormat("%smTexName=%d mCurrentTexture=%d\n" - "%smCurrentCrop=[%d,%d,%d,%d] mCurrentTransform=%#x\n", - prefix, mTexName, mCurrentTexture, prefix, mCurrentCrop.left, - mCurrentCrop.top, mCurrentCrop.right, mCurrentCrop.bottom, - mCurrentTransform); - - ConsumerBase::dumpLocked(result, prefix); -} -}; // namespace android - -// TODO(b/129481165): remove the #pragma below and fix conversion issues -#pragma clang diagnostic pop // ignored "-Wconversion" diff --git a/services/surfaceflinger/BufferLayerConsumer.h b/services/surfaceflinger/BufferLayerConsumer.h deleted file mode 100644 index 23ad2a3f9153c43665e978aca2fee8de05496adf..0000000000000000000000000000000000000000 --- a/services/surfaceflinger/BufferLayerConsumer.h +++ /dev/null @@ -1,354 +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 ANDROID_BUFFERLAYERCONSUMER_H -#define ANDROID_BUFFERLAYERCONSUMER_H - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -namespace android { -// ---------------------------------------------------------------------------- - -class Layer; -class String8; - -namespace renderengine { -class RenderEngine; -} // namespace renderengine - -/* - * BufferLayerConsumer consumes buffers of graphics data from a BufferQueue, - * and makes them available to RenderEngine as a texture. - * - * A typical usage pattern is to call updateTexImage() when a new frame is - * desired. If a new frame is available, the frame is latched. If not, the - * previous contents are retained. The texture is attached and updated after - * bindTextureImage() is called. - * - * All calls to updateTexImage must be made with RenderEngine being current. - * The texture is attached to the TEXTURE_EXTERNAL texture target. - */ -class BufferLayerConsumer : public ConsumerBase { -public: - static const status_t BUFFER_REJECTED = UNKNOWN_ERROR + 8; - - class BufferRejecter { - friend class BufferLayerConsumer; - virtual bool reject(const sp& buf, const BufferItem& item) = 0; - - protected: - virtual ~BufferRejecter() {} - }; - - struct ContentsChangedListener : public FrameAvailableListener { - virtual void onSidebandStreamChanged() = 0; - }; - - // BufferLayerConsumer constructs a new BufferLayerConsumer object. The - // tex parameter indicates the name of the RenderEngine texture to which - // images are to be streamed. - BufferLayerConsumer(const sp& bq, renderengine::RenderEngine& engine, - uint32_t tex, Layer* layer); - - // Sets the contents changed listener. This should be used instead of - // ConsumerBase::setFrameAvailableListener(). - void setContentsChangedListener(const wp& listener); - - // updateTexImage acquires the most recently queued buffer, and sets the - // image contents of the target texture to it. - // - // This call may only be made while RenderEngine is current. - // - // This calls doFenceWait to ensure proper synchronization unless native - // fence is supported. - // - // Unlike the GLConsumer version, this version takes a functor that may be - // used to reject the newly acquired buffer. It also does not bind the - // RenderEngine texture until bindTextureImage is called. - status_t updateTexImage(BufferRejecter* rejecter, nsecs_t expectedPresentTime, - bool* autoRefresh, bool* queuedBuffer, uint64_t maxFrameNumber); - - // setReleaseFence stores a fence that will signal when the current buffer - // is no longer being read. This fence will be returned to the producer - // when the current buffer is released by updateTexImage(). Multiple - // fences can be set for a given buffer; they will be merged into a single - // union fence. - void setReleaseFence(const sp& fence); - - bool releasePendingBuffer(); - - sp getPrevFinalReleaseFence() const; - - // See GLConsumer::getTransformMatrix. - void getTransformMatrix(float mtx[16]); - - // getTimestamp retrieves the timestamp associated with the texture image - // set by the most recent call to updateTexImage. - // - // The timestamp is in nanoseconds, and is monotonically increasing. Its - // other semantics (zero point, etc) are source-dependent and should be - // documented by the source. - int64_t getTimestamp(); - - // getDataSpace retrieves the DataSpace associated with the texture image - // set by the most recent call to updateTexImage. - ui::Dataspace getCurrentDataSpace(); - - // getCurrentHdrMetadata retrieves the HDR metadata associated with the - // texture image set by the most recent call to updateTexImage. - const HdrMetadata& getCurrentHdrMetadata() const; - - // getFrameNumber retrieves the frame number associated with the texture - // image set by the most recent call to updateTexImage. - // - // The frame number is an incrementing counter set to 0 at the creation of - // the BufferQueue associated with this consumer. - uint64_t getFrameNumber(); - - bool getTransformToDisplayInverse() const; - - // must be called from SF main thread - const Region& getSurfaceDamage() const; - - // Merge the given damage region into the current damage region value. - void mergeSurfaceDamage(const Region& damage); - - // getCurrentApi retrieves the API which queues the current buffer. - int getCurrentApi() const; - - // See GLConsumer::setDefaultBufferSize. - status_t setDefaultBufferSize(uint32_t width, uint32_t height); - - // setFilteringEnabled sets whether the transform matrix should be computed - // for use with bilinear filtering. - void setFilteringEnabled(bool enabled); - - // getCurrentBuffer returns the buffer associated with the current image. - // When outSlot is not nullptr, the current buffer slot index is also - // returned. Simiarly, when outFence is not nullptr, the current output - // fence is returned. - std::shared_ptr getCurrentBuffer( - int* outSlot = nullptr, sp* outFence = nullptr) const; - - // getCurrentCrop returns the cropping rectangle of the current buffer. - Rect getCurrentCrop() const; - - // getCurrentTransform returns the transform of the current buffer. - uint32_t getCurrentTransform() const; - - // getCurrentScalingMode returns the scaling mode of the current buffer. - uint32_t getCurrentScalingMode() const; - - // getCurrentFence returns the fence indicating when the current buffer is - // ready to be read from. - sp getCurrentFence() const; - - // getCurrentFence returns the FenceTime indicating when the current - // buffer is ready to be read from. - std::shared_ptr getCurrentFenceTime() const; - - // setConsumerUsageBits overrides the ConsumerBase method to OR - // DEFAULT_USAGE_FLAGS to usage. - status_t setConsumerUsageBits(uint64_t usage); - void onBufferAvailable(const BufferItem& item) EXCLUDES(mImagesMutex); - -protected: - // abandonLocked overrides the ConsumerBase method to clear - // mCurrentTextureImage in addition to the ConsumerBase behavior. - virtual void abandonLocked() EXCLUDES(mImagesMutex); - - // dumpLocked overrides the ConsumerBase method to dump BufferLayerConsumer- - // specific info in addition to the ConsumerBase behavior. - virtual void dumpLocked(String8& result, const char* prefix) const; - - // See ConsumerBase::acquireBufferLocked - virtual status_t acquireBufferLocked(BufferItem* item, nsecs_t presentWhen, - uint64_t maxFrameNumber = 0) override - EXCLUDES(mImagesMutex); - - bool canUseImageCrop(const Rect& crop) const; - - struct PendingRelease { - PendingRelease() : isPending(false), currentTexture(-1), graphicBuffer() {} - - bool isPending; - int currentTexture; - sp graphicBuffer; - }; - - // This releases the buffer in the slot referenced by mCurrentTexture, - // then updates state to refer to the BufferItem, which must be a - // newly-acquired buffer. If pendingRelease is not null, the parameters - // which would have been passed to releaseBufferLocked upon the successful - // completion of the method will instead be returned to the caller, so that - // it may call releaseBufferLocked itself later. - status_t updateAndReleaseLocked(const BufferItem& item, - PendingRelease* pendingRelease = nullptr) - EXCLUDES(mImagesMutex); - -private: - // Utility class for managing GraphicBuffer references into renderengine - class Image { - public: - Image(const sp& graphicBuffer, renderengine::RenderEngine& engine); - virtual ~Image(); - const sp& graphicBuffer() { return mGraphicBuffer; } - - private: - // mGraphicBuffer is the buffer that was used to create this image. - sp mGraphicBuffer; - // Back-reference into renderengine to initiate cleanup. - renderengine::RenderEngine& mRE; - DISALLOW_COPY_AND_ASSIGN(Image); - }; - - // freeBufferLocked frees up the given buffer slot. If the slot has been - // initialized this will release the reference to the GraphicBuffer in - // that slot. Otherwise it has no effect. - // - // This method must be called with mMutex locked. - virtual void freeBufferLocked(int slotIndex) EXCLUDES(mImagesMutex); - - // IConsumerListener interface - void onDisconnect() override; - void onSidebandStreamChanged() override; - void addAndGetFrameTimestamps(const NewFrameEventsEntry*, FrameEventHistoryDelta*) override {} - - // computeCurrentTransformMatrixLocked computes the transform matrix for the - // current texture. It uses mCurrentTransform and the current GraphicBuffer - // to compute this matrix and stores it in mCurrentTransformMatrix. - // mCurrentTextureImage must not be nullptr. - void computeCurrentTransformMatrixLocked(); - - // getCurrentCropLocked returns the cropping rectangle of the current buffer. - Rect getCurrentCropLocked() const; - - // The default consumer usage flags that BufferLayerConsumer always sets on its - // BufferQueue instance; these will be OR:d with any additional flags passed - // from the BufferLayerConsumer user. In particular, BufferLayerConsumer will always - // consume buffers as hardware textures. - static const uint64_t DEFAULT_USAGE_FLAGS = GraphicBuffer::USAGE_HW_TEXTURE; - - // mCurrentTextureBuffer is the buffer containing the current texture. It's - // possible that this buffer is not associated with any buffer slot, so we - // must track it separately in order to support the getCurrentBuffer method. - std::shared_ptr mCurrentTextureBuffer; - - // mCurrentCrop is the crop rectangle that applies to the current texture. - // It gets set each time updateTexImage is called. - Rect mCurrentCrop; - - // mCurrentTransform is the transform identifier for the current texture. It - // gets set each time updateTexImage is called. - uint32_t mCurrentTransform; - - // mCurrentScalingMode is the scaling mode for the current texture. It gets - // set each time updateTexImage is called. - uint32_t mCurrentScalingMode; - - // mCurrentFence is the fence received from BufferQueue in updateTexImage. - sp mCurrentFence; - - // The FenceTime wrapper around mCurrentFence. - std::shared_ptr mCurrentFenceTime{FenceTime::NO_FENCE}; - - // mCurrentTransformMatrix is the transform matrix for the current texture. - // It gets computed by computeTransformMatrix each time updateTexImage is - // called. - float mCurrentTransformMatrix[16]; - - // mCurrentTimestamp is the timestamp for the current texture. It - // gets set each time updateTexImage is called. - int64_t mCurrentTimestamp; - - // mCurrentDataSpace is the dataspace for the current texture. It - // gets set each time updateTexImage is called. - ui::Dataspace mCurrentDataSpace; - - // mCurrentHdrMetadata is the HDR metadata for the current texture. It - // gets set each time updateTexImage is called. - HdrMetadata mCurrentHdrMetadata; - - // mCurrentFrameNumber is the frame counter for the current texture. - // It gets set each time updateTexImage is called. - uint64_t mCurrentFrameNumber; - - // Indicates this buffer must be transformed by the inverse transform of the screen - // it is displayed onto. This is applied after BufferLayerConsumer::mCurrentTransform. - // This must be set/read from SurfaceFlinger's main thread. - bool mCurrentTransformToDisplayInverse; - - // The portion of this surface that has changed since the previous frame - Region mCurrentSurfaceDamage; - - int mCurrentApi; - - uint32_t mDefaultWidth, mDefaultHeight; - - // mFilteringEnabled indicates whether the transform matrix is computed for - // use with bilinear filtering. It defaults to true and is changed by - // setFilteringEnabled(). - bool mFilteringEnabled; - - renderengine::RenderEngine& mRE; - - // mTexName is the name of the RenderEngine texture to which streamed - // images will be bound when bindTexImage is called. It is set at - // construction time. - const uint32_t mTexName; - - // The layer for this BufferLayerConsumer. Always check mAbandoned before accessing. - Layer* mLayer GUARDED_BY(mMutex); - - wp mContentsChangedListener; - - // mCurrentTexture is the buffer slot index of the buffer that is currently - // bound to the RenderEngine texture. It is initialized to INVALID_BUFFER_SLOT, - // indicating that no buffer slot is currently bound to the texture. Note, - // however, that a value of INVALID_BUFFER_SLOT does not necessarily mean - // that no buffer is bound to the texture. A call to setBufferCount will - // reset mCurrentTexture to INVALID_BUFFER_SLOT. - int mCurrentTexture; - - // Shadow buffer cache for cleaning up renderengine references. - std::shared_ptr - mImages[BufferQueueDefs::NUM_BUFFER_SLOTS] GUARDED_BY(mImagesMutex); - - // Separate mutex guarding the shadow buffer cache. - // mImagesMutex can be manipulated with binder threads (e.g. onBuffersAllocated) - // which is contentious enough that we can't just use mMutex. - mutable std::mutex mImagesMutex; - - // A release that is pending on the receipt of a new release fence from - // presentDisplay - PendingRelease mPendingRelease; -}; - -// ---------------------------------------------------------------------------- -}; // namespace android - -#endif // ANDROID_BUFFERLAYERCONSUMER_H diff --git a/services/surfaceflinger/BufferQueueLayer.cpp b/services/surfaceflinger/BufferQueueLayer.cpp deleted file mode 100644 index bee4de32a6dab44a91793d47ffcb9bdb79f36102..0000000000000000000000000000000000000000 --- a/services/surfaceflinger/BufferQueueLayer.cpp +++ /dev/null @@ -1,575 +0,0 @@ -/* - * Copyright (C) 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. - */ - -// TODO(b/129481165): remove the #pragma below and fix conversion issues -#pragma clang diagnostic push -#pragma clang diagnostic ignored "-Wconversion" - -#undef LOG_TAG -#define LOG_TAG "BufferQueueLayer" -#define ATRACE_TAG ATRACE_TAG_GRAPHICS -#include "BufferQueueLayer.h" - -#include -#include -#include - -#include "LayerRejecter.h" -#include "SurfaceInterceptor.h" - -#include "FrameTracer/FrameTracer.h" -#include "Scheduler/LayerHistory.h" -#include "TimeStats/TimeStats.h" - -namespace android { -using PresentState = frametimeline::SurfaceFrame::PresentState; - -BufferQueueLayer::BufferQueueLayer(const LayerCreationArgs& args) : BufferLayer(args) {} - -BufferQueueLayer::~BufferQueueLayer() { - mContentsChangedListener->abandon(); - mConsumer->abandon(); -} - -// ----------------------------------------------------------------------- -// Interface implementation for Layer -// ----------------------------------------------------------------------- - -void BufferQueueLayer::onLayerDisplayed(ftl::SharedFuture futureFenceResult) { - const sp releaseFence = futureFenceResult.get().value_or(Fence::NO_FENCE); - mConsumer->setReleaseFence(releaseFence); - - // Prevent tracing the same release multiple times. - if (mPreviousFrameNumber != mPreviousReleasedFrameNumber) { - mFlinger->mFrameTracer->traceFence(getSequence(), mPreviousBufferId, mPreviousFrameNumber, - std::make_shared(releaseFence), - FrameTracer::FrameEvent::RELEASE_FENCE); - mPreviousReleasedFrameNumber = mPreviousFrameNumber; - } -} - -void BufferQueueLayer::setTransformHint(ui::Transform::RotationFlags displayTransformHint) { - BufferLayer::setTransformHint(displayTransformHint); - mConsumer->setTransformHint(mTransformHint); -} - -void BufferQueueLayer::releasePendingBuffer(nsecs_t) { - if (!mConsumer->releasePendingBuffer()) { - return; - } -} - -void BufferQueueLayer::setDefaultBufferSize(uint32_t w, uint32_t h) { - mConsumer->setDefaultBufferSize(w, h); -} - -int32_t BufferQueueLayer::getQueuedFrameCount() const { - return mQueuedFrames; -} - -bool BufferQueueLayer::isBufferDue(nsecs_t expectedPresentTime) const { - Mutex::Autolock lock(mQueueItemLock); - - const int64_t addedTime = mQueueItems[0].item.mTimestamp; - - // Ignore timestamps more than a second in the future - const bool isPlausible = addedTime < (expectedPresentTime + s2ns(1)); - ALOGW_IF(!isPlausible, - "[%s] Timestamp %" PRId64 " seems implausible " - "relative to expectedPresent %" PRId64, - getDebugName(), addedTime, expectedPresentTime); - - if (!isPlausible) { - mFlinger->mTimeStats->incrementBadDesiredPresent(getSequence()); - } - - const bool isDue = addedTime < expectedPresentTime; - return isDue || !isPlausible; -} - -// ----------------------------------------------------------------------- -// Interface implementation for BufferLayer -// ----------------------------------------------------------------------- - -bool BufferQueueLayer::fenceHasSignaled() const { - Mutex::Autolock lock(mQueueItemLock); - - if (SurfaceFlinger::enableLatchUnsignaledConfig != LatchUnsignaledConfig::Disabled) { - return true; - } - - if (!hasFrameUpdate()) { - return true; - } - - if (mQueueItems[0].item.mIsDroppable) { - // Even though this buffer's fence may not have signaled yet, it could - // be replaced by another buffer before it has a chance to, which means - // that it's possible to get into a situation where a buffer is never - // able to be latched. To avoid this, grab this buffer anyway. - return true; - } - const bool fenceSignaled = - mQueueItems[0].item.mFenceTime->getSignalTime() != Fence::SIGNAL_TIME_PENDING; - if (!fenceSignaled) { - mFlinger->mTimeStats->incrementLatchSkipped(getSequence(), - TimeStats::LatchSkipReason::LateAcquire); - } - - return fenceSignaled; -} - -bool BufferQueueLayer::framePresentTimeIsCurrent(nsecs_t expectedPresentTime) const { - Mutex::Autolock lock(mQueueItemLock); - - if (!hasFrameUpdate() || isRemovedFromCurrentState()) { - return true; - } - - return mQueueItems[0].item.mTimestamp <= expectedPresentTime; -} - -bool BufferQueueLayer::latchSidebandStream(bool& recomputeVisibleRegions) { - // We need to update the sideband stream if the layer has both a buffer and a sideband stream. - editCompositionState()->sidebandStreamHasFrame = hasFrameUpdate() && mSidebandStream.get(); - - bool sidebandStreamChanged = true; - if (mSidebandStreamChanged.compare_exchange_strong(sidebandStreamChanged, false)) { - // mSidebandStreamChanged was changed to false - mSidebandStream = mConsumer->getSidebandStream(); - auto* layerCompositionState = editCompositionState(); - layerCompositionState->sidebandStream = mSidebandStream; - if (layerCompositionState->sidebandStream != nullptr) { - setTransactionFlags(eTransactionNeeded); - mFlinger->setTransactionFlags(eTraversalNeeded); - } - recomputeVisibleRegions = true; - - return true; - } - return false; -} - -bool BufferQueueLayer::hasFrameUpdate() const { - return mQueuedFrames > 0; -} - -status_t BufferQueueLayer::updateTexImage(bool& recomputeVisibleRegions, nsecs_t latchTime, - nsecs_t expectedPresentTime) { - // This boolean is used to make sure that SurfaceFlinger's shadow copy - // of the buffer queue isn't modified when the buffer queue is returning - // BufferItem's that weren't actually queued. This can happen in shared - // buffer mode. - bool queuedBuffer = false; - const int32_t layerId = getSequence(); - LayerRejecter r(mDrawingState, getDrawingState(), recomputeVisibleRegions, - getProducerStickyTransform() != 0, mName, - getTransformToDisplayInverse()); - - if (isRemovedFromCurrentState()) { - expectedPresentTime = 0; - } - - // updateTexImage() below might drop the some buffers at the head of the queue if there is a - // buffer behind them which is timely to be presented. However this buffer may not be signaled - // yet. The code below makes sure that this wouldn't happen by setting maxFrameNumber to the - // last buffer that was signaled. - uint64_t lastSignaledFrameNumber = mLastFrameNumberReceived; - { - Mutex::Autolock lock(mQueueItemLock); - for (size_t i = 0; i < mQueueItems.size(); i++) { - bool fenceSignaled = - mQueueItems[i].item.mFenceTime->getSignalTime() != Fence::SIGNAL_TIME_PENDING; - if (!fenceSignaled) { - break; - } - lastSignaledFrameNumber = mQueueItems[i].item.mFrameNumber; - } - } - const uint64_t maxFrameNumberToAcquire = - std::min(mLastFrameNumberReceived.load(), lastSignaledFrameNumber); - - bool autoRefresh; - status_t updateResult = mConsumer->updateTexImage(&r, expectedPresentTime, &autoRefresh, - &queuedBuffer, maxFrameNumberToAcquire); - mDrawingState.autoRefresh = autoRefresh; - if (updateResult == BufferQueue::PRESENT_LATER) { - // Producer doesn't want buffer to be displayed yet. Signal a - // layer update so we check again at the next opportunity. - mFlinger->onLayerUpdate(); - return BAD_VALUE; - } else if (updateResult == BufferLayerConsumer::BUFFER_REJECTED) { - // If the buffer has been rejected, remove it from the shadow queue - // and return early - if (queuedBuffer) { - Mutex::Autolock lock(mQueueItemLock); - if (mQueuedFrames > 0) { - mConsumer->mergeSurfaceDamage(mQueueItems[0].item.mSurfaceDamage); - mFlinger->mTimeStats->removeTimeRecord(layerId, mQueueItems[0].item.mFrameNumber); - if (mQueueItems[0].surfaceFrame) { - addSurfaceFrameDroppedForBuffer(mQueueItems[0].surfaceFrame); - } - mQueueItems.erase(mQueueItems.begin()); - mQueuedFrames--; - } - } - return BAD_VALUE; - } else if (updateResult != NO_ERROR || mUpdateTexImageFailed) { - // This can occur if something goes wrong when trying to create the - // EGLImage for this buffer. If this happens, the buffer has already - // been released, so we need to clean up the queue and bug out - // early. - if (queuedBuffer) { - Mutex::Autolock lock(mQueueItemLock); - for (auto& [item, surfaceFrame] : mQueueItems) { - if (surfaceFrame) { - addSurfaceFrameDroppedForBuffer(surfaceFrame); - } - } - mQueueItems.clear(); - mQueuedFrames = 0; - mFlinger->mTimeStats->onDestroy(layerId); - mFlinger->mFrameTracer->onDestroy(layerId); - } - - // Once we have hit this state, the shadow queue may no longer - // correctly reflect the incoming BufferQueue's contents, so even if - // updateTexImage starts working, the only safe course of action is - // to continue to ignore updates. - mUpdateTexImageFailed = true; - - return BAD_VALUE; - } - - bool more_frames_pending = false; - if (queuedBuffer) { - // Autolock scope - auto currentFrameNumber = mConsumer->getFrameNumber(); - - Mutex::Autolock lock(mQueueItemLock); - - // Remove any stale buffers that have been dropped during - // updateTexImage - while (mQueuedFrames > 0 && mQueueItems[0].item.mFrameNumber != currentFrameNumber) { - mConsumer->mergeSurfaceDamage(mQueueItems[0].item.mSurfaceDamage); - mFlinger->mTimeStats->removeTimeRecord(layerId, mQueueItems[0].item.mFrameNumber); - if (mQueueItems[0].surfaceFrame) { - addSurfaceFrameDroppedForBuffer(mQueueItems[0].surfaceFrame); - } - mQueueItems.erase(mQueueItems.begin()); - mQueuedFrames--; - } - - uint64_t bufferID = mQueueItems[0].item.mGraphicBuffer->getId(); - mFlinger->mTimeStats->setLatchTime(layerId, currentFrameNumber, latchTime); - mFlinger->mFrameTracer->traceTimestamp(layerId, bufferID, currentFrameNumber, latchTime, - FrameTracer::FrameEvent::LATCH); - - if (mQueueItems[0].surfaceFrame) { - addSurfaceFramePresentedForBuffer(mQueueItems[0].surfaceFrame, - mQueueItems[0].item.mFenceTime->getSignalTime(), - latchTime); - } - mQueueItems.erase(mQueueItems.begin()); - more_frames_pending = (mQueuedFrames.fetch_sub(1) > 1); - } - - // Decrement the queued-frames count. Signal another event if we - // have more frames pending. - if ((queuedBuffer && more_frames_pending) || mDrawingState.autoRefresh) { - mFlinger->onLayerUpdate(); - } - - return NO_ERROR; -} - -status_t BufferQueueLayer::updateActiveBuffer() { - // update the active buffer - mPreviousBufferId = getCurrentBufferId(); - mBufferInfo.mBuffer = - mConsumer->getCurrentBuffer(&mBufferInfo.mBufferSlot, &mBufferInfo.mFence); - - if (mBufferInfo.mBuffer == nullptr) { - // this can only happen if the very first buffer was rejected. - return BAD_VALUE; - } - return NO_ERROR; -} - -status_t BufferQueueLayer::updateFrameNumber() { - mPreviousFrameNumber = mCurrentFrameNumber; - mCurrentFrameNumber = mConsumer->getFrameNumber(); - return NO_ERROR; -} - -void BufferQueueLayer::setFrameTimelineInfoForBuffer(const FrameTimelineInfo& frameTimelineInfo) { - mFrameTimelineInfo = frameTimelineInfo; -} - -// ----------------------------------------------------------------------- -// Interface implementation for BufferLayerConsumer::ContentsChangedListener -// ----------------------------------------------------------------------- - -void BufferQueueLayer::onFrameDequeued(const uint64_t bufferId) { - const int32_t layerId = getSequence(); - mFlinger->mFrameTracer->traceNewLayer(layerId, getName().c_str()); - mFlinger->mFrameTracer->traceTimestamp(layerId, bufferId, FrameTracer::UNSPECIFIED_FRAME_NUMBER, - systemTime(), FrameTracer::FrameEvent::DEQUEUE); -} - -void BufferQueueLayer::onFrameDetached(const uint64_t bufferId) { - const int32_t layerId = getSequence(); - mFlinger->mFrameTracer->traceNewLayer(layerId, getName().c_str()); - mFlinger->mFrameTracer->traceTimestamp(layerId, bufferId, FrameTracer::UNSPECIFIED_FRAME_NUMBER, - systemTime(), FrameTracer::FrameEvent::DETACH); -} - -void BufferQueueLayer::onFrameCancelled(const uint64_t bufferId) { - const int32_t layerId = getSequence(); - mFlinger->mFrameTracer->traceTimestamp(layerId, bufferId, FrameTracer::UNSPECIFIED_FRAME_NUMBER, - systemTime(), FrameTracer::FrameEvent::CANCEL); -} - -void BufferQueueLayer::onFrameAvailable(const BufferItem& item) { - const int32_t layerId = getSequence(); - const uint64_t bufferId = item.mGraphicBuffer->getId(); - mFlinger->mFrameTracer->traceTimestamp(layerId, bufferId, item.mFrameNumber, systemTime(), - FrameTracer::FrameEvent::QUEUE); - mFlinger->mFrameTracer->traceFence(layerId, bufferId, item.mFrameNumber, - std::make_shared(item.mFence), - FrameTracer::FrameEvent::ACQUIRE_FENCE); - - ATRACE_CALL(); - // Add this buffer from our internal queue tracker - { // Autolock scope - const nsecs_t presentTime = item.mIsAutoTimestamp ? 0 : item.mTimestamp; - - using LayerUpdateType = scheduler::LayerHistory::LayerUpdateType; - mFlinger->mScheduler->recordLayerHistory(this, presentTime, LayerUpdateType::Buffer); - - Mutex::Autolock lock(mQueueItemLock); - // Reset the frame number tracker when we receive the first buffer after - // a frame number reset - if (item.mFrameNumber == 1) { - mLastFrameNumberReceived = 0; - } - - // Ensure that callbacks are handled in order - while (item.mFrameNumber != mLastFrameNumberReceived + 1) { - status_t result = mQueueItemCondition.waitRelative(mQueueItemLock, ms2ns(500)); - if (result != NO_ERROR) { - ALOGE("[%s] Timed out waiting on callback", getDebugName()); - break; - } - } - - auto surfaceFrame = createSurfaceFrameForBuffer(mFrameTimelineInfo, systemTime(), mName); - - mQueueItems.push_back({item, surfaceFrame}); - mQueuedFrames++; - - // Wake up any pending callbacks - mLastFrameNumberReceived = item.mFrameNumber; - mQueueItemCondition.broadcast(); - } - - mFlinger->mInterceptor->saveBufferUpdate(layerId, item.mGraphicBuffer->getWidth(), - item.mGraphicBuffer->getHeight(), item.mFrameNumber); - - mFlinger->onLayerUpdate(); - mConsumer->onBufferAvailable(item); -} - -void BufferQueueLayer::onFrameReplaced(const BufferItem& item) { - ATRACE_CALL(); - { // Autolock scope - Mutex::Autolock lock(mQueueItemLock); - - // Ensure that callbacks are handled in order - while (item.mFrameNumber != mLastFrameNumberReceived + 1) { - status_t result = mQueueItemCondition.waitRelative(mQueueItemLock, ms2ns(500)); - if (result != NO_ERROR) { - ALOGE("[%s] Timed out waiting on callback", getDebugName()); - break; - } - } - - if (!hasFrameUpdate()) { - ALOGE("Can't replace a frame on an empty queue"); - return; - } - - auto surfaceFrame = createSurfaceFrameForBuffer(mFrameTimelineInfo, systemTime(), mName); - mQueueItems[mQueueItems.size() - 1].item = item; - mQueueItems[mQueueItems.size() - 1].surfaceFrame = std::move(surfaceFrame); - - // Wake up any pending callbacks - mLastFrameNumberReceived = item.mFrameNumber; - mQueueItemCondition.broadcast(); - } - - const int32_t layerId = getSequence(); - const uint64_t bufferId = item.mGraphicBuffer->getId(); - mFlinger->mFrameTracer->traceTimestamp(layerId, bufferId, item.mFrameNumber, systemTime(), - FrameTracer::FrameEvent::QUEUE); - mFlinger->mFrameTracer->traceFence(layerId, bufferId, item.mFrameNumber, - std::make_shared(item.mFence), - FrameTracer::FrameEvent::ACQUIRE_FENCE); - mConsumer->onBufferAvailable(item); -} - -void BufferQueueLayer::onSidebandStreamChanged() { - bool sidebandStreamChanged = false; - if (mSidebandStreamChanged.compare_exchange_strong(sidebandStreamChanged, true)) { - // mSidebandStreamChanged was changed to true - mFlinger->onLayerUpdate(); - } -} - -// ----------------------------------------------------------------------- - -void BufferQueueLayer::onFirstRef() { - BufferLayer::onFirstRef(); - - // Creates a custom BufferQueue for SurfaceFlingerConsumer to use - sp producer; - sp consumer; - mFlinger->getFactory().createBufferQueue(&producer, &consumer, true); - mProducer = mFlinger->getFactory().createMonitoredProducer(producer, mFlinger, this); - mConsumer = - mFlinger->getFactory().createBufferLayerConsumer(consumer, mFlinger->getRenderEngine(), - mTextureName, this); - mConsumer->setConsumerUsageBits(getEffectiveUsage(0)); - - mContentsChangedListener = new ContentsChangedListener(this); - mConsumer->setContentsChangedListener(mContentsChangedListener); - mConsumer->setName(String8(mName.data(), mName.size())); - - mProducer->setMaxDequeuedBufferCount(2); -} - -status_t BufferQueueLayer::setDefaultBufferProperties(uint32_t w, uint32_t h, PixelFormat format) { - // never allow a surface larger than what our underlying GL implementation - // can handle. - if (mFlinger->exceedsMaxRenderTargetSize(w, h)) { - ALOGE("dimensions too large %" PRIu32 " x %" PRIu32, w, h); - return BAD_VALUE; - } - - setDefaultBufferSize(w, h); - mConsumer->setDefaultBufferFormat(format); - mConsumer->setConsumerUsageBits(getEffectiveUsage(0)); - - return NO_ERROR; -} - -sp BufferQueueLayer::getProducer() const { - return mProducer; -} - -uint32_t BufferQueueLayer::getProducerStickyTransform() const { - int producerStickyTransform = 0; - int ret = mProducer->query(NATIVE_WINDOW_STICKY_TRANSFORM, &producerStickyTransform); - if (ret != OK) { - ALOGW("%s: Error %s (%d) while querying window sticky transform.", __FUNCTION__, - strerror(-ret), ret); - return 0; - } - return static_cast(producerStickyTransform); -} - -void BufferQueueLayer::gatherBufferInfo() { - BufferLayer::gatherBufferInfo(); - - mBufferInfo.mDesiredPresentTime = mConsumer->getTimestamp(); - mBufferInfo.mFenceTime = mConsumer->getCurrentFenceTime(); - mBufferInfo.mFence = mConsumer->getCurrentFence(); - mBufferInfo.mTransform = mConsumer->getCurrentTransform(); - mBufferInfo.mDataspace = translateDataspace(mConsumer->getCurrentDataSpace()); - mBufferInfo.mCrop = mConsumer->getCurrentCrop(); - mBufferInfo.mScaleMode = mConsumer->getCurrentScalingMode(); - mBufferInfo.mSurfaceDamage = mConsumer->getSurfaceDamage(); - mBufferInfo.mHdrMetadata = mConsumer->getCurrentHdrMetadata(); - mBufferInfo.mApi = mConsumer->getCurrentApi(); - mBufferInfo.mTransformToDisplayInverse = mConsumer->getTransformToDisplayInverse(); -} - -sp BufferQueueLayer::createClone() { - LayerCreationArgs args(mFlinger.get(), nullptr, mName + " (Mirror)", 0, LayerMetadata()); - args.textureName = mTextureName; - sp layer = mFlinger->getFactory().createBufferQueueLayer(args); - layer->setInitialValuesForClone(this); - - return layer; -} - -// ----------------------------------------------------------------------- -// Interface implementation for BufferLayerConsumer::ContentsChangedListener -// ----------------------------------------------------------------------- - -void BufferQueueLayer::ContentsChangedListener::onFrameAvailable(const BufferItem& item) { - Mutex::Autolock lock(mMutex); - if (mBufferQueueLayer != nullptr) { - mBufferQueueLayer->onFrameAvailable(item); - } -} - -void BufferQueueLayer::ContentsChangedListener::onFrameReplaced(const BufferItem& item) { - Mutex::Autolock lock(mMutex); - if (mBufferQueueLayer != nullptr) { - mBufferQueueLayer->onFrameReplaced(item); - } -} - -void BufferQueueLayer::ContentsChangedListener::onSidebandStreamChanged() { - Mutex::Autolock lock(mMutex); - if (mBufferQueueLayer != nullptr) { - mBufferQueueLayer->onSidebandStreamChanged(); - } -} - -void BufferQueueLayer::ContentsChangedListener::onFrameDequeued(const uint64_t bufferId) { - Mutex::Autolock lock(mMutex); - if (mBufferQueueLayer != nullptr) { - mBufferQueueLayer->onFrameDequeued(bufferId); - } -} - -void BufferQueueLayer::ContentsChangedListener::onFrameDetached(const uint64_t bufferId) { - Mutex::Autolock lock(mMutex); - if (mBufferQueueLayer != nullptr) { - mBufferQueueLayer->onFrameDetached(bufferId); - } -} - -void BufferQueueLayer::ContentsChangedListener::onFrameCancelled(const uint64_t bufferId) { - Mutex::Autolock lock(mMutex); - if (mBufferQueueLayer != nullptr) { - mBufferQueueLayer->onFrameCancelled(bufferId); - } -} - -void BufferQueueLayer::ContentsChangedListener::abandon() { - Mutex::Autolock lock(mMutex); - mBufferQueueLayer = nullptr; -} - -// ----------------------------------------------------------------------- - -} // namespace android - -// TODO(b/129481165): remove the #pragma below and fix conversion issues -#pragma clang diagnostic pop // ignored "-Wconversion" diff --git a/services/surfaceflinger/BufferQueueLayer.h b/services/surfaceflinger/BufferQueueLayer.h deleted file mode 100644 index e1c80d581d626cb9692a268366c939e8e4e13738..0000000000000000000000000000000000000000 --- a/services/surfaceflinger/BufferQueueLayer.h +++ /dev/null @@ -1,154 +0,0 @@ -/* - * Copyright (C) 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. - */ - -#pragma once - -#include "BufferLayer.h" - -#include - -namespace android { - -namespace frametimeline { -class SurfaceFrame; -} - -/* - * A new BufferQueue and a new BufferLayerConsumer are created when the - * BufferLayer is first referenced. - * - * This also implements onFrameAvailable(), which notifies SurfaceFlinger - * that new data has arrived. - */ -class BufferQueueLayer : public BufferLayer { -public: - // Only call while mStateLock is held - explicit BufferQueueLayer(const LayerCreationArgs&); - ~BufferQueueLayer() override; - - // Implements Layer. - const char* getType() const override { return "BufferQueueLayer"; } - - void onLayerDisplayed(ftl::SharedFuture) override; - - // If a buffer was replaced this frame, release the former buffer - void releasePendingBuffer(nsecs_t dequeueReadyTime) override; - - void setDefaultBufferSize(uint32_t w, uint32_t h) override; - - int32_t getQueuedFrameCount() const override; - - // Returns true if the next buffer should be presented at the expected present time - bool isBufferDue(nsecs_t expectedPresentTime) const override; - - // Implements BufferLayer. - bool fenceHasSignaled() const override; - bool framePresentTimeIsCurrent(nsecs_t expectedPresentTime) const override; - - status_t setDefaultBufferProperties(uint32_t w, uint32_t h, PixelFormat format); - sp getProducer() const; - - void setSizeForTest(uint32_t w, uint32_t h) { - mDrawingState.active_legacy.w = w; - mDrawingState.active_legacy.h = h; - } - -protected: - void gatherBufferInfo() override; - - // ----------------------------------------------------------------------- - // Interface implementation for BufferLayerConsumer::ContentsChangedListener - // ----------------------------------------------------------------------- - class ContentsChangedListener : public BufferLayerConsumer::ContentsChangedListener { - public: - ContentsChangedListener(BufferQueueLayer* bufferQueueLayer) - : mBufferQueueLayer(bufferQueueLayer) {} - void abandon(); - - protected: - void onFrameAvailable(const BufferItem& item) override; - void onFrameReplaced(const BufferItem& item) override; - void onSidebandStreamChanged() override; - void onFrameDequeued(const uint64_t bufferId) override; - void onFrameDetached(const uint64_t bufferId) override; - void onFrameCancelled(const uint64_t bufferId) override; - - private: - BufferQueueLayer* mBufferQueueLayer = nullptr; - Mutex mMutex; - }; - -private: - - bool latchSidebandStream(bool& recomputeVisibleRegions) override; - void setTransformHint(ui::Transform::RotationFlags displayTransformHint) override; - - bool hasFrameUpdate() const override; - - status_t updateTexImage(bool& recomputeVisibleRegions, nsecs_t latchTime, - nsecs_t expectedPresentTime) override; - - status_t updateActiveBuffer() override; - status_t updateFrameNumber() override; - void setFrameTimelineInfoForBuffer(const FrameTimelineInfo& frameTimelineInfo) override; - - sp createClone() override; - - void onFirstRef() override; - - void onFrameAvailable(const BufferItem& item); - void onFrameReplaced(const BufferItem& item); - void onSidebandStreamChanged(); - void onFrameDequeued(const uint64_t bufferId); - void onFrameDetached(const uint64_t bufferId); - void onFrameCancelled(const uint64_t bufferId); - - // Temporary - Used only for LEGACY camera mode. - uint32_t getProducerStickyTransform() const; - - sp mConsumer; - sp mProducer; - - bool mUpdateTexImageFailed{false}; - - uint64_t mPreviousBufferId = 0; - uint64_t mPreviousReleasedFrameNumber = 0; - - // Local copy of the queued contents of the incoming BufferQueue - mutable Mutex mQueueItemLock; - Condition mQueueItemCondition; - - struct BufferData { - BufferData(BufferItem item, std::shared_ptr surfaceFrame) - : item(item), surfaceFrame(surfaceFrame) {} - BufferItem item; - std::shared_ptr surfaceFrame; - }; - std::vector mQueueItems; - std::atomic mLastFrameNumberReceived{0}; - - // thread-safe - std::atomic mQueuedFrames{0}; - - sp mContentsChangedListener; - - // The last vsync info received on this layer. This will be used when we get - // a buffer to correlate the buffer with the vsync id. Can only be accessed - // with the SF state lock held. - FrameTimelineInfo mFrameTimelineInfo; -}; - -} // namespace android diff --git a/services/surfaceflinger/BufferStateLayer.cpp b/services/surfaceflinger/BufferStateLayer.cpp deleted file mode 100644 index e06f3c4d4240a84fba80d55a2191758e4ad23597..0000000000000000000000000000000000000000 --- a/services/surfaceflinger/BufferStateLayer.cpp +++ /dev/null @@ -1,1086 +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. - */ - -//#define LOG_NDEBUG 0 -#undef LOG_TAG -#define LOG_TAG "BufferStateLayer" -#define ATRACE_TAG ATRACE_TAG_GRAPHICS - -#include "BufferStateLayer.h" - -#include - -#include -#include -#include -#include -#include -#include "TunnelModeEnabledReporter.h" - -#include "EffectLayer.h" -#include "FrameTracer/FrameTracer.h" -#include "TimeStats/TimeStats.h" - -#define EARLY_RELEASE_ENABLED false - -namespace android { - -using PresentState = frametimeline::SurfaceFrame::PresentState; -namespace { -void callReleaseBufferCallback(const sp& listener, - const sp& buffer, uint64_t framenumber, - const sp& releaseFence, - uint32_t currentMaxAcquiredBufferCount) { - if (!listener) { - return; - } - listener->onReleaseBuffer({buffer->getId(), framenumber}, - releaseFence ? releaseFence : Fence::NO_FENCE, - currentMaxAcquiredBufferCount); -} -} // namespace - -BufferStateLayer::BufferStateLayer(const LayerCreationArgs& args) - : BufferLayer(args), mHwcSlotGenerator(new HwcSlotGenerator()) { - mDrawingState.dataspace = ui::Dataspace::V0_SRGB; -} - -BufferStateLayer::~BufferStateLayer() { - // The original layer and the clone layer share the same texture and buffer. Therefore, only - // one of the layers, in this case the original layer, needs to handle the deletion. The - // original layer and the clone should be removed at the same time so there shouldn't be any - // issue with the clone layer trying to use the texture. - if (mBufferInfo.mBuffer != nullptr) { - callReleaseBufferCallback(mDrawingState.releaseBufferListener, - mBufferInfo.mBuffer->getBuffer(), mBufferInfo.mFrameNumber, - mBufferInfo.mFence, - mFlinger->getMaxAcquiredBufferCountForCurrentRefreshRate( - mOwnerUid)); - } -} - -// ----------------------------------------------------------------------- -// Interface implementation for Layer -// ----------------------------------------------------------------------- -void BufferStateLayer::onLayerDisplayed(ftl::SharedFuture futureFenceResult) { - // 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 - // to merge all the client comp fences. We could do this, but for now we just - // disable the optimization when a layer is composed on multiple displays. - if (mClearClientCompositionFenceOnLayerDisplayed) { - mLastClientCompositionFence = nullptr; - } else { - mClearClientCompositionFenceOnLayerDisplayed = true; - } - - // The previous release fence notifies the client that SurfaceFlinger is done with the previous - // buffer that was presented on this layer. The first transaction that came in this frame that - // replaced the previous buffer on this layer needs this release fence, because the fence will - // let the client know when that previous buffer is removed from the screen. - // - // Every other transaction on this layer does not need a release fence because no other - // Transactions that were set on this layer this frame are going to have their preceeding buffer - // removed from the display this frame. - // - // For example, if we have 3 transactions this frame. The first transaction doesn't contain a - // buffer so it doesn't need a previous release fence because the layer still needs the previous - // buffer. The second transaction contains a buffer so it needs a previous release fence because - // the previous buffer will be released this frame. The third transaction also contains a - // buffer. It replaces the buffer in the second transaction. The buffer in the second - // transaction will now no longer be presented so it is released immediately and the third - // transaction doesn't need a previous release fence. - sp ch; - for (auto& handle : mDrawingState.callbackHandles) { - if (handle->releasePreviousBuffer && - mDrawingState.releaseBufferEndpoint == handle->listener) { - ch = handle; - break; - } - } - - // Prevent tracing the same release multiple times. - if (mPreviousFrameNumber != mPreviousReleasedFrameNumber) { - mPreviousReleasedFrameNumber = mPreviousFrameNumber; - } - - if (ch != nullptr) { - ch->previousReleaseCallbackId = mPreviousReleaseCallbackId; - ch->previousReleaseFences.emplace_back(std::move(futureFenceResult)); - ch->name = mName; - } -} - -void BufferStateLayer::onSurfaceFrameCreated( - const std::shared_ptr& surfaceFrame) { - while (mPendingJankClassifications.size() >= kPendingClassificationMaxSurfaceFrames) { - // Too many SurfaceFrames pending classification. The front of the deque is probably not - // tracked by FrameTimeline and will never be presented. This will only result in a memory - // leak. - ALOGW("Removing the front of pending jank deque from layer - %s to prevent memory leak", - mName.c_str()); - std::string miniDump = mPendingJankClassifications.front()->miniDump(); - ALOGD("Head SurfaceFrame mini dump\n%s", miniDump.c_str()); - mPendingJankClassifications.pop_front(); - } - mPendingJankClassifications.emplace_back(surfaceFrame); -} - -void BufferStateLayer::releasePendingBuffer(nsecs_t dequeueReadyTime) { - for (const auto& handle : mDrawingState.callbackHandles) { - handle->transformHint = mTransformHint; - handle->dequeueReadyTime = dequeueReadyTime; - handle->currentMaxAcquiredBufferCount = - mFlinger->getMaxAcquiredBufferCountForCurrentRefreshRate(mOwnerUid); - } - - for (auto& handle : mDrawingState.callbackHandles) { - if (handle->releasePreviousBuffer && - mDrawingState.releaseBufferEndpoint == handle->listener) { - handle->previousReleaseCallbackId = mPreviousReleaseCallbackId; - break; - } - } - - std::vector jankData; - jankData.reserve(mPendingJankClassifications.size()); - while (!mPendingJankClassifications.empty() - && mPendingJankClassifications.front()->getJankType()) { - std::shared_ptr surfaceFrame = - mPendingJankClassifications.front(); - mPendingJankClassifications.pop_front(); - jankData.emplace_back( - JankData(surfaceFrame->getToken(), surfaceFrame->getJankType().value())); - } - - mFlinger->getTransactionCallbackInvoker().addCallbackHandles( - mDrawingState.callbackHandles, jankData); - - sp releaseFence = Fence::NO_FENCE; - for (auto& handle : mDrawingState.callbackHandles) { - if (handle->releasePreviousBuffer && - mDrawingState.releaseBufferEndpoint == handle->listener) { - releaseFence = - handle->previousReleaseFence ? handle->previousReleaseFence : Fence::NO_FENCE; - break; - } - } - - mDrawingState.callbackHandles = {}; -} - -void BufferStateLayer::finalizeFrameEventHistory(const std::shared_ptr& glDoneFence, - const CompositorTiming& compositorTiming) { - for (const auto& handle : mDrawingState.callbackHandles) { - handle->gpuCompositionDoneFence = glDoneFence; - handle->compositorTiming = compositorTiming; - } -} - -bool BufferStateLayer::willPresentCurrentTransaction() const { - // Returns true if the most recent Transaction applied to CurrentState will be presented. - return (getSidebandStreamChanged() || getAutoRefresh() || - (mDrawingState.modified && - (mDrawingState.buffer != nullptr || mDrawingState.bgColorLayer != nullptr))); -} - -Rect BufferStateLayer::getCrop(const Layer::State& s) const { - return s.crop; -} - -bool BufferStateLayer::setTransform(uint32_t transform) { - if (mDrawingState.bufferTransform == transform) return false; - mDrawingState.bufferTransform = transform; - mDrawingState.modified = true; - setTransactionFlags(eTransactionNeeded); - return true; -} - -bool BufferStateLayer::setTransformToDisplayInverse(bool transformToDisplayInverse) { - if (mDrawingState.transformToDisplayInverse == transformToDisplayInverse) return false; - mDrawingState.sequence++; - mDrawingState.transformToDisplayInverse = transformToDisplayInverse; - mDrawingState.modified = true; - setTransactionFlags(eTransactionNeeded); - return true; -} - -bool BufferStateLayer::setCrop(const Rect& crop) { - if (mDrawingState.crop == crop) return false; - mDrawingState.sequence++; - mDrawingState.crop = crop; - - mDrawingState.modified = true; - setTransactionFlags(eTransactionNeeded); - return true; -} - -bool BufferStateLayer::setBufferCrop(const Rect& bufferCrop) { - if (mDrawingState.bufferCrop == bufferCrop) return false; - - mDrawingState.sequence++; - mDrawingState.bufferCrop = bufferCrop; - - mDrawingState.modified = true; - setTransactionFlags(eTransactionNeeded); - return true; -} - -bool BufferStateLayer::setDestinationFrame(const Rect& destinationFrame) { - if (mDrawingState.destinationFrame == destinationFrame) return false; - - mDrawingState.sequence++; - mDrawingState.destinationFrame = destinationFrame; - - mDrawingState.modified = true; - setTransactionFlags(eTransactionNeeded); - return true; -} - -static bool assignTransform(ui::Transform* dst, ui::Transform& from) { - if (*dst == from) { - return false; - } - *dst = from; - return true; -} - -// Translate destination frame into scale and position. If a destination frame is not set, use the -// provided scale and position -bool BufferStateLayer::updateGeometry() { - if ((mDrawingState.flags & layer_state_t::eIgnoreDestinationFrame) || - mDrawingState.destinationFrame.isEmpty()) { - // If destination frame is not set, use the requested transform set via - // BufferStateLayer::setPosition and BufferStateLayer::setMatrix. - return assignTransform(&mDrawingState.transform, mRequestedTransform); - } - - Rect destRect = mDrawingState.destinationFrame; - int32_t destW = destRect.width(); - int32_t destH = destRect.height(); - if (destRect.left < 0) { - destRect.left = 0; - destRect.right = destW; - } - if (destRect.top < 0) { - destRect.top = 0; - destRect.bottom = destH; - } - - if (!mDrawingState.buffer) { - ui::Transform t; - t.set(destRect.left, destRect.top); - return assignTransform(&mDrawingState.transform, t); - } - - uint32_t bufferWidth = mDrawingState.buffer->getWidth(); - uint32_t bufferHeight = mDrawingState.buffer->getHeight(); - // Undo any transformations on the buffer. - if (mDrawingState.bufferTransform & ui::Transform::ROT_90) { - std::swap(bufferWidth, bufferHeight); - } - uint32_t invTransform = DisplayDevice::getPrimaryDisplayRotationFlags(); - if (mDrawingState.transformToDisplayInverse) { - if (invTransform & ui::Transform::ROT_90) { - std::swap(bufferWidth, bufferHeight); - } - } - - float sx = destW / static_cast(bufferWidth); - float sy = destH / static_cast(bufferHeight); - ui::Transform t; - t.set(sx, 0, 0, sy); - t.set(destRect.left, destRect.top); - return assignTransform(&mDrawingState.transform, t); -} - -bool BufferStateLayer::setMatrix(const layer_state_t::matrix22_t& matrix) { - if (mRequestedTransform.dsdx() == matrix.dsdx && mRequestedTransform.dtdy() == matrix.dtdy && - mRequestedTransform.dtdx() == matrix.dtdx && mRequestedTransform.dsdy() == matrix.dsdy) { - return false; - } - - ui::Transform t; - t.set(matrix.dsdx, matrix.dtdy, matrix.dtdx, matrix.dsdy); - - mRequestedTransform.set(matrix.dsdx, matrix.dtdy, matrix.dtdx, matrix.dsdy); - - mDrawingState.sequence++; - mDrawingState.modified = true; - setTransactionFlags(eTransactionNeeded); - - return true; -} - -bool BufferStateLayer::setPosition(float x, float y) { - if (mRequestedTransform.tx() == x && mRequestedTransform.ty() == y) { - return false; - } - - mRequestedTransform.set(x, y); - - mDrawingState.sequence++; - mDrawingState.modified = true; - setTransactionFlags(eTransactionNeeded); - - return true; -} - -bool BufferStateLayer::setBuffer(std::shared_ptr& buffer, - const BufferData& bufferData, nsecs_t postTime, - nsecs_t desiredPresentTime, bool isAutoTimestamp, - std::optional dequeueTime, - const FrameTimelineInfo& info) { - ATRACE_CALL(); - - if (!buffer) { - return false; - } - - const bool frameNumberChanged = - bufferData.flags.test(BufferData::BufferDataChange::frameNumberChanged); - const uint64_t frameNumber = - frameNumberChanged ? bufferData.frameNumber : mDrawingState.frameNumber + 1; - - if (mDrawingState.buffer) { - mReleasePreviousBuffer = true; - if (!mBufferInfo.mBuffer || - (!mDrawingState.buffer->hasSameBuffer(*mBufferInfo.mBuffer) || - mDrawingState.frameNumber != mBufferInfo.mFrameNumber)) { - // If mDrawingState has a buffer, and we are about to update again - // before swapping to drawing state, then the first buffer will be - // dropped and we should decrement the pending buffer count and - // call any release buffer callbacks if set. - callReleaseBufferCallback(mDrawingState.releaseBufferListener, - mDrawingState.buffer->getBuffer(), mDrawingState.frameNumber, - mDrawingState.acquireFence, - mFlinger->getMaxAcquiredBufferCountForCurrentRefreshRate( - mOwnerUid)); - decrementPendingBufferCount(); - if (mDrawingState.bufferSurfaceFrameTX != nullptr && - mDrawingState.bufferSurfaceFrameTX->getPresentState() != PresentState::Presented) { - addSurfaceFrameDroppedForBuffer(mDrawingState.bufferSurfaceFrameTX); - mDrawingState.bufferSurfaceFrameTX.reset(); - } - } else if (EARLY_RELEASE_ENABLED && mLastClientCompositionFence != nullptr) { - callReleaseBufferCallback(mDrawingState.releaseBufferListener, - mDrawingState.buffer->getBuffer(), mDrawingState.frameNumber, - mLastClientCompositionFence, - mFlinger->getMaxAcquiredBufferCountForCurrentRefreshRate( - mOwnerUid)); - mLastClientCompositionFence = nullptr; - } - } - - mDrawingState.frameNumber = frameNumber; - mDrawingState.releaseBufferListener = bufferData.releaseBufferListener; - mDrawingState.buffer = std::move(buffer); - mDrawingState.clientCacheId = bufferData.cachedBuffer; - - mDrawingState.acquireFence = bufferData.flags.test(BufferData::BufferDataChange::fenceChanged) - ? bufferData.acquireFence - : Fence::NO_FENCE; - mDrawingState.acquireFenceTime = std::make_unique(mDrawingState.acquireFence); - if (mDrawingState.acquireFenceTime->getSignalTime() == Fence::SIGNAL_TIME_PENDING) { - // We latched this buffer unsiganled, so we need to pass the acquire fence - // on the callback instead of just the acquire time, since it's unknown at - // this point. - mCallbackHandleAcquireTimeOrFence = mDrawingState.acquireFence; - } else { - mCallbackHandleAcquireTimeOrFence = mDrawingState.acquireFenceTime->getSignalTime(); - } - - mDrawingState.modified = true; - setTransactionFlags(eTransactionNeeded); - - const int32_t layerId = getSequence(); - mFlinger->mTimeStats->setPostTime(layerId, mDrawingState.frameNumber, getName().c_str(), - mOwnerUid, postTime, getGameMode()); - mDrawingState.desiredPresentTime = desiredPresentTime; - mDrawingState.isAutoTimestamp = isAutoTimestamp; - - const nsecs_t presentTime = [&] { - if (!isAutoTimestamp) return desiredPresentTime; - - const auto prediction = - mFlinger->mFrameTimeline->getTokenManager()->getPredictionsForToken(info.vsyncId); - if (prediction.has_value()) return prediction->presentTime; - - return static_cast(0); - }(); - - using LayerUpdateType = scheduler::LayerHistory::LayerUpdateType; - mFlinger->mScheduler->recordLayerHistory(this, presentTime, LayerUpdateType::Buffer); - - setFrameTimelineVsyncForBufferTransaction(info, postTime); - - if (dequeueTime && *dequeueTime != 0) { - const uint64_t bufferId = mDrawingState.buffer->getId(); - mFlinger->mFrameTracer->traceNewLayer(layerId, getName().c_str()); - mFlinger->mFrameTracer->traceTimestamp(layerId, bufferId, frameNumber, *dequeueTime, - FrameTracer::FrameEvent::DEQUEUE); - mFlinger->mFrameTracer->traceTimestamp(layerId, bufferId, frameNumber, postTime, - FrameTracer::FrameEvent::QUEUE); - } - - mDrawingState.width = mDrawingState.buffer->getWidth(); - mDrawingState.height = mDrawingState.buffer->getHeight(); - mDrawingState.releaseBufferEndpoint = bufferData.releaseBufferEndpoint; - return true; -} - -bool BufferStateLayer::setDataspace(ui::Dataspace dataspace) { - if (mDrawingState.dataspace == dataspace) return false; - mDrawingState.dataspace = dataspace; - mDrawingState.modified = true; - setTransactionFlags(eTransactionNeeded); - return true; -} - -bool BufferStateLayer::setHdrMetadata(const HdrMetadata& hdrMetadata) { - if (mDrawingState.hdrMetadata == hdrMetadata) return false; - mDrawingState.hdrMetadata = hdrMetadata; - mDrawingState.modified = true; - setTransactionFlags(eTransactionNeeded); - return true; -} - -bool BufferStateLayer::setSurfaceDamageRegion(const Region& surfaceDamage) { - if (mDrawingState.surfaceDamageRegion.hasSameRects(surfaceDamage)) return false; - mDrawingState.surfaceDamageRegion = surfaceDamage; - mDrawingState.modified = true; - setTransactionFlags(eTransactionNeeded); - return true; -} - -bool BufferStateLayer::setApi(int32_t api) { - if (mDrawingState.api == api) return false; - mDrawingState.api = api; - mDrawingState.modified = true; - setTransactionFlags(eTransactionNeeded); - return true; -} - -bool BufferStateLayer::setSidebandStream(const sp& sidebandStream) { - if (mDrawingState.sidebandStream == sidebandStream) return false; - - if (mDrawingState.sidebandStream != nullptr && sidebandStream == nullptr) { - mFlinger->mTunnelModeEnabledReporter->decrementTunnelModeCount(); - } else if (sidebandStream != nullptr) { - mFlinger->mTunnelModeEnabledReporter->incrementTunnelModeCount(); - } - - mDrawingState.sidebandStream = sidebandStream; - mDrawingState.modified = true; - setTransactionFlags(eTransactionNeeded); - if (!mSidebandStreamChanged.exchange(true)) { - // mSidebandStreamChanged was false - mFlinger->onLayerUpdate(); - } - return true; -} - -bool BufferStateLayer::setTransactionCompletedListeners( - const std::vector>& handles) { - // If there is no handle, we will not send a callback so reset mReleasePreviousBuffer and return - if (handles.empty()) { - mReleasePreviousBuffer = false; - return false; - } - - const bool willPresent = willPresentCurrentTransaction(); - - for (const auto& handle : handles) { - // If this transaction set a buffer on this layer, release its previous buffer - handle->releasePreviousBuffer = mReleasePreviousBuffer; - - // If this layer will be presented in this frame - if (willPresent) { - // If this transaction set an acquire fence on this layer, set its acquire time - handle->acquireTimeOrFence = mCallbackHandleAcquireTimeOrFence; - handle->frameNumber = mDrawingState.frameNumber; - - // Store so latched time and release fence can be set - mDrawingState.callbackHandles.push_back(handle); - - } else { // If this layer will NOT need to be relatched and presented this frame - // Notify the transaction completed thread this handle is done - mFlinger->getTransactionCallbackInvoker().registerUnpresentedCallbackHandle(handle); - } - } - - mReleasePreviousBuffer = false; - mCallbackHandleAcquireTimeOrFence = -1; - - return willPresent; -} - -bool BufferStateLayer::setTransparentRegionHint(const Region& transparent) { - mDrawingState.sequence++; - mDrawingState.transparentRegionHint = transparent; - mDrawingState.modified = true; - setTransactionFlags(eTransactionNeeded); - return true; -} - -Rect BufferStateLayer::getBufferSize(const State& /*s*/) const { - // for buffer state layers we use the display frame size as the buffer size. - - if (mBufferInfo.mBuffer == nullptr) { - return Rect::INVALID_RECT; - } - - uint32_t bufWidth = mBufferInfo.mBuffer->getWidth(); - uint32_t bufHeight = mBufferInfo.mBuffer->getHeight(); - - // Undo any transformations on the buffer and return the result. - if (mBufferInfo.mTransform & ui::Transform::ROT_90) { - std::swap(bufWidth, bufHeight); - } - - if (getTransformToDisplayInverse()) { - uint32_t invTransform = DisplayDevice::getPrimaryDisplayRotationFlags(); - if (invTransform & ui::Transform::ROT_90) { - std::swap(bufWidth, bufHeight); - } - } - - return Rect(0, 0, static_cast(bufWidth), static_cast(bufHeight)); -} - -FloatRect BufferStateLayer::computeSourceBounds(const FloatRect& parentBounds) const { - if (mBufferInfo.mBuffer == nullptr) { - return parentBounds; - } - - return getBufferSize(getDrawingState()).toFloatRect(); -} - -// ----------------------------------------------------------------------- - -// ----------------------------------------------------------------------- -// Interface implementation for BufferLayer -// ----------------------------------------------------------------------- -bool BufferStateLayer::fenceHasSignaled() const { - if (SurfaceFlinger::enableLatchUnsignaledConfig != LatchUnsignaledConfig::Disabled) { - return true; - } - - const bool fenceSignaled = - getDrawingState().acquireFence->getStatus() == Fence::Status::Signaled; - if (!fenceSignaled) { - mFlinger->mTimeStats->incrementLatchSkipped(getSequence(), - TimeStats::LatchSkipReason::LateAcquire); - } - - return fenceSignaled; -} - -bool BufferStateLayer::framePresentTimeIsCurrent(nsecs_t expectedPresentTime) const { - if (!hasFrameUpdate() || isRemovedFromCurrentState()) { - return true; - } - - return mDrawingState.isAutoTimestamp || mDrawingState.desiredPresentTime <= expectedPresentTime; -} - -bool BufferStateLayer::onPreComposition(nsecs_t refreshStartTime) { - for (const auto& handle : mDrawingState.callbackHandles) { - handle->refreshStartTime = refreshStartTime; - } - return BufferLayer::onPreComposition(refreshStartTime); -} - -void BufferStateLayer::setAutoRefresh(bool autoRefresh) { - mDrawingState.autoRefresh = autoRefresh; -} - -bool BufferStateLayer::latchSidebandStream(bool& recomputeVisibleRegions) { - // We need to update the sideband stream if the layer has both a buffer and a sideband stream. - editCompositionState()->sidebandStreamHasFrame = hasFrameUpdate() && mSidebandStream.get(); - - if (mSidebandStreamChanged.exchange(false)) { - const State& s(getDrawingState()); - // mSidebandStreamChanged was true - mSidebandStream = s.sidebandStream; - editCompositionState()->sidebandStream = mSidebandStream; - if (mSidebandStream != nullptr) { - setTransactionFlags(eTransactionNeeded); - mFlinger->setTransactionFlags(eTraversalNeeded); - } - recomputeVisibleRegions = true; - - return true; - } - return false; -} - -bool BufferStateLayer::hasFrameUpdate() const { - const State& c(getDrawingState()); - return (mDrawingStateModified || mDrawingState.modified) && (c.buffer != nullptr || c.bgColorLayer != nullptr); -} - -status_t BufferStateLayer::updateTexImage(bool& /*recomputeVisibleRegions*/, nsecs_t latchTime, - nsecs_t /*expectedPresentTime*/) { - const State& s(getDrawingState()); - - if (!s.buffer) { - if (s.bgColorLayer) { - for (auto& handle : mDrawingState.callbackHandles) { - handle->latchTime = latchTime; - } - } - return NO_ERROR; - } - - for (auto& handle : mDrawingState.callbackHandles) { - if (handle->frameNumber == mDrawingState.frameNumber) { - handle->latchTime = latchTime; - } - } - - const int32_t layerId = getSequence(); - const uint64_t bufferId = mDrawingState.buffer->getId(); - const uint64_t frameNumber = mDrawingState.frameNumber; - const auto acquireFence = std::make_shared(mDrawingState.acquireFence); - mFlinger->mTimeStats->setAcquireFence(layerId, frameNumber, acquireFence); - mFlinger->mTimeStats->setLatchTime(layerId, frameNumber, latchTime); - - mFlinger->mFrameTracer->traceFence(layerId, bufferId, frameNumber, acquireFence, - FrameTracer::FrameEvent::ACQUIRE_FENCE); - mFlinger->mFrameTracer->traceTimestamp(layerId, bufferId, frameNumber, latchTime, - FrameTracer::FrameEvent::LATCH); - - auto& bufferSurfaceFrame = mDrawingState.bufferSurfaceFrameTX; - if (bufferSurfaceFrame != nullptr && - bufferSurfaceFrame->getPresentState() != PresentState::Presented) { - // Update only if the bufferSurfaceFrame wasn't already presented. A Presented - // bufferSurfaceFrame could be seen here if a pending state was applied successfully and we - // are processing the next state. - addSurfaceFramePresentedForBuffer(bufferSurfaceFrame, - mDrawingState.acquireFenceTime->getSignalTime(), - latchTime); - mDrawingState.bufferSurfaceFrameTX.reset(); - } - - std::deque> remainingHandles; - mFlinger->getTransactionCallbackInvoker() - .addOnCommitCallbackHandles(mDrawingState.callbackHandles, remainingHandles); - mDrawingState.callbackHandles = remainingHandles; - - mDrawingStateModified = false; - - return NO_ERROR; -} - -status_t BufferStateLayer::updateActiveBuffer() { - const State& s(getDrawingState()); - - if (s.buffer == nullptr) { - return BAD_VALUE; - } - - if (!mBufferInfo.mBuffer || !s.buffer->hasSameBuffer(*mBufferInfo.mBuffer)) { - decrementPendingBufferCount(); - } - - mPreviousReleaseCallbackId = {getCurrentBufferId(), mBufferInfo.mFrameNumber}; - mBufferInfo.mBuffer = s.buffer; - mBufferInfo.mFence = s.acquireFence; - mBufferInfo.mFrameNumber = s.frameNumber; - - return NO_ERROR; -} - -status_t BufferStateLayer::updateFrameNumber() { - // TODO(marissaw): support frame history events - mPreviousFrameNumber = mCurrentFrameNumber; - mCurrentFrameNumber = mDrawingState.frameNumber; - return NO_ERROR; -} - -void BufferStateLayer::HwcSlotGenerator::bufferErased(const client_cache_t& clientCacheId) { - std::lock_guard lock(mMutex); - if (!clientCacheId.isValid()) { - ALOGE("invalid process, failed to erase buffer"); - return; - } - eraseBufferLocked(clientCacheId); -} - -int BufferStateLayer::HwcSlotGenerator::getHwcCacheSlot(const client_cache_t& clientCacheId) { - std::lock_guard lock(mMutex); - auto itr = mCachedBuffers.find(clientCacheId); - if (itr == mCachedBuffers.end()) { - return addCachedBuffer(clientCacheId); - } - auto& [hwcCacheSlot, counter] = itr->second; - counter = mCounter++; - return hwcCacheSlot; -} - -int BufferStateLayer::HwcSlotGenerator::addCachedBuffer(const client_cache_t& clientCacheId) - REQUIRES(mMutex) { - if (!clientCacheId.isValid()) { - ALOGE("invalid process, returning invalid slot"); - return BufferQueue::INVALID_BUFFER_SLOT; - } - - ClientCache::getInstance().registerErasedRecipient(clientCacheId, wp(this)); - - int hwcCacheSlot = getFreeHwcCacheSlot(); - mCachedBuffers[clientCacheId] = {hwcCacheSlot, mCounter++}; - return hwcCacheSlot; -} - -int BufferStateLayer::HwcSlotGenerator::getFreeHwcCacheSlot() REQUIRES(mMutex) { - if (mFreeHwcCacheSlots.empty()) { - evictLeastRecentlyUsed(); - } - - int hwcCacheSlot = mFreeHwcCacheSlots.top(); - mFreeHwcCacheSlots.pop(); - return hwcCacheSlot; -} - -void BufferStateLayer::HwcSlotGenerator::evictLeastRecentlyUsed() REQUIRES(mMutex) { - uint64_t minCounter = UINT_MAX; - client_cache_t minClientCacheId = {}; - for (const auto& [clientCacheId, slotCounter] : mCachedBuffers) { - const auto& [hwcCacheSlot, counter] = slotCounter; - if (counter < minCounter) { - minCounter = counter; - minClientCacheId = clientCacheId; - } - } - eraseBufferLocked(minClientCacheId); - - ClientCache::getInstance().unregisterErasedRecipient(minClientCacheId, this); -} - -void BufferStateLayer::HwcSlotGenerator::eraseBufferLocked(const client_cache_t& clientCacheId) - REQUIRES(mMutex) { - auto itr = mCachedBuffers.find(clientCacheId); - if (itr == mCachedBuffers.end()) { - return; - } - auto& [hwcCacheSlot, counter] = itr->second; - - // TODO send to hwc cache and resources - - mFreeHwcCacheSlots.push(hwcCacheSlot); - mCachedBuffers.erase(clientCacheId); -} - -void BufferStateLayer::gatherBufferInfo() { - BufferLayer::gatherBufferInfo(); - - const State& s(getDrawingState()); - mBufferInfo.mDesiredPresentTime = s.desiredPresentTime; - mBufferInfo.mFenceTime = std::make_shared(s.acquireFence); - mBufferInfo.mFence = s.acquireFence; - mBufferInfo.mTransform = s.bufferTransform; - auto lastDataspace = mBufferInfo.mDataspace; - mBufferInfo.mDataspace = translateDataspace(s.dataspace); - if (lastDataspace != mBufferInfo.mDataspace) { - mFlinger->mSomeDataspaceChanged = true; - } - mBufferInfo.mCrop = computeBufferCrop(s); - mBufferInfo.mScaleMode = NATIVE_WINDOW_SCALING_MODE_SCALE_TO_WINDOW; - mBufferInfo.mSurfaceDamage = s.surfaceDamageRegion; - mBufferInfo.mHdrMetadata = s.hdrMetadata; - mBufferInfo.mApi = s.api; - mBufferInfo.mTransformToDisplayInverse = s.transformToDisplayInverse; - mBufferInfo.mBufferSlot = mHwcSlotGenerator->getHwcCacheSlot(s.clientCacheId); -} - -uint32_t BufferStateLayer::getEffectiveScalingMode() const { - return NATIVE_WINDOW_SCALING_MODE_SCALE_TO_WINDOW; -} - -Rect BufferStateLayer::computeBufferCrop(const State& s) { - if (s.buffer && !s.bufferCrop.isEmpty()) { - Rect bufferCrop; - s.buffer->getBounds().intersect(s.bufferCrop, &bufferCrop); - return bufferCrop; - } else if (s.buffer) { - return s.buffer->getBounds(); - } else { - return s.bufferCrop; - } -} - -sp BufferStateLayer::createClone() { - LayerCreationArgs args(mFlinger.get(), nullptr, mName + " (Mirror)", 0, LayerMetadata()); - args.textureName = mTextureName; - sp layer = mFlinger->getFactory().createBufferStateLayer(args); - layer->mHwcSlotGenerator = mHwcSlotGenerator; - layer->setInitialValuesForClone(this); - return layer; -} - -bool BufferStateLayer::bufferNeedsFiltering() const { - const State& s(getDrawingState()); - if (!s.buffer) { - return false; - } - - int32_t bufferWidth = static_cast(s.buffer->getWidth()); - int32_t bufferHeight = static_cast(s.buffer->getHeight()); - - // Undo any transformations on the buffer and return the result. - if (s.bufferTransform & ui::Transform::ROT_90) { - std::swap(bufferWidth, bufferHeight); - } - - if (s.transformToDisplayInverse) { - uint32_t invTransform = DisplayDevice::getPrimaryDisplayRotationFlags(); - if (invTransform & ui::Transform::ROT_90) { - std::swap(bufferWidth, bufferHeight); - } - } - - const Rect layerSize{getBounds()}; - return layerSize.width() != bufferWidth || layerSize.height() != bufferHeight; -} - -void BufferStateLayer::decrementPendingBufferCount() { - int32_t pendingBuffers = --mPendingBufferTransactions; - tracePendingBufferCount(pendingBuffers); -} - -void BufferStateLayer::tracePendingBufferCount(int32_t pendingBuffers) { - ATRACE_INT(mBlastTransactionName.c_str(), pendingBuffers); -} - - -/* - * We don't want to send the layer's transform to input, but rather the - * parent's transform. This is because BufferStateLayer's transform is - * information about how the buffer is placed on screen. The parent's - * transform makes more sense to send since it's information about how the - * layer is placed on screen. This transform is used by input to determine - * how to go from screen space back to window space. - */ -ui::Transform BufferStateLayer::getInputTransform() const { - sp parent = mDrawingParent.promote(); - if (parent == nullptr) { - return ui::Transform(); - } - - return parent->getTransform(); -} - -/** - * Similar to getInputTransform, we need to update the bounds to include the transform. - * This is because bounds for BSL doesn't include buffer transform, where the input assumes - * that's already included. - */ -Rect BufferStateLayer::getInputBounds() const { - Rect bufferBounds = getCroppedBufferSize(getDrawingState()); - if (mDrawingState.transform.getType() == ui::Transform::IDENTITY || !bufferBounds.isValid()) { - return bufferBounds; - } - return mDrawingState.transform.transform(bufferBounds); -} - -bool BufferStateLayer::simpleBufferUpdate(const layer_state_t& s) const { - const uint64_t requiredFlags = layer_state_t::eBufferChanged; - - 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::eAutoRefreshChanged | - layer_state_t::eReparent; - - const uint64_t allowedFlags = layer_state_t::eHasListenerCallbacksChanged | - layer_state_t::eFrameRateSelectionPriority | layer_state_t::eFrameRateChanged | - layer_state_t::eSurfaceDamageRegionChanged | layer_state_t::eApiChanged | - layer_state_t::eMetadataChanged | layer_state_t::eDropInputModeChanged | - layer_state_t::eInputInfoChanged; - - if ((s.what & requiredFlags) != requiredFlags) { - ALOGV("%s: false [missing required flags 0x%" PRIx64 "]", __func__, - (s.what | requiredFlags) & ~s.what); - return false; - } - - if (s.what & deniedFlags) { - ALOGV("%s: false [has denied flags 0x%" PRIx64 "]", __func__, s.what & deniedFlags); - return false; - } - - if (s.what & allowedFlags) { - ALOGV("%s: [has allowed flags 0x%" PRIx64 "]", __func__, s.what & allowedFlags); - } - - if (s.what & layer_state_t::ePositionChanged) { - if (mRequestedTransform.tx() != s.x || mRequestedTransform.ty() != s.y) { - ALOGV("%s: false [ePositionChanged changed]", __func__); - return false; - } - } - - if (s.what & layer_state_t::eAlphaChanged) { - if (mDrawingState.color.a != s.alpha) { - ALOGV("%s: false [eAlphaChanged changed]", __func__); - return false; - } - } - - if (s.what & layer_state_t::eColorTransformChanged) { - if (mDrawingState.colorTransform != s.colorTransform) { - ALOGV("%s: false [eColorTransformChanged changed]", __func__); - return false; - } - } - - if (s.what & layer_state_t::eBackgroundColorChanged) { - if (mDrawingState.bgColorLayer || s.bgColorAlpha != 0) { - ALOGV("%s: false [eBackgroundColorChanged changed]", __func__); - return false; - } - } - - if (s.what & layer_state_t::eMatrixChanged) { - if (mRequestedTransform.dsdx() != s.matrix.dsdx || - mRequestedTransform.dtdy() != s.matrix.dtdy || - mRequestedTransform.dtdx() != s.matrix.dtdx || - mRequestedTransform.dsdy() != s.matrix.dsdy) { - ALOGV("%s: false [eMatrixChanged changed]", __func__); - return false; - } - } - - if (s.what & layer_state_t::eCornerRadiusChanged) { - if (mDrawingState.cornerRadius != s.cornerRadius) { - ALOGV("%s: false [eCornerRadiusChanged changed]", __func__); - return false; - } - } - - if (s.what & layer_state_t::eBackgroundBlurRadiusChanged) { - if (mDrawingState.backgroundBlurRadius != static_cast(s.backgroundBlurRadius)) { - ALOGV("%s: false [eBackgroundBlurRadiusChanged changed]", __func__); - return false; - } - } - - if (s.what & layer_state_t::eTransformChanged) { - if (mDrawingState.bufferTransform != s.transform) { - ALOGV("%s: false [eTransformChanged changed]", __func__); - return false; - } - } - - if (s.what & layer_state_t::eTransformToDisplayInverseChanged) { - if (mDrawingState.transformToDisplayInverse != s.transformToDisplayInverse) { - ALOGV("%s: false [eTransformToDisplayInverseChanged changed]", __func__); - return false; - } - } - - if (s.what & layer_state_t::eCropChanged) { - if (mDrawingState.crop != s.crop) { - ALOGV("%s: false [eCropChanged changed]", __func__); - return false; - } - } - - if (s.what & layer_state_t::eDataspaceChanged) { - if (mDrawingState.dataspace != s.dataspace) { - ALOGV("%s: false [eDataspaceChanged changed]", __func__); - return false; - } - } - - if (s.what & layer_state_t::eHdrMetadataChanged) { - if (mDrawingState.hdrMetadata != s.hdrMetadata) { - ALOGV("%s: false [eHdrMetadataChanged changed]", __func__); - return false; - } - } - - if (s.what & layer_state_t::eSidebandStreamChanged) { - if (mDrawingState.sidebandStream != s.sidebandStream) { - ALOGV("%s: false [eSidebandStreamChanged changed]", __func__); - return false; - } - } - - if (s.what & layer_state_t::eColorSpaceAgnosticChanged) { - if (mDrawingState.colorSpaceAgnostic != s.colorSpaceAgnostic) { - ALOGV("%s: false [eColorSpaceAgnosticChanged changed]", __func__); - return false; - } - } - - if (s.what & layer_state_t::eShadowRadiusChanged) { - if (mDrawingState.shadowRadius != s.shadowRadius) { - ALOGV("%s: false [eShadowRadiusChanged changed]", __func__); - return false; - } - } - - if (s.what & layer_state_t::eFixedTransformHintChanged) { - if (mDrawingState.fixedTransformHint != s.fixedTransformHint) { - ALOGV("%s: false [eFixedTransformHintChanged changed]", __func__); - return false; - } - } - - if (s.what & layer_state_t::eTrustedOverlayChanged) { - if (mDrawingState.isTrustedOverlay != s.isTrustedOverlay) { - ALOGV("%s: false [eTrustedOverlayChanged changed]", __func__); - return false; - } - } - - if (s.what & layer_state_t::eStretchChanged) { - StretchEffect temp = s.stretchEffect; - temp.sanitize(); - if (mDrawingState.stretchEffect != temp) { - ALOGV("%s: false [eStretchChanged changed]", __func__); - return false; - } - } - - if (s.what & layer_state_t::eBufferCropChanged) { - if (mDrawingState.bufferCrop != s.bufferCrop) { - ALOGV("%s: false [eBufferCropChanged changed]", __func__); - return false; - } - } - - if (s.what & layer_state_t::eDestinationFrameChanged) { - if (mDrawingState.destinationFrame != s.destinationFrame) { - ALOGV("%s: false [eDestinationFrameChanged changed]", __func__); - return false; - } - } - - if (s.what & layer_state_t::eDimmingEnabledChanged) { - if (mDrawingState.dimmingEnabled != s.dimmingEnabled) { - ALOGV("%s: false [eDimmingEnabledChanged changed]", __func__); - return false; - } - } - - ALOGV("%s: true", __func__); - return true; -} - -} // namespace android diff --git a/services/surfaceflinger/BufferStateLayer.h b/services/surfaceflinger/BufferStateLayer.h deleted file mode 100644 index 3f0dbe403961efe685c85620a5182efd4649bc62..0000000000000000000000000000000000000000 --- a/services/surfaceflinger/BufferStateLayer.h +++ /dev/null @@ -1,211 +0,0 @@ -/* - * Copyright (C) 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. - */ - -#pragma once - -#include "BufferLayer.h" -#include "Layer.h" - -#include -#include -#include -#include - -#include - -namespace android { - -class SlotGenerationTest; - -class BufferStateLayer : public BufferLayer { -public: - explicit BufferStateLayer(const LayerCreationArgs&); - - ~BufferStateLayer() override; - - // Implements Layer. - const char* getType() const override { return "BufferStateLayer"; } - - void onLayerDisplayed(ftl::SharedFuture) override; - - void releasePendingBuffer(nsecs_t dequeueReadyTime) override; - - void finalizeFrameEventHistory(const std::shared_ptr& glDoneFence, - const CompositorTiming& compositorTiming) override; - - bool isBufferDue(nsecs_t /*expectedPresentTime*/) const override { return true; } - - Region getActiveTransparentRegion(const Layer::State& s) const override { - return s.transparentRegionHint; - } - Rect getCrop(const Layer::State& s) const; - - bool setTransform(uint32_t transform) override; - bool setTransformToDisplayInverse(bool transformToDisplayInverse) override; - bool setCrop(const Rect& crop) override; - bool setBuffer(std::shared_ptr& /* buffer */, - const BufferData& bufferData, nsecs_t postTime, nsecs_t desiredPresentTime, - bool isAutoTimestamp, std::optional dequeueTime, - const FrameTimelineInfo& info) override; - bool setDataspace(ui::Dataspace dataspace) override; - bool setHdrMetadata(const HdrMetadata& hdrMetadata) override; - bool setSurfaceDamageRegion(const Region& surfaceDamage) override; - bool setApi(int32_t api) override; - bool setSidebandStream(const sp& sidebandStream) override; - bool setTransactionCompletedListeners(const std::vector>& handles) override; - bool setPosition(float /*x*/, float /*y*/) override; - bool setMatrix(const layer_state_t::matrix22_t& /*matrix*/); - - // Override to ignore legacy layer state properties that are not used by BufferStateLayer - bool setSize(uint32_t /*w*/, uint32_t /*h*/) override { return false; } - bool setTransparentRegionHint(const Region& transparent) override; - - Rect getBufferSize(const State& s) const override; - FloatRect computeSourceBounds(const FloatRect& parentBounds) const override; - void setAutoRefresh(bool autoRefresh) override; - - bool setBufferCrop(const Rect& bufferCrop) override; - bool setDestinationFrame(const Rect& destinationFrame) override; - bool updateGeometry() override; - - // ----------------------------------------------------------------------- - - // ----------------------------------------------------------------------- - // Interface implementation for BufferLayer - // ----------------------------------------------------------------------- - bool fenceHasSignaled() const override; - bool framePresentTimeIsCurrent(nsecs_t expectedPresentTime) const override; - bool onPreComposition(nsecs_t refreshStartTime) override; - uint32_t getEffectiveScalingMode() const override; - - // See mPendingBufferTransactions - void decrementPendingBufferCount(); - std::atomic* getPendingBufferCounter() override { return &mPendingBufferTransactions; } - std::string getPendingBufferCounterName() override { return mBlastTransactionName; } - - bool shouldPresentNow(nsecs_t /*expectedPresentTime*/) const override { return true; } - -protected: - void gatherBufferInfo() override; - void onSurfaceFrameCreated(const std::shared_ptr& surfaceFrame); - ui::Transform getInputTransform() const override; - Rect getInputBounds() const override; - -private: - friend class SlotGenerationTest; - friend class TransactionFrameTracerTest; - friend class TransactionSurfaceFrameTest; - - inline void tracePendingBufferCount(int32_t pendingBuffers); - - bool updateFrameEventHistory(const sp& acquireFence, nsecs_t postedTime, - nsecs_t requestedPresentTime); - - bool latchSidebandStream(bool& recomputeVisibleRegions) override; - - bool hasFrameUpdate() const override; - - status_t updateTexImage(bool& recomputeVisibleRegions, nsecs_t latchTime, - nsecs_t expectedPresentTime) override; - - status_t updateActiveBuffer() override; - status_t updateFrameNumber() override; - - sp createClone() override; - - // Crop that applies to the buffer - Rect computeBufferCrop(const State& s); - - bool willPresentCurrentTransaction() const; - - bool bufferNeedsFiltering() const override; - - bool simpleBufferUpdate(const layer_state_t& s) const override; - - ReleaseCallbackId mPreviousReleaseCallbackId = ReleaseCallbackId::INVALID_ID; - uint64_t mPreviousReleasedFrameNumber = 0; - - uint64_t mPreviousBarrierFrameNumber = 0; - - bool mReleasePreviousBuffer = false; - - // Stores the last set acquire fence signal time used to populate the callback handle's acquire - // time. - std::variant> mCallbackHandleAcquireTimeOrFence = -1; - - std::deque> mPendingJankClassifications; - // An upper bound on the number of SurfaceFrames in the pending classifications deque. - static constexpr int kPendingClassificationMaxSurfaceFrames = 25; - - const std::string mBlastTransactionName{"BufferTX - " + mName}; - // This integer is incremented everytime a buffer arrives at the server for this layer, - // and decremented when a buffer is dropped or latched. When changed the integer is exported - // to systrace with ATRACE_INT and mBlastTransactionName. This way when debugging perf it is - // possible to see when a buffer arrived at the server, and in which frame it latched. - // - // You can understand the trace this way: - // - If the integer increases, a buffer arrived at the server. - // - If the integer decreases in latchBuffer, that buffer was latched - // - If the integer decreases in setBuffer or doTransaction, a buffer was dropped - std::atomic mPendingBufferTransactions{0}; - - // Contains requested position and matrix updates. This will be applied if the client does - // not specify a destination frame. - ui::Transform mRequestedTransform; - - // TODO(marissaw): support sticky transform for LEGACY camera mode - - class HwcSlotGenerator : public ClientCache::ErasedRecipient { - public: - HwcSlotGenerator() { - for (int i = 0; i < BufferQueue::NUM_BUFFER_SLOTS; i++) { - mFreeHwcCacheSlots.push(i); - } - } - - void bufferErased(const client_cache_t& clientCacheId); - - int getHwcCacheSlot(const client_cache_t& clientCacheId); - - private: - friend class SlotGenerationTest; - int addCachedBuffer(const client_cache_t& clientCacheId) REQUIRES(mMutex); - int getFreeHwcCacheSlot() REQUIRES(mMutex); - void evictLeastRecentlyUsed() REQUIRES(mMutex); - void eraseBufferLocked(const client_cache_t& clientCacheId) REQUIRES(mMutex); - - struct CachedBufferHash { - std::size_t operator()(const client_cache_t& clientCacheId) const { - return std::hash{}(clientCacheId.id); - } - }; - - std::mutex mMutex; - - std::unordered_map, - CachedBufferHash> - mCachedBuffers GUARDED_BY(mMutex); - std::stack mFreeHwcCacheSlots GUARDED_BY(mMutex); - - // The cache increments this counter value when a slot is updated or used. - // Used to track the least recently-used buffer - uint64_t mCounter = 0; - }; - - sp mHwcSlotGenerator; -}; - -} // namespace android diff --git a/services/surfaceflinger/Client.cpp b/services/surfaceflinger/Client.cpp index 6d7b732b366f6fe479e7fbc5e871ac59d252bd5b..bdbc79b8e1728a9182e6abff5dc784e8cadc71a9 100644 --- a/services/surfaceflinger/Client.cpp +++ b/services/surfaceflinger/Client.cpp @@ -21,12 +21,18 @@ #include +#include + #include "Client.h" +#include "FrontEnd/LayerCreationArgs.h" +#include "FrontEnd/LayerHandle.h" #include "Layer.h" #include "SurfaceFlinger.h" namespace android { +using gui::aidl_utils::binderStatusFromStatusT; + // --------------------------------------------------------------------------- const String16 sAccessSurfaceFlinger("android.permission.ACCESS_SURFACE_FLINGER"); @@ -42,82 +48,73 @@ status_t Client::initCheck() const { return NO_ERROR; } -void Client::attachLayer(const sp& handle, const sp& layer) -{ - Mutex::Autolock _l(mLock); - mLayers.add(handle, layer); -} - -void Client::detachLayer(const Layer* layer) -{ - Mutex::Autolock _l(mLock); - // we do a linear search here, because this doesn't happen often - const size_t count = mLayers.size(); - for (size_t i=0 ; i Client::getLayerUser(const sp& handle) const -{ - Mutex::Autolock _l(mLock); - sp lbc; - wp layer(mLayers.valueFor(handle)); - if (layer != 0) { - lbc = layer.promote(); - ALOGE_IF(lbc==0, "getLayerUser(name=%p) is dead", handle.get()); - } - return lbc; -} - -status_t Client::createSurface(const String8& name, uint32_t /* w */, uint32_t /* h */, - PixelFormat /* format */, uint32_t flags, - const sp& parentHandle, LayerMetadata metadata, - sp* outHandle, sp* /* gbp */, - int32_t* outLayerId, uint32_t* outTransformHint) { +binder::Status Client::createSurface(const std::string& name, int32_t flags, + const sp& parent, const gui::LayerMetadata& metadata, + gui::CreateSurfaceResult* outResult) { // We rely on createLayer to check permissions. - LayerCreationArgs args(mFlinger.get(), this, name.c_str(), flags, std::move(metadata)); - return mFlinger->createLayer(args, outHandle, parentHandle, outLayerId, nullptr, - outTransformHint); + sp handle; + LayerCreationArgs args(mFlinger.get(), sp::fromExisting(this), name.c_str(), + static_cast(flags), std::move(metadata)); + args.parentHandle = parent; + const status_t status = mFlinger->createLayer(args, *outResult); + return binderStatusFromStatusT(status); } -status_t Client::createWithSurfaceParent(const String8& /* name */, uint32_t /* w */, - uint32_t /* h */, PixelFormat /* format */, - uint32_t /* flags */, - const sp& /* parent */, - LayerMetadata /* metadata */, sp* /* handle */, - sp* /* gbp */, - int32_t* /* outLayerId */, - uint32_t* /* outTransformHint */) { - // This api does not make sense with blast since SF no longer tracks IGBP. This api should be - // removed. - return BAD_VALUE; -} - -status_t Client::mirrorSurface(const sp& mirrorFromHandle, sp* outHandle, - int32_t* outLayerId) { - LayerCreationArgs args(mFlinger.get(), this, "MirrorRoot", 0 /* flags */, LayerMetadata()); - return mFlinger->mirrorLayer(args, mirrorFromHandle, outHandle, outLayerId); -} - -status_t Client::clearLayerFrameStats(const sp& handle) const { - sp layer = getLayerUser(handle); +binder::Status Client::clearLayerFrameStats(const sp& handle) { + status_t status; + sp layer = LayerHandle::getLayer(handle); if (layer == nullptr) { - return NAME_NOT_FOUND; + status = NAME_NOT_FOUND; + } else { + layer->clearFrameStats(); + status = NO_ERROR; } - layer->clearFrameStats(); - return NO_ERROR; + return binderStatusFromStatusT(status); } -status_t Client::getLayerFrameStats(const sp& handle, FrameStats* outStats) const { - sp layer = getLayerUser(handle); +binder::Status Client::getLayerFrameStats(const sp& handle, gui::FrameStats* outStats) { + status_t status; + sp layer = LayerHandle::getLayer(handle); if (layer == nullptr) { - return NAME_NOT_FOUND; + status = NAME_NOT_FOUND; + } else { + FrameStats stats; + layer->getFrameStats(&stats); + outStats->refreshPeriodNano = stats.refreshPeriodNano; + outStats->desiredPresentTimesNano.reserve(stats.desiredPresentTimesNano.size()); + for (const auto& t : stats.desiredPresentTimesNano) { + outStats->desiredPresentTimesNano.push_back(t); + } + outStats->actualPresentTimesNano.reserve(stats.actualPresentTimesNano.size()); + for (const auto& t : stats.actualPresentTimesNano) { + outStats->actualPresentTimesNano.push_back(t); + } + outStats->frameReadyTimesNano.reserve(stats.frameReadyTimesNano.size()); + for (const auto& t : stats.frameReadyTimesNano) { + outStats->frameReadyTimesNano.push_back(t); + } + status = NO_ERROR; } - layer->getFrameStats(outStats); - return NO_ERROR; + return binderStatusFromStatusT(status); +} + +binder::Status Client::mirrorSurface(const sp& mirrorFromHandle, + gui::CreateSurfaceResult* outResult) { + sp handle; + LayerCreationArgs args(mFlinger.get(), sp::fromExisting(this), "MirrorRoot", + 0 /* flags */, gui::LayerMetadata()); + status_t status = mFlinger->mirrorLayer(args, mirrorFromHandle, *outResult); + return binderStatusFromStatusT(status); +} + +binder::Status Client::mirrorDisplay(int64_t displayId, gui::CreateSurfaceResult* outResult) { + sp handle; + LayerCreationArgs args(mFlinger.get(), sp::fromExisting(this), + "MirrorRoot-" + std::to_string(displayId), 0 /* flags */, + gui::LayerMetadata()); + std::optional id = DisplayId::fromValue(static_cast(displayId)); + status_t status = mFlinger->mirrorDisplay(*id, args, *outResult); + return binderStatusFromStatusT(status); } // --------------------------------------------------------------------------- diff --git a/services/surfaceflinger/Client.h b/services/surfaceflinger/Client.h index 15cd763822e1a2c0b2ac6aeab9d57850d73fe11f..af410ea19c3a41a206d8361af68acd8f4ffcbd56 100644 --- a/services/surfaceflinger/Client.h +++ b/services/surfaceflinger/Client.h @@ -24,55 +24,40 @@ #include #include -#include +#include namespace android { class Layer; class SurfaceFlinger; -class Client : public BnSurfaceComposerClient -{ +class Client : public gui::BnSurfaceComposerClient { public: explicit Client(const sp& flinger); ~Client() = default; status_t initCheck() const; - // protected by SurfaceFlinger::mStateLock - void attachLayer(const sp& handle, const sp& layer); - void detachLayer(const Layer* layer); - - sp getLayerUser(const sp& handle) const; - private: // ISurfaceComposerClient interface - virtual status_t createSurface(const String8& name, uint32_t w, uint32_t h, PixelFormat format, - uint32_t flags, const sp& parent, - LayerMetadata metadata, sp* handle, - sp* gbp, int32_t* outLayerId, - uint32_t* outTransformHint = nullptr); - virtual status_t createWithSurfaceParent(const String8& name, uint32_t w, uint32_t h, - PixelFormat format, uint32_t flags, - const sp& parent, - LayerMetadata metadata, sp* handle, - sp* gbp, int32_t* outLayerId, - uint32_t* outTransformHint = nullptr); + binder::Status createSurface(const std::string& name, int32_t flags, const sp& parent, + const gui::LayerMetadata& metadata, + gui::CreateSurfaceResult* outResult) override; - status_t mirrorSurface(const sp& mirrorFromHandle, sp* handle, - int32_t* outLayerId); + binder::Status clearLayerFrameStats(const sp& handle) override; - virtual status_t clearLayerFrameStats(const sp& handle) const; + binder::Status getLayerFrameStats(const sp& handle, + gui::FrameStats* outStats) override; - virtual status_t getLayerFrameStats(const sp& handle, FrameStats* outStats) const; + binder::Status mirrorSurface(const sp& mirrorFromHandle, + gui::CreateSurfaceResult* outResult) override; + + binder::Status mirrorDisplay(int64_t displayId, gui::CreateSurfaceResult* outResult) override; // constant sp mFlinger; - // protected by mLock - DefaultKeyedVector< wp, wp > mLayers; - // thread-safe mutable Mutex mLock; }; diff --git a/services/surfaceflinger/ClientCache.cpp b/services/surfaceflinger/ClientCache.cpp index 3c7b9d93aa225249c374518a7a5e0ac150e231cf..09e41ffede665d4cffbf183417f53fd746583fda 100644 --- a/services/surfaceflinger/ClientCache.cpp +++ b/services/surfaceflinger/ClientCache.cpp @@ -22,6 +22,7 @@ #include #include +#include #include #include "ClientCache.h" @@ -30,18 +31,18 @@ namespace android { ANDROID_SINGLETON_STATIC_INSTANCE(ClientCache); -ClientCache::ClientCache() : mDeathRecipient(new CacheDeathRecipient) {} +ClientCache::ClientCache() : mDeathRecipient(sp::make()) {} bool ClientCache::getBuffer(const client_cache_t& cacheId, ClientCacheBuffer** outClientCacheBuffer) { auto& [processToken, id] = cacheId; if (processToken == nullptr) { - ALOGE("failed to get buffer, invalid (nullptr) process token"); + ALOGE_AND_TRACE("ClientCache::getBuffer - invalid (nullptr) process token"); return false; } auto it = mBuffers.find(processToken); if (it == mBuffers.end()) { - ALOGE("failed to get buffer, invalid process token"); + ALOGE_AND_TRACE("ClientCache::getBuffer - invalid process token"); return false; } @@ -49,7 +50,7 @@ bool ClientCache::getBuffer(const client_cache_t& cacheId, auto bufItr = processBuffers.find(id); if (bufItr == processBuffers.end()) { - ALOGV("failed to get buffer, invalid buffer id"); + ALOGE_AND_TRACE("ClientCache::getBuffer - invalid buffer id"); return false; } @@ -58,16 +59,17 @@ bool ClientCache::getBuffer(const client_cache_t& cacheId, return true; } -bool ClientCache::add(const client_cache_t& cacheId, const sp& buffer) { +base::expected, ClientCache::AddError> +ClientCache::add(const client_cache_t& cacheId, const sp& buffer) { auto& [processToken, id] = cacheId; if (processToken == nullptr) { - ALOGE("failed to cache buffer: invalid process token"); - return false; + ALOGE_AND_TRACE("ClientCache::add - invalid (nullptr) process token"); + return base::unexpected(AddError::Unspecified); } if (!buffer) { - ALOGE("failed to cache buffer: invalid buffer"); - return false; + ALOGE_AND_TRACE("ClientCache::add - invalid (nullptr) buffer"); + return base::unexpected(AddError::Unspecified); } std::lock_guard lock(mMutex); @@ -79,16 +81,16 @@ bool ClientCache::add(const client_cache_t& cacheId, const sp& bu if (it == mBuffers.end()) { token = processToken.promote(); if (!token) { - ALOGE("failed to cache buffer: invalid token"); - return false; + ALOGE_AND_TRACE("ClientCache::add - invalid token"); + return base::unexpected(AddError::Unspecified); } // Only call linkToDeath if not a local binder if (token->localBinder() == nullptr) { status_t err = token->linkToDeath(mDeathRecipient); if (err != NO_ERROR) { - ALOGE("failed to cache buffer: could not link to death"); - return false; + ALOGE_AND_TRACE("ClientCache::add - could not link to death"); + return base::unexpected(AddError::Unspecified); } } auto [itr, success] = @@ -102,21 +104,22 @@ bool ClientCache::add(const client_cache_t& cacheId, const sp& bu auto& processBuffers = it->second.second; if (processBuffers.size() > BUFFER_CACHE_MAX_SIZE) { - ALOGE("failed to cache buffer: cache is full"); - return false; + ALOGE_AND_TRACE("ClientCache::add - cache is full"); + return base::unexpected(AddError::CacheFull); } LOG_ALWAYS_FATAL_IF(mRenderEngine == nullptr, "Attempted to build the ClientCache before a RenderEngine instance was " "ready!"); - processBuffers[id].buffer = std::make_shared< - renderengine::impl::ExternalTexture>(buffer, *mRenderEngine, - renderengine::impl::ExternalTexture::Usage:: - READABLE); - return true; + + return (processBuffers[id].buffer = std::make_shared< + renderengine::impl::ExternalTexture>(buffer, *mRenderEngine, + renderengine::impl::ExternalTexture:: + Usage::READABLE)); } -void ClientCache::erase(const client_cache_t& cacheId) { +sp ClientCache::erase(const client_cache_t& cacheId) { + sp buffer; auto& [processToken, id] = cacheId; std::vector> pendingErase; { @@ -124,9 +127,11 @@ void ClientCache::erase(const client_cache_t& cacheId) { ClientCacheBuffer* buf = nullptr; if (!getBuffer(cacheId, &buf)) { ALOGE("failed to erase buffer, could not retrieve buffer"); - return; + return nullptr; } + buffer = buf->buffer->getBuffer(); + for (auto& recipient : buf->recipients) { sp erasedRecipient = recipient.promote(); if (erasedRecipient) { @@ -140,6 +145,7 @@ void ClientCache::erase(const client_cache_t& cacheId) { for (auto& recipient : pendingErase) { recipient->bufferErased(cacheId); } + return buffer; } std::shared_ptr ClientCache::get(const client_cache_t& cacheId) { diff --git a/services/surfaceflinger/ClientCache.h b/services/surfaceflinger/ClientCache.h index a9b8177d701b6368600cc8d7ef1c68ba1c0381b1..b56b252d9fa8bdded189e34c08dc1d6e1834c27a 100644 --- a/services/surfaceflinger/ClientCache.h +++ b/services/surfaceflinger/ClientCache.h @@ -33,12 +33,27 @@ namespace android { +// This class manages a cache of buffer handles between SurfaceFlinger clients +// and the SurfaceFlinger process which optimizes away some of the cost of +// sending buffer handles across processes. +// +// Buffers are explicitly cached and uncached by the SurfaceFlinger client. When +// a buffer is uncached, it is not only purged from this cache, but the buffer +// ID is also passed down to CompositionEngine to purge it from a similar cache +// used between SurfaceFlinger and Composer HAL. The buffer ID used to purge +// both the SurfaceFlinger side of this other cache, as well as Composer HAL's +// side of the cache. +// class ClientCache : public Singleton { public: ClientCache(); - bool add(const client_cache_t& cacheId, const sp& buffer); - void erase(const client_cache_t& cacheId); + enum class AddError { CacheFull, Unspecified }; + + base::expected, AddError> add( + const client_cache_t& cacheId, const sp& buffer); + + sp erase(const client_cache_t& cacheId); std::shared_ptr get(const client_cache_t& cacheId); diff --git a/services/surfaceflinger/CompositionEngine/Android.bp b/services/surfaceflinger/CompositionEngine/Android.bp index f12d7b6a1750576c73d38600559ed3622058d04f..837b49a9dd8f2d622d94cfb1d637eadaacef11f3 100644 --- a/services/surfaceflinger/CompositionEngine/Android.bp +++ b/services/surfaceflinger/CompositionEngine/Android.bp @@ -9,7 +9,11 @@ package { cc_defaults { name: "libcompositionengine_defaults", - defaults: ["surfaceflinger_defaults"], + defaults: [ + "android.hardware.graphics.composer3-ndk_shared", + "librenderengine_deps", + "surfaceflinger_defaults", + ], cflags: [ "-DLOG_TAG=\"CompositionEngine\"", "-DATRACE_TAG=ATRACE_TAG_GRAPHICS", @@ -20,10 +24,9 @@ cc_defaults { "android.hardware.graphics.composer@2.2", "android.hardware.graphics.composer@2.3", "android.hardware.graphics.composer@2.4", - "android.hardware.graphics.composer3-V1-ndk", "android.hardware.power@1.0", "android.hardware.power@1.3", - "android.hardware.power-V2-cpp", + "android.hardware.power-V4-cpp", "libbase", "libcutils", "libgui", @@ -40,7 +43,6 @@ cc_defaults { "libmath", "librenderengine", "libtonemap", - "libtrace_proto", "libaidlcommonsupport", "libprocessgroup", "libcgrouprc", @@ -139,6 +141,11 @@ cc_test { "libgmock", "libgtest", ], + // For some reason, libvulkan isn't picked up from librenderengine + // Probably ASAN related? + shared_libs: [ + "libvulkan", + ], sanitize: { hwaddress: true, }, diff --git a/services/surfaceflinger/CompositionEngine/include/compositionengine/CompositionEngine.h b/services/surfaceflinger/CompositionEngine/include/compositionengine/CompositionEngine.h index 3faa068c03ef62bc4ba54fc612403c2941712575..7c10fa57ecf759ab5bc49b78bb1d0d881d0510ab 100644 --- a/services/surfaceflinger/CompositionEngine/include/compositionengine/CompositionEngine.h +++ b/services/surfaceflinger/CompositionEngine/include/compositionengine/CompositionEngine.h @@ -18,9 +18,10 @@ #include #include - #include +#include "Feature.h" + namespace android { class HWComposer; @@ -55,9 +56,9 @@ public: virtual void setHwComposer(std::unique_ptr) = 0; virtual renderengine::RenderEngine& getRenderEngine() const = 0; - virtual void setRenderEngine(std::unique_ptr) = 0; + virtual void setRenderEngine(renderengine::RenderEngine*) = 0; - virtual TimeStats& getTimeStats() const = 0; + virtual TimeStats* getTimeStats() const = 0; virtual void setTimeStats(const std::shared_ptr&) = 0; virtual bool needsAnotherUpdate() const = 0; @@ -72,6 +73,8 @@ public: // TODO(b/121291683): These will become private/internal virtual void preComposition(CompositionRefreshArgs&) = 0; + virtual FeatureFlags getFeatureFlags() const = 0; + // Debugging virtual void dump(std::string&) const = 0; }; diff --git a/services/surfaceflinger/CompositionEngine/include/compositionengine/CompositionRefreshArgs.h b/services/surfaceflinger/CompositionEngine/include/compositionengine/CompositionRefreshArgs.h index f201751d59bcae7f6b6245438ee29840f9888684..d93e25e4cad7ebc97c6ec923e29c366aed790431 100644 --- a/services/surfaceflinger/CompositionEngine/include/compositionengine/CompositionRefreshArgs.h +++ b/services/surfaceflinger/CompositionEngine/include/compositionengine/CompositionRefreshArgs.h @@ -32,6 +32,11 @@ namespace android::compositionengine { using Layers = std::vector>; using Outputs = std::vector>; +struct BorderRenderInfo { + float width = 0; + half4 color; + std::vector layerIds; +}; /** * A parameter object for refreshing a set of outputs */ @@ -47,6 +52,9 @@ struct CompositionRefreshArgs { // All the layers that have queued updates. Layers layersWithQueuedFrames; + // All graphic buffers that will no longer be used and should be removed from caches. + std::vector bufferIdsToUncache; + // Controls how the color mode is chosen for an output OutputColorSetting outputColorSetting{OutputColorSetting::kEnhanced}; @@ -59,9 +67,6 @@ struct CompositionRefreshArgs { // Used to correctly apply an inverse-display buffer transform if applicable ui::Transform::RotationFlags internalDisplayRotationFlags{ui::Transform::ROT_0}; - // If true, GPU clocks will be increased when rendering blurs - bool blursAreExpensive{false}; - // If true, the complete output geometry needs to be recomputed this frame bool updatingOutputGeometryThisFrame{false}; @@ -78,18 +83,19 @@ struct CompositionRefreshArgs { // If set, causes the dirty regions to flash with the delay std::optional devOptFlashDirtyRegionsDelay; - // The earliest time to send the present command to the HAL - std::chrono::steady_clock::time_point earliestPresentTime; - - // The previous present fence. Used together with earliestPresentTime - // to prevent an early presentation of a frame. - std::shared_ptr previousPresentFence; + // Optional. + // The earliest time to send the present command to the HAL. + std::optional earliestPresentTime; // The expected time for the next present nsecs_t expectedPresentTime{0}; // If set, a frame has been scheduled for that time. std::optional scheduledFrameTime; + + std::vector borderInfoList; + + bool hasTrustedPresentationListener = false; }; } // namespace android::compositionengine diff --git a/services/surfaceflinger/CompositionEngine/tests/TestUtils.h b/services/surfaceflinger/CompositionEngine/include/compositionengine/Feature.h similarity index 63% rename from services/surfaceflinger/CompositionEngine/tests/TestUtils.h rename to services/surfaceflinger/CompositionEngine/include/compositionengine/Feature.h index c80fde6ead265905bd29149b85c5d6a837e70524..ee8000ae1890bfe529d64bdebb33ad88e0bf107d 100644 --- a/services/surfaceflinger/CompositionEngine/tests/TestUtils.h +++ b/services/surfaceflinger/CompositionEngine/include/compositionengine/Feature.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2021 The Android Open Source Project + * Copyright 2019 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -13,19 +13,18 @@ * See the License for the specific language governing permissions and * limitations under the License. */ + #pragma once -#include +#include +#include namespace android::compositionengine { -namespace { -template -std::future futureOf(T obj) { - std::promise resultPromise; - std::future resultFuture = resultPromise.get_future(); - resultPromise.set_value(std::move(obj)); - return resultFuture; -} -} // namespace -} // namespace android::compositionengine +enum class Feature : int32_t { + kSnapshotLayerMetadata = 1 << 0, +}; + +using FeatureFlags = ftl::Flags; + +} // namespace android::compositionengine \ No newline at end of file diff --git a/services/surfaceflinger/CompositionEngine/include/compositionengine/LayerFE.h b/services/surfaceflinger/CompositionEngine/include/compositionengine/LayerFE.h index ec610c1b1d7767870bed6efda9ea9f1b76f6cc10..ccff1eccb629aa5b648d8fa7920f29e3918a2593 100644 --- a/services/surfaceflinger/CompositionEngine/include/compositionengine/LayerFE.h +++ b/services/surfaceflinger/CompositionEngine/include/compositionengine/LayerFE.h @@ -19,8 +19,7 @@ #include #include #include - -#include +#include "ui/LayerStack.h" // TODO(b/129481165): remove the #pragma below and fix conversion issues #pragma clang diagnostic push @@ -33,6 +32,7 @@ #pragma clang diagnostic pop // ignored "-Wconversion -Wextra" #include +#include #include #include @@ -40,6 +40,10 @@ namespace android { class Fence; +namespace gui { +struct LayerMetadata; +} + namespace compositionengine { struct LayerFECompositionState; @@ -54,31 +58,8 @@ 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) = 0; - - // Used with latchCompositionState() - enum class StateSubset { - // Gets the basic geometry (bounds, transparent region, visibility, - // transforms, alpha) for the layer, for computing visibility and - // coverage. - BasicGeometry, - - // Gets the full geometry (crops, buffer transforms, metadata) and - // content (buffer or color) state for the layer. - GeometryAndContent, - - // Gets the per frame content (buffer or color) state for the layer. - Content, - - // Gets the cursor state for the layer. - Cursor, - }; - - // Prepares the output-independent composition state for the layer. The - // StateSubset argument selects what portion of the state is actually needed - // by the CompositionEngine code, since computing everything may be - // expensive. - virtual void prepareCompositionState(StateSubset) = 0; + virtual bool onPreComposition(nsecs_t refreshStartTime, + bool updatingOutputGeometryThisFrame) = 0; struct ClientCompositionTargetSettings { enum class BlurSetting { @@ -138,6 +119,9 @@ public: // Requested white point of the layer in nits const float whitePointNits; + + // True if layers with 170M dataspace should be overridden to sRGB. + const bool treat170mAsSrgb; }; // A superset of LayerSettings required by RenderEngine to compose a layer @@ -150,14 +134,14 @@ public: uint64_t frameNumber = 0; }; - // Returns the z-ordered list of LayerSettings to pass to RenderEngine::drawLayers. The list - // may contain shadows casted by the layer or the content of the layer itself. If the layer - // does not render then an empty list will be returned. - virtual std::vector prepareClientCompositionList( - ClientCompositionTargetSettings&) = 0; + // 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. + virtual std::optional prepareClientComposition( + ClientCompositionTargetSettings&) const = 0; // Called after the layer is displayed to update the presentation fence - virtual void onLayerDisplayed(ftl::SharedFuture) = 0; + virtual void onLayerDisplayed(ftl::SharedFuture, ui::LayerStack layerStack) = 0; // Gets some kind of identifier for the layer for debug purposes. virtual const char* getDebugName() const = 0; @@ -168,6 +152,8 @@ public: // Whether the layer should be rendered with rounded corners. virtual bool hasRoundedCorners() const = 0; virtual void setWasClientComposed(const sp&) {} + virtual const gui::LayerMetadata* getMetadata() const = 0; + virtual const gui::LayerMetadata* getRelativeMetadata() const = 0; }; // TODO(b/121291683): Specialize std::hash<> for sp so these and others can diff --git a/services/surfaceflinger/CompositionEngine/include/compositionengine/LayerFECompositionState.h b/services/surfaceflinger/CompositionEngine/include/compositionengine/LayerFECompositionState.h index 974f7c6134c01357b48dc72427a67388b28f4fe5..35ca3a58d994f47667541e37cf1ea754c519408a 100644 --- a/services/surfaceflinger/CompositionEngine/include/compositionengine/LayerFECompositionState.h +++ b/services/surfaceflinger/CompositionEngine/include/compositionengine/LayerFECompositionState.h @@ -18,6 +18,7 @@ #include +#include #include #include #include @@ -163,7 +164,6 @@ struct LayerFECompositionState { // The buffer and related state sp buffer; - int bufferSlot{BufferQueue::INVALID_BUFFER_SLOT}; sp acquireFence = Fence::NO_FENCE; Region surfaceDamage; uint64_t frameNumber = 0; @@ -210,6 +210,10 @@ struct LayerFECompositionState { // The dimming flag bool dimmingEnabled{true}; + float currentHdrSdrRatio = 1.f; + float desiredHdrSdrRatio = 1.f; + + gui::CachingHint cachingHint = gui::CachingHint::Enabled; virtual ~LayerFECompositionState(); // Debugging diff --git a/services/surfaceflinger/CompositionEngine/include/compositionengine/Output.h b/services/surfaceflinger/CompositionEngine/include/compositionengine/Output.h index 2203639b1ade67770a50207fe6bb35fead400024..a3d86398cf20bba9bd3a11a073eaa52f6236082a 100644 --- a/services/surfaceflinger/CompositionEngine/include/compositionengine/Output.h +++ b/services/surfaceflinger/CompositionEngine/include/compositionengine/Output.h @@ -23,6 +23,7 @@ #include #include #include +#include #include #include @@ -152,6 +153,10 @@ public: Region aboveOpaqueLayers; // The region of the output which should be considered dirty Region dirtyRegion; + // The region of the output which is covered by layers, excluding display overlays. This + // only has a value if there's something needing it, like when a TrustedPresentationListener + // is set + std::optional aboveCoveredLayersExcludingOverlays; }; virtual ~Output(); @@ -262,9 +267,6 @@ public: // Presents the output, finalizing all composition details virtual void present(const CompositionRefreshArgs&) = 0; - // Latches the front-end layer state for each output layer - virtual void updateLayerStateFromFE(const CompositionRefreshArgs&) const = 0; - // Enables predicting composition strategy to run client composition earlier virtual void setPredictCompositionStrategy(bool) = 0; @@ -275,6 +277,7 @@ protected: virtual void setDisplayColorProfile(std::unique_ptr) = 0; virtual void setRenderSurface(std::unique_ptr) = 0; + virtual void uncacheBuffers(const std::vector&) = 0; virtual void rebuildLayerStacks(const CompositionRefreshArgs&, LayerFESet&) = 0; virtual void collectVisibleLayers(const CompositionRefreshArgs&, CoverageState&) = 0; virtual void ensureOutputLayerIfVisible(sp&, CoverageState&) = 0; @@ -291,12 +294,11 @@ protected: using GpuCompositionResult = compositionengine::impl::GpuCompositionResult; // Runs prepare frame in another thread while running client composition using // the previous frame's composition strategy. - virtual GpuCompositionResult prepareFrameAsync(const CompositionRefreshArgs&) = 0; + virtual GpuCompositionResult prepareFrameAsync() = 0; virtual void devOptRepaintFlash(const CompositionRefreshArgs&) = 0; - virtual void finishFrame(const CompositionRefreshArgs&, GpuCompositionResult&&) = 0; + virtual void finishFrame(GpuCompositionResult&&) = 0; virtual std::optional composeSurfaces( - const Region&, const compositionengine::CompositionRefreshArgs&, - std::shared_ptr, base::unique_fd&) = 0; + const Region&, std::shared_ptr, base::unique_fd&) = 0; virtual void postFramebuffer() = 0; virtual void renderCachedSets(const CompositionRefreshArgs&) = 0; virtual bool chooseCompositionStrategy( diff --git a/services/surfaceflinger/CompositionEngine/include/compositionengine/OutputLayer.h b/services/surfaceflinger/CompositionEngine/include/compositionengine/OutputLayer.h index bf5184e99779e316386d3cf0537307e32f531d49..4dbf8d2fce8760c7faf0447caa301a645a29fddf 100644 --- a/services/surfaceflinger/CompositionEngine/include/compositionengine/OutputLayer.h +++ b/services/surfaceflinger/CompositionEngine/include/compositionengine/OutputLayer.h @@ -19,6 +19,7 @@ #include #include #include +#include #include #include @@ -81,6 +82,10 @@ public: // TODO(lpique): Make this protected once it is only internally called. virtual CompositionState& editState() = 0; + // Clear the cache entries for a set of buffers that SurfaceFlinger no + // longer cares about. + virtual void uncacheBuffers(const std::vector& bufferIdsToUncache) = 0; + // Recalculates the state of the output layer from the output-independent // layer. If includeGeometry is false, the geometry state can be skipped. // internalDisplayRotationFlags must be set to the rotation flags for the @@ -126,9 +131,9 @@ public: // Returns true if the composition settings scale pixels virtual bool needsFiltering() const = 0; - // Returns a composition list to be used by RenderEngine if the layer has been overridden + // Returns LayerSettings to be used by RenderEngine if the layer has been overridden // during the composition process - virtual std::vector getOverrideCompositionList() const = 0; + virtual std::optional getOverrideCompositionSettings() const = 0; // Debugging virtual void dump(std::string& result) const = 0; diff --git a/services/surfaceflinger/CompositionEngine/include/compositionengine/RenderSurface.h b/services/surfaceflinger/CompositionEngine/include/compositionengine/RenderSurface.h index 9ee779cca11c792b68dc2df6091da969e7490f50..585467456cc8c47f657386bb6cc38c72cc0c8171 100644 --- a/services/surfaceflinger/CompositionEngine/include/compositionengine/RenderSurface.h +++ b/services/surfaceflinger/CompositionEngine/include/compositionengine/RenderSurface.h @@ -91,16 +91,9 @@ public: // Called after the HWC calls are made to present the display virtual void onPresentDisplayCompleted() = 0; - // Called after the surface has been rendering to signal the surface should - // be made ready for displaying - virtual void flip() = 0; - // Debugging - Dumps the state of the RenderSurface to a string virtual void dump(std::string& result) const = 0; - // Debugging - gets the page flip count for the RenderSurface - virtual std::uint32_t getPageFlipCount() const = 0; - // Returns true if the render surface supports client composition prediction. virtual bool supportsCompositionStrategyPrediction() const = 0; }; diff --git a/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/CompositionEngine.h b/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/CompositionEngine.h index 386808d71489992d3b2816f79ec4a797d37ea6b9..c6995576a1eac0e3796448e6d5af050ebcb6dce2 100644 --- a/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/CompositionEngine.h +++ b/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/CompositionEngine.h @@ -34,9 +34,9 @@ public: void setHwComposer(std::unique_ptr) override; renderengine::RenderEngine& getRenderEngine() const override; - void setRenderEngine(std::unique_ptr) override; + void setRenderEngine(renderengine::RenderEngine*) override; - TimeStats& getTimeStats() const override; + TimeStats* getTimeStats() const override; void setTimeStats(const std::shared_ptr&) override; bool needsAnotherUpdate() const override; @@ -48,17 +48,17 @@ public: void preComposition(CompositionRefreshArgs&) override; + FeatureFlags getFeatureFlags() const override; + // Debugging void dump(std::string&) const override; - void updateLayerStateFromFE(CompositionRefreshArgs& args); - // Testing void setNeedsAnotherUpdateForTest(bool); private: std::unique_ptr mHwComposer; - std::unique_ptr mRenderEngine; + renderengine::RenderEngine* mRenderEngine; std::shared_ptr mTimeStats; bool mNeedsAnotherUpdate = false; nsecs_t mRefreshStartTime = 0; diff --git a/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/Display.h b/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/Display.h index 33a10a36a7fb5b3ba7ba224f3dd61e9b72eaa7ca..6cf1d685b667f5bccb727df781acf6d7331eec68 100644 --- a/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/Display.h +++ b/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/Display.h @@ -61,7 +61,7 @@ public: bool getSkipColorTransform() const override; compositionengine::Output::FrameFences presentAndGetFrameFences() override; void setExpensiveRenderingExpected(bool) override; - void finishFrame(const CompositionRefreshArgs&, GpuCompositionResult&&) override; + void finishFrame(GpuCompositionResult&&) override; // compositionengine::Display overrides DisplayId getId() const override; diff --git a/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/HwcBufferCache.h b/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/HwcBufferCache.h index fd22aa3a2a56b93455d4a30622657723bb14f9a0..b6a4240b4be576430ef866d4b83c1479e88f0480 100644 --- a/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/HwcBufferCache.h +++ b/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/HwcBufferCache.h @@ -17,7 +17,8 @@ #pragma once #include -#include +#include +#include // TODO(b/129481165): remove the #pragma below and fix conversion issues #pragma clang diagnostic push @@ -37,35 +38,76 @@ class GraphicBuffer; namespace compositionengine::impl { -// With HIDLized hwcomposer HAL, the HAL can maintain a buffer cache for each -// HWC display and layer. When updating a display target or a layer buffer, -// we have the option to send the buffer handle over or to request the HAL to -// retrieve it from its cache. The latter is cheaper since it eliminates the -// overhead to transfer the handle over the trasport layer, and the overhead -// for the HAL to clone and retain the handle. +// The buffer cache returns both a slot and the buffer that should be sent to HWC. In cases +// where the buffer is already cached, the buffer is a nullptr and will not be sent to HWC as +// an optimization. +struct HwcSlotAndBuffer { + uint32_t slot; + sp buffer; +}; + +// +// Manages the slot assignments for a buffers stored in Composer HAL's cache. +// +// Cache slots are an optimization when communicating buffer handles to Composer +// HAL. When updating a layer's buffer, we can either send a new buffer handle +// along with it's slot assignment or request the HAL to reuse a buffer handle +// that we've already sent by using the slot assignment. The latter is cheaper +// since it eliminates the overhead to transfer the buffer handle over IPC and +// the overhead for the HAL to clone the handle. // -// To be able to find out whether a buffer is already in the HAL's cache, we -// use HWComposerBufferCache to mirror the cache in SF. class HwcBufferCache { +private: + static const constexpr size_t kMaxLayerBufferCount = BufferQueue::NUM_BUFFER_SLOTS; + public: + // public for testing + // Override buffers don't use the normal cache slots because we don't want them to evict client + // buffers from the cache. We add an extra slot at the end for the override buffers. + static const constexpr size_t kOverrideBufferSlot = kMaxLayerBufferCount; + HwcBufferCache(); - // Given a buffer, return the HWC cache slot and - // buffer to be sent to HWC. + + // + // Given a buffer, return the HWC cache slot and buffer to send to HWC. + // + // If the buffer is already in the cache, the buffer is null to optimize away sending HWC the + // buffer handle. // - // outBuffer is set to buffer when buffer is not in the HWC cache; - // otherwise, outBuffer is set to nullptr. - void getHwcBuffer(int slot, const sp& buffer, uint32_t* outSlot, - sp* outBuffer); + HwcSlotAndBuffer getHwcSlotAndBuffer(const sp& buffer); + // + // Given a buffer, return the HWC cache slot and buffer to send to HWC. + // + // A special slot number is used for override buffers. + // + // If the buffer is already in the cache, the buffer is null to optimize away sending HWC the + // buffer handle. + // + HwcSlotAndBuffer getOverrideHwcSlotAndBuffer(const sp& buffer); - // Special caching slot for the layer caching feature. - static const constexpr size_t FLATTENER_CACHING_SLOT = BufferQueue::NUM_BUFFER_SLOTS; + // + // When a client process discards a buffer, it needs to be purged from the HWC cache. + // + // Returns the slot number of the buffer, or UINT32_MAX if it wasn't found in the cache. + // + uint32_t uncache(uint64_t graphicBufferId); private: - // an array where the index corresponds to a slot and the value corresponds to a (counter, - // buffer) pair. "counter" is a unique value that indicates the last time this slot was updated - // or used and allows us to keep track of the least-recently used buffer. - static const constexpr size_t kMaxLayerBufferCount = BufferQueue::NUM_BUFFER_SLOTS + 1; - wp mBuffers[kMaxLayerBufferCount]; + uint32_t cache(const sp& buffer); + uint32_t getLeastRecentlyUsedSlot(); + + struct Cache { + sp buffer; + uint32_t slot; + // Cache entries are evicted according to least-recently-used when more than + // kMaxLayerBufferCount unique buffers have been sent to a layer. + uint64_t lruCounter; + }; + + std::unordered_map mCacheByBufferId; + sp mLastOverrideBuffer; + std::stack mFreeSlots; + uint64_t mLeastRecentlyUsedCounter; }; } // namespace compositionengine::impl diff --git a/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/Output.h b/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/Output.h index 428c19fe029c3d0addb9985a15ff3ecf5298f911..229a657236384947cdb01e81f19e426985811618 100644 --- a/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/Output.h +++ b/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/Output.h @@ -82,6 +82,7 @@ public: void prepare(const CompositionRefreshArgs&, LayerFESet&) override; void present(const CompositionRefreshArgs&) override; + void uncacheBuffers(const std::vector& bufferIdsToUncache) override; void rebuildLayerStacks(const CompositionRefreshArgs&, LayerFESet&) override; void collectVisibleLayers(const CompositionRefreshArgs&, compositionengine::Output::CoverageState&) override; @@ -89,18 +90,16 @@ public: compositionengine::Output::CoverageState&) override; void setReleasedLayers(const compositionengine::CompositionRefreshArgs&) override; - void updateLayerStateFromFE(const CompositionRefreshArgs&) const override; void updateCompositionState(const compositionengine::CompositionRefreshArgs&) override; void planComposition() override; void writeCompositionState(const compositionengine::CompositionRefreshArgs&) override; void updateColorProfile(const compositionengine::CompositionRefreshArgs&) override; void beginFrame() override; void prepareFrame() override; - GpuCompositionResult prepareFrameAsync(const CompositionRefreshArgs&) override; + GpuCompositionResult prepareFrameAsync() override; void devOptRepaintFlash(const CompositionRefreshArgs&) override; - void finishFrame(const CompositionRefreshArgs&, GpuCompositionResult&&) override; + void finishFrame(GpuCompositionResult&&) override; std::optional composeSurfaces(const Region&, - const compositionengine::CompositionRefreshArgs&, std::shared_ptr, base::unique_fd&) override; void postFramebuffer() override; @@ -135,9 +134,10 @@ protected: void applyCompositionStrategy(const std::optional&) override{}; bool getSkipColorTransform() const override; compositionengine::Output::FrameFences presentAndGetFrameFences() override; + virtual renderengine::DisplaySettings generateClientCompositionDisplaySettings() const; std::vector generateClientCompositionRequests( - bool supportsProtectedContent, ui::Dataspace outputDataspace, - std::vector &outLayerFEs) override; + bool supportsProtectedContent, ui::Dataspace outputDataspace, + std::vector& outLayerFEs) override; void appendRegionFlashRequests(const Region&, std::vector&) override; void setExpensiveRenderingExpected(bool enabled) override; void setHintSessionGpuFence(std::unique_ptr&& gpuFence) override; @@ -154,8 +154,11 @@ protected: bool mustRecompose() const; + const std::string& getNamePlusId() const { return mNamePlusId; } + private: void dirtyEntireOutput(); + void updateCompositionStateForBorder(const compositionengine::CompositionRefreshArgs&); compositionengine::OutputLayer* findLayerRequestingBackgroundComposition() const; void finishPrepareFrame(); ui::Dataspace getBestDataspace(ui::Dataspace*, bool*) const; @@ -163,6 +166,7 @@ private: const compositionengine::CompositionRefreshArgs&) const; std::string mName; + std::string mNamePlusId; std::unique_ptr mDisplayColorProfile; std::unique_ptr mRenderSurface; diff --git a/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/OutputCompositionState.h b/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/OutputCompositionState.h index 7709b967b604fae52328ef587252729f08376cef..a3fda61ecbb04bcc154b47da9dbf572c9f6dff68 100644 --- a/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/OutputCompositionState.h +++ b/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/OutputCompositionState.h @@ -33,6 +33,7 @@ #pragma clang diagnostic pop // ignored "-Wconversion -Wextra" #include +#include #include #include #include @@ -121,12 +122,9 @@ struct OutputCompositionState { bool previousDeviceRequestedSuccess = false; + // Optional. // The earliest time to send the present command to the HAL - std::chrono::steady_clock::time_point earliestPresentTime; - - // The previous present fence. Used together with earliestPresentTime - // to prevent an early presentation of a frame. - std::shared_ptr previousPresentFence; + std::optional earliestPresentTime; // The expected time for the next present nsecs_t expectedPresentTime{0}; @@ -164,6 +162,8 @@ struct OutputCompositionState { bool treat170mAsSrgb = false; + std::vector borderInfoList; + uint64_t lastOutputLayerHash = 0; uint64_t outputLayerHash = 0; diff --git a/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/OutputLayer.h b/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/OutputLayer.h index ecd432f62936602cabf8fc68745eade4fd28d010..f383392e558603e55ca9ca4cf5b9f7b2ceb24342 100644 --- a/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/OutputLayer.h +++ b/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/OutputLayer.h @@ -18,7 +18,9 @@ #include #include +#include #include +#include #include #include @@ -43,6 +45,8 @@ public: void setHwcLayer(std::shared_ptr) override; + void uncacheBuffers(const std::vector& bufferIdsToUncache) override; + void updateCompositionState(bool includeGeometry, bool forceClientComposition, ui::Transform::RotationFlags) override; void writeStateToHWC(bool includeGeometry, bool skipLayer, uint32_t z, bool zIsOverridden, @@ -57,7 +61,7 @@ public: void prepareForDeviceLayerRequests() override; void applyDeviceLayerRequest(Hwc2::IComposerClient::LayerRequest request) override; bool needsFiltering() const override; - std::vector getOverrideCompositionList() const override; + std::optional getOverrideCompositionSettings() const override; void dump(std::string&) const override; virtual FloatRect calculateOutputSourceCrop(uint32_t internalDisplayRotationFlags) const; diff --git a/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/OutputLayerCompositionState.h b/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/OutputLayerCompositionState.h index 2b383c1dfe5fccf4227ab191d658dc6cf7c74099..7b0af3a248771d7dc1d050ac1c9fb6539150477e 100644 --- a/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/OutputLayerCompositionState.h +++ b/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/OutputLayerCompositionState.h @@ -63,9 +63,15 @@ struct OutputLayerCompositionState { // The portion of the layer that is not obscured and is also opaque Region visibleNonTransparentRegion; - // The portion of the layer that is obscured by opaque layers on top + // The portion of the layer that is obscured by all layers on top. This includes transparent and + // opaque. Region coveredRegion; + // The portion of the layer that is obscured by all layers on top excluding display overlays. + // This only has a value if there's something needing it, like when a + // TrustedPresentationListener is set. + std::optional coveredRegionExcludingDisplayOverlays; + // The visibleRegion transformed to output space Region outputSpaceVisibleRegion; @@ -136,6 +142,10 @@ struct OutputLayerCompositionState { // cost of sending reused buffers to the HWC. HwcBufferCache hwcBufferCache; + // The previously-active buffer for this layer. + uint64_t activeBufferId; + uint32_t activeBufferSlot; + // Set to true when overridden info has been sent to HW composer bool stateOverridden = false; diff --git a/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/RenderSurface.h b/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/RenderSurface.h index e4cb1136457b75131b7f6e74530120d16d15cb2e..1c14a439203458d931d53ab894e684360e61c24a 100644 --- a/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/RenderSurface.h +++ b/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/RenderSurface.h @@ -62,15 +62,12 @@ public: base::unique_fd* bufferFence) override; void queueBuffer(base::unique_fd readyFence) override; void onPresentDisplayCompleted() override; - void flip() override; bool supportsCompositionStrategyPrediction() const override; // Debugging void dump(std::string& result) const override; - std::uint32_t getPageFlipCount() const override; // Testing - void setPageFlipCountForTest(std::uint32_t); void setSizeForTest(const ui::Size&); std::shared_ptr& mutableTextureForTest(); base::unique_fd& mutableBufferReadyForTest(); @@ -89,7 +86,6 @@ private: ui::Size mSize; const size_t mMaxTextureCacheSize; bool mProtected{false}; - std::uint32_t mPageFlipCount{0}; }; std::unique_ptr createRenderSurface( diff --git a/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/planner/CachedSet.h b/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/planner/CachedSet.h index 24a774454245d87bf5ba46b55a6b9a7b36d7ab0b..d26ca9dd002d0e4ad20f4f78280cfdd441790448 100644 --- a/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/planner/CachedSet.h +++ b/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/planner/CachedSet.h @@ -149,6 +149,9 @@ public: bool hasSolidColorLayers() const; + // True if any layer in this cached set has CachingHint::Disabled + bool cachingHintExcludesLayers() const; + private: const NonBufferHash mFingerprint; std::chrono::steady_clock::time_point mLastUpdate = std::chrono::steady_clock::now(); diff --git a/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/planner/LayerState.h b/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/planner/LayerState.h index 5aec7c2e88cf4160a03bbb37e11ebeee1848d180..ce2b96fc2b5c73c3e3d642d6e6fd71d8b6325cae 100644 --- a/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/planner/LayerState.h +++ b/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/planner/LayerState.h @@ -72,6 +72,8 @@ enum class LayerStateField : uint32_t { SolidColor = 1u << 16, BackgroundBlurRadius = 1u << 17, BlurRegions = 1u << 18, + HasProtectedContent = 1u << 19, + CachingHint = 1u << 20, }; // clang-format on @@ -245,9 +247,15 @@ public: ui::Dataspace getDataspace() const { return mOutputDataspace.get(); } - bool isProtected() const { - return getOutputLayer()->getLayerFE().getCompositionState()->hasProtectedContent; - } + float getHdrSdrRatio() const { + return getOutputLayer()->getLayerFE().getCompositionState()->currentHdrSdrRatio; + }; + + wp getBuffer() const { return mBuffer.get(); } + + bool isProtected() const { return mIsProtected.get(); } + + gui::CachingHint getCachingHint() const { return mCachingHint.get(); } bool hasSolidColorCompositionType() const { return getOutputLayer()->getLayerFE().getCompositionState()->compositionType == @@ -482,7 +490,19 @@ private: return hash; }}; - static const constexpr size_t kNumNonUniqueFields = 17; + OutputLayerState mIsProtected{[](auto layer) { + return layer->getLayerFE().getCompositionState()->hasProtectedContent; + }}; + + OutputLayerState + mCachingHint{[](auto layer) { + return layer->getLayerFE().getCompositionState()->cachingHint; + }, + [](const gui::CachingHint& cachingHint) { + return std::vector{toString(cachingHint)}; + }}; + + static const constexpr size_t kNumNonUniqueFields = 19; std::array getNonUniqueFields() { std::array constFields = @@ -496,13 +516,11 @@ private: } std::array getNonUniqueFields() const { - return { - &mDisplayFrame, &mSourceCrop, &mBufferTransform, &mBlendMode, + return {&mDisplayFrame, &mSourceCrop, &mBufferTransform, &mBlendMode, &mAlpha, &mLayerMetadata, &mVisibleRegion, &mOutputDataspace, &mPixelFormat, &mColorTransform, &mCompositionType, &mSidebandStream, &mBuffer, &mSolidColor, &mBackgroundBlurRadius, &mBlurRegions, - &mFrameNumber, - }; + &mFrameNumber, &mIsProtected, &mCachingHint}; } }; diff --git a/services/surfaceflinger/CompositionEngine/include/compositionengine/mock/CompositionEngine.h b/services/surfaceflinger/CompositionEngine/include/compositionengine/mock/CompositionEngine.h index f953d0b7d0eb77d3ff70a1e28bb01c154ef03f9d..9b2387b9666be9f6d2a22539e04088d81d9b4d65 100644 --- a/services/surfaceflinger/CompositionEngine/include/compositionengine/mock/CompositionEngine.h +++ b/services/surfaceflinger/CompositionEngine/include/compositionengine/mock/CompositionEngine.h @@ -40,9 +40,9 @@ public: MOCK_METHOD1(setHwComposer, void(std::unique_ptr)); MOCK_CONST_METHOD0(getRenderEngine, renderengine::RenderEngine&()); - MOCK_METHOD1(setRenderEngine, void(std::unique_ptr)); + MOCK_METHOD1(setRenderEngine, void(renderengine::RenderEngine*)); - MOCK_CONST_METHOD0(getTimeStats, TimeStats&()); + MOCK_CONST_METHOD0(getTimeStats, TimeStats*()); MOCK_METHOD1(setTimeStats, void(const std::shared_ptr&)); MOCK_CONST_METHOD0(needsAnotherUpdate, bool()); @@ -53,6 +53,8 @@ public: MOCK_METHOD1(preComposition, void(CompositionRefreshArgs&)); + MOCK_CONST_METHOD0(getFeatureFlags, FeatureFlags()); + MOCK_CONST_METHOD1(dump, void(std::string&)); }; diff --git a/services/surfaceflinger/CompositionEngine/include/compositionengine/mock/LayerFE.h b/services/surfaceflinger/CompositionEngine/include/compositionengine/mock/LayerFE.h index 1c5c10f8238c97491f70210506114abdce3a72dd..15e4577ae05e840a4cd0068fa7ec2fef49731ff5 100644 --- a/services/surfaceflinger/CompositionEngine/include/compositionengine/mock/LayerFE.h +++ b/services/surfaceflinger/CompositionEngine/include/compositionengine/mock/LayerFE.h @@ -36,24 +36,27 @@ private: // sp>::make() friend class sp; friend class testing::StrictMock; + friend class testing::NiceMock; public: virtual ~LayerFE(); MOCK_CONST_METHOD0(getCompositionState, const LayerFECompositionState*()); - MOCK_METHOD1(onPreComposition, bool(nsecs_t)); + MOCK_METHOD2(onPreComposition, bool(nsecs_t, bool)); - MOCK_METHOD1(prepareCompositionState, void(compositionengine::LayerFE::StateSubset)); - MOCK_METHOD1(prepareClientCompositionList, - std::vector( - compositionengine::LayerFE::ClientCompositionTargetSettings&)); + MOCK_CONST_METHOD1(prepareClientComposition, + std::optional( + compositionengine::LayerFE::ClientCompositionTargetSettings&)); - MOCK_METHOD(void, onLayerDisplayed, (ftl::SharedFuture), (override)); + MOCK_METHOD(void, onLayerDisplayed, (ftl::SharedFuture, ui::LayerStack), + (override)); MOCK_CONST_METHOD0(getDebugName, const char*()); MOCK_CONST_METHOD0(getSequence, int32_t()); MOCK_CONST_METHOD0(hasRoundedCorners, bool()); + MOCK_CONST_METHOD0(getMetadata, gui::LayerMetadata*()); + MOCK_CONST_METHOD0(getRelativeMetadata, gui::LayerMetadata*()); }; } // namespace android::compositionengine::mock diff --git a/services/surfaceflinger/CompositionEngine/include/compositionengine/mock/Output.h b/services/surfaceflinger/CompositionEngine/include/compositionengine/mock/Output.h index 2a04949cff0f939e344285d6a4e97138a2904705..a56fc790b4664d9fe4d33766042c7fc53ef7df77 100644 --- a/services/surfaceflinger/CompositionEngine/include/compositionengine/mock/Output.h +++ b/services/surfaceflinger/CompositionEngine/include/compositionengine/mock/Output.h @@ -82,6 +82,7 @@ public: MOCK_METHOD2(prepare, void(const compositionengine::CompositionRefreshArgs&, LayerFESet&)); MOCK_METHOD1(present, void(const compositionengine::CompositionRefreshArgs&)); + MOCK_METHOD1(uncacheBuffers, void(const std::vector&)); MOCK_METHOD2(rebuildLayerStacks, void(const compositionengine::CompositionRefreshArgs&, LayerFESet&)); MOCK_METHOD2(collectVisibleLayers, @@ -91,7 +92,6 @@ public: void(sp&, compositionengine::Output::CoverageState&)); MOCK_METHOD1(setReleasedLayers, void(const compositionengine::CompositionRefreshArgs&)); - MOCK_CONST_METHOD1(updateLayerStateFromFE, void(const CompositionRefreshArgs&)); MOCK_METHOD1(updateCompositionState, void(const CompositionRefreshArgs&)); MOCK_METHOD0(planComposition, void()); MOCK_METHOD1(writeCompositionState, void(const CompositionRefreshArgs&)); @@ -100,7 +100,7 @@ public: MOCK_METHOD0(beginFrame, void()); MOCK_METHOD0(prepareFrame, void()); - MOCK_METHOD1(prepareFrameAsync, GpuCompositionResult(const CompositionRefreshArgs&)); + MOCK_METHOD0(prepareFrameAsync, GpuCompositionResult()); MOCK_METHOD1(chooseCompositionStrategy, bool(std::optional*)); MOCK_METHOD1(chooseCompositionStrategyAsync, @@ -110,14 +110,12 @@ public: MOCK_METHOD1(devOptRepaintFlash, void(const compositionengine::CompositionRefreshArgs&)); - MOCK_METHOD2(finishFrame, - void(const compositionengine::CompositionRefreshArgs&, GpuCompositionResult&&)); + MOCK_METHOD1(finishFrame, void(GpuCompositionResult&&)); - MOCK_METHOD4(composeSurfaces, - std::optional( - const Region&, - const compositionengine::CompositionRefreshArgs& refreshArgs, - std::shared_ptr, base::unique_fd&)); + MOCK_METHOD3(composeSurfaces, + std::optional(const Region&, + std::shared_ptr, + base::unique_fd&)); MOCK_CONST_METHOD0(getSkipColorTransform, bool()); MOCK_METHOD0(postFramebuffer, void()); diff --git a/services/surfaceflinger/CompositionEngine/include/compositionengine/mock/OutputLayer.h b/services/surfaceflinger/CompositionEngine/include/compositionengine/mock/OutputLayer.h index a6cb81146812da35a30649f277003587e85b234d..5fef63a51090848f40e069686a767dea6766a1d2 100644 --- a/services/surfaceflinger/CompositionEngine/include/compositionengine/mock/OutputLayer.h +++ b/services/surfaceflinger/CompositionEngine/include/compositionengine/mock/OutputLayer.h @@ -16,6 +16,8 @@ #pragma once +#include + #include #include #include @@ -33,6 +35,8 @@ public: MOCK_METHOD1(setHwcLayer, void(std::shared_ptr)); + MOCK_METHOD1(uncacheBuffers, void(const std::vector&)); + MOCK_CONST_METHOD0(getOutput, const compositionengine::Output&()); MOCK_CONST_METHOD0(getLayerFE, compositionengine::LayerFE&()); @@ -51,7 +55,7 @@ public: MOCK_METHOD0(prepareForDeviceLayerRequests, void()); MOCK_METHOD1(applyDeviceLayerRequest, void(Hwc2::IComposerClient::LayerRequest request)); MOCK_CONST_METHOD0(needsFiltering, bool()); - MOCK_CONST_METHOD0(getOverrideCompositionList, std::vector()); + MOCK_CONST_METHOD0(getOverrideCompositionSettings, std::optional()); MOCK_CONST_METHOD1(dump, void(std::string&)); }; diff --git a/services/surfaceflinger/CompositionEngine/include/compositionengine/mock/RenderSurface.h b/services/surfaceflinger/CompositionEngine/include/compositionengine/mock/RenderSurface.h index e12aebb50c941e8497d2603cad9159d6223f3f50..af8d4bcb0b8d8e803ab45dd2e138253b6ac7655f 100644 --- a/services/surfaceflinger/CompositionEngine/include/compositionengine/mock/RenderSurface.h +++ b/services/surfaceflinger/CompositionEngine/include/compositionengine/mock/RenderSurface.h @@ -42,9 +42,7 @@ public: MOCK_METHOD1(dequeueBuffer, std::shared_ptr(base::unique_fd*)); MOCK_METHOD1(queueBuffer, void(base::unique_fd)); MOCK_METHOD0(onPresentDisplayCompleted, void()); - MOCK_METHOD0(flip, void()); MOCK_CONST_METHOD1(dump, void(std::string& result)); - MOCK_CONST_METHOD0(getPageFlipCount, std::uint32_t()); MOCK_CONST_METHOD0(supportsCompositionStrategyPrediction, bool()); }; diff --git a/services/surfaceflinger/CompositionEngine/src/CompositionEngine.cpp b/services/surfaceflinger/CompositionEngine/src/CompositionEngine.cpp index 6203dc6737cb491a8a957e05367400883660b046..15fadbc8eedeb8a2f24bfa8ac9c0913490b72ce6 100644 --- a/services/surfaceflinger/CompositionEngine/src/CompositionEngine.cpp +++ b/services/surfaceflinger/CompositionEngine/src/CompositionEngine.cpp @@ -65,15 +65,15 @@ void CompositionEngine::setHwComposer(std::unique_ptr hwComposer) { } renderengine::RenderEngine& CompositionEngine::getRenderEngine() const { - return *mRenderEngine.get(); + return *mRenderEngine; } -void CompositionEngine::setRenderEngine(std::unique_ptr renderEngine) { - mRenderEngine = std::move(renderEngine); +void CompositionEngine::setRenderEngine(renderengine::RenderEngine* renderEngine) { + mRenderEngine = renderEngine; } -TimeStats& CompositionEngine::getTimeStats() const { - return *mTimeStats.get(); +TimeStats* CompositionEngine::getTimeStats() const { + return mTimeStats.get(); } void CompositionEngine::setTimeStats(const std::shared_ptr& timeStats) { @@ -105,8 +105,6 @@ void CompositionEngine::present(CompositionRefreshArgs& args) { } } - updateLayerStateFromFE(args); - for (const auto& output : args.outputs) { output->present(args); } @@ -119,8 +117,6 @@ void CompositionEngine::updateCursorAsync(CompositionRefreshArgs& args) { for (const auto& output : args.outputs) { for (auto* layer : output->getOutputLayersOrderedByZ()) { if (layer->isHardwareCursor()) { - // Latch the cursor composition state from each front-end layer. - layer->getLayerFE().prepareCompositionState(LayerFE::StateSubset::Cursor); layer->writeCursorPositionToHWC(); } } @@ -136,7 +132,7 @@ void CompositionEngine::preComposition(CompositionRefreshArgs& args) { mRefreshStartTime = systemTime(SYSTEM_TIME_MONOTONIC); for (auto& layer : args.layers) { - if (layer->onPreComposition(mRefreshStartTime)) { + if (layer->onPreComposition(mRefreshStartTime, args.updatingOutputGeometryThisFrame)) { needsAnotherUpdate = true; } } @@ -144,6 +140,10 @@ void CompositionEngine::preComposition(CompositionRefreshArgs& args) { mNeedsAnotherUpdate = needsAnotherUpdate; } +FeatureFlags CompositionEngine::getFeatureFlags() const { + return {}; +} + void CompositionEngine::dump(std::string&) const { // The base class has no state to dump, but derived classes might. } @@ -152,12 +152,5 @@ void CompositionEngine::setNeedsAnotherUpdateForTest(bool value) { mNeedsAnotherUpdate = value; } -void CompositionEngine::updateLayerStateFromFE(CompositionRefreshArgs& args) { - // Update the composition state from each front-end layer - for (const auto& output : args.outputs) { - output->updateLayerStateFromFE(args); - } -} - } // namespace impl } // namespace android::compositionengine diff --git a/services/surfaceflinger/CompositionEngine/src/Display.cpp b/services/surfaceflinger/CompositionEngine/src/Display.cpp index 163d9a3748ca2fa9e139f7d055010e86861be12c..85fc09549bdf3943f5e91e91125f5b2065661692 100644 --- a/services/surfaceflinger/CompositionEngine/src/Display.cpp +++ b/services/surfaceflinger/CompositionEngine/src/Display.cpp @@ -25,6 +25,7 @@ #include #include #include +#include #include @@ -196,7 +197,7 @@ void Display::setReleasedLayers(const compositionengine::CompositionRefreshArgs& }); if (hasQueuedFrames) { - releasedLayers.emplace_back(layerFE); + releasedLayers.emplace_back(wp::fromExisting(layerFE)); } } @@ -235,7 +236,7 @@ void Display::beginFrame() { bool Display::chooseCompositionStrategy( std::optional* outChanges) { - ATRACE_CALL(); + ATRACE_FORMAT("%s for %s", __func__, getNamePlusId().c_str()); ALOGV(__FUNCTION__); if (mIsDisconnected) { @@ -248,16 +249,20 @@ bool Display::chooseCompositionStrategy( return false; } - const nsecs_t startTime = systemTime(); - // Get any composition changes requested by the HWC device, and apply them. std::optional changes; auto& hwc = getCompositionEngine().getHwComposer(); const bool requiresClientComposition = anyLayersRequireClientComposition(); + + if (isPowerHintSessionEnabled()) { + mPowerAdvisor->setRequiresClientComposition(mId, requiresClientComposition); + } + + const TimePoint hwcValidateStartTime = TimePoint::now(); + if (status_t result = hwc.getDeviceCompositionChanges(*halDisplayId, requiresClientComposition, getState().earliestPresentTime, - getState().previousPresentFence, getState().expectedPresentTime, outChanges); result != NO_ERROR) { ALOGE("chooseCompositionStrategy failed for %s: %d (%s)", getName().c_str(), result, @@ -266,8 +271,10 @@ bool Display::chooseCompositionStrategy( } if (isPowerHintSessionEnabled()) { - mPowerAdvisor->setHwcValidateTiming(mId, startTime, systemTime()); - mPowerAdvisor->setRequiresClientComposition(mId, requiresClientComposition); + mPowerAdvisor->setHwcValidateTiming(mId, hwcValidateStartTime, TimePoint::now()); + if (auto halDisplayId = HalDisplayId::tryCast(mId)) { + mPowerAdvisor->setSkippedValidate(mId, hwc.getValidateSkipped(*halDisplayId)); + } } return true; @@ -370,21 +377,16 @@ compositionengine::Output::FrameFences Display::presentAndGetFrameFences() { auto& hwc = getCompositionEngine().getHwComposer(); - const nsecs_t startTime = systemTime(); + const TimePoint startTime = TimePoint::now(); - if (isPowerHintSessionEnabled()) { - if (!getCompositionEngine().getHwComposer().getComposer()->isSupported( - Hwc2::Composer::OptionalFeature::ExpectedPresentTime) && - getState().previousPresentFence->getSignalTime() != Fence::SIGNAL_TIME_PENDING) { - mPowerAdvisor->setHwcPresentDelayedTime(mId, getState().earliestPresentTime); - } + if (isPowerHintSessionEnabled() && getState().earliestPresentTime) { + mPowerAdvisor->setHwcPresentDelayedTime(mId, *getState().earliestPresentTime); } - hwc.presentAndGetReleaseFences(*halDisplayIdOpt, getState().earliestPresentTime, - getState().previousPresentFence); + hwc.presentAndGetReleaseFences(*halDisplayIdOpt, getState().earliestPresentTime); if (isPowerHintSessionEnabled()) { - mPowerAdvisor->setHwcPresentTiming(mId, startTime, systemTime()); + mPowerAdvisor->setHwcPresentTiming(mId, startTime, TimePoint::now()); } fences.presentFence = hwc.getPresentFence(*halDisplayIdOpt); @@ -420,8 +422,7 @@ void Display::setHintSessionGpuFence(std::unique_ptr&& gpuFence) { mPowerAdvisor->setGpuFenceTime(mId, std::move(gpuFence)); } -void Display::finishFrame(const compositionengine::CompositionRefreshArgs& refreshArgs, - GpuCompositionResult&& result) { +void Display::finishFrame(GpuCompositionResult&& result) { // We only need to actually compose the display if: // 1) It is being handled by hardware composer, which may need this to // keep its virtual display state machine in sync, or @@ -431,14 +432,7 @@ void Display::finishFrame(const compositionengine::CompositionRefreshArgs& refre return; } - impl::Output::finishFrame(refreshArgs, std::move(result)); - - if (isPowerHintSessionEnabled()) { - auto& hwc = getCompositionEngine().getHwComposer(); - if (auto halDisplayId = HalDisplayId::tryCast(mId)) { - mPowerAdvisor->setSkippedValidate(mId, hwc.getValidateSkipped(*halDisplayId)); - } - } + impl::Output::finishFrame(std::move(result)); } } // namespace android::compositionengine::impl diff --git a/services/surfaceflinger/CompositionEngine/src/HwcBufferCache.cpp b/services/surfaceflinger/CompositionEngine/src/HwcBufferCache.cpp index f95382d92132d8f0ebc72e869c5955c45cd31598..f0105b278276450e1add413eb20869d8290caabc 100644 --- a/services/surfaceflinger/CompositionEngine/src/HwcBufferCache.cpp +++ b/services/surfaceflinger/CompositionEngine/src/HwcBufferCache.cpp @@ -16,43 +16,75 @@ #include -// TODO(b/129481165): remove the #pragma below and fix conversion issues -#pragma clang diagnostic push -#pragma clang diagnostic ignored "-Wconversion" - #include #include -// TODO(b/129481165): remove the #pragma below and fix conversion issues -#pragma clang diagnostic pop // ignored "-Wconversion" - namespace android::compositionengine::impl { HwcBufferCache::HwcBufferCache() { - std::fill(std::begin(mBuffers), std::end(mBuffers), wp(nullptr)); + for (uint32_t i = kMaxLayerBufferCount; i-- > 0;) { + mFreeSlots.push(i); + } +} + +HwcSlotAndBuffer HwcBufferCache::getHwcSlotAndBuffer(const sp& buffer) { + if (auto i = mCacheByBufferId.find(buffer->getId()); i != mCacheByBufferId.end()) { + Cache& cache = i->second; + // mark this cache slot as more recently used so it won't get evicted anytime soon + cache.lruCounter = mLeastRecentlyUsedCounter++; + return {cache.slot, nullptr}; + } + return {cache(buffer), buffer}; } -void HwcBufferCache::getHwcBuffer(int slot, const sp& buffer, uint32_t* outSlot, - sp* outBuffer) { - // default is 0 - if (slot == BufferQueue::INVALID_BUFFER_SLOT || slot < 0 || - slot >= static_cast(kMaxLayerBufferCount)) { - *outSlot = 0; - } else { - *outSlot = static_cast(slot); +HwcSlotAndBuffer HwcBufferCache::getOverrideHwcSlotAndBuffer(const sp& buffer) { + if (buffer == mLastOverrideBuffer) { + return {kOverrideBufferSlot, nullptr}; } + mLastOverrideBuffer = buffer; + return {kOverrideBufferSlot, buffer}; +} + +uint32_t HwcBufferCache::uncache(uint64_t bufferId) { + if (auto i = mCacheByBufferId.find(bufferId); i != mCacheByBufferId.end()) { + uint32_t slot = i->second.slot; + mCacheByBufferId.erase(i); + mFreeSlots.push(slot); + return slot; + } + if (mLastOverrideBuffer && bufferId == mLastOverrideBuffer->getId()) { + mLastOverrideBuffer = nullptr; + return kOverrideBufferSlot; + } + return UINT32_MAX; +} - auto& currentBuffer = mBuffers[*outSlot]; - wp weakCopy(buffer); - if (currentBuffer == weakCopy) { - // already cached in HWC, skip sending the buffer - *outBuffer = nullptr; - } else { - *outBuffer = buffer; +uint32_t HwcBufferCache::cache(const sp& buffer) { + Cache cache; + cache.slot = getLeastRecentlyUsedSlot(); + cache.lruCounter = mLeastRecentlyUsedCounter++; + cache.buffer = buffer; + mCacheByBufferId.emplace(buffer->getId(), cache); + return cache.slot; +} - // update cache - currentBuffer = buffer; +uint32_t HwcBufferCache::getLeastRecentlyUsedSlot() { + if (mFreeSlots.empty()) { + assert(!mCacheByBufferId.empty()); + // evict the least recently used cache entry + auto cacheToErase = mCacheByBufferId.begin(); + for (auto i = cacheToErase; i != mCacheByBufferId.end(); ++i) { + if (i->second.lruCounter < cacheToErase->second.lruCounter) { + cacheToErase = i; + } + } + uint32_t slot = cacheToErase->second.slot; + mCacheByBufferId.erase(cacheToErase); + mFreeSlots.push(slot); } + uint32_t slot = mFreeSlots.top(); + mFreeSlots.pop(); + return slot; } } // namespace android::compositionengine::impl diff --git a/services/surfaceflinger/CompositionEngine/src/LayerFECompositionState.cpp b/services/surfaceflinger/CompositionEngine/src/LayerFECompositionState.cpp index 6631a2772cb177bae8ec16e88fd42835c2b6f542..426cc57db8e2d0b2d1f4d6453ecfafcd5f8b4cb9 100644 --- a/services/surfaceflinger/CompositionEngine/src/LayerFECompositionState.cpp +++ b/services/surfaceflinger/CompositionEngine/src/LayerFECompositionState.cpp @@ -106,7 +106,6 @@ void LayerFECompositionState::dump(std::string& out) const { dumpVal(out, "composition type", toString(compositionType), compositionType); out.append("\n buffer: "); - dumpVal(out, "slot", bufferSlot); dumpVal(out, "buffer", buffer.get()); out.append("\n "); @@ -122,7 +121,12 @@ void LayerFECompositionState::dump(std::string& out) const { dumpVal(out, "dataspace", toString(dataspace), dataspace); dumpVal(out, "hdr metadata types", hdrMetadata.validTypes); dumpVal(out, "dimming enabled", dimmingEnabled); + if (currentHdrSdrRatio > 1.01f || desiredHdrSdrRatio > 1.01f) { + dumpVal(out, "current hdr/sdr ratio", currentHdrSdrRatio); + dumpVal(out, "desired hdr/sdr ratio", desiredHdrSdrRatio); + } dumpVal(out, "colorTransform", colorTransform); + dumpVal(out, "caching hint", toString(cachingHint)); out.append("\n"); } diff --git a/services/surfaceflinger/CompositionEngine/src/Output.cpp b/services/surfaceflinger/CompositionEngine/src/Output.cpp index d0c5803d42da0924cc78cfc1e702157be74e39d8..793959cea6c2e574a8125f1338e02ffec4cf3ab0 100644 --- a/services/surfaceflinger/CompositionEngine/src/Output.cpp +++ b/services/surfaceflinger/CompositionEngine/src/Output.cpp @@ -29,7 +29,9 @@ #include #include #include +#include +#include #include #include "renderengine/ExternalTexture.h" @@ -116,6 +118,10 @@ const std::string& Output::getName() const { void Output::setName(const std::string& name) { mName = name; + auto displayIdOpt = getDisplayId(); + mNamePlusId = displayIdOpt ? base::StringPrintf("%s (%s)", mName.c_str(), + to_string(*displayIdOpt).c_str()) + : mName; } void Output::setCompositionEnabled(bool enabled) { @@ -424,10 +430,11 @@ void Output::prepare(const compositionengine::CompositionRefreshArgs& refreshArg ALOGV(__FUNCTION__); rebuildLayerStacks(refreshArgs, geomSnapshots); + uncacheBuffers(refreshArgs.bufferIdsToUncache); } void Output::present(const compositionengine::CompositionRefreshArgs& refreshArgs) { - ATRACE_CALL(); + ATRACE_FORMAT("%s for %s", __func__, mNamePlusId.c_str()); ALOGV(__FUNCTION__); updateColorProfile(refreshArgs); @@ -440,17 +447,26 @@ void Output::present(const compositionengine::CompositionRefreshArgs& refreshArg GpuCompositionResult result; const bool predictCompositionStrategy = canPredictCompositionStrategy(refreshArgs); if (predictCompositionStrategy) { - result = prepareFrameAsync(refreshArgs); + result = prepareFrameAsync(); } else { prepareFrame(); } devOptRepaintFlash(refreshArgs); - finishFrame(refreshArgs, std::move(result)); + finishFrame(std::move(result)); postFramebuffer(); renderCachedSets(refreshArgs); } +void Output::uncacheBuffers(std::vector const& bufferIdsToUncache) { + if (bufferIdsToUncache.empty()) { + return; + } + for (auto outputLayer : getOutputLayersOrderedByZ()) { + outputLayer->uncacheBuffers(bufferIdsToUncache); + } +} + void Output::rebuildLayerStacks(const compositionengine::CompositionRefreshArgs& refreshArgs, LayerFESet& layerFESet) { ATRACE_CALL(); @@ -465,6 +481,9 @@ void Output::rebuildLayerStacks(const compositionengine::CompositionRefreshArgs& // Process the layers to determine visibility and coverage compositionengine::Output::CoverageState coverage{layerFESet}; + coverage.aboveCoveredLayersExcludingOverlays = refreshArgs.hasTrustedPresentationListener + ? std::make_optional() + : std::nullopt; collectVisibleLayers(refreshArgs, coverage); // Compute the resulting coverage for this output, and store it for later @@ -501,7 +520,6 @@ void Output::ensureOutputLayerIfVisible(sp& layerFE, // appear on multiple outputs. if (!coverage.latchedLayers.count(layerFE)) { coverage.latchedLayers.insert(layerFE); - layerFE->prepareCompositionState(compositionengine::LayerFE::StateSubset::BasicGeometry); } // Only consider the layers on this output @@ -520,6 +538,9 @@ void Output::ensureOutputLayerIfVisible(sp& layerFE, return; } + bool computeAboveCoveredExcludingOverlays = coverage.aboveCoveredLayersExcludingOverlays && + !layerFEState->outputFilter.toInternalDisplay; + /* * opaqueRegion: area of a surface that is fully opaque. */ @@ -561,6 +582,11 @@ void Output::ensureOutputLayerIfVisible(sp& layerFE, */ Region shadowRegion; + /** + * covered region above excluding internal display overlay layers + */ + std::optional coveredRegionExcludingDisplayOverlays = std::nullopt; + const ui::Transform& tr = layerFEState->geomLayerTransform; // Get the visible region @@ -633,6 +659,12 @@ void Output::ensureOutputLayerIfVisible(sp& layerFE, // Update accumAboveCoveredLayers for next (lower) layer coverage.aboveCoveredLayers.orSelf(visibleRegion); + if (CC_UNLIKELY(computeAboveCoveredExcludingOverlays)) { + coveredRegionExcludingDisplayOverlays = + coverage.aboveCoveredLayersExcludingOverlays->intersect(visibleRegion); + coverage.aboveCoveredLayersExcludingOverlays->orSelf(visibleRegion); + } + // subtract the opaque region covered by the layers above us visibleRegion.subtractSelf(coverage.aboveOpaqueLayers); @@ -719,20 +751,16 @@ void Output::ensureOutputLayerIfVisible(sp& layerFE, ? outputState.transform.transform( transparentRegion.intersect(outputState.layerStackSpace.getContent())) : Region(); + if (CC_UNLIKELY(computeAboveCoveredExcludingOverlays)) { + outputLayerState.coveredRegionExcludingDisplayOverlays = + std::move(coveredRegionExcludingDisplayOverlays); + } } void Output::setReleasedLayers(const compositionengine::CompositionRefreshArgs&) { // The base class does nothing with this call. } -void Output::updateLayerStateFromFE(const CompositionRefreshArgs& args) const { - for (auto* layer : getOutputLayersOrderedByZ()) { - layer->getLayerFE().prepareCompositionState( - args.updatingGeometryThisFrame ? LayerFE::StateSubset::GeometryAndContent - : LayerFE::StateSubset::Content); - } -} - void Output::updateCompositionState(const compositionengine::CompositionRefreshArgs& refreshArgs) { ATRACE_CALL(); ALOGV(__FUNCTION__); @@ -754,6 +782,44 @@ 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() { @@ -776,7 +842,6 @@ void Output::writeCompositionState(const compositionengine::CompositionRefreshAr } editState().earliestPresentTime = refreshArgs.earliestPresentTime; - editState().previousPresentFence = refreshArgs.previousPresentFence; editState().expectedPresentTime = refreshArgs.expectedPresentTime; compositionengine::OutputLayer* peekThroughLayer = nullptr; @@ -871,6 +936,13 @@ ui::Dataspace Output::getBestDataspace(ui::Dataspace* outHdrDataSpace, ui::Dataspace bestDataSpace = ui::Dataspace::V0_SRGB; *outHdrDataSpace = ui::Dataspace::UNKNOWN; + // An Output's layers may be stale when it is disabled. As a consequence, the layers returned by + // getOutputLayersOrderedByZ may not be in a valid state and it is not safe to access their + // properties. Return a default dataspace value in this case. + if (!getState().isEnabled) { + return ui::Dataspace::V0_SRGB; + } + for (const auto* layer : getOutputLayersOrderedByZ()) { switch (layer->getLayerFE().getCompositionState()->dataspace) { case ui::Dataspace::V0_SCRGB: @@ -1019,7 +1091,7 @@ std::future Output::chooseCompositionStrategyAsync( [&, changes]() { return chooseCompositionStrategy(changes); }); } -GpuCompositionResult Output::prepareFrameAsync(const CompositionRefreshArgs& refreshArgs) { +GpuCompositionResult Output::prepareFrameAsync() { ATRACE_CALL(); ALOGV(__FUNCTION__); auto& state = editState(); @@ -1039,7 +1111,7 @@ GpuCompositionResult Output::prepareFrameAsync(const CompositionRefreshArgs& ref GpuCompositionResult compositionResult; if (dequeueSucceeded) { std::optional optFd = - composeSurfaces(Region::INVALID_REGION, refreshArgs, buffer, bufferFence); + composeSurfaces(Region::INVALID_REGION, buffer, bufferFence); if (optFd) { compositionResult.fence = std::move(*optFd); } @@ -1077,7 +1149,7 @@ void Output::devOptRepaintFlash(const compositionengine::CompositionRefreshArgs& std::shared_ptr buffer; updateProtectedContentState(); dequeueRenderBuffer(&bufferFence, &buffer); - static_cast(composeSurfaces(dirtyRegion, refreshArgs, buffer, bufferFence)); + static_cast(composeSurfaces(dirtyRegion, buffer, bufferFence)); mRenderSurface->queueBuffer(base::unique_fd()); } } @@ -1089,7 +1161,7 @@ void Output::devOptRepaintFlash(const compositionengine::CompositionRefreshArgs& prepareFrame(); } -void Output::finishFrame(const CompositionRefreshArgs& refreshArgs, GpuCompositionResult&& result) { +void Output::finishFrame(GpuCompositionResult&& result) { ATRACE_CALL(); ALOGV(__FUNCTION__); const auto& outputState = getState(); @@ -1114,7 +1186,7 @@ void Output::finishFrame(const CompositionRefreshArgs& refreshArgs, GpuCompositi } // Repaint the framebuffer (if needed), getting the optional fence for when // the composition completes. - optReadyFence = composeSurfaces(Region::INVALID_REGION, refreshArgs, buffer, bufferFence); + optReadyFence = composeSurfaces(Region::INVALID_REGION, buffer, bufferFence); } if (!optReadyFence) { return; @@ -1122,7 +1194,8 @@ void Output::finishFrame(const CompositionRefreshArgs& refreshArgs, GpuCompositi if (isPowerHintSessionEnabled()) { // get fence end time to know when gpu is complete in display - setHintSessionGpuFence(std::make_unique(new Fence(dup(optReadyFence->get())))); + setHintSessionGpuFence( + std::make_unique(sp::make(dup(optReadyFence->get())))); } // swap buffers (presentation) mRenderSurface->queueBuffer(std::move(*optReadyFence)); @@ -1141,15 +1214,9 @@ void Output::updateProtectedContentState() { bool needsProtected = std::any_of(layers.begin(), layers.end(), [](auto* layer) { return layer->getLayerFE().getCompositionState()->hasProtectedContent; }); - if (needsProtected != renderEngine.isProtected()) { - renderEngine.useProtectedContext(needsProtected); - } - if (needsProtected != mRenderSurface->isProtected() && - needsProtected == renderEngine.isProtected()) { + if (needsProtected != mRenderSurface->isProtected()) { mRenderSurface->setProtected(needsProtected); } - } else if (!outputState.isSecure && renderEngine.isProtected()) { - renderEngine.useProtectedContext(false); } } @@ -1173,14 +1240,15 @@ bool Output::dequeueRenderBuffer(base::unique_fd* bufferFence, } std::optional Output::composeSurfaces( - const Region& debugRegion, const compositionengine::CompositionRefreshArgs& refreshArgs, - std::shared_ptr tex, base::unique_fd& fd) { + const Region& debugRegion, std::shared_ptr tex, + base::unique_fd& fd) { ATRACE_CALL(); ALOGV(__FUNCTION__); const auto& outputState = getState(); - const TracedOrdinal hasClientComposition = {"hasClientComposition", - outputState.usesClientComposition}; + const TracedOrdinal hasClientComposition = { + base::StringPrintf("hasClientComposition %s", mNamePlusId.c_str()), + outputState.usesClientComposition}; if (!hasClientComposition) { setExpensiveRenderingExpected(false); return base::unique_fd(); @@ -1195,33 +1263,8 @@ std::optional Output::composeSurfaces( ALOGV("hasClientComposition"); - renderengine::DisplaySettings clientCompositionDisplay; - clientCompositionDisplay.physicalDisplay = outputState.framebufferSpace.getContent(); - clientCompositionDisplay.clip = outputState.layerStackSpace.getContent(); - clientCompositionDisplay.orientation = - ui::Transform::toRotationFlags(outputState.displaySpace.getOrientation()); - clientCompositionDisplay.outputDataspace = mDisplayColorProfile->hasWideColorGamut() - ? outputState.dataspace - : ui::Dataspace::UNKNOWN; - - // If we have a valid current display brightness use that, otherwise fall back to the - // display's max desired - clientCompositionDisplay.currentLuminanceNits = outputState.displayBrightnessNits > 0.f - ? outputState.displayBrightnessNits - : mDisplayColorProfile->getHdrCapabilities().getDesiredMaxLuminance(); - clientCompositionDisplay.maxLuminance = - mDisplayColorProfile->getHdrCapabilities().getDesiredMaxLuminance(); - clientCompositionDisplay.targetLuminanceNits = - outputState.clientTargetBrightness * outputState.displayBrightnessNits; - clientCompositionDisplay.dimmingStage = outputState.clientTargetDimmingStage; - clientCompositionDisplay.renderIntent = - static_cast( - outputState.renderIntent); - - // Compute the global color transform matrix. - clientCompositionDisplay.colorTransform = outputState.colorTransformMatrix; - clientCompositionDisplay.deviceHandlesColorTransform = - outputState.usesDeviceComposition || getSkipColorTransform(); + renderengine::DisplaySettings clientCompositionDisplay = + generateClientCompositionDisplaySettings(); // Generate the client composition requests for the layers on this output. auto& renderEngine = getCompositionEngine().getRenderEngine(); @@ -1255,9 +1298,7 @@ std::optional Output::composeSurfaces( // or complex GPU shaders and it's expensive. We boost the GPU frequency so that // GPU composition can finish in time. We must reset GPU frequency afterwards, // because high frequency consumes extra battery. - const bool expensiveBlurs = - refreshArgs.blursAreExpensive && mLayerRequestingBackgroundBlur != nullptr; - const bool expensiveRenderingExpected = expensiveBlurs || + const bool expensiveRenderingExpected = std::any_of(clientCompositionLayers.begin(), clientCompositionLayers.end(), [outputDataspace = clientCompositionDisplay.outputDataspace](const auto& layer) { @@ -1284,11 +1325,10 @@ std::optional Output::composeSurfaces( // over to RenderEngine, in which case this flag can be removed from the drawLayers interface. const bool useFramebufferCache = outputState.layerFilter.toInternalDisplay; - auto fenceResult = - toFenceResult(renderEngine - .drawLayers(clientCompositionDisplay, clientRenderEngineLayers, - tex, useFramebufferCache, std::move(fd)) - .get()); + auto fenceResult = renderEngine + .drawLayers(clientCompositionDisplay, clientRenderEngineLayers, tex, + useFramebufferCache, std::move(fd)) + .get(); if (mClientCompositionRequestCache && fenceStatus(fenceResult) != NO_ERROR) { // If rendering was not successful, remove the request from the cache. @@ -1297,10 +1337,13 @@ std::optional Output::composeSurfaces( const auto fence = std::move(fenceResult).value_or(Fence::NO_FENCE); - if (auto& timeStats = getCompositionEngine().getTimeStats(); fence->isValid()) { - timeStats.recordRenderEngineDuration(renderEngineStart, std::make_shared(fence)); - } else { - timeStats.recordRenderEngineDuration(renderEngineStart, systemTime()); + if (auto timeStats = getCompositionEngine().getTimeStats()) { + if (fence->isValid()) { + timeStats->recordRenderEngineDuration(renderEngineStart, + std::make_shared(fence)); + } else { + timeStats->recordRenderEngineDuration(renderEngineStart, systemTime()); + } } for (auto* clientComposedLayer : clientCompositionLayersFE) { @@ -1310,6 +1353,47 @@ std::optional Output::composeSurfaces( return base::unique_fd(fence->dup()); } +renderengine::DisplaySettings Output::generateClientCompositionDisplaySettings() const { + const auto& outputState = getState(); + + renderengine::DisplaySettings clientCompositionDisplay; + clientCompositionDisplay.namePlusId = mNamePlusId; + clientCompositionDisplay.physicalDisplay = outputState.framebufferSpace.getContent(); + clientCompositionDisplay.clip = outputState.layerStackSpace.getContent(); + clientCompositionDisplay.orientation = + ui::Transform::toRotationFlags(outputState.displaySpace.getOrientation()); + clientCompositionDisplay.outputDataspace = mDisplayColorProfile->hasWideColorGamut() + ? outputState.dataspace + : ui::Dataspace::UNKNOWN; + + // If we have a valid current display brightness use that, otherwise fall back to the + // display's max desired + clientCompositionDisplay.currentLuminanceNits = outputState.displayBrightnessNits > 0.f + ? outputState.displayBrightnessNits + : mDisplayColorProfile->getHdrCapabilities().getDesiredMaxLuminance(); + clientCompositionDisplay.maxLuminance = + mDisplayColorProfile->getHdrCapabilities().getDesiredMaxLuminance(); + clientCompositionDisplay.targetLuminanceNits = + outputState.clientTargetBrightness * outputState.displayBrightnessNits; + clientCompositionDisplay.dimmingStage = outputState.clientTargetDimmingStage; + clientCompositionDisplay.renderIntent = + static_cast( + outputState.renderIntent); + + // 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; +} + std::vector Output::generateClientCompositionRequests( bool supportsProtectedContent, ui::Dataspace outputDataspace, std::vector& outLayerFEs) { std::vector clientCompositionLayers; @@ -1320,7 +1404,7 @@ std::vector Output::generateClientCompositionRequests( bool firstLayer = true; bool disableBlurs = false; - sp previousOverrideBuffer = nullptr; + uint64_t previousOverrideBufferId = 0; for (auto* layer : getOutputLayersOrderedByZ()) { const auto& layerState = layer->getState(); @@ -1356,11 +1440,10 @@ std::vector Output::generateClientCompositionRequests( !layerState.visibleRegion.subtract(layerState.shadowRegion).isEmpty(); if (clientComposition || clearClientComposition) { - std::vector results; - if (layer->getState().overrideInfo.buffer != nullptr) { - if (layer->getState().overrideInfo.buffer->getBuffer() != previousOverrideBuffer) { - results = layer->getOverrideCompositionList(); - previousOverrideBuffer = layer->getState().overrideInfo.buffer->getBuffer(); + if (auto overrideSettings = layer->getOverrideCompositionSettings()) { + if (overrideSettings->bufferId != previousOverrideBufferId) { + previousOverrideBufferId = overrideSettings->bufferId; + clientCompositionLayers.push_back(std::move(*overrideSettings)); ALOGV("Replacing [%s] with override in RE", layer->getLayerFE().getDebugName()); } else { ALOGV("Skipping redundant override buffer for [%s] in RE", @@ -1385,21 +1468,20 @@ std::vector Output::generateClientCompositionRequests( .realContentIsVisible = realContentIsVisible, .clearContent = !clientComposition, .blurSetting = blurSetting, - .whitePointNits = layerState.whitePointNits}; - results = layerFE.prepareClientCompositionList(targetSettings); - if (realContentIsVisible && !results.empty()) { - layer->editState().clientCompositionTimestamp = systemTime(); + .whitePointNits = layerState.whitePointNits, + .treat170mAsSrgb = outputState.treat170mAsSrgb}; + if (auto clientCompositionSettings = + layerFE.prepareClientComposition(targetSettings)) { + clientCompositionLayers.push_back(std::move(*clientCompositionSettings)); + if (realContentIsVisible) { + layer->editState().clientCompositionTimestamp = systemTime(); + } } } if (clientComposition) { outLayerFEs.push_back(&layerFE); } - - clientCompositionLayers.insert(clientCompositionLayers.end(), - std::make_move_iterator(results.begin()), - std::make_move_iterator(results.end())); - results.clear(); } firstLayer = false; @@ -1438,7 +1520,7 @@ bool Output::isPowerHintSessionEnabled() { } void Output::postFramebuffer() { - ATRACE_CALL(); + ATRACE_FORMAT("%s for %s", __func__, mNamePlusId.c_str()); ALOGV(__FUNCTION__); if (!getState().isEnabled) { @@ -1447,7 +1529,6 @@ void Output::postFramebuffer() { auto& outputState = editState(); outputState.dirtyRegion.clear(); - mRenderSurface->flip(); auto frame = presentAndGetFrameFences(); @@ -1475,8 +1556,9 @@ void Output::postFramebuffer() { releaseFence = Fence::merge("LayerRelease", releaseFence, frame.clientTargetAcquireFence); } - layer->getLayerFE().onLayerDisplayed( - ftl::yield(std::move(releaseFence)).share()); + 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 @@ -1484,7 +1566,8 @@ void Output::postFramebuffer() { // supply them with the present fence. for (auto& weakLayer : mReleasedLayers) { if (const auto layer = weakLayer.promote()) { - layer->onLayerDisplayed(ftl::yield(frame.presentFence).share()); + layer->onLayerDisplayed(ftl::yield(frame.presentFence).share(), + outputState.layerFilter.layerStack); } } @@ -1493,9 +1576,10 @@ void Output::postFramebuffer() { } void Output::renderCachedSets(const CompositionRefreshArgs& refreshArgs) { - if (mPlanner) { - mPlanner->renderCachedSets(getState(), refreshArgs.scheduledFrameTime, - getState().usesDeviceComposition || getSkipColorTransform()); + const auto& outputState = getState(); + if (mPlanner && outputState.isEnabled) { + mPlanner->renderCachedSets(outputState, refreshArgs.scheduledFrameTime, + outputState.usesDeviceComposition || getSkipColorTransform()); } } diff --git a/services/surfaceflinger/CompositionEngine/src/OutputCompositionState.cpp b/services/surfaceflinger/CompositionEngine/src/OutputCompositionState.cpp index 948c0c90bf8bafb701699147852f9a42f58935b1..c512a1e97fbdca066be9c8f94f4ea9a7e2c3a3c7 100644 --- a/services/surfaceflinger/CompositionEngine/src/OutputCompositionState.cpp +++ b/services/surfaceflinger/CompositionEngine/src/OutputCompositionState.cpp @@ -62,14 +62,18 @@ void OutputCompositionState::dump(std::string& out) const { dumpVal(out, "sdrWhitePointNits", sdrWhitePointNits); dumpVal(out, "clientTargetBrightness", clientTargetBrightness); dumpVal(out, "displayBrightness", displayBrightness); - out.append("\n "); dumpVal(out, "compositionStrategyPredictionState", ftl::enum_string(strategyPrediction)); + out.append("\n "); out.append("\n "); dumpVal(out, "treate170mAsSrgb", treat170mAsSrgb); - out += '\n'; + out.append("\n"); + for (const auto& borderRenderInfo : borderInfoList) { + dumpVal(out, "borderRegion", borderRenderInfo.combinedRegion); + } + out.append("\n"); } } // namespace android::compositionengine::impl diff --git a/services/surfaceflinger/CompositionEngine/src/OutputLayer.cpp b/services/surfaceflinger/CompositionEngine/src/OutputLayer.cpp index 1bb9d0eb639bef6597c1c9b0519fb0ac7c5d2f58..0ac0ecb727af5f549ae761406e2b27467b6e7c1a 100644 --- a/services/surfaceflinger/CompositionEngine/src/OutputLayer.cpp +++ b/services/surfaceflinger/CompositionEngine/src/OutputLayer.cpp @@ -340,10 +340,17 @@ void OutputLayer::updateCompositionState( state.dimmingRatio = 1.f; state.whitePointNits = getOutput().getState().displayBrightnessNits; } else { - state.dimmingRatio = std::clamp(getOutput().getState().sdrWhitePointNits / - getOutput().getState().displayBrightnessNits, - 0.f, 1.f); - state.whitePointNits = getOutput().getState().sdrWhitePointNits; + float layerBrightnessNits = getOutput().getState().sdrWhitePointNits; + // RANGE_EXTENDED can "self-promote" to HDR, but is still rendered for a particular + // range that we may need to re-adjust to the current display conditions + if ((state.dataspace & HAL_DATASPACE_RANGE_MASK) == HAL_DATASPACE_RANGE_EXTENDED && + layerFEState->currentHdrSdrRatio > 1.01f) { + layerBrightnessNits *= layerFEState->currentHdrSdrRatio; + } + state.dimmingRatio = + std::clamp(layerBrightnessNits / getOutput().getState().displayBrightnessNits, 0.f, + 1.f); + state.whitePointNits = layerBrightnessNits; } // These are evaluated every frame as they can potentially change at any @@ -578,6 +585,7 @@ void OutputLayer::writeOutputIndependentPerFrameStateToHWC( case Composition::CURSOR: case Composition::DEVICE: case Composition::DISPLAY_DECORATION: + case Composition::REFRESH_RATE_INDICATOR: writeBufferStateToHWC(hwcLayer, outputIndependentState, skipLayer); break; case Composition::INVALID: @@ -610,6 +618,40 @@ void OutputLayer::writeSidebandStateToHWC(HWC2::Layer* hwcLayer, } } +void OutputLayer::uncacheBuffers(const std::vector& bufferIdsToUncache) { + auto& state = editState(); + // Skip doing this if there is no HWC interface + if (!state.hwc) { + return; + } + + // Uncache the active buffer last so that it's the first buffer to be purged from the cache + // next time a buffer is sent to this layer. + bool uncacheActiveBuffer = false; + + std::vector slotsToClear; + for (uint64_t bufferId : bufferIdsToUncache) { + if (bufferId == state.hwc->activeBufferId) { + uncacheActiveBuffer = true; + } else { + uint32_t slot = state.hwc->hwcBufferCache.uncache(bufferId); + if (slot != UINT32_MAX) { + slotsToClear.push_back(slot); + } + } + } + if (uncacheActiveBuffer) { + slotsToClear.push_back(state.hwc->hwcBufferCache.uncache(state.hwc->activeBufferId)); + } + + hal::Error error = + state.hwc->hwcLayer->setBufferSlotsToClear(slotsToClear, state.hwc->activeBufferSlot); + if (error != hal::Error::NONE) { + ALOGE("[%s] Failed to clear buffer slots: %s (%d)", getLayerFE().getDebugName(), + to_string(error).c_str(), static_cast(error)); + } +} + void OutputLayer::writeBufferStateToHWC(HWC2::Layer* hwcLayer, const LayerFECompositionState& outputIndependentState, bool skipLayer) { @@ -622,27 +664,37 @@ void OutputLayer::writeBufferStateToHWC(HWC2::Layer* hwcLayer, to_string(error).c_str(), static_cast(error)); } - sp buffer = outputIndependentState.buffer; - sp acquireFence = outputIndependentState.acquireFence; - int slot = outputIndependentState.bufferSlot; - if (getState().overrideInfo.buffer != nullptr && !skipLayer) { - buffer = getState().overrideInfo.buffer->getBuffer(); - acquireFence = getState().overrideInfo.acquireFence; - slot = HwcBufferCache::FLATTENER_CACHING_SLOT; + HwcSlotAndBuffer hwcSlotAndBuffer; + sp hwcFence; + { + // Editing the state only because we update the HWC buffer cache and active buffer. + auto& state = editState(); + // Override buffers use a special cache slot so that they don't evict client buffers. + if (state.overrideInfo.buffer != nullptr && !skipLayer) { + hwcSlotAndBuffer = state.hwc->hwcBufferCache.getOverrideHwcSlotAndBuffer( + state.overrideInfo.buffer->getBuffer()); + hwcFence = state.overrideInfo.acquireFence; + // Keep track of the active buffer ID so when it's discarded we uncache it last so its + // slot will be used first, allowing the memory to be freed as soon as possible. + state.hwc->activeBufferId = state.overrideInfo.buffer->getBuffer()->getId(); + } else { + hwcSlotAndBuffer = + state.hwc->hwcBufferCache.getHwcSlotAndBuffer(outputIndependentState.buffer); + hwcFence = outputIndependentState.acquireFence; + // Keep track of the active buffer ID so when it's discarded we uncache it last so its + // slot will be used first, allowing the memory to be freed as soon as possible. + state.hwc->activeBufferId = outputIndependentState.buffer->getId(); + } + // Keep track of the active buffer slot, so we can restore it after clearing other buffer + // slots. + state.hwc->activeBufferSlot = hwcSlotAndBuffer.slot; } - ALOGV("Writing buffer %p", buffer.get()); - - uint32_t hwcSlot = 0; - sp hwcBuffer; - // We need access to the output-dependent state for the buffer cache there, - // though otherwise the buffer is not output-dependent. - editState().hwc->hwcBufferCache.getHwcBuffer(slot, buffer, &hwcSlot, &hwcBuffer); - - if (auto error = hwcLayer->setBuffer(hwcSlot, hwcBuffer, acquireFence); + if (auto error = hwcLayer->setBuffer(hwcSlotAndBuffer.slot, hwcSlotAndBuffer.buffer, hwcFence); error != hal::Error::NONE) { - ALOGE("[%s] Failed to set buffer %p: %s (%d)", getLayerFE().getDebugName(), buffer->handle, - to_string(error).c_str(), static_cast(error)); + ALOGE("[%s] Failed to set buffer %p: %s (%d)", getLayerFE().getDebugName(), + hwcSlotAndBuffer.buffer->handle, to_string(error).c_str(), + static_cast(error)); } } @@ -729,6 +781,7 @@ void OutputLayer::detectDisallowedCompositionTypeChange(Composition from, Compos case Composition::CURSOR: case Composition::SIDEBAND: case Composition::DISPLAY_DECORATION: + case Composition::REFRESH_RATE_INDICATOR: result = (to == Composition::CLIENT || to == Composition::DEVICE); break; } @@ -787,7 +840,7 @@ bool OutputLayer::needsFiltering() const { sourceCrop.getWidth() != displayFrame.getWidth(); } -std::vector OutputLayer::getOverrideCompositionList() const { +std::optional OutputLayer::getOverrideCompositionSettings() const { if (getState().overrideInfo.buffer == nullptr) { return {}; } @@ -816,7 +869,7 @@ std::vector OutputLayer::getOverrideCompositionList() co settings.alpha = 1.0f; settings.whitePointNits = getOutput().getState().sdrWhitePointNits; - return {static_cast(settings)}; + return settings; } void OutputLayer::dump(std::string& out) const { diff --git a/services/surfaceflinger/CompositionEngine/src/RenderSurface.cpp b/services/surfaceflinger/CompositionEngine/src/RenderSurface.cpp index 5a3af7bfea2545ef5440e4bd51718cf782349eae..0fe55db05c1c07902486fa1bba66a9164ddacd38 100644 --- a/services/surfaceflinger/CompositionEngine/src/RenderSurface.cpp +++ b/services/surfaceflinger/CompositionEngine/src/RenderSurface.cpp @@ -251,10 +251,6 @@ void RenderSurface::onPresentDisplayCompleted() { mDisplaySurface->onFrameCommitted(); } -void RenderSurface::flip() { - mPageFlipCount++; -} - void RenderSurface::dump(std::string& out) const { using android::base::StringAppendF; @@ -265,7 +261,6 @@ void RenderSurface::dump(std::string& out) const { dumpVal(out, "size", mSize); StringAppendF(&out, "ANativeWindow=%p (format %d) ", mNativeWindow.get(), ANativeWindow_getFormat(mNativeWindow.get())); - dumpVal(out, "flips", mPageFlipCount); out.append("\n"); String8 surfaceDump; @@ -273,14 +268,6 @@ void RenderSurface::dump(std::string& out) const { out.append(surfaceDump); } -std::uint32_t RenderSurface::getPageFlipCount() const { - return mPageFlipCount; -} - -void RenderSurface::setPageFlipCountForTest(std::uint32_t count) { - mPageFlipCount = count; -} - void RenderSurface::setSizeForTest(const ui::Size& size) { mSize = size; } diff --git a/services/surfaceflinger/CompositionEngine/src/planner/CachedSet.cpp b/services/surfaceflinger/CompositionEngine/src/planner/CachedSet.cpp index d6f02ee42a51cc5b9c96caf3e51bc089f811b82b..8ced0aca36b661a9a2254e4d01e03a8cc88421a7 100644 --- a/services/surfaceflinger/CompositionEngine/src/planner/CachedSet.cpp +++ b/services/surfaceflinger/CompositionEngine/src/planner/CachedSet.cpp @@ -193,11 +193,11 @@ void CachedSet::render(renderengine::RenderEngine& renderEngine, TexturePool& te std::vector layerSettings; renderengine::LayerSettings highlight; for (const auto& layer : mLayers) { - const auto clientCompositionList = - layer.getState()->getOutputLayer()->getLayerFE().prepareClientCompositionList( - targetSettings); - layerSettings.insert(layerSettings.end(), clientCompositionList.cbegin(), - clientCompositionList.cend()); + if (auto clientCompositionSettings = + layer.getState()->getOutputLayer()->getLayerFE().prepareClientComposition( + targetSettings)) { + layerSettings.push_back(std::move(*clientCompositionSettings)); + } } renderengine::LayerSettings blurLayerSettings; @@ -205,43 +205,40 @@ void CachedSet::render(renderengine::RenderEngine& renderEngine, TexturePool& te auto blurSettings = targetSettings; blurSettings.blurSetting = LayerFE::ClientCompositionTargetSettings::BlurSetting::BackgroundBlurOnly; - auto clientCompositionList = - mBlurLayer->getOutputLayer()->getLayerFE().prepareClientCompositionList( - blurSettings); - blurLayerSettings = clientCompositionList.back(); + + auto blurLayerSettings = + mBlurLayer->getOutputLayer()->getLayerFE().prepareClientComposition(blurSettings); // This mimics Layer::prepareClearClientComposition - blurLayerSettings.skipContentDraw = true; - blurLayerSettings.name = std::string("blur layer"); + blurLayerSettings->skipContentDraw = true; + blurLayerSettings->name = std::string("blur layer"); // Clear out the shadow settings - blurLayerSettings.shadow = {}; - layerSettings.push_back(blurLayerSettings); + blurLayerSettings->shadow = {}; + layerSettings.push_back(std::move(*blurLayerSettings)); } - renderengine::LayerSettings holePunchSettings; - renderengine::LayerSettings holePunchBackgroundSettings; if (mHolePunchLayer) { auto& layerFE = mHolePunchLayer->getOutputLayer()->getLayerFE(); - auto clientCompositionList = layerFE.prepareClientCompositionList(targetSettings); - // Assume that the final layer contains the buffer that we want to - // replace with a hole punch. - holePunchSettings = clientCompositionList.back(); + + auto holePunchSettings = layerFE.prepareClientComposition(targetSettings); // This mimics Layer::prepareClearClientComposition - holePunchSettings.source.buffer.buffer = nullptr; - holePunchSettings.source.solidColor = half3(0.0f, 0.0f, 0.0f); - holePunchSettings.disableBlending = true; - holePunchSettings.alpha = 0.0f; - holePunchSettings.name = + holePunchSettings->source.buffer.buffer = nullptr; + holePunchSettings->source.solidColor = half3(0.0f, 0.0f, 0.0f); + holePunchSettings->disableBlending = true; + holePunchSettings->alpha = 0.0f; + holePunchSettings->name = android::base::StringPrintf("hole punch layer for %s", layerFE.getDebugName()); - layerSettings.push_back(holePunchSettings); // Add a solid background as the first layer in case there is no opaque // buffer behind the punch hole + renderengine::LayerSettings holePunchBackgroundSettings; holePunchBackgroundSettings.alpha = 1.0f; holePunchBackgroundSettings.name = std::string("holePunchBackground"); - holePunchBackgroundSettings.geometry.boundaries = holePunchSettings.geometry.boundaries; + holePunchBackgroundSettings.geometry.boundaries = holePunchSettings->geometry.boundaries; holePunchBackgroundSettings.geometry.positionTransform = - holePunchSettings.geometry.positionTransform; - layerSettings.emplace(layerSettings.begin(), holePunchBackgroundSettings); + holePunchSettings->geometry.positionTransform; + layerSettings.emplace(layerSettings.begin(), std::move(holePunchBackgroundSettings)); + + layerSettings.push_back(std::move(*holePunchSettings)); } if (sDebugHighlighLayers) { @@ -276,11 +273,10 @@ void CachedSet::render(renderengine::RenderEngine& renderEngine, TexturePool& te constexpr bool kUseFramebufferCache = false; - auto fenceResult = - toFenceResult(renderEngine - .drawLayers(displaySettings, layerSettings, texture->get(), - kUseFramebufferCache, std::move(bufferFence)) - .get()); + auto fenceResult = renderEngine + .drawLayers(displaySettings, layerSettings, texture->get(), + kUseFramebufferCache, std::move(bufferFence)) + .get(); if (fenceStatus(fenceResult) == NO_ERROR) { mDrawFence = std::move(fenceResult).value_or(Fence::NO_FENCE); @@ -382,6 +378,10 @@ bool CachedSet::hasUnsupportedDataspace() const { // to avoid flickering/color differences. return true; } + // TODO(b/274804887): temp fix of overdimming issue, skip caching if hsdr/sdr ratio > 1.01f + if (layer.getState()->getHdrSdrRatio() > 1.01f) { + return true; + } return false; }); } @@ -397,6 +397,18 @@ bool CachedSet::hasSolidColorLayers() const { }); } +bool CachedSet::cachingHintExcludesLayers() const { + const bool shouldExcludeLayers = + std::any_of(mLayers.cbegin(), mLayers.cend(), [](const Layer& layer) { + return layer.getState()->getCachingHint() == gui::CachingHint::Disabled; + }); + + LOG_ALWAYS_FATAL_IF(shouldExcludeLayers && getLayerCount() > 1, + "CachedSet is invalid: should be excluded but contains %zu layers", + getLayerCount()); + return shouldExcludeLayers; +} + void CachedSet::dump(std::string& result) const { const auto now = std::chrono::steady_clock::now(); @@ -415,8 +427,8 @@ void CachedSet::dump(std::string& result) const { if (mLayers.size() == 1) { base::StringAppendF(&result, " Layer [%s]\n", mLayers[0].getName().c_str()); - if (auto* buffer = mLayers[0].getBuffer().get()) { - base::StringAppendF(&result, " Buffer %p", buffer); + if (const sp buffer = mLayers[0].getState()->getBuffer().promote()) { + base::StringAppendF(&result, " Buffer %p", buffer.get()); base::StringAppendF(&result, " Format %s", decodePixelFormat(buffer->getPixelFormat()).c_str()); } @@ -426,8 +438,8 @@ void CachedSet::dump(std::string& result) const { result.append(" Cached set of:\n"); for (const Layer& layer : mLayers) { base::StringAppendF(&result, " Layer [%s]\n", layer.getName().c_str()); - if (auto* buffer = layer.getBuffer().get()) { - base::StringAppendF(&result, " Buffer %p", buffer); + if (const sp buffer = layer.getState()->getBuffer().promote()) { + base::StringAppendF(&result, " Buffer %p", buffer.get()); base::StringAppendF(&result, " Format[%s]", decodePixelFormat(buffer->getPixelFormat()).c_str()); } diff --git a/services/surfaceflinger/CompositionEngine/src/planner/Flattener.cpp b/services/surfaceflinger/CompositionEngine/src/planner/Flattener.cpp index 9175dd01a1e644fad28b45bf4e571f77bfa78873..13b6307aeabdc5585713451c17d2f647dc3e72ee 100644 --- a/services/surfaceflinger/CompositionEngine/src/planner/Flattener.cpp +++ b/services/surfaceflinger/CompositionEngine/src/planner/Flattener.cpp @@ -413,6 +413,7 @@ std::vector Flattener::findCandidateRuns(time_point now) const { for (auto currentSet = mLayers.cbegin(); currentSet != mLayers.cend(); ++currentSet) { bool layerIsInactive = now - currentSet->getLastUpdate() > mTunables.mActiveLayerTimeout; const bool layerHasBlur = currentSet->hasBlurBehind(); + const bool layerDeniedFromCaching = currentSet->cachingHintExcludesLayers(); // Layers should also be considered inactive whenever their framerate is lower than 1fps. if (!layerIsInactive && currentSet->getLayerCount() == kNumLayersFpsConsideration) { @@ -424,7 +425,8 @@ std::vector Flattener::findCandidateRuns(time_point now) const { } } - if (layerIsInactive && (firstLayer || runHasFirstLayer || !layerHasBlur) && + if (!layerDeniedFromCaching && layerIsInactive && + (firstLayer || runHasFirstLayer || !layerHasBlur) && !currentSet->hasUnsupportedDataspace()) { if (isPartOfRun) { builder.increment(); diff --git a/services/surfaceflinger/CompositionEngine/src/planner/Predictor.cpp b/services/surfaceflinger/CompositionEngine/src/planner/Predictor.cpp index 2fc029fdc640a3bfada39157b3d3f08cf3db96d6..606412609927cb9b837ef1226e91cb91162935b2 100644 --- a/services/surfaceflinger/CompositionEngine/src/planner/Predictor.cpp +++ b/services/surfaceflinger/CompositionEngine/src/planner/Predictor.cpp @@ -151,6 +151,10 @@ std::string to_string(const Plan& plan) { // A for "Alpha", since the decoration is an alpha layer. result.append("A"); break; + case aidl::android::hardware::graphics::composer3::Composition::REFRESH_RATE_INDICATOR: + // R for "Refresh", since the layer is Refresh rate overlay. + result.append("R"); + break; } } return result; diff --git a/services/surfaceflinger/CompositionEngine/tests/CompositionEngineTest.cpp b/services/surfaceflinger/CompositionEngine/tests/CompositionEngineTest.cpp index de9de0150decc4be3c164705d223d97fdd23b02d..60ed660c7a24c799b27510072f88b06ec8d836b8 100644 --- a/services/surfaceflinger/CompositionEngine/tests/CompositionEngineTest.cpp +++ b/services/surfaceflinger/CompositionEngine/tests/CompositionEngineTest.cpp @@ -62,17 +62,16 @@ TEST_F(CompositionEngineTest, canSetHWComposer) { } TEST_F(CompositionEngineTest, canSetRenderEngine) { - renderengine::mock::RenderEngine* renderEngine = - new StrictMock(); - mEngine.setRenderEngine(std::unique_ptr(renderEngine)); + auto renderEngine = std::make_unique>(); + mEngine.setRenderEngine(renderEngine.get()); - EXPECT_EQ(renderEngine, &mEngine.getRenderEngine()); + EXPECT_EQ(renderEngine.get(), &mEngine.getRenderEngine()); } TEST_F(CompositionEngineTest, canSetTimeStats) { mEngine.setTimeStats(mTimeStats); - EXPECT_EQ(mTimeStats.get(), &mEngine.getTimeStats()); + EXPECT_EQ(mTimeStats.get(), mEngine.getTimeStats()); } /* @@ -108,12 +107,6 @@ TEST_F(CompositionEnginePresentTest, worksAsExpected) { EXPECT_CALL(*mOutput2, prepare(Ref(mRefreshArgs), _)); EXPECT_CALL(*mOutput3, prepare(Ref(mRefreshArgs), _)); - // The next step in presenting is to make sure all outputs have the latest - // state from the front-end (SurfaceFlinger). - EXPECT_CALL(*mOutput1, updateLayerStateFromFE(Ref(mRefreshArgs))); - EXPECT_CALL(*mOutput2, updateLayerStateFromFE(Ref(mRefreshArgs))); - EXPECT_CALL(*mOutput3, updateLayerStateFromFE(Ref(mRefreshArgs))); - // The last step is to actually present each output. EXPECT_CALL(*mOutput1, present(Ref(mRefreshArgs))); EXPECT_CALL(*mOutput2, present(Ref(mRefreshArgs))); @@ -175,21 +168,18 @@ TEST_F(CompositionEngineUpdateCursorAsyncTest, handlesMultipleLayersBeingCursorL { InSequence seq; EXPECT_CALL(mOutput2Layer1.outputLayer, isHardwareCursor()).WillRepeatedly(Return(true)); - EXPECT_CALL(*mOutput2Layer1.layerFE, prepareCompositionState(LayerFE::StateSubset::Cursor)); EXPECT_CALL(mOutput2Layer1.outputLayer, writeCursorPositionToHWC()); } { InSequence seq; EXPECT_CALL(mOutput3Layer1.outputLayer, isHardwareCursor()).WillRepeatedly(Return(true)); - EXPECT_CALL(*mOutput3Layer1.layerFE, prepareCompositionState(LayerFE::StateSubset::Cursor)); EXPECT_CALL(mOutput3Layer1.outputLayer, writeCursorPositionToHWC()); } { InSequence seq; EXPECT_CALL(mOutput3Layer2.outputLayer, isHardwareCursor()).WillRepeatedly(Return(true)); - EXPECT_CALL(*mOutput3Layer2.layerFE, prepareCompositionState(LayerFE::StateSubset::Cursor)); EXPECT_CALL(mOutput3Layer2.outputLayer, writeCursorPositionToHWC()); } @@ -222,9 +212,12 @@ 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}; @@ -238,9 +231,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); @@ -255,9 +248,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/CompositionEngine/tests/DisplayTest.cpp b/services/surfaceflinger/CompositionEngine/tests/DisplayTest.cpp index 5369642a1bfc2d6e21b542a7c328df7ce614d37b..9be6bc207574b82ce285d33c33a26492db9a2691 100644 --- a/services/surfaceflinger/CompositionEngine/tests/DisplayTest.cpp +++ b/services/surfaceflinger/CompositionEngine/tests/DisplayTest.cpp @@ -194,7 +194,7 @@ struct DisplayTestCommon : public testing::Test { StrictMock mPowerAdvisor; StrictMock mRenderEngine; StrictMock mCompositionEngine; - sp mNativeWindow = new StrictMock(); + sp mNativeWindow = sp>::make(); }; struct PartialMockDisplayTestCommon : public DisplayTestCommon { @@ -524,7 +524,7 @@ TEST_F(DisplaySetReleasedLayersTest, doesNothingIfGpuDisplay) { auto args = getDisplayCreationArgsForGpuVirtualDisplay(); std::shared_ptr gpuDisplay = impl::createDisplay(mCompositionEngine, args); - sp layerXLayerFE = new StrictMock(); + sp layerXLayerFE = sp>::make(); { Output::ReleasedLayers releasedLayers; @@ -542,7 +542,7 @@ TEST_F(DisplaySetReleasedLayersTest, doesNothingIfGpuDisplay) { } TEST_F(DisplaySetReleasedLayersTest, doesNothingIfNoLayersWithQueuedFrames) { - sp layerXLayerFE = new StrictMock(); + sp layerXLayerFE = sp>::make(); { Output::ReleasedLayers releasedLayers; @@ -558,7 +558,7 @@ TEST_F(DisplaySetReleasedLayersTest, doesNothingIfNoLayersWithQueuedFrames) { } TEST_F(DisplaySetReleasedLayersTest, setReleasedLayers) { - sp unknownLayer = new StrictMock(); + sp unknownLayer = sp>::make(); CompositionRefreshArgs refreshArgs; refreshArgs.layersWithQueuedFrames.push_back(mLayer1.layerFE); @@ -595,7 +595,7 @@ TEST_F(DisplayChooseCompositionStrategyTest, takesEarlyOutIfGpuDisplay) { TEST_F(DisplayChooseCompositionStrategyTest, takesEarlyOutOnHwcError) { EXPECT_CALL(*mDisplay, anyLayersRequireClientComposition()).WillOnce(Return(false)); EXPECT_CALL(mHwComposer, - getDeviceCompositionChanges(HalDisplayId(DEFAULT_DISPLAY_ID), false, _, _, _, _)) + getDeviceCompositionChanges(HalDisplayId(DEFAULT_DISPLAY_ID), false, _, _, _)) .WillOnce(Return(INVALID_OPERATION)); chooseCompositionStrategy(mDisplay.get()); @@ -619,8 +619,8 @@ TEST_F(DisplayChooseCompositionStrategyTest, normalOperation) { .WillOnce(Return(false)); EXPECT_CALL(mHwComposer, - getDeviceCompositionChanges(HalDisplayId(DEFAULT_DISPLAY_ID), true, _, _, _, _)) - .WillOnce(testing::DoAll(testing::SetArgPointee<5>(mDeviceRequestedChanges), + getDeviceCompositionChanges(HalDisplayId(DEFAULT_DISPLAY_ID), true, _, _, _)) + .WillOnce(testing::DoAll(testing::SetArgPointee<4>(mDeviceRequestedChanges), Return(NO_ERROR))); EXPECT_CALL(*mDisplay, applyChangedTypesToLayers(mDeviceRequestedChanges.changedTypes)) .Times(1); @@ -672,8 +672,8 @@ TEST_F(DisplayChooseCompositionStrategyTest, normalOperationWithChanges) { .WillOnce(Return(false)); EXPECT_CALL(mHwComposer, - getDeviceCompositionChanges(HalDisplayId(DEFAULT_DISPLAY_ID), true, _, _, _, _)) - .WillOnce(DoAll(SetArgPointee<5>(mDeviceRequestedChanges), Return(NO_ERROR))); + getDeviceCompositionChanges(HalDisplayId(DEFAULT_DISPLAY_ID), true, _, _, _)) + .WillOnce(DoAll(SetArgPointee<4>(mDeviceRequestedChanges), Return(NO_ERROR))); EXPECT_CALL(*mDisplay, applyChangedTypesToLayers(mDeviceRequestedChanges.changedTypes)) .Times(1); EXPECT_CALL(*mDisplay, applyDisplayRequests(mDeviceRequestedChanges.displayRequests)).Times(1); @@ -897,11 +897,11 @@ TEST_F(DisplayPresentAndGetFrameFencesTest, returnsNoFencesOnGpuDisplay) { } TEST_F(DisplayPresentAndGetFrameFencesTest, returnsPresentAndLayerFences) { - sp presentFence = new Fence(); - sp layer1Fence = new Fence(); - sp layer2Fence = new Fence(); + sp presentFence = sp::make(); + sp layer1Fence = sp::make(); + sp layer2Fence = sp::make(); - EXPECT_CALL(mHwComposer, presentAndGetReleaseFences(HalDisplayId(DEFAULT_DISPLAY_ID), _, _)) + EXPECT_CALL(mHwComposer, presentAndGetReleaseFences(HalDisplayId(DEFAULT_DISPLAY_ID), _)) .Times(1); EXPECT_CALL(mHwComposer, getPresentFence(HalDisplayId(DEFAULT_DISPLAY_ID))) .WillOnce(Return(presentFence)); @@ -959,7 +959,7 @@ TEST_F(DisplayFinishFrameTest, doesNotSkipCompositionIfNotDirtyOnHwcDisplay) { mDisplay->editState().layerStackSpace.setContent(Rect(0, 0, 1, 1)); mDisplay->editState().dirtyRegion = Region::INVALID_REGION; - mDisplay->finishFrame({}, std::move(mResultWithBuffer)); + mDisplay->finishFrame(std::move(mResultWithBuffer)); } TEST_F(DisplayFinishFrameTest, skipsCompositionIfNotDirty) { @@ -980,7 +980,7 @@ TEST_F(DisplayFinishFrameTest, skipsCompositionIfNotDirty) { gpuDisplay->editState().lastCompositionHadVisibleLayers = true; gpuDisplay->beginFrame(); - gpuDisplay->finishFrame({}, std::move(mResultWithoutBuffer)); + gpuDisplay->finishFrame(std::move(mResultWithoutBuffer)); } TEST_F(DisplayFinishFrameTest, skipsCompositionIfEmpty) { @@ -1001,7 +1001,7 @@ TEST_F(DisplayFinishFrameTest, skipsCompositionIfEmpty) { gpuDisplay->editState().lastCompositionHadVisibleLayers = false; gpuDisplay->beginFrame(); - gpuDisplay->finishFrame({}, std::move(mResultWithoutBuffer)); + gpuDisplay->finishFrame(std::move(mResultWithoutBuffer)); } TEST_F(DisplayFinishFrameTest, performsCompositionIfDirtyAndNotEmpty) { @@ -1022,7 +1022,7 @@ TEST_F(DisplayFinishFrameTest, performsCompositionIfDirtyAndNotEmpty) { gpuDisplay->editState().lastCompositionHadVisibleLayers = true; gpuDisplay->beginFrame(); - gpuDisplay->finishFrame({}, std::move(mResultWithBuffer)); + gpuDisplay->finishFrame(std::move(mResultWithBuffer)); } /* @@ -1046,8 +1046,8 @@ struct DisplayFunctionalTest : public testing::Test { NiceMock mHwComposer; NiceMock mPowerAdvisor; NiceMock mCompositionEngine; - sp mNativeWindow = new NiceMock(); - sp mDisplaySurface = new NiceMock(); + sp mNativeWindow = sp>::make(); + sp mDisplaySurface = sp>::make(); std::shared_ptr mDisplay; impl::RenderSurface* mRenderSurface; @@ -1078,7 +1078,7 @@ TEST_F(DisplayFunctionalTest, postFramebufferCriticalCallsAreOrdered) { mDisplay->editState().isEnabled = true; - EXPECT_CALL(mHwComposer, presentAndGetReleaseFences(_, _, _)); + EXPECT_CALL(mHwComposer, presentAndGetReleaseFences(_, _)); EXPECT_CALL(*mDisplaySurface, onFrameCommitted()); mDisplay->postFramebuffer(); diff --git a/services/surfaceflinger/CompositionEngine/tests/HwcBufferCacheTest.cpp b/services/surfaceflinger/CompositionEngine/tests/HwcBufferCacheTest.cpp index 00eafb15f5854b12f0bb6131d6b6512a6e502e36..c5fb5944fb3f9ec7c6e6291a8e04de3abe17eb39 100644 --- a/services/surfaceflinger/CompositionEngine/tests/HwcBufferCacheTest.cpp +++ b/services/surfaceflinger/CompositionEngine/tests/HwcBufferCacheTest.cpp @@ -22,64 +22,172 @@ namespace android::compositionengine { namespace { -class TestableHwcBufferCache : public impl::HwcBufferCache { -public: - void getHwcBuffer(int slot, const sp& buffer, uint32_t* outSlot, - sp* outBuffer) { - HwcBufferCache::getHwcBuffer(slot, buffer, outSlot, outBuffer); - } -}; +using impl::HwcBufferCache; +using impl::HwcSlotAndBuffer; class HwcBufferCacheTest : public testing::Test { public: ~HwcBufferCacheTest() override = default; - void testSlot(const int inSlot, const uint32_t expectedSlot) { - uint32_t outSlot; - sp outBuffer; - - // The first time, the output is the same as the input - mCache.getHwcBuffer(inSlot, mBuffer1, &outSlot, &outBuffer); - EXPECT_EQ(expectedSlot, outSlot); - EXPECT_EQ(mBuffer1, outBuffer); - - // The second time with the same buffer, the outBuffer is nullptr. - mCache.getHwcBuffer(inSlot, mBuffer1, &outSlot, &outBuffer); - EXPECT_EQ(expectedSlot, outSlot); - EXPECT_EQ(nullptr, outBuffer.get()); - - // With a new buffer, the outBuffer is the input. - mCache.getHwcBuffer(inSlot, mBuffer2, &outSlot, &outBuffer); - EXPECT_EQ(expectedSlot, outSlot); - EXPECT_EQ(mBuffer2, outBuffer); - - // Again, the second request with the same buffer sets outBuffer to nullptr. - mCache.getHwcBuffer(inSlot, mBuffer2, &outSlot, &outBuffer); - EXPECT_EQ(expectedSlot, outSlot); - EXPECT_EQ(nullptr, outBuffer.get()); - - // Setting a slot to use nullptr lookslike works, but note that - // the output values make it look like no new buffer is being set.... - mCache.getHwcBuffer(inSlot, sp(), &outSlot, &outBuffer); - EXPECT_EQ(expectedSlot, outSlot); - EXPECT_EQ(nullptr, outBuffer.get()); + sp mBuffer1 = + sp::make(1u, 1u, HAL_PIXEL_FORMAT_RGBA_8888, 1u, 0u); + sp mBuffer2 = + sp::make(1u, 1u, HAL_PIXEL_FORMAT_RGBA_8888, 1u, 0u); +}; + +TEST_F(HwcBufferCacheTest, getHwcSlotAndBuffer_returnsUniqueSlotNumberForEachBuffer) { + HwcBufferCache cache; + sp outBuffer; + + HwcSlotAndBuffer slotAndBufferFor1 = cache.getHwcSlotAndBuffer(mBuffer1); + EXPECT_NE(slotAndBufferFor1.slot, UINT32_MAX); + EXPECT_EQ(slotAndBufferFor1.buffer, mBuffer1); + + HwcSlotAndBuffer slotAndBufferFor2 = cache.getHwcSlotAndBuffer(mBuffer2); + EXPECT_NE(slotAndBufferFor2.slot, slotAndBufferFor1.slot); + EXPECT_NE(slotAndBufferFor2.slot, UINT32_MAX); + EXPECT_EQ(slotAndBufferFor2.buffer, mBuffer2); +} + +TEST_F(HwcBufferCacheTest, getHwcSlotAndBuffer_whenCached_returnsSameSlotNumberAndNullBuffer) { + HwcBufferCache cache; + sp outBuffer; + + HwcSlotAndBuffer originalSlotAndBuffer = cache.getHwcSlotAndBuffer(mBuffer1); + EXPECT_NE(originalSlotAndBuffer.slot, UINT32_MAX); + EXPECT_EQ(originalSlotAndBuffer.buffer, mBuffer1); + + HwcSlotAndBuffer finalSlotAndBuffer = cache.getHwcSlotAndBuffer(mBuffer1); + EXPECT_EQ(finalSlotAndBuffer.slot, originalSlotAndBuffer.slot); + EXPECT_EQ(finalSlotAndBuffer.buffer, nullptr); +} + +TEST_F(HwcBufferCacheTest, getHwcSlotAndBuffer_whenSlotsFull_evictsOldestCachedBuffer) { + HwcBufferCache cache; + sp outBuffer; + + sp graphicBuffers[100]; + HwcSlotAndBuffer slotsAndBuffers[100]; + int finalCachedBufferIndex = 0; + for (int i = 0; i < 100; ++i) { + graphicBuffers[i] = sp::make(1u, 1u, HAL_PIXEL_FORMAT_RGBA_8888, 1u, 0u); + slotsAndBuffers[i] = cache.getHwcSlotAndBuffer(graphicBuffers[i]); + // we fill up the cache when the slot number for the first buffer is reused + if (i > 0 && slotsAndBuffers[i].slot == slotsAndBuffers[0].slot) { + finalCachedBufferIndex = i; + break; + } } + ASSERT_GT(finalCachedBufferIndex, 1); + // the final cached buffer has the same slot value as the oldest buffer + EXPECT_EQ(slotsAndBuffers[finalCachedBufferIndex].slot, slotsAndBuffers[0].slot); + // the oldest buffer is no longer in the cache because it was evicted + EXPECT_EQ(cache.uncache(graphicBuffers[0]->getId()), UINT32_MAX); +} - impl::HwcBufferCache mCache; - sp mBuffer1{new GraphicBuffer(1, 1, HAL_PIXEL_FORMAT_RGBA_8888, 1, 0)}; - sp mBuffer2{new GraphicBuffer(1, 1, HAL_PIXEL_FORMAT_RGBA_8888, 1, 0)}; -}; +TEST_F(HwcBufferCacheTest, uncache_whenCached_returnsSlotNumber) { + HwcBufferCache cache; + sp outBuffer; + + HwcSlotAndBuffer slotAndBufferFor1 = cache.getHwcSlotAndBuffer(mBuffer1); + ASSERT_NE(slotAndBufferFor1.slot, UINT32_MAX); -TEST_F(HwcBufferCacheTest, cacheWorksForSlotZero) { - testSlot(0, 0); + HwcSlotAndBuffer slotAndBufferFor2 = cache.getHwcSlotAndBuffer(mBuffer2); + ASSERT_NE(slotAndBufferFor2.slot, UINT32_MAX); + + // the 1st buffer should be found in the cache with a slot number + EXPECT_EQ(cache.uncache(mBuffer1->getId()), slotAndBufferFor1.slot); + // since the 1st buffer has been previously uncached, we should no longer receive a slot number + EXPECT_EQ(cache.uncache(mBuffer1->getId()), UINT32_MAX); + // the 2nd buffer should be still found in the cache with a slot number + EXPECT_EQ(cache.uncache(mBuffer2->getId()), slotAndBufferFor2.slot); + // since the 2nd buffer has been previously uncached, we should no longer receive a slot number + EXPECT_EQ(cache.uncache(mBuffer2->getId()), UINT32_MAX); } -TEST_F(HwcBufferCacheTest, cacheWorksForMaxSlot) { - testSlot(BufferQueue::NUM_BUFFER_SLOTS - 1, BufferQueue::NUM_BUFFER_SLOTS - 1); +TEST_F(HwcBufferCacheTest, uncache_whenUncached_returnsInvalidSlotNumber) { + HwcBufferCache cache; + sp outBuffer; + + HwcSlotAndBuffer slotAndBufferFor1 = cache.getHwcSlotAndBuffer(mBuffer1); + ASSERT_NE(slotAndBufferFor1.slot, UINT32_MAX); + + EXPECT_EQ(cache.uncache(mBuffer2->getId()), UINT32_MAX); +} + +TEST_F(HwcBufferCacheTest, getOverrideHwcSlotAndBuffer_whenCached_returnsSameSlotAndNullBuffer) { + HwcBufferCache cache; + sp outBuffer; + + HwcSlotAndBuffer originalSlotAndBuffer = cache.getOverrideHwcSlotAndBuffer(mBuffer1); + EXPECT_NE(originalSlotAndBuffer.slot, UINT32_MAX); + EXPECT_EQ(originalSlotAndBuffer.buffer, mBuffer1); + + HwcSlotAndBuffer finalSlotAndBuffer = cache.getOverrideHwcSlotAndBuffer(mBuffer1); + EXPECT_EQ(finalSlotAndBuffer.slot, originalSlotAndBuffer.slot); + EXPECT_EQ(finalSlotAndBuffer.buffer, nullptr); +} + +TEST_F(HwcBufferCacheTest, getOverrideHwcSlotAndBuffer_whenSlotsFull_returnsIndependentSlot) { + HwcBufferCache cache; + sp outBuffer; + + sp graphicBuffers[100]; + HwcSlotAndBuffer slotsAndBuffers[100]; + int finalCachedBufferIndex = -1; + for (int i = 0; i < 100; ++i) { + graphicBuffers[i] = sp::make(1u, 1u, HAL_PIXEL_FORMAT_RGBA_8888, 1u, 0u); + slotsAndBuffers[i] = cache.getHwcSlotAndBuffer(graphicBuffers[i]); + // we fill up the cache when the slot number for the first buffer is reused + if (i > 0 && slotsAndBuffers[i].slot == slotsAndBuffers[0].slot) { + finalCachedBufferIndex = i; + break; + } + } + // expect to have cached at least a few buffers before evicting + ASSERT_GT(finalCachedBufferIndex, 1); + + sp overrideBuffer = + sp::make(1u, 1u, HAL_PIXEL_FORMAT_RGBA_8888, 1u, 0u); + HwcSlotAndBuffer overrideSlotAndBuffer = cache.getOverrideHwcSlotAndBuffer(overrideBuffer); + // expect us to have a slot number + EXPECT_NE(overrideSlotAndBuffer.slot, UINT32_MAX); + // expect this to be the first time we cached the buffer + EXPECT_NE(overrideSlotAndBuffer.buffer, nullptr); + + // expect the slot number to not equal any other slot number, even after the slots have been + // exhausted, indicating that the override buffer slot is independent from the slots for + // non-override buffers + for (int i = 0; i < finalCachedBufferIndex; ++i) { + EXPECT_NE(overrideSlotAndBuffer.slot, slotsAndBuffers[i].slot); + } + // the override buffer is independently uncached from the oldest cached buffer + // expect to find the override buffer still in the override buffer slot + EXPECT_EQ(cache.uncache(overrideBuffer->getId()), overrideSlotAndBuffer.slot); + // expect that the first buffer was not evicted from the cache when the override buffer was + // cached + EXPECT_EQ(cache.uncache(graphicBuffers[1]->getId()), slotsAndBuffers[1].slot); } -TEST_F(HwcBufferCacheTest, cacheMapsNegativeSlotToZero) { - testSlot(-123, 0); +TEST_F(HwcBufferCacheTest, uncache_whenOverrideCached_returnsSlotNumber) { + HwcBufferCache cache; + sp outBuffer; + + HwcSlotAndBuffer hwcSlotAndBuffer = cache.getOverrideHwcSlotAndBuffer(mBuffer1); + ASSERT_NE(hwcSlotAndBuffer.slot, UINT32_MAX); + + EXPECT_EQ(cache.uncache(mBuffer1->getId()), hwcSlotAndBuffer.slot); + EXPECT_EQ(cache.uncache(mBuffer1->getId()), UINT32_MAX); +} + +TEST_F(HwcBufferCacheTest, uncache_whenOverrideUncached_returnsInvalidSlotNumber) { + HwcBufferCache cache; + sp outBuffer; + + HwcSlotAndBuffer hwcSlotAndBuffer = cache.getOverrideHwcSlotAndBuffer(mBuffer1); + ASSERT_NE(hwcSlotAndBuffer.slot, UINT32_MAX); + + EXPECT_EQ(cache.uncache(mBuffer2->getId()), UINT32_MAX); } } // namespace diff --git a/services/surfaceflinger/CompositionEngine/tests/MockHWC2.h b/services/surfaceflinger/CompositionEngine/tests/MockHWC2.h index d933b94da9f82a0c9319fd4b720f8f0a4c0ef35b..b0b1a021643b494e740cd6ca7161d378624009d8 100644 --- a/services/surfaceflinger/CompositionEngine/tests/MockHWC2.h +++ b/services/surfaceflinger/CompositionEngine/tests/MockHWC2.h @@ -56,6 +56,7 @@ public: MOCK_METHOD3(setBuffer, Error(uint32_t, const android::sp&, const android::sp&)); + MOCK_METHOD2(setBufferSlotsToClear, Error(const std::vector&, uint32_t)); MOCK_METHOD1(setSurfaceDamage, Error(const android::Region&)); MOCK_METHOD1(setBlendMode, Error(hal::BlendMode)); MOCK_METHOD1(setColor, Error(aidl::android::hardware::graphics::composer3::Color)); diff --git a/services/surfaceflinger/CompositionEngine/tests/MockHWComposer.h b/services/surfaceflinger/CompositionEngine/tests/MockHWComposer.h index d7704a893d6eb1c7364cbc4d5711aadf5dd6d26f..67b94ee74986261ac6080e21600b268f686a11d2 100644 --- a/services/surfaceflinger/CompositionEngine/tests/MockHWComposer.h +++ b/services/surfaceflinger/CompositionEngine/tests/MockHWComposer.h @@ -54,22 +54,21 @@ public: MOCK_METHOD2(allocatePhysicalDisplay, void(hal::HWDisplayId, PhysicalDisplayId)); MOCK_METHOD1(createLayer, std::shared_ptr(HalDisplayId)); - MOCK_METHOD6(getDeviceCompositionChanges, - status_t(HalDisplayId, bool, std::chrono::steady_clock::time_point, - const std::shared_ptr&, nsecs_t, - std::optional*)); + MOCK_METHOD5(getDeviceCompositionChanges, + status_t(HalDisplayId, bool, std::optional, + nsecs_t, std::optional*)); MOCK_METHOD5(setClientTarget, status_t(HalDisplayId, uint32_t, const sp&, const sp&, ui::Dataspace)); - MOCK_METHOD3(presentAndGetReleaseFences, - status_t(HalDisplayId, std::chrono::steady_clock::time_point, - const std::shared_ptr&)); + MOCK_METHOD2(presentAndGetReleaseFences, + status_t(HalDisplayId, std::optional)); MOCK_METHOD2(setPowerMode, status_t(PhysicalDisplayId, hal::PowerMode)); MOCK_METHOD2(setActiveConfig, status_t(HalDisplayId, size_t)); MOCK_METHOD2(setColorTransform, status_t(HalDisplayId, const mat4&)); MOCK_METHOD1(disconnectDisplay, void(HalDisplayId)); MOCK_CONST_METHOD1(hasDeviceComposition, bool(const std::optional&)); MOCK_CONST_METHOD1(getPresentFence, sp(HalDisplayId)); + MOCK_METHOD(nsecs_t, getPresentTimestamp, (PhysicalDisplayId), (const, override)); MOCK_CONST_METHOD2(getLayerReleaseFence, sp(HalDisplayId, HWC2::Layer*)); MOCK_METHOD3(setOutputBuffer, status_t(HalVirtualDisplayId, const sp&, const sp&)); @@ -92,7 +91,7 @@ public: MOCK_METHOD2(onHotplug, std::optional(hal::HWDisplayId, hal::Connection)); MOCK_CONST_METHOD0(updatesDeviceProductInfoOnHotplugReconnect, bool()); - MOCK_METHOD2(onVsync, bool(hal::HWDisplayId, int64_t)); + MOCK_METHOD(std::optional, onVsync, (hal::HWDisplayId, int64_t)); MOCK_METHOD2(setVsyncEnabled, void(PhysicalDisplayId, hal::Vsync)); MOCK_CONST_METHOD1(isConnected, bool(PhysicalDisplayId)); MOCK_CONST_METHOD1(getModes, std::vector(PhysicalDisplayId)); @@ -111,6 +110,12 @@ public: MOCK_METHOD1(clearBootDisplayMode, status_t(PhysicalDisplayId)); MOCK_METHOD1(getPreferredBootDisplayMode, std::optional(PhysicalDisplayId)); MOCK_METHOD0(getBootDisplayModeSupport, bool()); + MOCK_CONST_METHOD0( + getHdrConversionCapabilities, + std::vector()); + MOCK_METHOD2(setHdrConversionStrategy, + status_t(aidl::android::hardware::graphics::common::HdrConversionStrategy, + aidl::android::hardware::graphics::common::Hdr*)); MOCK_METHOD2(setAutoLowLatencyMode, status_t(PhysicalDisplayId, bool)); MOCK_METHOD(status_t, getSupportedContentTypes, (PhysicalDisplayId, std::vector*), (const, override)); @@ -138,6 +143,9 @@ public: MOCK_METHOD(Hwc2::AidlTransform, getPhysicalDisplayOrientation, (PhysicalDisplayId), (const, override)); MOCK_METHOD(bool, getValidateSkipped, (HalDisplayId), (const, override)); + MOCK_METHOD(const aidl::android::hardware::graphics::composer3::OverlayProperties&, + getOverlaySupport, (), (const, override)); + MOCK_METHOD(status_t, setRefreshRateChangedCallbackDebugEnabled, (PhysicalDisplayId, bool)); }; } // namespace mock diff --git a/services/surfaceflinger/CompositionEngine/tests/MockPowerAdvisor.h b/services/surfaceflinger/CompositionEngine/tests/MockPowerAdvisor.h index c8bd5e436c2007d4970fcf01a48b58e37fdf291d..961ec808e81fe6173823de260514d6cb0101ead5 100644 --- a/services/surfaceflinger/CompositionEngine/tests/MockPowerAdvisor.h +++ b/services/surfaceflinger/CompositionEngine/tests/MockPowerAdvisor.h @@ -34,37 +34,35 @@ public: MOCK_METHOD(void, setExpensiveRenderingExpected, (DisplayId displayId, bool expected), (override)); MOCK_METHOD(bool, isUsingExpensiveRendering, (), (override)); - MOCK_METHOD(void, notifyDisplayUpdateImminent, (), (override)); + MOCK_METHOD(void, notifyDisplayUpdateImminentAndCpuReset, (), (override)); MOCK_METHOD(bool, usePowerHintSession, (), (override)); MOCK_METHOD(bool, supportsPowerHintSession, (), (override)); - MOCK_METHOD(bool, isPowerHintSessionRunning, (), (override)); - MOCK_METHOD(void, setTargetWorkDuration, (int64_t targetDuration), (override)); - MOCK_METHOD(void, sendActualWorkDuration, (), (override)); - MOCK_METHOD(void, sendPredictedWorkDuration, (), (override)); - MOCK_METHOD(void, enablePowerHint, (bool enabled), (override)); + MOCK_METHOD(bool, ensurePowerHintSessionRunning, (), (override)); + MOCK_METHOD(void, updateTargetWorkDuration, (Duration targetDuration), (override)); + MOCK_METHOD(void, reportActualWorkDuration, (), (override)); + MOCK_METHOD(void, enablePowerHintSession, (bool enabled), (override)); MOCK_METHOD(bool, startPowerHintSession, (const std::vector& threadIds), (override)); MOCK_METHOD(void, setGpuFenceTime, (DisplayId displayId, std::unique_ptr&& fenceTime), (override)); MOCK_METHOD(void, setHwcValidateTiming, - (DisplayId displayId, nsecs_t valiateStartTime, nsecs_t validateEndTime), + (DisplayId displayId, TimePoint validateStartTime, TimePoint validateEndTime), (override)); MOCK_METHOD(void, setHwcPresentTiming, - (DisplayId displayId, nsecs_t presentStartTime, nsecs_t presentEndTime), + (DisplayId displayId, TimePoint presentStartTime, TimePoint presentEndTime), (override)); MOCK_METHOD(void, setSkippedValidate, (DisplayId displayId, bool skipped), (override)); MOCK_METHOD(void, setRequiresClientComposition, (DisplayId displayId, bool requiresClientComposition), (override)); - MOCK_METHOD(void, setExpectedPresentTime, (nsecs_t expectedPresentTime), (override)); - MOCK_METHOD(void, setSfPresentTiming, (nsecs_t presentFenceTime, nsecs_t presentEndTime), + MOCK_METHOD(void, setExpectedPresentTime, (TimePoint expectedPresentTime), (override)); + MOCK_METHOD(void, setSfPresentTiming, (TimePoint presentFenceTime, TimePoint presentEndTime), (override)); MOCK_METHOD(void, setHwcPresentDelayedTime, - (DisplayId displayId, - std::chrono::steady_clock::time_point earliestFrameStartTime)); - MOCK_METHOD(void, setFrameDelay, (nsecs_t frameDelayDuration), (override)); - MOCK_METHOD(void, setCommitStart, (nsecs_t commitStartTime), (override)); - MOCK_METHOD(void, setCompositeEnd, (nsecs_t compositeEndtime), (override)); + (DisplayId displayId, TimePoint earliestFrameStartTime)); + MOCK_METHOD(void, setFrameDelay, (Duration frameDelayDuration), (override)); + MOCK_METHOD(void, setCommitStart, (TimePoint commitStartTime), (override)); + MOCK_METHOD(void, setCompositeEnd, (TimePoint compositeEndTime), (override)); MOCK_METHOD(void, setDisplays, (std::vector & displayIds), (override)); - MOCK_METHOD(void, setTotalFrameTargetWorkDuration, (int64_t targetDuration), (override)); + MOCK_METHOD(void, setTotalFrameTargetWorkDuration, (Duration targetDuration), (override)); }; } // namespace mock diff --git a/services/surfaceflinger/CompositionEngine/tests/OutputLayerTest.cpp b/services/surfaceflinger/CompositionEngine/tests/OutputLayerTest.cpp index 5290bd9cba4bf62148509d8d6e83943862568132..aa83883e95ed4bd2bab146d9fe3cdc4ce24cb243 100644 --- a/services/surfaceflinger/CompositionEngine/tests/OutputLayerTest.cpp +++ b/services/surfaceflinger/CompositionEngine/tests/OutputLayerTest.cpp @@ -42,6 +42,8 @@ namespace hal = android::hardware::graphics::composer::hal; using testing::_; using testing::InSequence; +using testing::Mock; +using testing::NiceMock; using testing::Return; using testing::ReturnRef; using testing::StrictMock; @@ -82,13 +84,13 @@ ui::Rotation toRotation(uint32_t rotationFlag) { struct OutputLayerTest : public testing::Test { struct OutputLayer final : public impl::OutputLayer { - OutputLayer(const compositionengine::Output& output, sp layerFE) + OutputLayer(const compositionengine::Output& output, compositionengine::LayerFE& layerFE) : mOutput(output), mLayerFE(layerFE) {} ~OutputLayer() override = default; // compositionengine::OutputLayer overrides const compositionengine::Output& getOutput() const override { return mOutput; } - compositionengine::LayerFE& getLayerFE() const override { return *mLayerFE; } + compositionengine::LayerFE& getLayerFE() const override { return mLayerFE; } const impl::OutputLayerCompositionState& getState() const override { return mState; } impl::OutputLayerCompositionState& editState() override { return mState; } @@ -96,21 +98,22 @@ struct OutputLayerTest : public testing::Test { void dumpState(std::string& out) const override { mState.dump(out); } const compositionengine::Output& mOutput; - sp mLayerFE; + compositionengine::LayerFE& mLayerFE; impl::OutputLayerCompositionState mState; }; OutputLayerTest() { - EXPECT_CALL(*mLayerFE, getDebugName()).WillRepeatedly(Return("Test LayerFE")); - EXPECT_CALL(mOutput, getName()).WillRepeatedly(ReturnRef(kOutputName)); + ON_CALL(mLayerFE, getDebugName()).WillByDefault(Return("Test LayerFE")); + ON_CALL(mOutput, getName()).WillByDefault(ReturnRef(kOutputName)); - EXPECT_CALL(*mLayerFE, getCompositionState()).WillRepeatedly(Return(&mLayerFEState)); - EXPECT_CALL(mOutput, getState()).WillRepeatedly(ReturnRef(mOutputState)); + ON_CALL(mLayerFE, getCompositionState()).WillByDefault(Return(&mLayerFEState)); + ON_CALL(mOutput, getState()).WillByDefault(ReturnRef(mOutputState)); } - compositionengine::mock::Output mOutput; - sp> mLayerFE = - sp>::make(); + NiceMock mOutput; + sp> mLayerFE_ = + sp>::make(); + NiceMock& mLayerFE = *mLayerFE_; OutputLayer mOutputLayer{mOutput, mLayerFE}; LayerFECompositionState mLayerFEState; @@ -530,7 +533,7 @@ TEST_F(OutputLayerTest, struct OutputLayerPartialMockForUpdateCompositionState : public impl::OutputLayer { OutputLayerPartialMockForUpdateCompositionState(const compositionengine::Output& output, - sp layerFE) + compositionengine::LayerFE& layerFE) : mOutput(output), mLayerFE(layerFE) {} // Mock everything called by updateCompositionState to simplify testing it. MOCK_CONST_METHOD1(calculateOutputSourceCrop, FloatRect(uint32_t)); @@ -539,7 +542,7 @@ struct OutputLayerPartialMockForUpdateCompositionState : public impl::OutputLaye // compositionengine::OutputLayer overrides const compositionengine::Output& getOutput() const override { return mOutput; } - compositionengine::LayerFE& getLayerFE() const override { return *mLayerFE; } + compositionengine::LayerFE& getLayerFE() const override { return mLayerFE; } const impl::OutputLayerCompositionState& getState() const override { return mState; } impl::OutputLayerCompositionState& editState() override { return mState; } @@ -547,7 +550,7 @@ struct OutputLayerPartialMockForUpdateCompositionState : public impl::OutputLaye MOCK_CONST_METHOD1(dumpState, void(std::string&)); const compositionengine::Output& mOutput; - sp mLayerFE; + compositionengine::LayerFE& mLayerFE; impl::OutputLayerCompositionState mState; }; @@ -588,7 +591,7 @@ public: }; TEST_F(OutputLayerUpdateCompositionStateTest, doesNothingIfNoFECompositionState) { - EXPECT_CALL(*mLayerFE, getCompositionState()).WillOnce(Return(nullptr)); + EXPECT_CALL(mLayerFE, getCompositionState()).WillOnce(Return(nullptr)); mOutputLayer.updateCompositionState(true, false, ui::Transform::RotationFlags::ROT_90); } @@ -774,7 +777,7 @@ struct OutputLayerWriteStateToHWCTest : public OutputLayerTest { static constexpr ui::Dataspace kOverrideDataspace = static_cast(72); static constexpr int kSupportedPerFrameMetadata = 101; static constexpr int kExpectedHwcSlot = 0; - static constexpr int kOverrideHwcSlot = impl::HwcBufferCache::FLATTENER_CACHING_SLOT; + static constexpr int kOverrideHwcSlot = impl::HwcBufferCache::kOverrideBufferSlot; static constexpr bool kLayerGenericMetadata1Mandatory = true; static constexpr bool kLayerGenericMetadata2Mandatory = true; static constexpr float kWhitePointNits = 200.f; @@ -823,7 +826,6 @@ struct OutputLayerWriteStateToHWCTest : public OutputLayerTest { mLayerFEState.hdrMetadata = kHdrMetadata; mLayerFEState.sidebandStream = NativeHandle::create(kSidebandStreamHandle, false); mLayerFEState.buffer = kBuffer; - mLayerFEState.bufferSlot = BufferQueue::INVALID_BUFFER_SLOT; mLayerFEState.acquireFence = kFence; mOutputState.displayBrightnessNits = kDisplayBrightnessNits; @@ -834,7 +836,6 @@ struct OutputLayerWriteStateToHWCTest : public OutputLayerTest { EXPECT_CALL(mDisplayColorProfile, getSupportedPerFrameMetadata()) .WillRepeatedly(Return(kSupportedPerFrameMetadata)); } - // Some tests may need to simulate unsupported HWC calls enum class SimulateUnsupported { None, ColorTransform }; @@ -953,13 +954,16 @@ const Region OutputLayerWriteStateToHWCTest::kOverrideSurfaceDamage{Rect{1026, 1 const HdrMetadata OutputLayerWriteStateToHWCTest::kHdrMetadata{{/* LightFlattenable */}, 1029}; native_handle_t* OutputLayerWriteStateToHWCTest::kSidebandStreamHandle = reinterpret_cast(1031); -const sp OutputLayerWriteStateToHWCTest::kBuffer; +const sp OutputLayerWriteStateToHWCTest::kBuffer = + sp::make(1, 2, PIXEL_FORMAT_RGBA_8888, + AHARDWAREBUFFER_USAGE_CPU_WRITE_OFTEN | + AHARDWAREBUFFER_USAGE_CPU_READ_OFTEN); const sp OutputLayerWriteStateToHWCTest::kOverrideBuffer = - new GraphicBuffer(4, 5, PIXEL_FORMAT_RGBA_8888, - AHARDWAREBUFFER_USAGE_CPU_WRITE_OFTEN | - AHARDWAREBUFFER_USAGE_CPU_READ_OFTEN); + sp::make(4, 5, PIXEL_FORMAT_RGBA_8888, + AHARDWAREBUFFER_USAGE_CPU_WRITE_OFTEN | + AHARDWAREBUFFER_USAGE_CPU_READ_OFTEN); const sp OutputLayerWriteStateToHWCTest::kFence; -const sp OutputLayerWriteStateToHWCTest::kOverrideFence = new Fence(); +const sp OutputLayerWriteStateToHWCTest::kOverrideFence = sp::make(); const std::string OutputLayerWriteStateToHWCTest::kLayerGenericMetadata1Key = "com.example.metadata.1"; const std::vector OutputLayerWriteStateToHWCTest::kLayerGenericMetadata1Value{{1, 2, 3}}; @@ -969,7 +973,7 @@ const std::vector OutputLayerWriteStateToHWCTest::kLayerGenericMetadata {4, 5, 6, 7}}; TEST_F(OutputLayerWriteStateToHWCTest, doesNothingIfNoFECompositionState) { - EXPECT_CALL(*mLayerFE, getCompositionState()).WillOnce(Return(nullptr)); + EXPECT_CALL(mLayerFE, getCompositionState()).WillOnce(Return(nullptr)); mOutputLayer.writeStateToHWC(/*includeGeometry*/ true, /*skipLayer*/ false, 0, /*zIsOverridden*/ false, /*isPeekingThrough*/ false); @@ -994,7 +998,7 @@ TEST_F(OutputLayerWriteStateToHWCTest, canSetAllState) { expectPerFrameCommonCalls(); expectNoSetCompositionTypeCall(); - EXPECT_CALL(*mLayerFE, hasRoundedCorners()).WillOnce(Return(false)); + EXPECT_CALL(mLayerFE, hasRoundedCorners()).WillOnce(Return(false)); mOutputLayer.writeStateToHWC(/*includeGeometry*/ true, /*skipLayer*/ false, 0, /*zIsOverridden*/ false, /*isPeekingThrough*/ false); @@ -1019,7 +1023,6 @@ TEST_F(OutputLayerWriteStateToHWCTest, canSetPerFrameStateForSolidColor) { mLayerFEState.compositionType = Composition::SOLID_COLOR; expectPerFrameCommonCalls(); - EXPECT_CALL(*mLayerFE, hasRoundedCorners()).WillOnce(Return(false)); // Setting the composition type should happen before setting the color. We // check this in this test only by setting up an testing::InSeqeuence @@ -1039,8 +1042,6 @@ TEST_F(OutputLayerWriteStateToHWCTest, canSetPerFrameStateForSideband) { expectSetSidebandHandleCall(); expectSetCompositionTypeCall(Composition::SIDEBAND); - EXPECT_CALL(*mLayerFE, hasRoundedCorners()).WillOnce(Return(false)); - mOutputLayer.writeStateToHWC(/*includeGeometry*/ false, /*skipLayer*/ false, 0, /*zIsOverridden*/ false, /*isPeekingThrough*/ false); } @@ -1052,8 +1053,6 @@ TEST_F(OutputLayerWriteStateToHWCTest, canSetPerFrameStateForCursor) { expectSetHdrMetadataAndBufferCalls(); expectSetCompositionTypeCall(Composition::CURSOR); - EXPECT_CALL(*mLayerFE, hasRoundedCorners()).WillOnce(Return(false)); - mOutputLayer.writeStateToHWC(/*includeGeometry*/ false, /*skipLayer*/ false, 0, /*zIsOverridden*/ false, /*isPeekingThrough*/ false); } @@ -1065,8 +1064,6 @@ TEST_F(OutputLayerWriteStateToHWCTest, canSetPerFrameStateForDevice) { expectSetHdrMetadataAndBufferCalls(); expectSetCompositionTypeCall(Composition::DEVICE); - EXPECT_CALL(*mLayerFE, hasRoundedCorners()).WillOnce(Return(false)); - mOutputLayer.writeStateToHWC(/*includeGeometry*/ false, /*skipLayer*/ false, 0, /*zIsOverridden*/ false, /*isPeekingThrough*/ false); } @@ -1080,8 +1077,6 @@ TEST_F(OutputLayerWriteStateToHWCTest, compositionTypeIsNotSetIfUnchanged) { expectSetColorCall(); expectNoSetCompositionTypeCall(); - EXPECT_CALL(*mLayerFE, hasRoundedCorners()).WillOnce(Return(false)); - mOutputLayer.writeStateToHWC(/*includeGeometry*/ false, /*skipLayer*/ false, 0, /*zIsOverridden*/ false, /*isPeekingThrough*/ false); } @@ -1120,8 +1115,6 @@ TEST_F(OutputLayerWriteStateToHWCTest, allStateIncludesMetadataIfPresent) { expectGenericLayerMetadataCalls(); expectSetCompositionTypeCall(Composition::DEVICE); - EXPECT_CALL(*mLayerFE, hasRoundedCorners()).WillOnce(Return(false)); - mOutputLayer.writeStateToHWC(/*includeGeometry*/ true, /*skipLayer*/ false, 0, /*zIsOverridden*/ false, /*isPeekingThrough*/ false); } @@ -1134,8 +1127,6 @@ TEST_F(OutputLayerWriteStateToHWCTest, perFrameStateDoesNotIncludeMetadataIfPres expectSetHdrMetadataAndBufferCalls(); expectSetCompositionTypeCall(Composition::DEVICE); - EXPECT_CALL(*mLayerFE, hasRoundedCorners()).WillOnce(Return(false)); - mOutputLayer.writeStateToHWC(/*includeGeometry*/ false, /*skipLayer*/ false, 0, /*zIsOverridden*/ false, /*isPeekingThrough*/ false); } @@ -1150,7 +1141,6 @@ TEST_F(OutputLayerWriteStateToHWCTest, overriddenSkipLayerDoesNotSendBuffer) { kOverrideSurfaceDamage, kOverrideLayerBrightness); expectSetHdrMetadataAndBufferCalls(); expectSetCompositionTypeCall(Composition::DEVICE); - EXPECT_CALL(*mLayerFE, hasRoundedCorners()).WillRepeatedly(Return(false)); mOutputLayer.writeStateToHWC(/*includeGeometry*/ true, /*skipLayer*/ true, 0, /*zIsOverridden*/ false, /*isPeekingThrough*/ false); @@ -1166,7 +1156,6 @@ TEST_F(OutputLayerWriteStateToHWCTest, overriddenSkipLayerForSolidColorDoesNotSe kOverrideSurfaceDamage, kOverrideLayerBrightness); expectSetHdrMetadataAndBufferCalls(); expectSetCompositionTypeCall(Composition::DEVICE); - EXPECT_CALL(*mLayerFE, hasRoundedCorners()).WillRepeatedly(Return(false)); mOutputLayer.writeStateToHWC(/*includeGeometry*/ true, /*skipLayer*/ true, 0, /*zIsOverridden*/ false, /*isPeekingThrough*/ false); @@ -1182,7 +1171,6 @@ TEST_F(OutputLayerWriteStateToHWCTest, includesOverrideInfoIfPresent) { kOverrideSurfaceDamage, kOverrideLayerBrightness); expectSetHdrMetadataAndBufferCalls(kOverrideHwcSlot, kOverrideBuffer, kOverrideFence); expectSetCompositionTypeCall(Composition::DEVICE); - EXPECT_CALL(*mLayerFE, hasRoundedCorners()).WillRepeatedly(Return(false)); mOutputLayer.writeStateToHWC(/*includeGeometry*/ true, /*skipLayer*/ false, 0, /*zIsOverridden*/ false, /*isPeekingThrough*/ false); @@ -1198,7 +1186,6 @@ TEST_F(OutputLayerWriteStateToHWCTest, includesOverrideInfoForSolidColorIfPresen kOverrideSurfaceDamage, kOverrideLayerBrightness); expectSetHdrMetadataAndBufferCalls(kOverrideHwcSlot, kOverrideBuffer, kOverrideFence); expectSetCompositionTypeCall(Composition::DEVICE); - EXPECT_CALL(*mLayerFE, hasRoundedCorners()).WillRepeatedly(Return(false)); mOutputLayer.writeStateToHWC(/*includeGeometry*/ true, /*skipLayer*/ false, 0, /*zIsOverridden*/ false, /*isPeekingThrough*/ false); @@ -1213,7 +1200,6 @@ TEST_F(OutputLayerWriteStateToHWCTest, previousOverriddenLayerSendsSurfaceDamage Region::INVALID_REGION); expectSetHdrMetadataAndBufferCalls(); expectSetCompositionTypeCall(Composition::DEVICE); - EXPECT_CALL(*mLayerFE, hasRoundedCorners()).WillRepeatedly(Return(false)); mOutputLayer.writeStateToHWC(/*includeGeometry*/ true, /*skipLayer*/ false, 0, /*zIsOverridden*/ false, /*isPeekingThrough*/ false); @@ -1230,7 +1216,6 @@ TEST_F(OutputLayerWriteStateToHWCTest, previousSkipLayerSendsUpdatedDeviceCompos Region::INVALID_REGION); expectSetHdrMetadataAndBufferCalls(); expectSetCompositionTypeCall(Composition::DEVICE); - EXPECT_CALL(*mLayerFE, hasRoundedCorners()).WillOnce(Return(false)); mOutputLayer.writeStateToHWC(/*includeGeometry*/ true, /*skipLayer*/ false, 0, /*zIsOverridden*/ false, /*isPeekingThrough*/ false); @@ -1248,22 +1233,20 @@ TEST_F(OutputLayerWriteStateToHWCTest, previousSkipLayerSendsUpdatedClientCompos Region::INVALID_REGION); expectSetHdrMetadataAndBufferCalls(); expectSetCompositionTypeCall(Composition::CLIENT); - EXPECT_CALL(*mLayerFE, hasRoundedCorners()).WillRepeatedly(Return(false)); mOutputLayer.writeStateToHWC(/*includeGeometry*/ true, /*skipLayer*/ false, 0, /*zIsOverridden*/ false, /*isPeekingThrough*/ false); } TEST_F(OutputLayerWriteStateToHWCTest, peekThroughChangesBlendMode) { - auto peekThroughLayerFE = sp::make(); - OutputLayer peekThroughLayer{mOutput, peekThroughLayerFE}; + auto peekThroughLayerFE = sp>::make(); + OutputLayer peekThroughLayer{mOutput, *peekThroughLayerFE}; mOutputLayer.mState.overrideInfo.peekThroughLayer = &peekThroughLayer; expectGeometryCommonCalls(kDisplayFrame, kSourceCrop, kBufferTransform, Hwc2::IComposerClient::BlendMode::PREMULTIPLIED); expectPerFrameCommonCalls(); - EXPECT_CALL(*mLayerFE, hasRoundedCorners()).WillOnce(Return(false)); mOutputLayer.writeStateToHWC(/*includeGeometry*/ true, /*skipLayer*/ false, 0, /*zIsOverridden*/ false, /*isPeekingThrough*/ false); @@ -1281,7 +1264,6 @@ TEST_F(OutputLayerWriteStateToHWCTest, isPeekingThroughSetsOverride) { TEST_F(OutputLayerWriteStateToHWCTest, zIsOverriddenSetsOverride) { expectGeometryCommonCalls(); expectPerFrameCommonCalls(); - EXPECT_CALL(*mLayerFE, hasRoundedCorners()).WillOnce(Return(false)); mOutputLayer.writeStateToHWC(/*includeGeometry*/ true, /*skipLayer*/ false, 0, /*zIsOverridden*/ true, /*isPeekingThrough*/ @@ -1292,7 +1274,7 @@ TEST_F(OutputLayerWriteStateToHWCTest, zIsOverriddenSetsOverride) { TEST_F(OutputLayerWriteStateToHWCTest, roundedCornersForceClientComposition) { expectGeometryCommonCalls(); expectPerFrameCommonCalls(); - EXPECT_CALL(*mLayerFE, hasRoundedCorners()).WillOnce(Return(true)); + EXPECT_CALL(mLayerFE, hasRoundedCorners()).WillOnce(Return(true)); expectSetCompositionTypeCall(Composition::CLIENT); mOutputLayer.writeStateToHWC(/*includeGeometry*/ true, /*skipLayer*/ false, 0, @@ -1304,7 +1286,7 @@ TEST_F(OutputLayerWriteStateToHWCTest, roundedCornersPeekingThroughAllowsDeviceC expectGeometryCommonCalls(); expectPerFrameCommonCalls(); expectSetHdrMetadataAndBufferCalls(); - EXPECT_CALL(*mLayerFE, hasRoundedCorners()).WillRepeatedly(Return(true)); + EXPECT_CALL(mLayerFE, hasRoundedCorners()).WillRepeatedly(Return(true)); expectSetCompositionTypeCall(Composition::DEVICE); mLayerFEState.compositionType = Composition::DEVICE; @@ -1323,7 +1305,6 @@ TEST_F(OutputLayerWriteStateToHWCTest, setBlockingRegion) { expectPerFrameCommonCalls(SimulateUnsupported::None, kDataspace, kOutputSpaceVisibleRegion, kSurfaceDamage, kLayerBrightness, blockingRegion); expectSetHdrMetadataAndBufferCalls(); - EXPECT_CALL(*mLayerFE, hasRoundedCorners()).WillRepeatedly(Return(false)); expectSetCompositionTypeCall(Composition::DISPLAY_DECORATION); mOutputLayer.writeStateToHWC(/*includeGeometry*/ true, /*skipLayer*/ false, 0, @@ -1331,6 +1312,105 @@ TEST_F(OutputLayerWriteStateToHWCTest, setBlockingRegion) { false); } +TEST_F(OutputLayerWriteStateToHWCTest, setCompositionTypeRefreshRateIndicator) { + mLayerFEState.compositionType = Composition::REFRESH_RATE_INDICATOR; + + expectGeometryCommonCalls(); + expectPerFrameCommonCalls(); + expectSetHdrMetadataAndBufferCalls(); + expectSetCompositionTypeCall(Composition::REFRESH_RATE_INDICATOR); + + mOutputLayer.writeStateToHWC(/*includeGeometry*/ true, /*skipLayer*/ false, 0, + /*zIsOverridden*/ false, /*isPeekingThrough*/ false); +} + +/* + * OutputLayer::uncacheBuffers + */ +struct OutputLayerUncacheBufferTest : public OutputLayerTest { + static const sp kBuffer1; + static const sp kBuffer2; + static const sp kBuffer3; + static const sp kFence; + + OutputLayerUncacheBufferTest() { + auto& outputLayerState = mOutputLayer.editState(); + outputLayerState.hwc = impl::OutputLayerCompositionState::Hwc(mHwcLayer_); + + mLayerFEState.compositionType = Composition::DEVICE; + mLayerFEState.acquireFence = kFence; + + ON_CALL(mOutput, getDisplayColorProfile()).WillByDefault(Return(&mDisplayColorProfile)); + } + + std::shared_ptr mHwcLayer_{std::make_shared>()}; + HWC2::mock::Layer& mHwcLayer = *mHwcLayer_; + NiceMock mDisplayColorProfile; +}; + +const sp OutputLayerUncacheBufferTest::kBuffer1 = + sp::make(1, 2, PIXEL_FORMAT_RGBA_8888, + AHARDWAREBUFFER_USAGE_CPU_WRITE_OFTEN | + AHARDWAREBUFFER_USAGE_CPU_READ_OFTEN); +const sp OutputLayerUncacheBufferTest::kBuffer2 = + sp::make(2, 3, PIXEL_FORMAT_RGBA_8888, + AHARDWAREBUFFER_USAGE_CPU_WRITE_OFTEN | + AHARDWAREBUFFER_USAGE_CPU_READ_OFTEN); +const sp OutputLayerUncacheBufferTest::kBuffer3 = + sp::make(4, 5, PIXEL_FORMAT_RGBA_8888, + AHARDWAREBUFFER_USAGE_CPU_WRITE_OFTEN | + AHARDWAREBUFFER_USAGE_CPU_READ_OFTEN); +const sp OutputLayerUncacheBufferTest::kFence = sp::make(); + +TEST_F(OutputLayerUncacheBufferTest, canUncacheAndReuseSlot) { + // Buffer1 is stored in slot 0 + mLayerFEState.buffer = kBuffer1; + EXPECT_CALL(mHwcLayer, setBuffer(/*slot*/ 0, kBuffer1, kFence)); + mOutputLayer.writeStateToHWC(/*includeGeometry*/ false, /*skipLayer*/ false, 0, + /*zIsOverridden*/ false, /*isPeekingThrough*/ false); + Mock::VerifyAndClearExpectations(&mHwcLayer); + + // Buffer2 is stored in slot 1 + mLayerFEState.buffer = kBuffer2; + EXPECT_CALL(mHwcLayer, setBuffer(/*slot*/ 1, kBuffer2, kFence)); + mOutputLayer.writeStateToHWC(/*includeGeometry*/ false, /*skipLayer*/ false, 0, + /*zIsOverridden*/ false, /*isPeekingThrough*/ false); + Mock::VerifyAndClearExpectations(&mHwcLayer); + + // Buffer3 is stored in slot 2 + mLayerFEState.buffer = kBuffer3; + EXPECT_CALL(mHwcLayer, setBuffer(/*slot*/ 2, kBuffer3, kFence)); + mOutputLayer.writeStateToHWC(/*includeGeometry*/ false, /*skipLayer*/ false, 0, + /*zIsOverridden*/ false, /*isPeekingThrough*/ false); + Mock::VerifyAndClearExpectations(&mHwcLayer); + + // Buffer2 becomes the active buffer again (with a nullptr) and reuses slot 1 + mLayerFEState.buffer = kBuffer2; + sp nullBuffer = nullptr; + EXPECT_CALL(mHwcLayer, setBuffer(/*slot*/ 1, nullBuffer, kFence)); + mOutputLayer.writeStateToHWC(/*includeGeometry*/ false, /*skipLayer*/ false, 0, + /*zIsOverridden*/ false, /*isPeekingThrough*/ false); + Mock::VerifyAndClearExpectations(&mHwcLayer); + + // Buffer slots are cleared + std::vector slotsToClear = {0, 2, 1}; // order doesn't matter + EXPECT_CALL(mHwcLayer, setBufferSlotsToClear(slotsToClear, /*activeBufferSlot*/ 1)); + // Uncache the active buffer in between other buffers to exercise correct algorithmic behavior. + mOutputLayer.uncacheBuffers({kBuffer1->getId(), kBuffer2->getId(), kBuffer3->getId()}); + Mock::VerifyAndClearExpectations(&mHwcLayer); + + // Buffer1 becomes active again, and rather than allocating a new slot, or re-using slot 0, + // the active buffer slot (slot 1 for Buffer2) is reused first, which allows HWC to free the + // memory for the active buffer. Note: slot 1 is different from the first and last buffer slot + // requested to be cleared in slotsToClear (slot 1), above, indicating that the algorithm + // correctly identifies the active buffer as the buffer in slot 1, despite ping-ponging. + mLayerFEState.buffer = kBuffer1; + EXPECT_CALL(mHwcLayer, setBuffer(/*slot*/ 1, kBuffer1, kFence)); + mOutputLayer.writeStateToHWC(/*includeGeometry*/ false, /*skipLayer*/ false, 0, + /*zIsOverridden*/ false, /*isPeekingThrough*/ false); + Mock::VerifyAndClearExpectations(&mHwcLayer); +} + /* * OutputLayer::writeCursorPositionToHWC() */ @@ -1359,7 +1439,7 @@ const Rect OutputLayerWriteCursorPositionToHWCTest::kDefaultDisplayViewport{0, 0 const Rect OutputLayerWriteCursorPositionToHWCTest::kDefaultCursorFrame{1, 2, 3, 4}; TEST_F(OutputLayerWriteCursorPositionToHWCTest, doesNothingIfNoFECompositionState) { - EXPECT_CALL(*mLayerFE, getCompositionState()).WillOnce(Return(nullptr)); + EXPECT_CALL(mLayerFE, getCompositionState()).WillOnce(Return(nullptr)); mOutputLayer.writeCursorPositionToHWC(); } diff --git a/services/surfaceflinger/CompositionEngine/tests/OutputTest.cpp b/services/surfaceflinger/CompositionEngine/tests/OutputTest.cpp index cf1289031022522ef64e9edf0752f96c29441eb1..9e0e7b5a53eb2a417b52ce64e36ed3586058484d 100644 --- a/services/surfaceflinger/CompositionEngine/tests/OutputTest.cpp +++ b/services/surfaceflinger/CompositionEngine/tests/OutputTest.cpp @@ -39,7 +39,6 @@ #include "CallOrderStateMachineHelper.h" #include "MockHWC2.h" #include "RegionMatcher.h" -#include "TestUtils.h" namespace android::compositionengine { namespace { @@ -293,7 +292,7 @@ TEST_F(OutputTest, setLayerCachingEnabled_disablesCachingAndResetsOverrideInfo) InjectedLayer layer; layer.outputLayerState.overrideInfo.buffer = std::make_shared< renderengine::impl:: - ExternalTexture>(new GraphicBuffer(), renderEngine, + ExternalTexture>(sp::make(), renderEngine, renderengine::impl::ExternalTexture::Usage::READABLE | renderengine::impl::ExternalTexture::Usage::WRITEABLE); injectOutputLayer(layer); @@ -758,56 +757,6 @@ TEST_F(OutputSetReleasedLayersTest, setReleasedLayersTakesGivenLayers) { ASSERT_EQ(layer3FE.get(), setLayers[2].promote().get()); } -/* - * Output::updateLayerStateFromFE() - */ - -using OutputUpdateLayerStateFromFETest = OutputTest; - -TEST_F(OutputUpdateLayerStateFromFETest, handlesNoOutputLayerCase) { - CompositionRefreshArgs refreshArgs; - - mOutput->updateLayerStateFromFE(refreshArgs); -} - -TEST_F(OutputUpdateLayerStateFromFETest, preparesContentStateForAllContainedLayers) { - InjectedLayer layer1; - InjectedLayer layer2; - InjectedLayer layer3; - - EXPECT_CALL(*layer1.layerFE.get(), prepareCompositionState(LayerFE::StateSubset::Content)); - EXPECT_CALL(*layer2.layerFE.get(), prepareCompositionState(LayerFE::StateSubset::Content)); - EXPECT_CALL(*layer3.layerFE.get(), prepareCompositionState(LayerFE::StateSubset::Content)); - - injectOutputLayer(layer1); - injectOutputLayer(layer2); - injectOutputLayer(layer3); - - CompositionRefreshArgs refreshArgs; - refreshArgs.updatingGeometryThisFrame = false; - - mOutput->updateLayerStateFromFE(refreshArgs); -} - -TEST_F(OutputUpdateLayerStateFromFETest, preparesGeometryAndContentStateForAllContainedLayers) { - InjectedLayer layer1; - InjectedLayer layer2; - InjectedLayer layer3; - - EXPECT_CALL(*layer1.layerFE, prepareCompositionState(LayerFE::StateSubset::GeometryAndContent)); - EXPECT_CALL(*layer2.layerFE, prepareCompositionState(LayerFE::StateSubset::GeometryAndContent)); - EXPECT_CALL(*layer3.layerFE, prepareCompositionState(LayerFE::StateSubset::GeometryAndContent)); - - injectOutputLayer(layer1); - injectOutputLayer(layer2); - injectOutputLayer(layer3); - - CompositionRefreshArgs refreshArgs; - refreshArgs.updatingGeometryThisFrame = true; - - mOutput->updateLayerStateFromFE(refreshArgs); -} - /* * Output::updateAndWriteCompositionState() */ @@ -850,20 +799,17 @@ TEST_F(OutputUpdateAndWriteCompositionStateTest, updatesLayerContentForAllLayers EXPECT_CALL(*layer1.outputLayer, writeStateToHWC(/*includeGeometry*/ false, /*skipLayer*/ false, z++, /*zIsOverridden*/ false, /*isPeekingThrough*/ false)); - EXPECT_CALL(*layer1.outputLayer, requiresClientComposition()) - .WillRepeatedly(Return(false)); + EXPECT_CALL(*layer1.outputLayer, requiresClientComposition()).WillRepeatedly(Return(false)); EXPECT_CALL(*layer2.outputLayer, updateCompositionState(false, false, ui::Transform::ROT_180)); EXPECT_CALL(*layer2.outputLayer, writeStateToHWC(/*includeGeometry*/ false, /*skipLayer*/ false, z++, /*zIsOverridden*/ false, /*isPeekingThrough*/ false)); - EXPECT_CALL(*layer2.outputLayer, requiresClientComposition()) - .WillRepeatedly(Return(false)); + EXPECT_CALL(*layer2.outputLayer, requiresClientComposition()).WillRepeatedly(Return(false)); EXPECT_CALL(*layer3.outputLayer, updateCompositionState(false, false, ui::Transform::ROT_180)); EXPECT_CALL(*layer3.outputLayer, writeStateToHWC(/*includeGeometry*/ false, /*skipLayer*/ false, z++, /*zIsOverridden*/ false, /*isPeekingThrough*/ false)); - EXPECT_CALL(*layer3.outputLayer, requiresClientComposition()) - .WillRepeatedly(Return(false)); + EXPECT_CALL(*layer3.outputLayer, requiresClientComposition()).WillRepeatedly(Return(false)); injectOutputLayer(layer1); injectOutputLayer(layer2); @@ -890,20 +836,17 @@ TEST_F(OutputUpdateAndWriteCompositionStateTest, updatesLayerGeometryAndContentF EXPECT_CALL(*layer1.outputLayer, writeStateToHWC(/*includeGeometry*/ true, /*skipLayer*/ false, z++, /*zIsOverridden*/ false, /*isPeekingThrough*/ false)); - EXPECT_CALL(*layer1.outputLayer, requiresClientComposition()) - .WillRepeatedly(Return(false)); + EXPECT_CALL(*layer1.outputLayer, requiresClientComposition()).WillRepeatedly(Return(false)); EXPECT_CALL(*layer2.outputLayer, updateCompositionState(true, false, ui::Transform::ROT_0)); EXPECT_CALL(*layer2.outputLayer, writeStateToHWC(/*includeGeometry*/ true, /*skipLayer*/ false, z++, /*zIsOverridden*/ false, /*isPeekingThrough*/ false)); - EXPECT_CALL(*layer2.outputLayer, requiresClientComposition()) - .WillRepeatedly(Return(false)); + EXPECT_CALL(*layer2.outputLayer, requiresClientComposition()).WillRepeatedly(Return(false)); EXPECT_CALL(*layer3.outputLayer, updateCompositionState(true, false, ui::Transform::ROT_0)); EXPECT_CALL(*layer3.outputLayer, writeStateToHWC(/*includeGeometry*/ true, /*skipLayer*/ false, z++, /*zIsOverridden*/ false, /*isPeekingThrough*/ false)); - EXPECT_CALL(*layer3.outputLayer, requiresClientComposition()) - .WillRepeatedly(Return(false)); + EXPECT_CALL(*layer3.outputLayer, requiresClientComposition()).WillRepeatedly(Return(false)); injectOutputLayer(layer1); injectOutputLayer(layer2); @@ -929,20 +872,17 @@ TEST_F(OutputUpdateAndWriteCompositionStateTest, forcesClientCompositionForAllLa EXPECT_CALL(*layer1.outputLayer, writeStateToHWC(/*includeGeometry*/ false, /*skipLayer*/ false, z++, /*zIsOverridden*/ false, /*isPeekingThrough*/ false)); - EXPECT_CALL(*layer1.outputLayer, requiresClientComposition()) - .WillRepeatedly(Return(false)); + EXPECT_CALL(*layer1.outputLayer, requiresClientComposition()).WillRepeatedly(Return(false)); EXPECT_CALL(*layer2.outputLayer, updateCompositionState(false, true, ui::Transform::ROT_0)); EXPECT_CALL(*layer2.outputLayer, writeStateToHWC(/*includeGeometry*/ false, /*skipLayer*/ false, z++, /*zIsOverridden*/ false, /*isPeekingThrough*/ false)); - EXPECT_CALL(*layer2.outputLayer, requiresClientComposition()) - .WillRepeatedly(Return(false)); + EXPECT_CALL(*layer2.outputLayer, requiresClientComposition()).WillRepeatedly(Return(false)); EXPECT_CALL(*layer3.outputLayer, updateCompositionState(false, true, ui::Transform::ROT_0)); EXPECT_CALL(*layer3.outputLayer, writeStateToHWC(/*includeGeometry*/ false, /*skipLayer*/ false, z++, /*zIsOverridden*/ false, /*isPeekingThrough*/ false)); - EXPECT_CALL(*layer3.outputLayer, requiresClientComposition()) - .WillRepeatedly(Return(false)); + EXPECT_CALL(*layer3.outputLayer, requiresClientComposition()).WillRepeatedly(Return(false)); injectOutputLayer(layer1); injectOutputLayer(layer2); @@ -968,8 +908,7 @@ TEST_F(OutputUpdateAndWriteCompositionStateTest, peekThroughLayerChangesOrder) { InSequence seq; EXPECT_CALL(*layer0.outputLayer, updateCompositionState(true, false, ui::Transform::ROT_0)); EXPECT_CALL(*layer1.outputLayer, updateCompositionState(true, false, ui::Transform::ROT_0)); - EXPECT_CALL(*layer1.outputLayer, requiresClientComposition()) - .WillRepeatedly(Return(false)); + EXPECT_CALL(*layer1.outputLayer, requiresClientComposition()).WillRepeatedly(Return(false)); EXPECT_CALL(*layer2.outputLayer, updateCompositionState(true, false, ui::Transform::ROT_0)); EXPECT_CALL(*layer3.outputLayer, updateCompositionState(true, false, ui::Transform::ROT_0)); @@ -977,9 +916,7 @@ TEST_F(OutputUpdateAndWriteCompositionStateTest, peekThroughLayerChangesOrder) { EXPECT_CALL(*layer0.outputLayer, writeStateToHWC(/*includeGeometry*/ true, /*skipLayer*/ false, z++, /*zIsOverridden*/ false, /*isPeekingThrough*/ false)); - EXPECT_CALL(*layer0.outputLayer, requiresClientComposition()) - .WillRepeatedly(Return(false)); - + EXPECT_CALL(*layer0.outputLayer, requiresClientComposition()).WillRepeatedly(Return(false)); // After calling planComposition (which clears overrideInfo), this test sets // layer3 to be the peekThroughLayer for layer1 and layer2. As a result, it @@ -989,18 +926,15 @@ TEST_F(OutputUpdateAndWriteCompositionStateTest, peekThroughLayerChangesOrder) { writeStateToHWC(/*includeGeometry*/ true, /*skipLayer*/ false, z++, /*zIsOverridden*/ true, /*isPeekingThrough*/ true)); - EXPECT_CALL(*layer3.outputLayer, requiresClientComposition()) - .WillRepeatedly(Return(false)); + EXPECT_CALL(*layer3.outputLayer, requiresClientComposition()).WillRepeatedly(Return(false)); EXPECT_CALL(*layer1.outputLayer, writeStateToHWC(/*includeGeometry*/ true, /*skipLayer*/ false, z++, /*zIsOverridden*/ true, /*isPeekingThrough*/ false)); - EXPECT_CALL(*layer1.outputLayer, requiresClientComposition()) - .WillRepeatedly(Return(false)); + EXPECT_CALL(*layer1.outputLayer, requiresClientComposition()).WillRepeatedly(Return(false)); EXPECT_CALL(*layer2.outputLayer, writeStateToHWC(/*includeGeometry*/ true, /*skipLayer*/ true, z++, /*zIsOverridden*/ true, /*isPeekingThrough*/ false)); - EXPECT_CALL(*layer2.outputLayer, requiresClientComposition()) - .WillRepeatedly(Return(false)); + EXPECT_CALL(*layer2.outputLayer, requiresClientComposition()).WillRepeatedly(Return(false)); injectOutputLayer(layer0); injectOutputLayer(layer1); @@ -1017,7 +951,7 @@ TEST_F(OutputUpdateAndWriteCompositionStateTest, peekThroughLayerChangesOrder) { std::shared_ptr buffer = std::make_shared< renderengine::impl:: - ExternalTexture>(new GraphicBuffer(), renderEngine, + ExternalTexture>(sp::make(), renderEngine, renderengine::impl::ExternalTexture::Usage::READABLE | renderengine::impl::ExternalTexture::Usage::WRITEABLE); layer1.outputLayerState.overrideInfo.buffer = buffer; @@ -1101,10 +1035,10 @@ struct OutputPrepareFrameAsyncTest : public testing::Test { MOCK_METHOD1( chooseCompositionStrategyAsync, std::future(std::optional*)); - MOCK_METHOD4(composeSurfaces, - std::optional( - const Region&, const compositionengine::CompositionRefreshArgs&, - std::shared_ptr, base::unique_fd&)); + MOCK_METHOD3(composeSurfaces, + std::optional(const Region&, + std::shared_ptr, + base::unique_fd&)); MOCK_METHOD0(resetCompositionStrategy, void()); }; @@ -1138,9 +1072,9 @@ TEST_F(OutputPrepareFrameAsyncTest, delegatesToChooseCompositionStrategyAndRende EXPECT_CALL(mOutput, chooseCompositionStrategyAsync(_)) .WillOnce(DoAll(SetArgPointee<0>(mOutput.editState().previousDeviceRequestedChanges), Return(ByMove(p.get_future())))); - EXPECT_CALL(mOutput, composeSurfaces(_, Ref(mRefreshArgs), _, _)); + EXPECT_CALL(mOutput, composeSurfaces(_, _, _)); - impl::GpuCompositionResult result = mOutput.prepareFrameAsync(mRefreshArgs); + impl::GpuCompositionResult result = mOutput.prepareFrameAsync(); EXPECT_EQ(mOutput.getState().strategyPrediction, CompositionStrategyPredictionState::SUCCESS); EXPECT_FALSE(result.bufferAvailable()); } @@ -1163,7 +1097,7 @@ TEST_F(OutputPrepareFrameAsyncTest, skipCompositionOnDequeueFailure) { .WillOnce(DoAll(SetArgPointee<0>(mOutput.editState().previousDeviceRequestedChanges), Return(ByMove(p.get_future())))); - impl::GpuCompositionResult result = mOutput.prepareFrameAsync(mRefreshArgs); + impl::GpuCompositionResult result = mOutput.prepareFrameAsync(); EXPECT_EQ(mOutput.getState().strategyPrediction, CompositionStrategyPredictionState::FAIL); EXPECT_FALSE(result.bufferAvailable()); } @@ -1191,9 +1125,9 @@ TEST_F(OutputPrepareFrameAsyncTest, chooseCompositionStrategyFailureCallsPrepare EXPECT_CALL(mOutput, chooseCompositionStrategyAsync(_)).WillOnce([&] { return p.get_future(); }); - EXPECT_CALL(mOutput, composeSurfaces(_, Ref(mRefreshArgs), _, _)); + EXPECT_CALL(mOutput, composeSurfaces(_, _, _)); - impl::GpuCompositionResult result = mOutput.prepareFrameAsync(mRefreshArgs); + impl::GpuCompositionResult result = mOutput.prepareFrameAsync(); EXPECT_EQ(mOutput.getState().strategyPrediction, CompositionStrategyPredictionState::FAIL); EXPECT_TRUE(result.bufferAvailable()); } @@ -1223,9 +1157,9 @@ TEST_F(OutputPrepareFrameAsyncTest, predictionMiss) { EXPECT_CALL(mOutput, chooseCompositionStrategyAsync(_)).WillOnce([&] { return p.get_future(); }); - EXPECT_CALL(mOutput, composeSurfaces(_, Ref(mRefreshArgs), _, _)); + EXPECT_CALL(mOutput, composeSurfaces(_, _, _)); - impl::GpuCompositionResult result = mOutput.prepareFrameAsync(mRefreshArgs); + impl::GpuCompositionResult result = mOutput.prepareFrameAsync(); EXPECT_EQ(mOutput.getState().strategyPrediction, CompositionStrategyPredictionState::FAIL); EXPECT_TRUE(result.bufferAvailable()); } @@ -1243,14 +1177,49 @@ struct OutputPrepareTest : public testing::Test { compositionengine::LayerFESet&)); }; + OutputPrepareTest() { + EXPECT_CALL(mOutput, getOutputLayerCount()).WillRepeatedly(Return(2u)); + EXPECT_CALL(mOutput, getOutputLayerOrderedByZByIndex(0)) + .WillRepeatedly(Return(&mLayer1.outputLayer)); + EXPECT_CALL(mOutput, getOutputLayerOrderedByZByIndex(1)) + .WillRepeatedly(Return(&mLayer2.outputLayer)); + + mRefreshArgs.layers.push_back(mLayer1.layerFE); + mRefreshArgs.layers.push_back(mLayer2.layerFE); + } + + struct Layer { + StrictMock outputLayer; + sp> layerFE = sp>::make(); + }; + StrictMock mOutput; CompositionRefreshArgs mRefreshArgs; LayerFESet mGeomSnapshots; + Layer mLayer1; + Layer mLayer2; }; -TEST_F(OutputPrepareTest, justInvokesRebuildLayerStacks) { +TEST_F(OutputPrepareTest, callsUncacheBuffersOnEachOutputLayerAndThenRebuildsLayerStacks) { + InSequence seq; + + mRefreshArgs.bufferIdsToUncache = {1, 3, 5}; + + EXPECT_CALL(mOutput, rebuildLayerStacks(Ref(mRefreshArgs), Ref(mGeomSnapshots))); + EXPECT_CALL(mLayer1.outputLayer, uncacheBuffers(Ref(mRefreshArgs.bufferIdsToUncache))); + EXPECT_CALL(mLayer2.outputLayer, uncacheBuffers(Ref(mRefreshArgs.bufferIdsToUncache))); + + mOutput.prepare(mRefreshArgs, mGeomSnapshots); +} + +TEST_F(OutputPrepareTest, skipsUncacheBuffersIfEmptyAndThenRebuildsLayerStacks) { InSequence seq; + + mRefreshArgs.bufferIdsToUncache = {}; + EXPECT_CALL(mOutput, rebuildLayerStacks(Ref(mRefreshArgs), Ref(mGeomSnapshots))); + EXPECT_CALL(mLayer1.outputLayer, uncacheBuffers(_)).Times(0); + EXPECT_CALL(mLayer2.outputLayer, uncacheBuffers(_)).Times(0); mOutput.prepare(mRefreshArgs, mGeomSnapshots); } @@ -1537,9 +1506,6 @@ const Region OutputEnsureOutputLayerIfVisibleTest::kTransparentRegionHintNegativ TEST_F(OutputEnsureOutputLayerIfVisibleTest, performsGeomLatchBeforeCheckingIfLayerIncluded) { EXPECT_CALL(mOutput, includesLayer(sp(mLayer.layerFE))).WillOnce(Return(false)); - EXPECT_CALL(*mLayer.layerFE, - prepareCompositionState(compositionengine::LayerFE::StateSubset::BasicGeometry)); - mGeomSnapshots.clear(); ensureOutputLayerIfVisible(); @@ -2054,11 +2020,9 @@ struct OutputPresentTest : public testing::Test { MOCK_METHOD1(setColorTransform, void(const compositionengine::CompositionRefreshArgs&)); MOCK_METHOD0(beginFrame, void()); MOCK_METHOD0(prepareFrame, void()); - MOCK_METHOD1(prepareFrameAsync, GpuCompositionResult(const CompositionRefreshArgs&)); + MOCK_METHOD0(prepareFrameAsync, GpuCompositionResult()); MOCK_METHOD1(devOptRepaintFlash, void(const compositionengine::CompositionRefreshArgs&)); - MOCK_METHOD2(finishFrame, - void(const compositionengine::CompositionRefreshArgs&, - GpuCompositionResult&&)); + MOCK_METHOD1(finishFrame, void(GpuCompositionResult&&)); MOCK_METHOD0(postFramebuffer, void()); MOCK_METHOD1(renderCachedSets, void(const compositionengine::CompositionRefreshArgs&)); MOCK_METHOD1(canPredictCompositionStrategy, bool(const CompositionRefreshArgs&)); @@ -2080,7 +2044,7 @@ TEST_F(OutputPresentTest, justInvokesChildFunctionsInSequence) { EXPECT_CALL(mOutput, canPredictCompositionStrategy(Ref(args))).WillOnce(Return(false)); EXPECT_CALL(mOutput, prepareFrame()); EXPECT_CALL(mOutput, devOptRepaintFlash(Ref(args))); - EXPECT_CALL(mOutput, finishFrame(Ref(args), _)); + EXPECT_CALL(mOutput, finishFrame(_)); EXPECT_CALL(mOutput, postFramebuffer()); EXPECT_CALL(mOutput, renderCachedSets(Ref(args))); @@ -2098,9 +2062,9 @@ TEST_F(OutputPresentTest, predictingCompositionStrategyInvokesPrepareFrameAsync) EXPECT_CALL(mOutput, setColorTransform(Ref(args))); EXPECT_CALL(mOutput, beginFrame()); EXPECT_CALL(mOutput, canPredictCompositionStrategy(Ref(args))).WillOnce(Return(true)); - EXPECT_CALL(mOutput, prepareFrameAsync(Ref(args))); + EXPECT_CALL(mOutput, prepareFrameAsync()); EXPECT_CALL(mOutput, devOptRepaintFlash(Ref(args))); - EXPECT_CALL(mOutput, finishFrame(Ref(args), _)); + EXPECT_CALL(mOutput, finishFrame(_)); EXPECT_CALL(mOutput, postFramebuffer()); EXPECT_CALL(mOutput, renderCachedSets(Ref(args))); @@ -2135,6 +2099,7 @@ struct OutputUpdateColorProfileTest : public testing::Test { mOutput.setDisplayColorProfileForTest( std::unique_ptr(mDisplayColorProfile)); mOutput.setRenderSurfaceForTest(std::unique_ptr(mRenderSurface)); + mOutput.editState().isEnabled = true; EXPECT_CALL(mOutput, getOutputLayerOrderedByZByIndex(0)) .WillRepeatedly(Return(&mLayer1.mOutputLayer)); @@ -2998,10 +2963,10 @@ struct OutputDevOptRepaintFlashTest : public testing::Test { // Sets up the helper functions called by the function under test to use // mock implementations. MOCK_METHOD(Region, getDirtyRegion, (), (const)); - MOCK_METHOD4(composeSurfaces, - std::optional( - const Region&, const compositionengine::CompositionRefreshArgs&, - std::shared_ptr, base::unique_fd&)); + MOCK_METHOD3(composeSurfaces, + std::optional(const Region&, + std::shared_ptr, + base::unique_fd&)); MOCK_METHOD0(postFramebuffer, void()); MOCK_METHOD0(prepareFrame, void()); MOCK_METHOD0(updateProtectedContentState, void()); @@ -3065,7 +3030,7 @@ TEST_F(OutputDevOptRepaintFlashTest, alsoComposesSurfacesAndQueuesABufferIfDirty EXPECT_CALL(mOutput, getDirtyRegion()).WillOnce(Return(kNotEmptyRegion)); EXPECT_CALL(mOutput, updateProtectedContentState()); EXPECT_CALL(mOutput, dequeueRenderBuffer(_, _)); - EXPECT_CALL(mOutput, composeSurfaces(RegionEq(kNotEmptyRegion), Ref(mRefreshArgs), _, _)); + EXPECT_CALL(mOutput, composeSurfaces(RegionEq(kNotEmptyRegion), _, _)); EXPECT_CALL(*mRenderSurface, queueBuffer(_)); EXPECT_CALL(mOutput, postFramebuffer()); EXPECT_CALL(mOutput, prepareFrame()); @@ -3081,10 +3046,10 @@ struct OutputFinishFrameTest : public testing::Test { struct OutputPartialMock : public OutputPartialMockBase { // Sets up the helper functions called by the function under test to use // mock implementations. - MOCK_METHOD4(composeSurfaces, - std::optional( - const Region&, const compositionengine::CompositionRefreshArgs&, - std::shared_ptr, base::unique_fd&)); + MOCK_METHOD3(composeSurfaces, + std::optional(const Region&, + std::shared_ptr, + base::unique_fd&)); MOCK_METHOD0(postFramebuffer, void()); MOCK_METHOD0(updateProtectedContentState, void()); MOCK_METHOD2(dequeueRenderBuffer, @@ -3100,24 +3065,23 @@ struct OutputFinishFrameTest : public testing::Test { StrictMock mOutput; mock::DisplayColorProfile* mDisplayColorProfile = new StrictMock(); mock::RenderSurface* mRenderSurface = new StrictMock(); - CompositionRefreshArgs mRefreshArgs; }; TEST_F(OutputFinishFrameTest, ifNotEnabledDoesNothing) { mOutput.mState.isEnabled = false; impl::GpuCompositionResult result; - mOutput.finishFrame(mRefreshArgs, std::move(result)); + mOutput.finishFrame(std::move(result)); } TEST_F(OutputFinishFrameTest, takesEarlyOutifComposeSurfacesReturnsNoFence) { mOutput.mState.isEnabled = true; EXPECT_CALL(mOutput, updateProtectedContentState()); EXPECT_CALL(mOutput, dequeueRenderBuffer(_, _)).WillOnce(Return(true)); - EXPECT_CALL(mOutput, composeSurfaces(RegionEq(Region::INVALID_REGION), _, _, _)); + EXPECT_CALL(mOutput, composeSurfaces(RegionEq(Region::INVALID_REGION), _, _)); impl::GpuCompositionResult result; - mOutput.finishFrame(mRefreshArgs, std::move(result)); + mOutput.finishFrame(std::move(result)); } TEST_F(OutputFinishFrameTest, queuesBufferIfComposeSurfacesReturnsAFence) { @@ -3126,12 +3090,12 @@ TEST_F(OutputFinishFrameTest, queuesBufferIfComposeSurfacesReturnsAFence) { InSequence seq; EXPECT_CALL(mOutput, updateProtectedContentState()); EXPECT_CALL(mOutput, dequeueRenderBuffer(_, _)).WillOnce(Return(true)); - EXPECT_CALL(mOutput, composeSurfaces(RegionEq(Region::INVALID_REGION), _, _, _)) + EXPECT_CALL(mOutput, composeSurfaces(RegionEq(Region::INVALID_REGION), _, _)) .WillOnce(Return(ByMove(base::unique_fd()))); EXPECT_CALL(*mRenderSurface, queueBuffer(_)); impl::GpuCompositionResult result; - mOutput.finishFrame(mRefreshArgs, std::move(result)); + mOutput.finishFrame(std::move(result)); } TEST_F(OutputFinishFrameTest, predictionSucceeded) { @@ -3141,7 +3105,7 @@ TEST_F(OutputFinishFrameTest, predictionSucceeded) { EXPECT_CALL(*mRenderSurface, queueBuffer(_)); impl::GpuCompositionResult result; - mOutput.finishFrame(mRefreshArgs, std::move(result)); + mOutput.finishFrame(std::move(result)); } TEST_F(OutputFinishFrameTest, predictionFailedAndBufferIsReused) { @@ -3157,11 +3121,11 @@ TEST_F(OutputFinishFrameTest, predictionFailedAndBufferIsReused) { 2); EXPECT_CALL(mOutput, - composeSurfaces(RegionEq(Region::INVALID_REGION), _, result.buffer, + composeSurfaces(RegionEq(Region::INVALID_REGION), result.buffer, Eq(ByRef(result.fence)))) .WillOnce(Return(ByMove(base::unique_fd()))); EXPECT_CALL(*mRenderSurface, queueBuffer(_)); - mOutput.finishFrame(mRefreshArgs, std::move(result)); + mOutput.finishFrame(std::move(result)); } /* @@ -3227,7 +3191,6 @@ TEST_F(OutputPostFramebufferTest, ifEnabledMustFlipThenPresentThenSendPresentCom // setup below are satisfied in the specific order. InSequence seq; - EXPECT_CALL(*mRenderSurface, flip()); EXPECT_CALL(mOutput, presentAndGetFrameFences()).WillOnce(Return(frameFences)); EXPECT_CALL(*mRenderSurface, onPresentDisplayCompleted()); @@ -3250,7 +3213,6 @@ TEST_F(OutputPostFramebufferTest, releaseFencesAreSentToLayerFE) { frameFences.layerFences.emplace(&mLayer2.hwc2Layer, layer2Fence); frameFences.layerFences.emplace(&mLayer3.hwc2Layer, layer3Fence); - EXPECT_CALL(*mRenderSurface, flip()); EXPECT_CALL(mOutput, presentAndGetFrameFences()).WillOnce(Return(frameFences)); EXPECT_CALL(*mRenderSurface, onPresentDisplayCompleted()); @@ -3258,16 +3220,19 @@ TEST_F(OutputPostFramebufferTest, releaseFencesAreSentToLayerFE) { // 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, onLayerDisplayed(_)) - .WillOnce([&layer1Fence](ftl::SharedFuture futureFenceResult) { + EXPECT_CALL(*mLayer1.layerFE, onLayerDisplayed(_, _)) + .WillOnce([&layer1Fence](ftl::SharedFuture futureFenceResult, + ui::LayerStack) { EXPECT_EQ(FenceResult(layer1Fence), futureFenceResult.get()); }); - EXPECT_CALL(*mLayer2.layerFE, onLayerDisplayed(_)) - .WillOnce([&layer2Fence](ftl::SharedFuture futureFenceResult) { + EXPECT_CALL(*mLayer2.layerFE, onLayerDisplayed(_, _)) + .WillOnce([&layer2Fence](ftl::SharedFuture futureFenceResult, + ui::LayerStack) { EXPECT_EQ(FenceResult(layer2Fence), futureFenceResult.get()); }); - EXPECT_CALL(*mLayer3.layerFE, onLayerDisplayed(_)) - .WillOnce([&layer3Fence](ftl::SharedFuture futureFenceResult) { + EXPECT_CALL(*mLayer3.layerFE, onLayerDisplayed(_, _)) + .WillOnce([&layer3Fence](ftl::SharedFuture futureFenceResult, + ui::LayerStack) { EXPECT_EQ(FenceResult(layer3Fence), futureFenceResult.get()); }); @@ -3284,7 +3249,6 @@ TEST_F(OutputPostFramebufferTest, releaseFencesIncludeClientTargetAcquireFence) frameFences.layerFences.emplace(&mLayer2.hwc2Layer, sp::make()); frameFences.layerFences.emplace(&mLayer3.hwc2Layer, sp::make()); - EXPECT_CALL(*mRenderSurface, flip()); EXPECT_CALL(mOutput, presentAndGetFrameFences()).WillOnce(Return(frameFences)); EXPECT_CALL(*mRenderSurface, onPresentDisplayCompleted()); @@ -3320,21 +3284,23 @@ TEST_F(OutputPostFramebufferTest, releasedLayersSentPresentFence) { Output::FrameFences frameFences; frameFences.presentFence = presentFence; - EXPECT_CALL(*mRenderSurface, flip()); EXPECT_CALL(mOutput, presentAndGetFrameFences()).WillOnce(Return(frameFences)); EXPECT_CALL(*mRenderSurface, onPresentDisplayCompleted()); // Each released layer should be given the presentFence. - EXPECT_CALL(*releasedLayer1, onLayerDisplayed(_)) - .WillOnce([&presentFence](ftl::SharedFuture futureFenceResult) { + EXPECT_CALL(*releasedLayer1, onLayerDisplayed(_, _)) + .WillOnce([&presentFence](ftl::SharedFuture futureFenceResult, + ui::LayerStack) { EXPECT_EQ(FenceResult(presentFence), futureFenceResult.get()); }); - EXPECT_CALL(*releasedLayer2, onLayerDisplayed(_)) - .WillOnce([&presentFence](ftl::SharedFuture futureFenceResult) { + EXPECT_CALL(*releasedLayer2, onLayerDisplayed(_, _)) + .WillOnce([&presentFence](ftl::SharedFuture futureFenceResult, + ui::LayerStack) { EXPECT_EQ(FenceResult(presentFence), futureFenceResult.get()); }); - EXPECT_CALL(*releasedLayer3, onLayerDisplayed(_)) - .WillOnce([&presentFence](ftl::SharedFuture futureFenceResult) { + EXPECT_CALL(*releasedLayer3, onLayerDisplayed(_, _)) + .WillOnce([&presentFence](ftl::SharedFuture futureFenceResult, + ui::LayerStack) { EXPECT_EQ(FenceResult(presentFence), futureFenceResult.get()); }); @@ -3356,7 +3322,8 @@ struct OutputComposeSurfacesTest : public testing::Test { // mock implementations. MOCK_CONST_METHOD0(getSkipColorTransform, bool()); MOCK_METHOD3(generateClientCompositionRequests, - std::vector(bool, ui::Dataspace, std::vector&)); + std::vector(bool, ui::Dataspace, + std::vector&)); MOCK_METHOD2(appendRegionFlashRequests, void(const Region&, std::vector&)); MOCK_METHOD1(setExpensiveRenderingExpected, void(bool)); @@ -3389,8 +3356,7 @@ struct OutputComposeSurfacesTest : public testing::Test { EXPECT_CALL(mOutput, getCompositionEngine()).WillRepeatedly(ReturnRef(mCompositionEngine)); EXPECT_CALL(mCompositionEngine, getRenderEngine()).WillRepeatedly(ReturnRef(mRenderEngine)); - EXPECT_CALL(mCompositionEngine, getTimeStats()) - .WillRepeatedly(ReturnRef(*mTimeStats.get())); + EXPECT_CALL(mCompositionEngine, getTimeStats()).WillRepeatedly(Return(mTimeStats.get())); EXPECT_CALL(*mDisplayColorProfile, getHdrCapabilities()) .WillRepeatedly(ReturnRef(kHdrCapabilities)); } @@ -3403,8 +3369,8 @@ struct OutputComposeSurfacesTest : public testing::Test { getInstance()->mOutput.dequeueRenderBuffer(&fence, &externalTexture); if (success) { getInstance()->mReadyFence = - getInstance()->mOutput.composeSurfaces(kDebugRegion, kDefaultRefreshArgs, - externalTexture, fence); + getInstance()->mOutput.composeSurfaces(kDebugRegion, externalTexture, + fence); } return nextState(); } @@ -3449,7 +3415,7 @@ struct OutputComposeSurfacesTest : public testing::Test { StrictMock mOutput; std::shared_ptr mOutputBuffer = std::make_shared< renderengine::impl:: - ExternalTexture>(new GraphicBuffer(), mRenderEngine, + ExternalTexture>(sp::make(), mRenderEngine, renderengine::impl::ExternalTexture::Usage::READABLE | renderengine::impl::ExternalTexture::Usage::WRITEABLE); @@ -3531,9 +3497,8 @@ TEST_F(OutputComposeSurfacesTest, handlesZeroCompositionRequests) { .WillRepeatedly([&](const renderengine::DisplaySettings&, const std::vector&, const std::shared_ptr&, const bool, - base::unique_fd&&) - -> std::future { - return futureOf({NO_ERROR, base::unique_fd()}); + base::unique_fd&&) -> ftl::Future { + return ftl::yield(Fence::NO_FENCE); }); verify().execute().expectAFenceWasReturned(); } @@ -3563,9 +3528,8 @@ TEST_F(OutputComposeSurfacesTest, buildsAndRendersRequestList) { .WillRepeatedly([&](const renderengine::DisplaySettings&, const std::vector&, const std::shared_ptr&, const bool, - base::unique_fd&&) - -> std::future { - return futureOf({NO_ERROR, base::unique_fd()}); + base::unique_fd&&) -> ftl::Future { + return ftl::yield(Fence::NO_FENCE); }); verify().execute().expectAFenceWasReturned(); @@ -3598,9 +3562,8 @@ TEST_F(OutputComposeSurfacesTest, .WillRepeatedly([&](const renderengine::DisplaySettings&, const std::vector&, const std::shared_ptr&, const bool, - base::unique_fd&&) - -> std::future { - return futureOf({NO_ERROR, base::unique_fd()}); + base::unique_fd&&) -> ftl::Future { + return ftl::yield(Fence::NO_FENCE); }); verify().execute().expectAFenceWasReturned(); @@ -3626,10 +3589,8 @@ TEST_F(OutputComposeSurfacesTest, renderDuplicateClientCompositionRequestsWithou EXPECT_CALL(*mRenderSurface, dequeueBuffer(_)).WillRepeatedly(Return(mOutputBuffer)); EXPECT_CALL(mRenderEngine, drawLayers(_, ElementsAre(r1, r2), _, false, _)) .Times(2) - .WillOnce(Return(ByMove( - futureOf({NO_ERROR, base::unique_fd()})))) - .WillOnce(Return(ByMove( - futureOf({NO_ERROR, base::unique_fd()})))); + .WillOnce(Return(ByMove(ftl::yield(Fence::NO_FENCE)))) + .WillOnce(Return(ByMove(ftl::yield(Fence::NO_FENCE)))); verify().execute().expectAFenceWasReturned(); EXPECT_FALSE(mOutput.mState.reusedClientComposition); @@ -3657,8 +3618,7 @@ TEST_F(OutputComposeSurfacesTest, skipDuplicateClientCompositionRequests) { EXPECT_CALL(*mRenderSurface, dequeueBuffer(_)).WillRepeatedly(Return(mOutputBuffer)); EXPECT_CALL(mRenderEngine, drawLayers(_, ElementsAre(r1, r2), _, false, _)) - .WillOnce(Return(ByMove( - futureOf({NO_ERROR, base::unique_fd()})))); + .WillOnce(Return(ByMove(ftl::yield(Fence::NO_FENCE)))); EXPECT_CALL(mOutput, setExpensiveRenderingExpected(false)); verify().execute().expectAFenceWasReturned(); @@ -3687,7 +3647,7 @@ TEST_F(OutputComposeSurfacesTest, clientCompositionIfBufferChanges) { const auto otherOutputBuffer = std::make_shared< renderengine::impl:: - ExternalTexture>(new GraphicBuffer(), mRenderEngine, + ExternalTexture>(sp::make(), mRenderEngine, renderengine::impl::ExternalTexture::Usage::READABLE | renderengine::impl::ExternalTexture::Usage::WRITEABLE); EXPECT_CALL(*mRenderSurface, dequeueBuffer(_)) @@ -3697,9 +3657,8 @@ TEST_F(OutputComposeSurfacesTest, clientCompositionIfBufferChanges) { .WillRepeatedly([&](const renderengine::DisplaySettings&, const std::vector&, const std::shared_ptr&, const bool, - base::unique_fd&&) - -> std::future { - return futureOf({NO_ERROR, base::unique_fd()}); + base::unique_fd&&) -> ftl::Future { + return ftl::yield(Fence::NO_FENCE); }); verify().execute().expectAFenceWasReturned(); @@ -3730,11 +3689,9 @@ TEST_F(OutputComposeSurfacesTest, clientCompositionIfRequestChanges) { EXPECT_CALL(*mRenderSurface, dequeueBuffer(_)).WillRepeatedly(Return(mOutputBuffer)); EXPECT_CALL(mRenderEngine, drawLayers(_, ElementsAre(r1, r2), _, false, _)) - .WillOnce(Return(ByMove( - futureOf({NO_ERROR, base::unique_fd()})))); + .WillOnce(Return(ByMove(ftl::yield(Fence::NO_FENCE)))); EXPECT_CALL(mRenderEngine, drawLayers(_, ElementsAre(r1, r3), _, false, _)) - .WillOnce(Return(ByMove( - futureOf({NO_ERROR, base::unique_fd()})))); + .WillOnce(Return(ByMove(ftl::yield(Fence::NO_FENCE)))); verify().execute().expectAFenceWasReturned(); EXPECT_FALSE(mOutput.mState.reusedClientComposition); @@ -3811,8 +3768,7 @@ struct OutputComposeSurfacesTest_UsesExpectedDisplaySettings : public OutputComp : public CallOrderStateMachineHelper { auto thenExpectDisplaySettingsUsed(renderengine::DisplaySettings settings) { EXPECT_CALL(getInstance()->mRenderEngine, drawLayers(settings, _, _, false, _)) - .WillOnce(Return(ByMove(futureOf( - {NO_ERROR, base::unique_fd()})))); + .WillOnce(Return(ByMove(ftl::yield(Fence::NO_FENCE)))); return nextState(); } }; @@ -4065,60 +4021,30 @@ struct OutputComposeSurfacesTest_HandlesProtectedContent : public OutputComposeS .WillRepeatedly(Return()); EXPECT_CALL(*mRenderSurface, dequeueBuffer(_)).WillRepeatedly(Return(mOutputBuffer)); EXPECT_CALL(mRenderEngine, drawLayers(_, _, _, false, _)) - .WillRepeatedly( - [&](const renderengine::DisplaySettings&, - const std::vector&, - const std::shared_ptr&, const bool, - base::unique_fd&&) -> std::future { - return futureOf( - {NO_ERROR, base::unique_fd()}); - }); + .WillRepeatedly([&](const renderengine::DisplaySettings&, + const std::vector&, + const std::shared_ptr&, + const bool, base::unique_fd&&) -> ftl::Future { + return ftl::yield(Fence::NO_FENCE); + }); } Layer mLayer1; Layer mLayer2; }; -TEST_F(OutputComposeSurfacesTest_HandlesProtectedContent, ifDisplayIsNotSecure) { - mOutput.mState.isSecure = false; - mLayer2.mLayerFEState.hasProtectedContent = true; - EXPECT_CALL(mRenderEngine, supportsProtectedContent()).WillRepeatedly(Return(true)); - EXPECT_CALL(mRenderEngine, isProtected).WillOnce(Return(true)); - EXPECT_CALL(mRenderEngine, useProtectedContext(false)); - - base::unique_fd fd; - std::shared_ptr tex; - mOutput.updateProtectedContentState(); - mOutput.dequeueRenderBuffer(&fd, &tex); - mOutput.composeSurfaces(kDebugRegion, kDefaultRefreshArgs, tex, fd); -} - -TEST_F(OutputComposeSurfacesTest_HandlesProtectedContent, ifRenderEngineDoesNotSupportIt) { - mOutput.mState.isSecure = true; - mLayer2.mLayerFEState.hasProtectedContent = true; - EXPECT_CALL(mRenderEngine, supportsProtectedContent()).WillRepeatedly(Return(false)); - - base::unique_fd fd; - std::shared_ptr tex; - mOutput.updateProtectedContentState(); - mOutput.dequeueRenderBuffer(&fd, &tex); - mOutput.composeSurfaces(kDebugRegion, kDefaultRefreshArgs, tex, fd); -} - TEST_F(OutputComposeSurfacesTest_HandlesProtectedContent, ifNoProtectedContentLayers) { mOutput.mState.isSecure = true; mLayer2.mLayerFEState.hasProtectedContent = false; EXPECT_CALL(mRenderEngine, supportsProtectedContent()).WillRepeatedly(Return(true)); - EXPECT_CALL(mRenderEngine, isProtected).WillOnce(Return(true)).WillOnce(Return(false)); EXPECT_CALL(*mRenderSurface, isProtected).WillOnce(Return(true)); - EXPECT_CALL(mRenderEngine, useProtectedContext(false)); EXPECT_CALL(*mRenderSurface, setProtected(false)); base::unique_fd fd; std::shared_ptr tex; mOutput.updateProtectedContentState(); mOutput.dequeueRenderBuffer(&fd, &tex); - mOutput.composeSurfaces(kDebugRegion, kDefaultRefreshArgs, tex, fd); + mOutput.composeSurfaces(kDebugRegion, tex, fd); } TEST_F(OutputComposeSurfacesTest_HandlesProtectedContent, ifNotEnabled) { @@ -4129,81 +4055,44 @@ TEST_F(OutputComposeSurfacesTest_HandlesProtectedContent, ifNotEnabled) { // For this test, we also check the call order of key functions. InSequence seq; - EXPECT_CALL(mRenderEngine, isProtected).WillOnce(Return(false)); - EXPECT_CALL(mRenderEngine, useProtectedContext(true)); EXPECT_CALL(*mRenderSurface, isProtected).WillOnce(Return(false)); - EXPECT_CALL(mRenderEngine, isProtected).WillOnce(Return(true)); EXPECT_CALL(*mRenderSurface, setProtected(true)); // Must happen after setting the protected content state. EXPECT_CALL(*mRenderSurface, dequeueBuffer(_)).WillRepeatedly(Return(mOutputBuffer)); EXPECT_CALL(mRenderEngine, drawLayers(_, _, _, false, _)) - .WillOnce(Return(ByMove( - futureOf({NO_ERROR, base::unique_fd()})))); + .WillOnce(Return(ByMove(ftl::yield(Fence::NO_FENCE)))); base::unique_fd fd; std::shared_ptr tex; mOutput.updateProtectedContentState(); mOutput.dequeueRenderBuffer(&fd, &tex); - mOutput.composeSurfaces(kDebugRegion, kDefaultRefreshArgs, tex, fd); + mOutput.composeSurfaces(kDebugRegion, tex, fd); } TEST_F(OutputComposeSurfacesTest_HandlesProtectedContent, ifAlreadyEnabledEverywhere) { mOutput.mState.isSecure = true; mLayer2.mLayerFEState.hasProtectedContent = true; EXPECT_CALL(mRenderEngine, supportsProtectedContent()).WillRepeatedly(Return(true)); - EXPECT_CALL(mRenderEngine, isProtected).WillOnce(Return(true)); EXPECT_CALL(*mRenderSurface, isProtected).WillOnce(Return(true)); base::unique_fd fd; std::shared_ptr tex; mOutput.updateProtectedContentState(); mOutput.dequeueRenderBuffer(&fd, &tex); - mOutput.composeSurfaces(kDebugRegion, kDefaultRefreshArgs, tex, fd); -} - -TEST_F(OutputComposeSurfacesTest_HandlesProtectedContent, ifFailsToEnableInRenderEngine) { - mOutput.mState.isSecure = true; - mLayer2.mLayerFEState.hasProtectedContent = true; - EXPECT_CALL(mRenderEngine, supportsProtectedContent()).WillRepeatedly(Return(true)); - EXPECT_CALL(mRenderEngine, isProtected).WillOnce(Return(false)).WillOnce(Return(false)); - EXPECT_CALL(*mRenderSurface, isProtected).WillOnce(Return(false)); - EXPECT_CALL(mRenderEngine, useProtectedContext(true)); - - base::unique_fd fd; - std::shared_ptr tex; - mOutput.updateProtectedContentState(); - mOutput.dequeueRenderBuffer(&fd, &tex); - mOutput.composeSurfaces(kDebugRegion, kDefaultRefreshArgs, tex, fd); -} - -TEST_F(OutputComposeSurfacesTest_HandlesProtectedContent, ifAlreadyEnabledInRenderEngine) { - mOutput.mState.isSecure = true; - mLayer2.mLayerFEState.hasProtectedContent = true; - EXPECT_CALL(mRenderEngine, supportsProtectedContent()).WillRepeatedly(Return(true)); - EXPECT_CALL(mRenderEngine, isProtected).WillOnce(Return(true)).WillOnce(Return(true)); - EXPECT_CALL(*mRenderSurface, isProtected).WillOnce(Return(false)); - EXPECT_CALL(*mRenderSurface, setProtected(true)); - - base::unique_fd fd; - std::shared_ptr tex; - mOutput.updateProtectedContentState(); - mOutput.dequeueRenderBuffer(&fd, &tex); - mOutput.composeSurfaces(kDebugRegion, kDefaultRefreshArgs, tex, fd); + mOutput.composeSurfaces(kDebugRegion, tex, fd); } TEST_F(OutputComposeSurfacesTest_HandlesProtectedContent, ifAlreadyEnabledInRenderSurface) { mOutput.mState.isSecure = true; mLayer2.mLayerFEState.hasProtectedContent = true; EXPECT_CALL(mRenderEngine, supportsProtectedContent()).WillRepeatedly(Return(true)); - EXPECT_CALL(mRenderEngine, isProtected).WillOnce(Return(false)); EXPECT_CALL(*mRenderSurface, isProtected).WillOnce(Return(true)); - EXPECT_CALL(mRenderEngine, useProtectedContext(true)); base::unique_fd fd; std::shared_ptr tex; mOutput.updateProtectedContentState(); mOutput.dequeueRenderBuffer(&fd, &tex); - mOutput.composeSurfaces(kDebugRegion, kDefaultRefreshArgs, tex, fd); + mOutput.composeSurfaces(kDebugRegion, tex, fd); } struct OutputComposeSurfacesTest_SetsExpensiveRendering : public OutputComposeSurfacesTest { @@ -4230,69 +4119,13 @@ TEST_F(OutputComposeSurfacesTest_SetsExpensiveRendering, IfExepensiveOutputDatas EXPECT_CALL(mOutput, setExpensiveRenderingExpected(true)); EXPECT_CALL(mRenderEngine, drawLayers(_, _, _, false, _)) - .WillOnce(Return(ByMove( - futureOf({NO_ERROR, base::unique_fd()})))); + .WillOnce(Return(ByMove(ftl::yield(Fence::NO_FENCE)))); base::unique_fd fd; std::shared_ptr tex; mOutput.updateProtectedContentState(); mOutput.dequeueRenderBuffer(&fd, &tex); - mOutput.composeSurfaces(kDebugRegion, kDefaultRefreshArgs, tex, fd); -} - -struct OutputComposeSurfacesTest_SetsExpensiveRendering_ForBlur - : public OutputComposeSurfacesTest_SetsExpensiveRendering { - OutputComposeSurfacesTest_SetsExpensiveRendering_ForBlur() { - mLayer.layerFEState.backgroundBlurRadius = 10; - mLayer.layerFEState.isOpaque = false; - mOutput.editState().isEnabled = true; - - EXPECT_CALL(mLayer.outputLayer, updateCompositionState(false, true, ui::Transform::ROT_0)); - EXPECT_CALL(mLayer.outputLayer, - writeStateToHWC(/*includeGeometry*/ false, /*skipLayer*/ false, 0, - /*zIsOverridden*/ false, /*isPeekingThrough*/ false)); - EXPECT_CALL(mOutput, generateClientCompositionRequests(_, kDefaultOutputDataspace, _)) - .WillOnce(Return(std::vector{})); - EXPECT_CALL(mRenderEngine, drawLayers(_, _, _, false, _)) - .WillOnce(Return(ByMove(futureOf( - {NO_ERROR, base::unique_fd()})))); - EXPECT_CALL(mOutput, getOutputLayerCount()).WillRepeatedly(Return(1u)); - EXPECT_CALL(mOutput, getOutputLayerOrderedByZByIndex(0u)) - .WillRepeatedly(Return(&mLayer.outputLayer)); - } - - NonInjectedLayer mLayer; - compositionengine::CompositionRefreshArgs mRefreshArgs; -}; - -TEST_F(OutputComposeSurfacesTest_SetsExpensiveRendering_ForBlur, IfBlursAreExpensive) { - mRefreshArgs.blursAreExpensive = true; - mOutput.updateCompositionState(mRefreshArgs); - mOutput.planComposition(); - mOutput.writeCompositionState(mRefreshArgs); - - EXPECT_CALL(mOutput, setExpensiveRenderingExpected(true)); - - base::unique_fd fd; - std::shared_ptr tex; - mOutput.updateProtectedContentState(); - mOutput.dequeueRenderBuffer(&fd, &tex); - mOutput.composeSurfaces(kDebugRegion, mRefreshArgs, tex, fd); -} - -TEST_F(OutputComposeSurfacesTest_SetsExpensiveRendering_ForBlur, IfBlursAreNotExpensive) { - mRefreshArgs.blursAreExpensive = false; - mOutput.updateCompositionState(mRefreshArgs); - mOutput.planComposition(); - mOutput.writeCompositionState(mRefreshArgs); - - EXPECT_CALL(mOutput, setExpensiveRenderingExpected(true)).Times(0); - - base::unique_fd fd; - std::shared_ptr tex; - mOutput.updateProtectedContentState(); - mOutput.dequeueRenderBuffer(&fd, &tex); - mOutput.composeSurfaces(kDebugRegion, mRefreshArgs, tex, fd); + mOutput.composeSurfaces(kDebugRegion, tex, fd); } /* @@ -4303,7 +4136,7 @@ struct GenerateClientCompositionRequestsTest : public testing::Test { struct OutputPartialMock : public OutputPartialMockBase { // compositionengine::Output overrides std::vector generateClientCompositionRequestsHelper( - bool supportsProtectedContent, ui::Dataspace dataspace) { + bool supportsProtectedContent, ui::Dataspace dataspace) { std::vector ignore; return impl::Output::generateClientCompositionRequests(supportsProtectedContent, dataspace, ignore); @@ -4312,6 +4145,8 @@ struct GenerateClientCompositionRequestsTest : public testing::Test { struct Layer { Layer() { + EXPECT_CALL(mOutputLayer, getOverrideCompositionSettings()) + .WillRepeatedly(Return(std::nullopt)); EXPECT_CALL(mOutputLayer, getState()).WillRepeatedly(ReturnRef(mOutputLayerState)); EXPECT_CALL(mOutputLayer, editState()).WillRepeatedly(ReturnRef(mOutputLayerState)); EXPECT_CALL(mOutputLayer, getLayerFE()).WillRepeatedly(ReturnRef(*mLayerFE)); @@ -4393,8 +4228,9 @@ TEST_F(GenerateClientCompositionRequestsTest_ThreeLayers, handlesNoClientCompost EXPECT_CALL(mLayers[1].mOutputLayer, requiresClientComposition()).WillOnce(Return(false)); EXPECT_CALL(mLayers[2].mOutputLayer, requiresClientComposition()).WillOnce(Return(false)); - auto requests = mOutput.generateClientCompositionRequestsHelper(false /* supportsProtectedContent */, - kDisplayDataspace); + auto requests = + mOutput.generateClientCompositionRequestsHelper(false /* supportsProtectedContent */, + kDisplayDataspace); EXPECT_EQ(0u, requests.size()); } @@ -4403,29 +4239,26 @@ TEST_F(GenerateClientCompositionRequestsTest_ThreeLayers, requiresVisibleRegionA mLayers[1].mOutputLayerState.visibleRegion = Region(Rect(4000, 0, 4010, 10)); mLayers[2].mOutputLayerState.visibleRegion = Region(Rect(-10, -10, 0, 0)); - auto requests = mOutput.generateClientCompositionRequestsHelper(false /* supportsProtectedContent */, - kDisplayDataspace); + auto requests = + mOutput.generateClientCompositionRequestsHelper(false /* supportsProtectedContent */, + kDisplayDataspace); EXPECT_EQ(0u, requests.size()); } TEST_F(GenerateClientCompositionRequestsTest_ThreeLayers, gathersClientCompositionRequests) { - LayerFE::LayerSettings mShadowSettings; - mShadowSettings.source.solidColor = {0.1f, 0.1f, 0.1f}; + EXPECT_CALL(*mLayers[0].mLayerFE, prepareClientComposition(_)) + .WillOnce(Return(std::optional())); + EXPECT_CALL(*mLayers[1].mLayerFE, prepareClientComposition(_)) + .WillOnce(Return(std::optional(mLayers[1].mLayerSettings))); + EXPECT_CALL(*mLayers[2].mLayerFE, prepareClientComposition(_)) + .WillOnce(Return(std::optional(mLayers[2].mLayerSettings))); - EXPECT_CALL(*mLayers[0].mLayerFE, prepareClientCompositionList(_)) - .WillOnce(Return(std::vector())); - EXPECT_CALL(*mLayers[1].mLayerFE, prepareClientCompositionList(_)) - .WillOnce(Return(std::vector({mLayers[1].mLayerSettings}))); - EXPECT_CALL(*mLayers[2].mLayerFE, prepareClientCompositionList(_)) - .WillOnce(Return(std::vector( - {mShadowSettings, mLayers[2].mLayerSettings}))); - - auto requests = mOutput.generateClientCompositionRequestsHelper(false /* supportsProtectedContent */, - kDisplayDataspace); - ASSERT_EQ(3u, requests.size()); + auto requests = + mOutput.generateClientCompositionRequestsHelper(false /* supportsProtectedContent */, + kDisplayDataspace); + ASSERT_EQ(2u, requests.size()); EXPECT_EQ(mLayers[1].mLayerSettings, requests[0]); - EXPECT_EQ(mShadowSettings, requests[1]); - EXPECT_EQ(mLayers[2].mLayerSettings, requests[2]); + EXPECT_EQ(mLayers[2].mLayerSettings, requests[1]); // Check that a timestamp was set for the layers that generated requests EXPECT_TRUE(0 == mLayers[0].mOutputLayerState.clientCompositionTimestamp); @@ -4442,27 +4275,22 @@ MATCHER_P(ClientCompositionTargetSettingsBlurSettingsEq, expectedBlurSetting, "" } TEST_F(GenerateClientCompositionRequestsTest_ThreeLayers, overridesBlur) { - LayerFE::LayerSettings mShadowSettings; - mShadowSettings.source.solidColor = {0.1f, 0.1f, 0.1f}; - mLayers[2].mOutputLayerState.overrideInfo.disableBackgroundBlur = true; - EXPECT_CALL(*mLayers[0].mLayerFE, prepareClientCompositionList(_)) - .WillOnce(Return(std::vector())); - EXPECT_CALL(*mLayers[1].mLayerFE, prepareClientCompositionList(_)) - .WillOnce(Return(std::vector({mLayers[1].mLayerSettings}))); + EXPECT_CALL(*mLayers[0].mLayerFE, prepareClientComposition(_)) + .WillOnce(Return(std::optional())); + EXPECT_CALL(*mLayers[1].mLayerFE, prepareClientComposition(_)) + .WillOnce(Return(std::optional(mLayers[1].mLayerSettings))); EXPECT_CALL(*mLayers[2].mLayerFE, - prepareClientCompositionList(ClientCompositionTargetSettingsBlurSettingsEq( + prepareClientComposition(ClientCompositionTargetSettingsBlurSettingsEq( LayerFE::ClientCompositionTargetSettings::BlurSetting::BlurRegionsOnly))) - .WillOnce(Return(std::vector( - {mShadowSettings, mLayers[2].mLayerSettings}))); - - auto requests = mOutput.generateClientCompositionRequestsHelper(false /* supportsProtectedContent */, - kDisplayDataspace); - ASSERT_EQ(3u, requests.size()); + .WillOnce(Return(std::optional(mLayers[2].mLayerSettings))); + auto requests = + mOutput.generateClientCompositionRequestsHelper(false /* supportsProtectedContent */, + kDisplayDataspace); + ASSERT_EQ(2u, requests.size()); EXPECT_EQ(mLayers[1].mLayerSettings, requests[0]); - EXPECT_EQ(mShadowSettings, requests[1]); - EXPECT_EQ(mLayers[2].mLayerSettings, requests[2]); + EXPECT_EQ(mLayers[2].mLayerSettings, requests[1]); // Check that a timestamp was set for the layers that generated requests EXPECT_TRUE(0 == mLayers[0].mOutputLayerState.clientCompositionTimestamp); @@ -4484,11 +4312,12 @@ TEST_F(GenerateClientCompositionRequestsTest_ThreeLayers, mLayers[1].mLayerFEState.isOpaque = true; mLayers[2].mLayerFEState.isOpaque = true; - EXPECT_CALL(*mLayers[2].mLayerFE, prepareClientCompositionList(_)) - .WillOnce(Return(std::vector({mLayers[2].mLayerSettings}))); + EXPECT_CALL(*mLayers[2].mLayerFE, prepareClientComposition(_)) + .WillOnce(Return(std::optional(mLayers[2].mLayerSettings))); - auto requests = mOutput.generateClientCompositionRequestsHelper(false /* supportsProtectedContent */, - kDisplayDataspace); + auto requests = + mOutput.generateClientCompositionRequestsHelper(false /* supportsProtectedContent */, + kDisplayDataspace); ASSERT_EQ(1u, requests.size()); EXPECT_EQ(mLayers[2].mLayerSettings, requests[0]); } @@ -4507,11 +4336,12 @@ TEST_F(GenerateClientCompositionRequestsTest_ThreeLayers, mLayers[1].mLayerFEState.isOpaque = false; mLayers[2].mLayerFEState.isOpaque = false; - EXPECT_CALL(*mLayers[2].mLayerFE, prepareClientCompositionList(_)) - .WillOnce(Return(std::vector({mLayers[2].mLayerSettings}))); + EXPECT_CALL(*mLayers[2].mLayerFE, prepareClientComposition(_)) + .WillOnce(Return(std::optional(mLayers[2].mLayerSettings))); - auto requests = mOutput.generateClientCompositionRequestsHelper(false /* supportsProtectedContent */, - kDisplayDataspace); + auto requests = + mOutput.generateClientCompositionRequestsHelper(false /* supportsProtectedContent */, + kDisplayDataspace); ASSERT_EQ(1u, requests.size()); EXPECT_EQ(mLayers[2].mLayerSettings, requests[0]); } @@ -4546,6 +4376,7 @@ TEST_F(GenerateClientCompositionRequestsTest_ThreeLayers, clearsHWCLayersIfOpaqu true /* clearContent */, compositionengine::LayerFE::ClientCompositionTargetSettings::BlurSetting::Enabled, kLayerWhitePointNits, + false /* treat170mAsSrgb */, }; compositionengine::LayerFE::ClientCompositionTargetSettings layer2TargetSettings{ Region(kDisplayFrame), @@ -4558,6 +4389,7 @@ TEST_F(GenerateClientCompositionRequestsTest_ThreeLayers, clearsHWCLayersIfOpaqu false /* clearContent */, compositionengine::LayerFE::ClientCompositionTargetSettings::BlurSetting::Enabled, kLayerWhitePointNits, + false /* treat170mAsSrgb */, }; LayerFE::LayerSettings mBlackoutSettings = mLayers[1].mLayerSettings; @@ -4566,13 +4398,14 @@ TEST_F(GenerateClientCompositionRequestsTest_ThreeLayers, clearsHWCLayersIfOpaqu mBlackoutSettings.alpha = 0.f; mBlackoutSettings.disableBlending = true; - EXPECT_CALL(*mLayers[1].mLayerFE, prepareClientCompositionList(Eq(ByRef(layer1TargetSettings)))) - .WillOnce(Return(std::vector({mBlackoutSettings}))); - EXPECT_CALL(*mLayers[2].mLayerFE, prepareClientCompositionList(Eq(ByRef(layer2TargetSettings)))) - .WillOnce(Return(std::vector({mLayers[2].mLayerSettings}))); + EXPECT_CALL(*mLayers[1].mLayerFE, prepareClientComposition(Eq(ByRef(layer1TargetSettings)))) + .WillOnce(Return(std::optional(mBlackoutSettings))); + EXPECT_CALL(*mLayers[2].mLayerFE, prepareClientComposition(Eq(ByRef(layer2TargetSettings)))) + .WillOnce(Return(std::optional(mLayers[2].mLayerSettings))); - auto requests = mOutput.generateClientCompositionRequestsHelper(false /* supportsProtectedContent */, - kDisplayDataspace); + auto requests = + mOutput.generateClientCompositionRequestsHelper(false /* supportsProtectedContent */, + kDisplayDataspace); ASSERT_EQ(2u, requests.size()); // The second layer is expected to be rendered as alpha=0 black with no blending @@ -4598,6 +4431,7 @@ TEST_F(GenerateClientCompositionRequestsTest_ThreeLayers, false /* clearContent */, compositionengine::LayerFE::ClientCompositionTargetSettings::BlurSetting::Enabled, kLayerWhitePointNits, + false /* treat170mAsSrgb */, }; compositionengine::LayerFE::ClientCompositionTargetSettings layer1TargetSettings{ Region(Rect(0, 0, 30, 30)), @@ -4610,6 +4444,7 @@ TEST_F(GenerateClientCompositionRequestsTest_ThreeLayers, false /* clearContent */, compositionengine::LayerFE::ClientCompositionTargetSettings::BlurSetting::Enabled, kLayerWhitePointNits, + false /* treat170mAsSrgb */, }; compositionengine::LayerFE::ClientCompositionTargetSettings layer2TargetSettings{ Region(Rect(0, 0, 40, 201)), @@ -4622,18 +4457,19 @@ TEST_F(GenerateClientCompositionRequestsTest_ThreeLayers, false /* clearContent */, compositionengine::LayerFE::ClientCompositionTargetSettings::BlurSetting::Enabled, kLayerWhitePointNits, + false /* treat170mAsSrgb */, }; - EXPECT_CALL(*mLayers[0].mLayerFE, prepareClientCompositionList(Eq(ByRef(layer0TargetSettings)))) - .WillOnce(Return(std::vector())); - EXPECT_CALL(*mLayers[1].mLayerFE, prepareClientCompositionList(Eq(ByRef(layer1TargetSettings)))) - .WillOnce(Return(std::vector())); - EXPECT_CALL(*mLayers[2].mLayerFE, prepareClientCompositionList(Eq(ByRef(layer2TargetSettings)))) - .WillOnce(Return(std::vector())); + EXPECT_CALL(*mLayers[0].mLayerFE, prepareClientComposition(Eq(ByRef(layer0TargetSettings)))) + .WillOnce(Return(std::optional())); + EXPECT_CALL(*mLayers[1].mLayerFE, prepareClientComposition(Eq(ByRef(layer1TargetSettings)))) + .WillOnce(Return(std::optional())); + EXPECT_CALL(*mLayers[2].mLayerFE, prepareClientComposition(Eq(ByRef(layer2TargetSettings)))) + .WillOnce(Return(std::optional())); static_cast( mOutput.generateClientCompositionRequestsHelper(false /* supportsProtectedContent */, - kDisplayDataspace)); + kDisplayDataspace)); } TEST_F(GenerateClientCompositionRequestsTest_ThreeLayers, @@ -4652,6 +4488,7 @@ TEST_F(GenerateClientCompositionRequestsTest_ThreeLayers, false /* clearContent */, compositionengine::LayerFE::ClientCompositionTargetSettings::BlurSetting::Enabled, kLayerWhitePointNits, + false /* treat170mAsSrgb */, }; compositionengine::LayerFE::ClientCompositionTargetSettings layer1TargetSettings{ Region(kDisplayFrame), @@ -4664,6 +4501,7 @@ TEST_F(GenerateClientCompositionRequestsTest_ThreeLayers, false /* clearContent */, compositionengine::LayerFE::ClientCompositionTargetSettings::BlurSetting::Enabled, kLayerWhitePointNits, + false /* treat170mAsSrgb */, }; compositionengine::LayerFE::ClientCompositionTargetSettings layer2TargetSettings{ Region(kDisplayFrame), @@ -4676,14 +4514,15 @@ TEST_F(GenerateClientCompositionRequestsTest_ThreeLayers, false /* clearContent */, compositionengine::LayerFE::ClientCompositionTargetSettings::BlurSetting::Enabled, kLayerWhitePointNits, + false /* treat170mAsSrgb */, }; - EXPECT_CALL(*mLayers[0].mLayerFE, prepareClientCompositionList(Eq(ByRef(layer0TargetSettings)))) - .WillOnce(Return(std::vector())); - EXPECT_CALL(*mLayers[1].mLayerFE, prepareClientCompositionList(Eq(ByRef(layer1TargetSettings)))) - .WillOnce(Return(std::vector())); - EXPECT_CALL(*mLayers[2].mLayerFE, prepareClientCompositionList(Eq(ByRef(layer2TargetSettings)))) - .WillOnce(Return(std::vector())); + EXPECT_CALL(*mLayers[0].mLayerFE, prepareClientComposition(Eq(ByRef(layer0TargetSettings)))) + .WillOnce(Return(std::optional())); + EXPECT_CALL(*mLayers[1].mLayerFE, prepareClientComposition(Eq(ByRef(layer1TargetSettings)))) + .WillOnce(Return(std::optional())); + EXPECT_CALL(*mLayers[2].mLayerFE, prepareClientComposition(Eq(ByRef(layer2TargetSettings)))) + .WillOnce(Return(std::optional())); static_cast( mOutput.generateClientCompositionRequestsHelper(false /* supportsProtectedContent */, @@ -4706,6 +4545,7 @@ TEST_F(GenerateClientCompositionRequestsTest_ThreeLayers, false /* clearContent */, compositionengine::LayerFE::ClientCompositionTargetSettings::BlurSetting::Enabled, kLayerWhitePointNits, + false /* treat170mAsSrgb */, }; compositionengine::LayerFE::ClientCompositionTargetSettings layer1TargetSettings{ Region(kDisplayFrame), @@ -4718,6 +4558,7 @@ TEST_F(GenerateClientCompositionRequestsTest_ThreeLayers, false /* clearContent */, compositionengine::LayerFE::ClientCompositionTargetSettings::BlurSetting::Enabled, kLayerWhitePointNits, + false /* treat170mAsSrgb */, }; compositionengine::LayerFE::ClientCompositionTargetSettings layer2TargetSettings{ Region(kDisplayFrame), @@ -4730,14 +4571,15 @@ TEST_F(GenerateClientCompositionRequestsTest_ThreeLayers, false /* clearContent */, compositionengine::LayerFE::ClientCompositionTargetSettings::BlurSetting::Enabled, kLayerWhitePointNits, + false /* treat170mAsSrgb */, }; - EXPECT_CALL(*mLayers[0].mLayerFE, prepareClientCompositionList(Eq(ByRef(layer0TargetSettings)))) - .WillOnce(Return(std::vector())); - EXPECT_CALL(*mLayers[1].mLayerFE, prepareClientCompositionList(Eq(ByRef(layer1TargetSettings)))) - .WillOnce(Return(std::vector())); - EXPECT_CALL(*mLayers[2].mLayerFE, prepareClientCompositionList(Eq(ByRef(layer2TargetSettings)))) - .WillOnce(Return(std::vector())); + EXPECT_CALL(*mLayers[0].mLayerFE, prepareClientComposition(Eq(ByRef(layer0TargetSettings)))) + .WillOnce(Return(std::optional())); + EXPECT_CALL(*mLayers[1].mLayerFE, prepareClientComposition(Eq(ByRef(layer1TargetSettings)))) + .WillOnce(Return(std::optional())); + EXPECT_CALL(*mLayers[2].mLayerFE, prepareClientComposition(Eq(ByRef(layer2TargetSettings)))) + .WillOnce(Return(std::optional())); static_cast( mOutput.generateClientCompositionRequestsHelper(false /* supportsProtectedContent */, @@ -4759,6 +4601,7 @@ TEST_F(GenerateClientCompositionRequestsTest_ThreeLayers, false /* clearContent */, compositionengine::LayerFE::ClientCompositionTargetSettings::BlurSetting::Enabled, kLayerWhitePointNits, + false /* treat170mAsSrgb */, }; compositionengine::LayerFE::ClientCompositionTargetSettings layer1TargetSettings{ Region(kDisplayFrame), @@ -4771,6 +4614,7 @@ TEST_F(GenerateClientCompositionRequestsTest_ThreeLayers, false /* clearContent */, compositionengine::LayerFE::ClientCompositionTargetSettings::BlurSetting::Enabled, kLayerWhitePointNits, + false /* treat170mAsSrgb */, }; compositionengine::LayerFE::ClientCompositionTargetSettings layer2TargetSettings{ Region(kDisplayFrame), @@ -4783,14 +4627,15 @@ TEST_F(GenerateClientCompositionRequestsTest_ThreeLayers, false /* clearContent */, compositionengine::LayerFE::ClientCompositionTargetSettings::BlurSetting::Enabled, kLayerWhitePointNits, + false /* treat170mAsSrgb */, }; - EXPECT_CALL(*mLayers[0].mLayerFE, prepareClientCompositionList(Eq(ByRef(layer0TargetSettings)))) - .WillOnce(Return(std::vector())); - EXPECT_CALL(*mLayers[1].mLayerFE, prepareClientCompositionList(Eq(ByRef(layer1TargetSettings)))) - .WillOnce(Return(std::vector())); - EXPECT_CALL(*mLayers[2].mLayerFE, prepareClientCompositionList(Eq(ByRef(layer2TargetSettings)))) - .WillOnce(Return(std::vector())); + EXPECT_CALL(*mLayers[0].mLayerFE, prepareClientComposition(Eq(ByRef(layer0TargetSettings)))) + .WillOnce(Return(std::optional())); + EXPECT_CALL(*mLayers[1].mLayerFE, prepareClientComposition(Eq(ByRef(layer1TargetSettings)))) + .WillOnce(Return(std::optional())); + EXPECT_CALL(*mLayers[2].mLayerFE, prepareClientComposition(Eq(ByRef(layer2TargetSettings)))) + .WillOnce(Return(std::optional())); static_cast( mOutput.generateClientCompositionRequestsHelper(false /* supportsProtectedContent */, @@ -4810,6 +4655,7 @@ TEST_F(GenerateClientCompositionRequestsTest_ThreeLayers, false /* clearContent */, compositionengine::LayerFE::ClientCompositionTargetSettings::BlurSetting::Enabled, kLayerWhitePointNits, + false /* treat170mAsSrgb */, }; compositionengine::LayerFE::ClientCompositionTargetSettings layer1TargetSettings{ Region(kDisplayFrame), @@ -4822,6 +4668,7 @@ TEST_F(GenerateClientCompositionRequestsTest_ThreeLayers, false /* clearContent */, compositionengine::LayerFE::ClientCompositionTargetSettings::BlurSetting::Enabled, kLayerWhitePointNits, + false /* treat170mAsSrgb */, }; compositionengine::LayerFE::ClientCompositionTargetSettings layer2TargetSettings{ Region(kDisplayFrame), @@ -4834,17 +4681,19 @@ TEST_F(GenerateClientCompositionRequestsTest_ThreeLayers, false /* clearContent */, compositionengine::LayerFE::ClientCompositionTargetSettings::BlurSetting::Enabled, kLayerWhitePointNits, + false /* treat170mAsSrgb */, }; - EXPECT_CALL(*mLayers[0].mLayerFE, prepareClientCompositionList(Eq(ByRef(layer0TargetSettings)))) - .WillOnce(Return(std::vector())); - EXPECT_CALL(*mLayers[1].mLayerFE, prepareClientCompositionList(Eq(ByRef(layer1TargetSettings)))) - .WillOnce(Return(std::vector())); - EXPECT_CALL(*mLayers[2].mLayerFE, prepareClientCompositionList(Eq(ByRef(layer2TargetSettings)))) - .WillOnce(Return(std::vector())); + EXPECT_CALL(*mLayers[0].mLayerFE, prepareClientComposition(Eq(ByRef(layer0TargetSettings)))) + .WillOnce(Return(std::optional())); + EXPECT_CALL(*mLayers[1].mLayerFE, prepareClientComposition(Eq(ByRef(layer1TargetSettings)))) + .WillOnce(Return(std::optional())); + EXPECT_CALL(*mLayers[2].mLayerFE, prepareClientComposition(Eq(ByRef(layer2TargetSettings)))) + .WillOnce(Return(std::optional())); - static_cast(mOutput.generateClientCompositionRequestsHelper(true /* supportsProtectedContent */, - kDisplayDataspace)); + static_cast( + mOutput.generateClientCompositionRequestsHelper(true /* supportsProtectedContent */, + kDisplayDataspace)); } TEST_F(OutputUpdateAndWriteCompositionStateTest, noBackgroundBlurWhenOpaque) { @@ -4857,14 +4706,12 @@ TEST_F(OutputUpdateAndWriteCompositionStateTest, noBackgroundBlurWhenOpaque) { EXPECT_CALL(*layer1.outputLayer, writeStateToHWC(/*includeGeometry*/ false, /*skipLayer*/ false, z++, /*zIsOverridden*/ false, /*isPeekingThrough*/ false)); - EXPECT_CALL(*layer1.outputLayer, requiresClientComposition()) - .WillRepeatedly(Return(false)); + EXPECT_CALL(*layer1.outputLayer, requiresClientComposition()).WillRepeatedly(Return(false)); EXPECT_CALL(*layer2.outputLayer, updateCompositionState(false, false, ui::Transform::ROT_0)); EXPECT_CALL(*layer2.outputLayer, writeStateToHWC(/*includeGeometry*/ false, /*skipLayer*/ false, z++, /*zIsOverridden*/ false, /*isPeekingThrough*/ false)); - EXPECT_CALL(*layer2.outputLayer, requiresClientComposition()) - .WillRepeatedly(Return(false)); + EXPECT_CALL(*layer2.outputLayer, requiresClientComposition()).WillRepeatedly(Return(false)); layer2.layerFEState.backgroundBlurRadius = 10; layer2.layerFEState.isOpaque = true; @@ -4893,20 +4740,17 @@ TEST_F(OutputUpdateAndWriteCompositionStateTest, handlesBackgroundBlurRequests) EXPECT_CALL(*layer1.outputLayer, writeStateToHWC(/*includeGeometry*/ false, /*skipLayer*/ false, z++, /*zIsOverridden*/ false, /*isPeekingThrough*/ false)); - EXPECT_CALL(*layer1.outputLayer, requiresClientComposition()) - .WillRepeatedly(Return(false)); + EXPECT_CALL(*layer1.outputLayer, requiresClientComposition()).WillRepeatedly(Return(false)); EXPECT_CALL(*layer2.outputLayer, updateCompositionState(false, true, ui::Transform::ROT_0)); EXPECT_CALL(*layer2.outputLayer, writeStateToHWC(/*includeGeometry*/ false, /*skipLayer*/ false, z++, /*zIsOverridden*/ false, /*isPeekingThrough*/ false)); - EXPECT_CALL(*layer2.outputLayer, requiresClientComposition()) - .WillRepeatedly(Return(false)); + EXPECT_CALL(*layer2.outputLayer, requiresClientComposition()).WillRepeatedly(Return(false)); EXPECT_CALL(*layer3.outputLayer, updateCompositionState(false, false, ui::Transform::ROT_0)); EXPECT_CALL(*layer3.outputLayer, writeStateToHWC(/*includeGeometry*/ false, /*skipLayer*/ false, z++, /*zIsOverridden*/ false, /*isPeekingThrough*/ false)); - EXPECT_CALL(*layer3.outputLayer, requiresClientComposition()) - .WillRepeatedly(Return(false)); + EXPECT_CALL(*layer3.outputLayer, requiresClientComposition()).WillRepeatedly(Return(false)); layer2.layerFEState.backgroundBlurRadius = 10; layer2.layerFEState.isOpaque = false; @@ -4936,20 +4780,17 @@ TEST_F(OutputUpdateAndWriteCompositionStateTest, handlesBlurRegionRequests) { EXPECT_CALL(*layer1.outputLayer, writeStateToHWC(/*includeGeometry*/ false, /*skipLayer*/ false, z++, /*zIsOverridden*/ false, /*isPeekingThrough*/ false)); - EXPECT_CALL(*layer1.outputLayer, requiresClientComposition()) - .WillRepeatedly(Return(false)); + EXPECT_CALL(*layer1.outputLayer, requiresClientComposition()).WillRepeatedly(Return(false)); EXPECT_CALL(*layer2.outputLayer, updateCompositionState(false, true, ui::Transform::ROT_0)); EXPECT_CALL(*layer2.outputLayer, writeStateToHWC(/*includeGeometry*/ false, /*skipLayer*/ false, z++, /*zIsOverridden*/ false, /*isPeekingThrough*/ false)); - EXPECT_CALL(*layer2.outputLayer, requiresClientComposition()) - .WillRepeatedly(Return(false)); + EXPECT_CALL(*layer2.outputLayer, requiresClientComposition()).WillRepeatedly(Return(false)); EXPECT_CALL(*layer3.outputLayer, updateCompositionState(false, false, ui::Transform::ROT_0)); EXPECT_CALL(*layer3.outputLayer, writeStateToHWC(/*includeGeometry*/ false, /*skipLayer*/ false, z++, /*zIsOverridden*/ false, /*isPeekingThrough*/ false)); - EXPECT_CALL(*layer3.outputLayer, requiresClientComposition()) - .WillRepeatedly(Return(false)); + EXPECT_CALL(*layer3.outputLayer, requiresClientComposition()).WillRepeatedly(Return(false)); BlurRegion region; layer2.layerFEState.blurRegions.push_back(region); @@ -5018,12 +4859,13 @@ TEST_F(GenerateClientCompositionRequestsTest, handlesLandscapeModeSplitScreenReq false /* clearContent */, compositionengine::LayerFE::ClientCompositionTargetSettings::BlurSetting::Enabled, kLayerWhitePointNits, + false /* treat170mAsSrgb */, }; EXPECT_CALL(leftLayer.mOutputLayer, requiresClientComposition()).WillRepeatedly(Return(true)); EXPECT_CALL(leftLayer.mOutputLayer, needsFiltering()).WillRepeatedly(Return(false)); - EXPECT_CALL(*leftLayer.mLayerFE, prepareClientCompositionList(Eq(ByRef(leftLayerSettings)))) - .WillOnce(Return(std::vector({leftLayer.mLayerSettings}))); + EXPECT_CALL(*leftLayer.mLayerFE, prepareClientComposition(Eq(ByRef(leftLayerSettings)))) + .WillOnce(Return(std::optional(leftLayer.mLayerSettings))); compositionengine::LayerFE::ClientCompositionTargetSettings rightLayerSettings{ Region(Rect(1000, 0, 2000, 1000)), @@ -5036,16 +4878,17 @@ TEST_F(GenerateClientCompositionRequestsTest, handlesLandscapeModeSplitScreenReq false /* clearContent */, compositionengine::LayerFE::ClientCompositionTargetSettings::BlurSetting::Enabled, kLayerWhitePointNits, + false /* treat170mAsSrgb */, }; EXPECT_CALL(rightLayer.mOutputLayer, requiresClientComposition()).WillRepeatedly(Return(true)); EXPECT_CALL(rightLayer.mOutputLayer, needsFiltering()).WillRepeatedly(Return(false)); - EXPECT_CALL(*rightLayer.mLayerFE, prepareClientCompositionList(Eq(ByRef(rightLayerSettings)))) - .WillOnce(Return(std::vector({rightLayer.mLayerSettings}))); + EXPECT_CALL(*rightLayer.mLayerFE, prepareClientComposition(Eq(ByRef(rightLayerSettings)))) + .WillOnce(Return(std::optional(rightLayer.mLayerSettings))); constexpr bool supportsProtectedContent = true; - auto requests = - mOutput.generateClientCompositionRequestsHelper(supportsProtectedContent, kOutputDataspace); + auto requests = mOutput.generateClientCompositionRequestsHelper(supportsProtectedContent, + kOutputDataspace); ASSERT_EQ(2u, requests.size()); EXPECT_EQ(leftLayer.mLayerSettings, requests[0]); EXPECT_EQ(rightLayer.mLayerSettings, requests[1]); @@ -5069,6 +4912,7 @@ TEST_F(GenerateClientCompositionRequestsTest_ThreeLayers, false /* clearContent */, compositionengine::LayerFE::ClientCompositionTargetSettings::BlurSetting::Enabled, kLayerWhitePointNits, + false /* treat170mAsSrgb */, }; LayerFE::LayerSettings mShadowSettings; @@ -5079,11 +4923,12 @@ TEST_F(GenerateClientCompositionRequestsTest_ThreeLayers, EXPECT_CALL(mLayers[0].mOutputLayer, requiresClientComposition()).WillOnce(Return(false)); EXPECT_CALL(mLayers[1].mOutputLayer, requiresClientComposition()).WillOnce(Return(false)); - EXPECT_CALL(*mLayers[2].mLayerFE, prepareClientCompositionList(Eq(ByRef(layer2Settings)))) - .WillOnce(Return(std::vector({mShadowSettings}))); + EXPECT_CALL(*mLayers[2].mLayerFE, prepareClientComposition(Eq(ByRef(layer2Settings)))) + .WillOnce(Return(std::optional(mShadowSettings))); - auto requests = mOutput.generateClientCompositionRequestsHelper(false /* supportsProtectedContent */, - kDisplayDataspace); + auto requests = + mOutput.generateClientCompositionRequestsHelper(false /* supportsProtectedContent */, + kDisplayDataspace); ASSERT_EQ(1u, requests.size()); EXPECT_EQ(mShadowSettings, requests[0]); @@ -5097,9 +4942,6 @@ TEST_F(GenerateClientCompositionRequestsTest_ThreeLayers, const Region kPartialContentWithPartialShadowRegion = Region(kContentWithShadow).subtract(Rect(40, 40, 50, 80)); - LayerFE::LayerSettings mShadowSettings; - mShadowSettings.source.solidColor = {0.1f, 0.1f, 0.1f}; - mLayers[2].mOutputLayerState.visibleRegion = kPartialContentWithPartialShadowRegion; mLayers[2].mOutputLayerState.shadowRegion = kShadowRegion; @@ -5114,20 +4956,20 @@ TEST_F(GenerateClientCompositionRequestsTest_ThreeLayers, false /* clearContent */, compositionengine::LayerFE::ClientCompositionTargetSettings::BlurSetting::Enabled, kLayerWhitePointNits, + false /* treat170mAsSrgb */, }; EXPECT_CALL(mLayers[0].mOutputLayer, requiresClientComposition()).WillOnce(Return(false)); EXPECT_CALL(mLayers[1].mOutputLayer, requiresClientComposition()).WillOnce(Return(false)); - EXPECT_CALL(*mLayers[2].mLayerFE, prepareClientCompositionList(Eq(ByRef(layer2Settings)))) - .WillOnce(Return(std::vector( - {mShadowSettings, mLayers[2].mLayerSettings}))); + EXPECT_CALL(*mLayers[2].mLayerFE, prepareClientComposition(Eq(ByRef(layer2Settings)))) + .WillOnce(Return(std::optional(mLayers[2].mLayerSettings))); - auto requests = mOutput.generateClientCompositionRequestsHelper(false /* supportsProtectedContent */, - kDisplayDataspace); - ASSERT_EQ(2u, requests.size()); + auto requests = + mOutput.generateClientCompositionRequestsHelper(false /* supportsProtectedContent */, + kDisplayDataspace); + ASSERT_EQ(1u, requests.size()); - EXPECT_EQ(mShadowSettings, requests[0]); - EXPECT_EQ(mLayers[2].mLayerSettings, requests[1]); + EXPECT_EQ(mLayers[2].mLayerSettings, requests[0]); } } // namespace diff --git a/services/surfaceflinger/CompositionEngine/tests/RenderSurfaceTest.cpp b/services/surfaceflinger/CompositionEngine/tests/RenderSurfaceTest.cpp index e5f9ebfbebf6f281590a5c1e0754acb028203e44..83937a679ea16f55f01c3786d717d5d99d08a20f 100644 --- a/services/surfaceflinger/CompositionEngine/tests/RenderSurfaceTest.cpp +++ b/services/surfaceflinger/CompositionEngine/tests/RenderSurfaceTest.cpp @@ -61,8 +61,8 @@ public: StrictMock mRenderEngine; StrictMock mCompositionEngine; StrictMock mDisplay; - sp mNativeWindow = new StrictMock(); - sp mDisplaySurface = new StrictMock(); + sp mNativeWindow = sp>::make(); + sp mDisplaySurface = sp>::make(); impl::RenderSurface mSurface{mCompositionEngine, mDisplay, RenderSurfaceCreationArgsBuilder() .setDisplayWidth(DEFAULT_DISPLAY_WIDTH) @@ -109,7 +109,7 @@ TEST_F(RenderSurfaceTest, sizeReturnsConstructedSize) { */ TEST_F(RenderSurfaceTest, getClientTargetAcquireFenceForwardsCall) { - sp fence = new Fence(); + sp fence = sp::make(); EXPECT_CALL(*mDisplaySurface, getClientTargetAcquireFence()).WillOnce(ReturnRef(fence)); @@ -234,7 +234,7 @@ TEST_F(RenderSurfaceTest, prepareFrameHandlesNoComposition) { */ TEST_F(RenderSurfaceTest, dequeueBufferObtainsABuffer) { - sp buffer = new GraphicBuffer(); + sp buffer = sp::make(); EXPECT_CALL(*mNativeWindow, dequeueBuffer(_, _)) .WillOnce( @@ -253,7 +253,7 @@ TEST_F(RenderSurfaceTest, dequeueBufferObtainsABuffer) { TEST_F(RenderSurfaceTest, queueBufferHandlesNoClientComposition) { const auto buffer = std::make_shared< renderengine::impl:: - ExternalTexture>(new GraphicBuffer(), mRenderEngine, + ExternalTexture>(sp::make(), mRenderEngine, renderengine::impl::ExternalTexture::Usage::READABLE | renderengine::impl::ExternalTexture::Usage::WRITEABLE); mSurface.mutableTextureForTest() = buffer; @@ -271,8 +271,9 @@ TEST_F(RenderSurfaceTest, queueBufferHandlesNoClientComposition) { } TEST_F(RenderSurfaceTest, queueBufferHandlesClientComposition) { - const auto buffer = std::make_shared(new GraphicBuffer(), - mRenderEngine, false); + const auto buffer = + std::make_shared(sp::make(), + mRenderEngine, false); mSurface.mutableTextureForTest() = buffer; impl::OutputCompositionState state; @@ -290,8 +291,9 @@ TEST_F(RenderSurfaceTest, queueBufferHandlesClientComposition) { } TEST_F(RenderSurfaceTest, queueBufferHandlesFlipClientTargetRequest) { - const auto buffer = std::make_shared(new GraphicBuffer(), - mRenderEngine, false); + const auto buffer = + std::make_shared(sp::make(), + mRenderEngine, false); mSurface.mutableTextureForTest() = buffer; impl::OutputCompositionState state; @@ -309,7 +311,7 @@ TEST_F(RenderSurfaceTest, queueBufferHandlesFlipClientTargetRequest) { } TEST_F(RenderSurfaceTest, queueBufferHandlesFlipClientTargetRequestWithNoBufferYetDequeued) { - sp buffer = new GraphicBuffer(); + sp buffer = sp::make(); impl::OutputCompositionState state; state.usesClientComposition = false; @@ -329,8 +331,9 @@ TEST_F(RenderSurfaceTest, queueBufferHandlesFlipClientTargetRequestWithNoBufferY } TEST_F(RenderSurfaceTest, queueBufferHandlesNativeWindowQueueBufferFailureOnVirtualDisplay) { - const auto buffer = std::make_shared(new GraphicBuffer(), - mRenderEngine, false); + const auto buffer = + std::make_shared(sp::make(), + mRenderEngine, false); mSurface.mutableTextureForTest() = buffer; impl::OutputCompositionState state; @@ -359,17 +362,5 @@ TEST_F(RenderSurfaceTest, onPresentDisplayCompletedForwardsSignal) { mSurface.onPresentDisplayCompleted(); } -/* - * RenderSurface::flip() - */ - -TEST_F(RenderSurfaceTest, flipForwardsSignal) { - mSurface.setPageFlipCountForTest(500); - - mSurface.flip(); - - EXPECT_EQ(501u, mSurface.getPageFlipCount()); -} - } // namespace } // namespace android::compositionengine diff --git a/services/surfaceflinger/CompositionEngine/tests/planner/CachedSetTest.cpp b/services/surfaceflinger/CompositionEngine/tests/planner/CachedSetTest.cpp index 0e9db369e86ead5d7a748d51a2fb98aea3a59b4d..bd030d090fceb3cbc05aff1e8a45e793f05fb506 100644 --- a/services/surfaceflinger/CompositionEngine/tests/planner/CachedSetTest.cpp +++ b/services/surfaceflinger/CompositionEngine/tests/planner/CachedSetTest.cpp @@ -28,8 +28,6 @@ #include #include -#include "tests/TestUtils.h" - namespace android::compositionengine { using namespace std::chrono_literals; @@ -345,19 +343,18 @@ TEST_F(CachedSetTest, renderUnsecureOutput) { CachedSet cachedSet(layer1); cachedSet.append(CachedSet(layer2)); - std::vector clientCompList1; - clientCompList1.push_back({}); - clientCompList1[0].alpha = 0.5f; + std::optional clientComp1; + clientComp1.emplace(); + clientComp1->alpha = 0.5f; - std::vector clientCompList2; - clientCompList2.push_back({}); - clientCompList2[0].alpha = 0.75f; + std::optional clientComp2; + clientComp2.emplace(); + clientComp2->alpha = 0.75f; - const auto drawLayers = - [&](const renderengine::DisplaySettings& displaySettings, - const std::vector& layers, - const std::shared_ptr&, const bool, - base::unique_fd&&) -> std::future { + const auto drawLayers = [&](const renderengine::DisplaySettings& displaySettings, + const std::vector& layers, + const std::shared_ptr&, const bool, + base::unique_fd&&) -> ftl::Future { EXPECT_EQ(mOutputState.framebufferSpace.getContent(), displaySettings.physicalDisplay); EXPECT_EQ(mOutputState.layerStackSpace.getContent(), displaySettings.clip); EXPECT_EQ(ui::Transform::toRotationFlags(mOutputState.framebufferSpace.getOrientation()), @@ -365,15 +362,13 @@ TEST_F(CachedSetTest, renderUnsecureOutput) { EXPECT_EQ(0.5f, layers[0].alpha); EXPECT_EQ(0.75f, layers[1].alpha); EXPECT_EQ(ui::Dataspace::SRGB, displaySettings.outputDataspace); - return futureOf({NO_ERROR, base::unique_fd()}); + return ftl::yield(Fence::NO_FENCE); }; - EXPECT_CALL(*layerFE1, - prepareClientCompositionList(ClientCompositionTargetSettingsSecureEq(false))) - .WillOnce(Return(clientCompList1)); - EXPECT_CALL(*layerFE2, - prepareClientCompositionList(ClientCompositionTargetSettingsSecureEq(false))) - .WillOnce(Return(clientCompList2)); + EXPECT_CALL(*layerFE1, prepareClientComposition(ClientCompositionTargetSettingsSecureEq(false))) + .WillOnce(Return(clientComp1)); + EXPECT_CALL(*layerFE2, prepareClientComposition(ClientCompositionTargetSettingsSecureEq(false))) + .WillOnce(Return(clientComp2)); EXPECT_CALL(mRenderEngine, drawLayers(_, _, _, _, _)).WillOnce(Invoke(drawLayers)); mOutputState.isSecure = false; cachedSet.render(mRenderEngine, mTexturePool, mOutputState, true); @@ -397,19 +392,18 @@ TEST_F(CachedSetTest, renderSecureOutput) { CachedSet cachedSet(layer1); cachedSet.append(CachedSet(layer2)); - std::vector clientCompList1; - clientCompList1.push_back({}); - clientCompList1[0].alpha = 0.5f; + std::optional clientComp1; + clientComp1.emplace(); + clientComp1->alpha = 0.5f; - std::vector clientCompList2; - clientCompList2.push_back({}); - clientCompList2[0].alpha = 0.75f; + std::optional clientComp2; + clientComp2.emplace(); + clientComp2->alpha = 0.75f; - const auto drawLayers = - [&](const renderengine::DisplaySettings& displaySettings, - const std::vector& layers, - const std::shared_ptr&, const bool, - base::unique_fd&&) -> std::future { + const auto drawLayers = [&](const renderengine::DisplaySettings& displaySettings, + const std::vector& layers, + const std::shared_ptr&, const bool, + base::unique_fd&&) -> ftl::Future { EXPECT_EQ(mOutputState.framebufferSpace.getContent(), displaySettings.physicalDisplay); EXPECT_EQ(mOutputState.layerStackSpace.getContent(), displaySettings.clip); EXPECT_EQ(ui::Transform::toRotationFlags(mOutputState.framebufferSpace.getOrientation()), @@ -418,15 +412,13 @@ TEST_F(CachedSetTest, renderSecureOutput) { EXPECT_EQ(0.75f, layers[1].alpha); EXPECT_EQ(ui::Dataspace::SRGB, displaySettings.outputDataspace); - return futureOf({NO_ERROR, base::unique_fd()}); + return ftl::yield(Fence::NO_FENCE); }; - EXPECT_CALL(*layerFE1, - prepareClientCompositionList(ClientCompositionTargetSettingsSecureEq(true))) - .WillOnce(Return(clientCompList1)); - EXPECT_CALL(*layerFE2, - prepareClientCompositionList(ClientCompositionTargetSettingsSecureEq(true))) - .WillOnce(Return(clientCompList2)); + EXPECT_CALL(*layerFE1, prepareClientComposition(ClientCompositionTargetSettingsSecureEq(true))) + .WillOnce(Return(clientComp1)); + EXPECT_CALL(*layerFE2, prepareClientComposition(ClientCompositionTargetSettingsSecureEq(true))) + .WillOnce(Return(clientComp2)); EXPECT_CALL(mRenderEngine, drawLayers(_, _, _, _, _)).WillOnce(Invoke(drawLayers)); mOutputState.isSecure = true; cachedSet.render(mRenderEngine, mTexturePool, mOutputState, true); @@ -450,31 +442,30 @@ TEST_F(CachedSetTest, renderWhitePoint) { CachedSet cachedSet(layer1); cachedSet.append(CachedSet(layer2)); - std::vector clientCompList1; - clientCompList1.push_back({}); + std::optional clientComp1; + clientComp1.emplace(); - std::vector clientCompList2; - clientCompList2.push_back({}); + std::optional clientComp2; + clientComp2.emplace(); mOutputState.displayBrightnessNits = 400.f; - const auto drawLayers = - [&](const renderengine::DisplaySettings& displaySettings, - const std::vector&, - const std::shared_ptr&, const bool, - base::unique_fd&&) -> std::future { + const auto drawLayers = [&](const renderengine::DisplaySettings& displaySettings, + const std::vector&, + const std::shared_ptr&, const bool, + base::unique_fd&&) -> ftl::Future { EXPECT_EQ(mOutputState.displayBrightnessNits, displaySettings.targetLuminanceNits); - return futureOf({NO_ERROR, base::unique_fd()}); + return ftl::yield(Fence::NO_FENCE); }; EXPECT_CALL(*layerFE1, - prepareClientCompositionList(ClientCompositionTargetSettingsWhitePointEq( + prepareClientComposition(ClientCompositionTargetSettingsWhitePointEq( mOutputState.displayBrightnessNits))) - .WillOnce(Return(clientCompList1)); + .WillOnce(Return(clientComp1)); EXPECT_CALL(*layerFE2, - prepareClientCompositionList(ClientCompositionTargetSettingsWhitePointEq( + prepareClientComposition(ClientCompositionTargetSettingsWhitePointEq( mOutputState.displayBrightnessNits))) - .WillOnce(Return(clientCompList2)); + .WillOnce(Return(clientComp2)); EXPECT_CALL(mRenderEngine, drawLayers(_, _, _, _, _)).WillOnce(Invoke(drawLayers)); mOutputState.isSecure = true; cachedSet.render(mRenderEngine, mTexturePool, mOutputState, true); @@ -501,31 +492,30 @@ TEST_F(CachedSetTest, renderWhitePointNoColorTransform) { CachedSet cachedSet(layer1); cachedSet.append(CachedSet(layer2)); - std::vector clientCompList1; - clientCompList1.push_back({}); + std::optional clientComp1; + clientComp1.emplace(); - std::vector clientCompList2; - clientCompList2.push_back({}); + std::optional clientComp2; + clientComp2.emplace(); mOutputState.displayBrightnessNits = 400.f; - const auto drawLayers = - [&](const renderengine::DisplaySettings& displaySettings, - const std::vector&, - const std::shared_ptr&, const bool, - base::unique_fd&&) -> std::future { + const auto drawLayers = [&](const renderengine::DisplaySettings& displaySettings, + const std::vector&, + const std::shared_ptr&, const bool, + base::unique_fd&&) -> ftl::Future { EXPECT_EQ(mOutputState.displayBrightnessNits, displaySettings.targetLuminanceNits); - return futureOf({NO_ERROR, base::unique_fd()}); + return ftl::yield(Fence::NO_FENCE); }; EXPECT_CALL(*layerFE1, - prepareClientCompositionList(ClientCompositionTargetSettingsWhitePointEq( + prepareClientComposition(ClientCompositionTargetSettingsWhitePointEq( mOutputState.displayBrightnessNits))) - .WillOnce(Return(clientCompList1)); + .WillOnce(Return(clientComp1)); EXPECT_CALL(*layerFE2, - prepareClientCompositionList(ClientCompositionTargetSettingsWhitePointEq( + prepareClientComposition(ClientCompositionTargetSettingsWhitePointEq( mOutputState.displayBrightnessNits))) - .WillOnce(Return(clientCompList2)); + .WillOnce(Return(clientComp2)); EXPECT_CALL(mRenderEngine, drawLayers(_, _, _, _, _)).WillOnce(Invoke(drawLayers)); mOutputState.isSecure = true; cachedSet.render(mRenderEngine, mTexturePool, mOutputState, false); @@ -549,21 +539,20 @@ TEST_F(CachedSetTest, rendersWithOffsetFramebufferContent) { CachedSet cachedSet(layer1); cachedSet.append(CachedSet(layer2)); - std::vector clientCompList1; - clientCompList1.push_back({}); - clientCompList1[0].alpha = 0.5f; + std::optional clientComp1; + clientComp1.emplace(); + clientComp1->alpha = 0.5f; - std::vector clientCompList2; - clientCompList2.push_back({}); - clientCompList2[0].alpha = 0.75f; + std::optional clientComp2; + clientComp2.emplace(); + clientComp2->alpha = 0.75f; mOutputState.framebufferSpace = ProjectionSpace(ui::Size(10, 20), Rect(2, 3, 10, 5)); - const auto drawLayers = - [&](const renderengine::DisplaySettings& displaySettings, - const std::vector& layers, - const std::shared_ptr&, const bool, - base::unique_fd&&) -> std::future { + const auto drawLayers = [&](const renderengine::DisplaySettings& displaySettings, + const std::vector& layers, + const std::shared_ptr&, const bool, + base::unique_fd&&) -> ftl::Future { EXPECT_EQ(mOutputState.framebufferSpace.getContent(), displaySettings.physicalDisplay); EXPECT_EQ(mOutputState.layerStackSpace.getContent(), displaySettings.clip); EXPECT_EQ(ui::Transform::toRotationFlags(mOutputState.framebufferSpace.getOrientation()), @@ -572,11 +561,11 @@ TEST_F(CachedSetTest, rendersWithOffsetFramebufferContent) { EXPECT_EQ(0.75f, layers[1].alpha); EXPECT_EQ(ui::Dataspace::SRGB, displaySettings.outputDataspace); - return futureOf({NO_ERROR, base::unique_fd()}); + return ftl::yield(Fence::NO_FENCE); }; - EXPECT_CALL(*layerFE1, prepareClientCompositionList(_)).WillOnce(Return(clientCompList1)); - EXPECT_CALL(*layerFE2, prepareClientCompositionList(_)).WillOnce(Return(clientCompList2)); + EXPECT_CALL(*layerFE1, prepareClientComposition(_)).WillOnce(Return(clientComp1)); + EXPECT_CALL(*layerFE2, prepareClientComposition(_)).WillOnce(Return(clientComp2)); EXPECT_CALL(mRenderEngine, drawLayers(_, _, _, _, _)).WillOnce(Invoke(drawLayers)); cachedSet.render(mRenderEngine, mTexturePool, mOutputState, true); expectReadyBuffer(cachedSet); @@ -588,6 +577,20 @@ TEST_F(CachedSetTest, rendersWithOffsetFramebufferContent) { cachedSet.append(CachedSet(layer3)); } +TEST_F(CachedSetTest, cachingHintIncludesLayersByDefault) { + CachedSet cachedSet(*mTestLayers[0]->cachedSetLayer.get()); + EXPECT_FALSE(cachedSet.cachingHintExcludesLayers()); +} + +TEST_F(CachedSetTest, cachingHintExcludesLayersWhenDisabled) { + CachedSet::Layer& layer1 = *mTestLayers[0]->cachedSetLayer.get(); + mTestLayers[0]->layerFECompositionState.cachingHint = gui::CachingHint::Disabled; + mTestLayers[0]->layerState->update(&mTestLayers[0]->outputLayer); + + CachedSet cachedSet(layer1); + EXPECT_TRUE(cachedSet.cachingHintExcludesLayers()); +} + TEST_F(CachedSetTest, holePunch_requiresBuffer) { CachedSet::Layer& layer1 = *mTestLayers[0]->cachedSetLayer.get(); auto& layerFECompositionState = mTestLayers[0]->layerFECompositionState; @@ -659,6 +662,26 @@ TEST_F(CachedSetTest, holePunch_requiresNonBT601_625) { EXPECT_FALSE(cachedSet.requiresHolePunch()); } +TEST_F(CachedSetTest, holePunch_requiresNonHdrWithExtendedBrightness) { + const auto dataspace = static_cast(ui::Dataspace::STANDARD_DCI_P3 | + ui::Dataspace::TRANSFER_SRGB | + ui::Dataspace::RANGE_EXTENDED); + mTestLayers[0]->outputLayerCompositionState.dataspace = dataspace; + mTestLayers[0]->layerFECompositionState.currentHdrSdrRatio = 5.f; + mTestLayers[0]->layerState->update(&mTestLayers[0]->outputLayer); + + CachedSet::Layer& layer = *mTestLayers[0]->cachedSetLayer.get(); + auto& layerFECompositionState = mTestLayers[0]->layerFECompositionState; + layerFECompositionState.buffer = sp::make(); + layerFECompositionState.blendMode = hal::BlendMode::NONE; + sp layerFE = mTestLayers[0]->layerFE; + + CachedSet cachedSet(layer); + EXPECT_CALL(*layerFE, hasRoundedCorners()).WillRepeatedly(Return(true)); + + EXPECT_FALSE(cachedSet.requiresHolePunch()); +} + TEST_F(CachedSetTest, holePunch_requiresNoBlending) { CachedSet::Layer& layer = *mTestLayers[0]->cachedSetLayer.get(); auto& layerFECompositionState = mTestLayers[0]->layerFECompositionState; @@ -773,28 +796,27 @@ TEST_F(CachedSetTest, addHolePunch) { cachedSet.addHolePunchLayerIfFeasible(layer3, true); - std::vector clientCompList1; - clientCompList1.push_back({}); - std::vector clientCompList2; - clientCompList2.push_back({}); - std::vector clientCompList3; - clientCompList3.push_back({}); + std::optional clientComp1; + clientComp1.emplace(); + std::optional clientComp2; + clientComp2.emplace(); + std::optional clientComp3; + clientComp3.emplace(); - clientCompList3[0].source.buffer.buffer = + clientComp3->source.buffer.buffer = std::make_shared(1U /*width*/, 1U /*height*/, 1ULL /* bufferId */, HAL_PIXEL_FORMAT_RGBA_8888, 0ULL /*usage*/); - EXPECT_CALL(*layerFE1, prepareClientCompositionList(_)).WillOnce(Return(clientCompList1)); - EXPECT_CALL(*layerFE2, prepareClientCompositionList(_)).WillOnce(Return(clientCompList2)); - EXPECT_CALL(*layerFE3, prepareClientCompositionList(_)).WillOnce(Return(clientCompList3)); + EXPECT_CALL(*layerFE1, prepareClientComposition(_)).WillOnce(Return(clientComp1)); + EXPECT_CALL(*layerFE2, prepareClientComposition(_)).WillOnce(Return(clientComp2)); + EXPECT_CALL(*layerFE3, prepareClientComposition(_)).WillOnce(Return(clientComp3)); - const auto drawLayers = - [&](const renderengine::DisplaySettings&, - const std::vector& layers, - const std::shared_ptr&, const bool, - base::unique_fd&&) -> std::future { + const auto drawLayers = [&](const renderengine::DisplaySettings&, + const std::vector& layers, + const std::shared_ptr&, const bool, + base::unique_fd&&) -> ftl::Future { // If the highlight layer is enabled, it will increase the size by 1. // We're interested in the third layer either way. EXPECT_GE(layers.size(), 4u); @@ -814,7 +836,7 @@ TEST_F(CachedSetTest, addHolePunch) { EXPECT_EQ(1.0f, holePunchBackgroundSettings.alpha); } - return futureOf({NO_ERROR, base::unique_fd()}); + return ftl::yield(Fence::NO_FENCE); }; EXPECT_CALL(mRenderEngine, drawLayers(_, _, _, _, _)).WillOnce(Invoke(drawLayers)); @@ -822,7 +844,7 @@ TEST_F(CachedSetTest, addHolePunch) { } TEST_F(CachedSetTest, addHolePunch_noBuffer) { - // Same as addHolePunch, except that clientCompList3 does not contain a + // Same as addHolePunch, except that clientComp3 does not contain a // buffer. This imitates the case where the buffer had protected content, so // BufferLayer did not add it to the LayerSettings. This should not assert. mTestLayers[0]->outputLayerCompositionState.displayFrame = Rect(0, 0, 5, 5); @@ -840,22 +862,21 @@ TEST_F(CachedSetTest, addHolePunch_noBuffer) { cachedSet.addHolePunchLayerIfFeasible(layer3, true); - std::vector clientCompList1; - clientCompList1.push_back({}); - std::vector clientCompList2; - clientCompList2.push_back({}); - std::vector clientCompList3; - clientCompList3.push_back({}); - - EXPECT_CALL(*layerFE1, prepareClientCompositionList(_)).WillOnce(Return(clientCompList1)); - EXPECT_CALL(*layerFE2, prepareClientCompositionList(_)).WillOnce(Return(clientCompList2)); - EXPECT_CALL(*layerFE3, prepareClientCompositionList(_)).WillOnce(Return(clientCompList3)); - - const auto drawLayers = - [&](const renderengine::DisplaySettings&, - const std::vector& layers, - const std::shared_ptr&, const bool, - base::unique_fd&&) -> std::future { + std::optional clientComp1; + clientComp1.emplace(); + std::optional clientComp2; + clientComp2.emplace(); + std::optional clientComp3; + clientComp3.emplace(); + + EXPECT_CALL(*layerFE1, prepareClientComposition(_)).WillOnce(Return(clientComp1)); + EXPECT_CALL(*layerFE2, prepareClientComposition(_)).WillOnce(Return(clientComp2)); + EXPECT_CALL(*layerFE3, prepareClientComposition(_)).WillOnce(Return(clientComp3)); + + const auto drawLayers = [&](const renderengine::DisplaySettings&, + const std::vector& layers, + const std::shared_ptr&, const bool, + base::unique_fd&&) -> ftl::Future { // If the highlight layer is enabled, it will increase the size by 1. // We're interested in the third layer either way. EXPECT_GE(layers.size(), 4u); @@ -876,7 +897,7 @@ TEST_F(CachedSetTest, addHolePunch_noBuffer) { EXPECT_EQ(1.0f, holePunchBackgroundSettings.alpha); } - return futureOf({NO_ERROR, base::unique_fd()}); + return ftl::yield(Fence::NO_FENCE); }; EXPECT_CALL(mRenderEngine, drawLayers(_, _, _, _, _)).WillOnce(Invoke(drawLayers)); @@ -974,40 +995,39 @@ TEST_F(CachedSetTest, addBlur) { cachedSet.addBackgroundBlurLayer(layer3); - std::vector clientCompList1; - clientCompList1.push_back({}); - std::vector clientCompList2; - clientCompList2.push_back({}); - std::vector clientCompList3; - clientCompList3.push_back({}); + std::optional clientComp1; + clientComp1.emplace(); + std::optional clientComp2; + clientComp2.emplace(); + std::optional clientComp3; + clientComp3.emplace(); - clientCompList3[0].source.buffer.buffer = + clientComp3->source.buffer.buffer = std::make_shared(1U /*width*/, 1U /*height*/, 1ULL /* bufferId */, HAL_PIXEL_FORMAT_RGBA_8888, 0ULL /*usage*/); EXPECT_CALL(*layerFE1, - prepareClientCompositionList(ClientCompositionTargetSettingsBlurSettingsEq( + prepareClientComposition(ClientCompositionTargetSettingsBlurSettingsEq( compositionengine::LayerFE::ClientCompositionTargetSettings::BlurSetting:: Enabled))) - .WillOnce(Return(clientCompList1)); + .WillOnce(Return(clientComp1)); EXPECT_CALL(*layerFE2, - prepareClientCompositionList(ClientCompositionTargetSettingsBlurSettingsEq( + prepareClientComposition(ClientCompositionTargetSettingsBlurSettingsEq( compositionengine::LayerFE::ClientCompositionTargetSettings::BlurSetting:: Enabled))) - .WillOnce(Return(clientCompList2)); + .WillOnce(Return(clientComp2)); EXPECT_CALL(*layerFE3, - prepareClientCompositionList(ClientCompositionTargetSettingsBlurSettingsEq( + prepareClientComposition(ClientCompositionTargetSettingsBlurSettingsEq( compositionengine::LayerFE::ClientCompositionTargetSettings::BlurSetting:: BackgroundBlurOnly))) - .WillOnce(Return(clientCompList3)); + .WillOnce(Return(clientComp3)); - const auto drawLayers = - [&](const renderengine::DisplaySettings&, - const std::vector& layers, - const std::shared_ptr&, const bool, - base::unique_fd&&) -> std::future { + const auto drawLayers = [&](const renderengine::DisplaySettings&, + const std::vector& layers, + const std::shared_ptr&, const bool, + base::unique_fd&&) -> ftl::Future { // If the highlight layer is enabled, it will increase the size by 1. // We're interested in the third layer either way. EXPECT_GE(layers.size(), 3u); @@ -1016,7 +1036,7 @@ TEST_F(CachedSetTest, addBlur) { EXPECT_EQ(half3(0.0f, 0.0f, 0.0f), blurSettings.source.solidColor); EXPECT_EQ(0.0f, blurSettings.alpha); - return futureOf({NO_ERROR, base::unique_fd()}); + return ftl::yield(Fence::NO_FENCE); }; EXPECT_CALL(mRenderEngine, drawLayers(_, _, _, _, _)).WillOnce(Invoke(drawLayers)); diff --git a/services/surfaceflinger/CompositionEngine/tests/planner/FlattenerTest.cpp b/services/surfaceflinger/CompositionEngine/tests/planner/FlattenerTest.cpp index 96021ec10472f329229511f3eec89c2907a17bcd..778a0a8c9322467d0616f39a8985f05d8ffaf69a 100644 --- a/services/surfaceflinger/CompositionEngine/tests/planner/FlattenerTest.cpp +++ b/services/surfaceflinger/CompositionEngine/tests/planner/FlattenerTest.cpp @@ -27,8 +27,6 @@ #include #include -#include "tests/TestUtils.h" - namespace android::compositionengine { using namespace std::chrono_literals; using impl::planner::CachedSet; @@ -108,11 +106,12 @@ void FlattenerTest::SetUp() { testLayer->outputLayerCompositionState.visibleRegion = Region(Rect(pos + 1, pos + 1, pos + 2, pos + 2)); + const auto kUsageFlags = + static_cast(GRALLOC_USAGE_SW_READ_OFTEN | GRALLOC_USAGE_SW_WRITE_OFTEN | + GRALLOC_USAGE_HW_RENDER | GRALLOC_USAGE_HW_TEXTURE); testLayer->layerFECompositionState.buffer = - new GraphicBuffer(100, 100, HAL_PIXEL_FORMAT_RGBA_8888, 1, - GRALLOC_USAGE_SW_READ_OFTEN | GRALLOC_USAGE_SW_WRITE_OFTEN | - GRALLOC_USAGE_HW_RENDER | GRALLOC_USAGE_HW_TEXTURE, - "output"); + sp::make(100u, 100u, HAL_PIXEL_FORMAT_RGBA_8888, 1u, kUsageFlags, + "output"); testLayer->layerFE = sp::make(); @@ -123,12 +122,11 @@ void FlattenerTest::SetUp() { EXPECT_CALL(*testLayer->layerFE, getCompositionState) .WillRepeatedly(Return(&testLayer->layerFECompositionState)); - std::vector clientCompositionList = { - LayerFE::LayerSettings{}, - }; + std::optional clientComposition; + clientComposition.emplace(); - EXPECT_CALL(*testLayer->layerFE, prepareClientCompositionList) - .WillRepeatedly(Return(clientCompositionList)); + EXPECT_CALL(*testLayer->layerFE, prepareClientComposition) + .WillRepeatedly(Return(clientComposition)); EXPECT_CALL(testLayer->outputLayer, getLayerFE) .WillRepeatedly(ReturnRef(*testLayer->layerFE)); EXPECT_CALL(testLayer->outputLayer, getState) @@ -171,8 +169,7 @@ void FlattenerTest::initializeFlattener(const std::vector& la void FlattenerTest::expectAllLayersFlattened(const std::vector& layers) { // layers would be flattened but the buffer would not be overridden EXPECT_CALL(mRenderEngine, drawLayers(_, _, _, _, _)) - .WillOnce(Return(ByMove( - futureOf({NO_ERROR, base::unique_fd()})))); + .WillOnce(Return(ByMove(ftl::yield(Fence::NO_FENCE)))); initializeOverrideBuffer(layers); EXPECT_EQ(getNonBufferHash(layers), @@ -423,8 +420,7 @@ TEST_F(FlattenerTest, flattenLayers_BufferUpdateToFlatten) { layerState1->resetFramesSinceBufferUpdate(); EXPECT_CALL(mRenderEngine, drawLayers(_, _, _, _, _)) - .WillOnce(Return(ByMove( - futureOf({NO_ERROR, base::unique_fd()})))); + .WillOnce(Return(ByMove(ftl::yield(Fence::NO_FENCE)))); initializeOverrideBuffer(layers); EXPECT_EQ(getNonBufferHash(layers), mFlattener->flattenLayers(layers, getNonBufferHash(layers), mTime)); @@ -447,8 +443,7 @@ TEST_F(FlattenerTest, flattenLayers_BufferUpdateToFlatten) { mTime += 200ms; EXPECT_CALL(mRenderEngine, drawLayers(_, _, _, _, _)) - .WillOnce(Return(ByMove( - futureOf({NO_ERROR, base::unique_fd()})))); + .WillOnce(Return(ByMove(ftl::yield(Fence::NO_FENCE)))); initializeOverrideBuffer(layers); EXPECT_NE(getNonBufferHash(layers), mFlattener->flattenLayers(layers, getNonBufferHash(layers), mTime)); @@ -500,8 +495,7 @@ TEST_F(FlattenerTest, flattenLayers_BufferUpdateForMiddleLayer) { layerState3->resetFramesSinceBufferUpdate(); EXPECT_CALL(mRenderEngine, drawLayers(_, _, _, _, _)) - .WillOnce(Return(ByMove( - futureOf({NO_ERROR, base::unique_fd()})))); + .WillOnce(Return(ByMove(ftl::yield(Fence::NO_FENCE)))); initializeOverrideBuffer(layers); EXPECT_EQ(getNonBufferHash(layers), mFlattener->flattenLayers(layers, getNonBufferHash(layers), mTime)); @@ -515,8 +509,7 @@ TEST_F(FlattenerTest, flattenLayers_BufferUpdateForMiddleLayer) { // Layers 1 and 2 will be flattened a new drawFrame would be called for Layer4 and Layer5 EXPECT_CALL(mRenderEngine, drawLayers(_, _, _, _, _)) - .WillOnce(Return(ByMove( - futureOf({NO_ERROR, base::unique_fd()})))); + .WillOnce(Return(ByMove(ftl::yield(Fence::NO_FENCE)))); initializeOverrideBuffer(layers); EXPECT_NE(getNonBufferHash(layers), mFlattener->flattenLayers(layers, getNonBufferHash(layers), mTime)); @@ -545,8 +538,7 @@ TEST_F(FlattenerTest, flattenLayers_BufferUpdateForMiddleLayer) { layerState3->incrementFramesSinceBufferUpdate(); mTime += 200ms; EXPECT_CALL(mRenderEngine, drawLayers(_, _, _, _, _)) - .WillOnce(Return(ByMove( - futureOf({NO_ERROR, base::unique_fd()})))); + .WillOnce(Return(ByMove(ftl::yield(Fence::NO_FENCE)))); initializeOverrideBuffer(layers); EXPECT_NE(getNonBufferHash(layers), mFlattener->flattenLayers(layers, getNonBufferHash(layers), mTime)); @@ -601,8 +593,7 @@ TEST_F(FlattenerTest, flattenLayers_pipRequiresRoundedCorners) { // This will render a CachedSet. EXPECT_CALL(mRenderEngine, drawLayers(_, _, _, _, _)) - .WillOnce(Return(ByMove( - futureOf({NO_ERROR, base::unique_fd()})))); + .WillOnce(Return(ByMove(ftl::yield(Fence::NO_FENCE)))); mFlattener->renderCachedSets(mOutputState, std::nullopt, true); // We've rendered a CachedSet, but we haven't merged it in. @@ -637,16 +628,15 @@ TEST_F(FlattenerTest, flattenLayers_pip) { EXPECT_CALL(*mTestLayers[2]->layerFE, hasRoundedCorners()).WillRepeatedly(Return(true)); - std::vector clientCompositionList = { - LayerFE::LayerSettings{}, - }; - clientCompositionList[0].source.buffer.buffer = std::make_shared< + std::optional clientComposition; + clientComposition.emplace(); + clientComposition->source.buffer.buffer = std::make_shared< renderengine::impl::ExternalTexture>(mTestLayers[2]->layerFECompositionState.buffer, mRenderEngine, renderengine::impl::ExternalTexture::Usage:: READABLE); - EXPECT_CALL(*mTestLayers[2]->layerFE, prepareClientCompositionList(_)) - .WillOnce(Return(clientCompositionList)); + EXPECT_CALL(*mTestLayers[2]->layerFE, prepareClientComposition(_)) + .WillOnce(Return(clientComposition)); const std::vector layers = { layerState1.get(), @@ -667,8 +657,7 @@ TEST_F(FlattenerTest, flattenLayers_pip) { // This will render a CachedSet. EXPECT_CALL(mRenderEngine, drawLayers(_, _, _, _, _)) - .WillOnce(Return(ByMove( - futureOf({NO_ERROR, base::unique_fd()})))); + .WillOnce(Return(ByMove(ftl::yield(Fence::NO_FENCE)))); mFlattener->renderCachedSets(mOutputState, std::nullopt, true); // We've rendered a CachedSet, but we haven't merged it in. @@ -711,16 +700,15 @@ TEST_F(FlattenerTest, flattenLayers_holePunchSingleLayer) { EXPECT_CALL(*mTestLayers[1]->layerFE, hasRoundedCorners()).WillRepeatedly(Return(true)); - std::vector clientCompositionList = { - LayerFE::LayerSettings{}, - }; - clientCompositionList[0].source.buffer.buffer = std::make_shared< + std::optional clientComposition; + clientComposition.emplace(); + clientComposition->source.buffer.buffer = std::make_shared< renderengine::impl::ExternalTexture>(mTestLayers[1]->layerFECompositionState.buffer, mRenderEngine, renderengine::impl::ExternalTexture::Usage:: READABLE); - EXPECT_CALL(*mTestLayers[1]->layerFE, prepareClientCompositionList(_)) - .WillOnce(Return(clientCompositionList)); + EXPECT_CALL(*mTestLayers[1]->layerFE, prepareClientComposition(_)) + .WillOnce(Return(clientComposition)); const std::vector layers = { layerState0.get(), @@ -741,8 +729,7 @@ TEST_F(FlattenerTest, flattenLayers_holePunchSingleLayer) { // This will render a CachedSet of layer 0. Though it is just one layer, it satisfies the // exception that there would be a hole punch above it. EXPECT_CALL(mRenderEngine, drawLayers(_, _, _, _, _)) - .WillOnce(Return(ByMove( - futureOf({NO_ERROR, base::unique_fd()})))); + .WillOnce(Return(ByMove(ftl::yield(Fence::NO_FENCE)))); mFlattener->renderCachedSets(mOutputState, std::nullopt, true); // We've rendered a CachedSet, but we haven't merged it in. @@ -783,16 +770,15 @@ TEST_F(FlattenerTest, flattenLayers_holePunchSingleColorLayer) { EXPECT_CALL(*mTestLayers[1]->layerFE, hasRoundedCorners()).WillRepeatedly(Return(true)); - std::vector clientCompositionList = { - LayerFE::LayerSettings{}, - }; - clientCompositionList[0].source.buffer.buffer = std::make_shared< + std::optional clientComposition; + clientComposition.emplace(); + clientComposition->source.buffer.buffer = std::make_shared< renderengine::impl::ExternalTexture>(mTestLayers[1]->layerFECompositionState.buffer, mRenderEngine, renderengine::impl::ExternalTexture::Usage:: READABLE); - EXPECT_CALL(*mTestLayers[1]->layerFE, prepareClientCompositionList(_)) - .WillOnce(Return(clientCompositionList)); + EXPECT_CALL(*mTestLayers[1]->layerFE, prepareClientComposition(_)) + .WillOnce(Return(clientComposition)); const std::vector layers = { layerState0.get(), @@ -813,8 +799,7 @@ TEST_F(FlattenerTest, flattenLayers_holePunchSingleColorLayer) { // This will render a CachedSet of layer 0. Though it is just one layer, it satisfies the // exception that there would be a hole punch above it. EXPECT_CALL(mRenderEngine, drawLayers(_, _, _, _, _)) - .WillOnce(Return(ByMove( - futureOf({NO_ERROR, base::unique_fd()})))); + .WillOnce(Return(ByMove(ftl::yield(Fence::NO_FENCE)))); mFlattener->renderCachedSets(mOutputState, std::nullopt, true); // We've rendered a CachedSet, but we haven't merged it in. @@ -865,8 +850,7 @@ TEST_F(FlattenerTest, flattenLayers_flattensBlurBehindRunIfFirstRun) { // layers would be flattened but the buffer would not be overridden EXPECT_CALL(mRenderEngine, drawLayers(_, _, _, _, _)) - .WillOnce(Return(ByMove( - futureOf({NO_ERROR, base::unique_fd()})))); + .WillOnce(Return(ByMove(ftl::yield(Fence::NO_FENCE)))); initializeOverrideBuffer(layers); EXPECT_EQ(getNonBufferHash(layers), @@ -911,8 +895,7 @@ TEST_F(FlattenerTest, flattenLayers_doesNotFlattenBlurBehindRun) { // layers would be flattened but the buffer would not be overridden EXPECT_CALL(mRenderEngine, drawLayers(_, _, _, _, _)) - .WillRepeatedly(Return(ByMove( - futureOf({NO_ERROR, base::unique_fd()})))); + .WillRepeatedly(Return(ByMove(ftl::yield(Fence::NO_FENCE)))); initializeOverrideBuffer(layers); EXPECT_EQ(getNonBufferHash(layers), @@ -965,8 +948,7 @@ TEST_F(FlattenerTest, flattenLayers_flattenSkipsLayerWithBlurBehind) { // layers would be flattened but the buffer would not be overridden EXPECT_CALL(mRenderEngine, drawLayers(_, _, _, _, _)) - .WillOnce(Return(ByMove( - futureOf({NO_ERROR, base::unique_fd()})))); + .WillOnce(Return(ByMove(ftl::yield(Fence::NO_FENCE)))); initializeOverrideBuffer(layers); EXPECT_EQ(getNonBufferHash(layers), @@ -1014,8 +996,7 @@ TEST_F(FlattenerTest, flattenLayers_whenBlurLayerIsChanging_appliesBlurToInactiv // layers would be flattened but the buffer would not be overridden EXPECT_CALL(mRenderEngine, drawLayers(_, _, _, _, _)) - .WillOnce(Return(ByMove( - futureOf({NO_ERROR, base::unique_fd()})))); + .WillOnce(Return(ByMove(ftl::yield(Fence::NO_FENCE)))); initializeOverrideBuffer(layers); EXPECT_EQ(getNonBufferHash(layers), @@ -1057,8 +1038,7 @@ TEST_F(FlattenerTest, flattenLayers_renderCachedSets_doesNotRenderTwice) { mTime += 200ms; // layers would be flattened but the buffer would not be overridden EXPECT_CALL(mRenderEngine, drawLayers(_, _, _, _, _)) - .WillOnce(Return(ByMove( - futureOf({NO_ERROR, base::unique_fd()})))); + .WillOnce(Return(ByMove(ftl::yield(Fence::NO_FENCE)))); initializeOverrideBuffer(layers); EXPECT_EQ(getNonBufferHash(layers), @@ -1125,14 +1105,62 @@ TEST_F(FlattenerRenderSchedulingTest, flattenLayers_renderCachedSets_defersUpToM } EXPECT_CALL(mRenderEngine, drawLayers(_, _, _, _, _)) - .WillOnce(Return(ByMove( - futureOf({NO_ERROR, base::unique_fd()})))); + .WillOnce(Return(ByMove(ftl::yield(Fence::NO_FENCE)))); mFlattener->renderCachedSets(mOutputState, std::chrono::steady_clock::now() - (kCachedSetRenderDuration + 10ms), true); } +TEST_F(FlattenerTest, flattenLayers_skipsLayersDisabledFromCaching) { + auto& layerState1 = mTestLayers[0]->layerState; + const auto& overrideBuffer1 = layerState1->getOutputLayer()->getState().overrideInfo.buffer; + + auto& layerState2 = mTestLayers[1]->layerState; + const auto& overrideBuffer2 = layerState2->getOutputLayer()->getState().overrideInfo.buffer; + + // The third layer has a CachingHint that prevents caching from running + auto& layerState3 = mTestLayers[2]->layerState; + const auto& overrideBuffer3 = layerState3->getOutputLayer()->getState().overrideInfo.buffer; + mTestLayers[2]->layerFECompositionState.cachingHint = gui::CachingHint::Disabled; + mTestLayers[2]->layerState->update(&mTestLayers[2]->outputLayer); + + const std::vector layers = { + layerState1.get(), + layerState2.get(), + layerState3.get(), + }; + + initializeFlattener(layers); + + mTime += 200ms; + initializeOverrideBuffer(layers); + EXPECT_EQ(getNonBufferHash(layers), + mFlattener->flattenLayers(layers, getNonBufferHash(layers), mTime)); + + // This will render a CachedSet. + EXPECT_CALL(mRenderEngine, drawLayers(_, _, _, _, _)) + .WillOnce(Return(ByMove(ftl::yield(Fence::NO_FENCE)))); + mFlattener->renderCachedSets(mOutputState, std::nullopt, true); + + // We've rendered a CachedSet, but we haven't merged it in. + EXPECT_EQ(nullptr, overrideBuffer1); + EXPECT_EQ(nullptr, overrideBuffer2); + EXPECT_EQ(nullptr, overrideBuffer3); + + // This time we merge the CachedSet in, so we have a new hash, and we should + // only have two sets. + EXPECT_CALL(mRenderEngine, drawLayers(_, _, _, _, _)).Times(0); + initializeOverrideBuffer(layers); + EXPECT_NE(getNonBufferHash(layers), + mFlattener->flattenLayers(layers, getNonBufferHash(layers), mTime)); + mFlattener->renderCachedSets(mOutputState, std::nullopt, true); + + EXPECT_NE(nullptr, overrideBuffer1); + EXPECT_EQ(overrideBuffer1, overrideBuffer2); + EXPECT_EQ(nullptr, overrideBuffer3); +} + TEST_F(FlattenerTest, flattenLayers_skipsBT601_625) { auto& layerState1 = mTestLayers[0]->layerState; const auto& overrideBuffer1 = layerState1->getOutputLayer()->getState().overrideInfo.buffer; @@ -1162,8 +1190,7 @@ TEST_F(FlattenerTest, flattenLayers_skipsBT601_625) { // This will render a CachedSet. EXPECT_CALL(mRenderEngine, drawLayers(_, _, _, _, _)) - .WillOnce(Return(ByMove( - futureOf({NO_ERROR, base::unique_fd()})))); + .WillOnce(Return(ByMove(ftl::yield(Fence::NO_FENCE)))); mFlattener->renderCachedSets(mOutputState, std::nullopt, true); // We've rendered a CachedSet, but we haven't merged it in. @@ -1213,8 +1240,7 @@ TEST_F(FlattenerTest, flattenLayers_skipsHDR) { // This will render a CachedSet. EXPECT_CALL(mRenderEngine, drawLayers(_, _, _, _, _)) - .WillOnce(Return(ByMove( - futureOf({NO_ERROR, base::unique_fd()})))); + .WillOnce(Return(ByMove(ftl::yield(Fence::NO_FENCE)))); mFlattener->renderCachedSets(mOutputState, std::nullopt, true); // We've rendered a CachedSet, but we haven't merged it in. @@ -1264,8 +1290,7 @@ TEST_F(FlattenerTest, flattenLayers_skipsHDR2) { // This will render a CachedSet. EXPECT_CALL(mRenderEngine, drawLayers(_, _, _, _, _)) - .WillOnce(Return(ByMove( - futureOf({NO_ERROR, base::unique_fd()})))); + .WillOnce(Return(ByMove(ftl::yield(Fence::NO_FENCE)))); mFlattener->renderCachedSets(mOutputState, std::nullopt, true); // We've rendered a CachedSet, but we haven't merged it in. @@ -1318,8 +1343,7 @@ TEST_F(FlattenerTest, flattenLayers_skipsColorLayers) { // This will render a CachedSet. EXPECT_CALL(mRenderEngine, drawLayers(_, _, _, _, _)) - .WillOnce(Return(ByMove( - futureOf({NO_ERROR, base::unique_fd()})))); + .WillOnce(Return(ByMove(ftl::yield(Fence::NO_FENCE)))); mFlattener->renderCachedSets(mOutputState, std::nullopt, true); // We've rendered a CachedSet, but we haven't merged it in. @@ -1371,8 +1395,7 @@ TEST_F(FlattenerTest, flattenLayers_includes_DISPLAY_DECORATION) { // This will render a CachedSet. EXPECT_CALL(mRenderEngine, drawLayers(_, _, _, _, _)) - .WillOnce(Return(ByMove( - futureOf({NO_ERROR, base::unique_fd()})))); + .WillOnce(Return(ByMove(ftl::yield(Fence::NO_FENCE)))); mFlattener->renderCachedSets(mOutputState, std::nullopt, true); // We've rendered a CachedSet, but we haven't merged it in. diff --git a/services/surfaceflinger/CompositionEngine/tests/planner/LayerStateTest.cpp b/services/surfaceflinger/CompositionEngine/tests/planner/LayerStateTest.cpp index 5c6e8da58cb49d784c194de048d53759cb434805..044917ead9c3edcef4f70e9ac601579351e3aeae 100644 --- a/services/surfaceflinger/CompositionEngine/tests/planner/LayerStateTest.cpp +++ b/services/surfaceflinger/CompositionEngine/tests/planner/LayerStateTest.cpp @@ -112,6 +112,9 @@ struct LayerStateTest : public testing::Test { sp mLayerFE = sp::make(); mock::OutputLayer mOutputLayer; std::unique_ptr mLayerState; + + static constexpr auto kUsageFlags = static_cast( + AHARDWAREBUFFER_USAGE_CPU_READ_OFTEN | AHARDWAREBUFFER_USAGE_CPU_WRITE_OFTEN); }; TEST_F(LayerStateTest, getOutputLayer) { @@ -346,7 +349,7 @@ TEST_F(LayerStateTest, compareCompositionType) { TEST_F(LayerStateTest, updateBuffer) { OutputLayerCompositionState outputLayerCompositionState; LayerFECompositionState layerFECompositionState; - layerFECompositionState.buffer = new GraphicBuffer(); + layerFECompositionState.buffer = sp::make(); setupMocksForLayer(mOutputLayer, *mLayerFE, outputLayerCompositionState, layerFECompositionState); mLayerState = std::make_unique(&mOutputLayer); @@ -354,7 +357,7 @@ TEST_F(LayerStateTest, updateBuffer) { mock::OutputLayer newOutputLayer; sp newLayerFE = sp::make(); LayerFECompositionState layerFECompositionStateTwo; - layerFECompositionStateTwo.buffer = new GraphicBuffer(); + layerFECompositionStateTwo.buffer = sp::make(); setupMocksForLayer(newOutputLayer, *newLayerFE, outputLayerCompositionState, layerFECompositionStateTwo); ftl::Flags updates = mLayerState->update(&newOutputLayer); @@ -364,7 +367,7 @@ TEST_F(LayerStateTest, updateBuffer) { TEST_F(LayerStateTest, updateBufferSingleBufferedLegacy) { OutputLayerCompositionState outputLayerCompositionState; LayerFECompositionState layerFECompositionState; - layerFECompositionState.buffer = new GraphicBuffer(); + layerFECompositionState.buffer = sp::make(); setupMocksForLayer(mOutputLayer, *mLayerFE, outputLayerCompositionState, layerFECompositionState); mLayerState = std::make_unique(&mOutputLayer); @@ -372,7 +375,7 @@ TEST_F(LayerStateTest, updateBufferSingleBufferedLegacy) { mock::OutputLayer newOutputLayer; sp newLayerFE = sp::make(); LayerFECompositionState layerFECompositionStateTwo; - layerFECompositionStateTwo.buffer = new GraphicBuffer(); + layerFECompositionStateTwo.buffer = sp::make(); setupMocksForLayer(newOutputLayer, *newLayerFE, outputLayerCompositionState, layerFECompositionStateTwo); @@ -388,7 +391,7 @@ TEST_F(LayerStateTest, updateBufferSingleBufferedLegacy) { TEST_F(LayerStateTest, updateBufferSingleBufferedUsage) { OutputLayerCompositionState outputLayerCompositionState; LayerFECompositionState layerFECompositionState; - layerFECompositionState.buffer = new GraphicBuffer(); + layerFECompositionState.buffer = sp::make(); setupMocksForLayer(mOutputLayer, *mLayerFE, outputLayerCompositionState, layerFECompositionState); mLayerState = std::make_unique(&mOutputLayer); @@ -396,7 +399,7 @@ TEST_F(LayerStateTest, updateBufferSingleBufferedUsage) { mock::OutputLayer newOutputLayer; sp newLayerFE = sp::make(); LayerFECompositionState layerFECompositionStateTwo; - layerFECompositionStateTwo.buffer = new GraphicBuffer(); + layerFECompositionStateTwo.buffer = sp::make(); layerFECompositionStateTwo.buffer->usage = static_cast(BufferUsage::FRONT_BUFFER); setupMocksForLayer(newOutputLayer, *newLayerFE, outputLayerCompositionState, layerFECompositionStateTwo); @@ -412,14 +415,14 @@ TEST_F(LayerStateTest, updateBufferSingleBufferedUsage) { TEST_F(LayerStateTest, compareBuffer) { OutputLayerCompositionState outputLayerCompositionState; LayerFECompositionState layerFECompositionState; - layerFECompositionState.buffer = new GraphicBuffer(); + layerFECompositionState.buffer = sp::make(); setupMocksForLayer(mOutputLayer, *mLayerFE, outputLayerCompositionState, layerFECompositionState); mLayerState = std::make_unique(&mOutputLayer); mock::OutputLayer newOutputLayer; sp newLayerFE = sp::make(); LayerFECompositionState layerFECompositionStateTwo; - layerFECompositionStateTwo.buffer = new GraphicBuffer(); + layerFECompositionStateTwo.buffer = sp::make(); setupMocksForLayer(newOutputLayer, *newLayerFE, outputLayerCompositionState, layerFECompositionStateTwo); auto otherLayerState = std::make_unique(&newOutputLayer); @@ -720,10 +723,7 @@ TEST_F(LayerStateTest, updatePixelFormat) { OutputLayerCompositionState outputLayerCompositionState; LayerFECompositionState layerFECompositionState; layerFECompositionState.buffer = - new GraphicBuffer(1, 1, PIXEL_FORMAT_RGBA_8888, - AHARDWAREBUFFER_USAGE_CPU_READ_OFTEN | - AHARDWAREBUFFER_USAGE_CPU_WRITE_OFTEN, - "buffer1"); + sp::make(1u, 1u, PIXEL_FORMAT_RGBA_8888, kUsageFlags, "buffer1"); setupMocksForLayer(mOutputLayer, *mLayerFE, outputLayerCompositionState, layerFECompositionState); mLayerState = std::make_unique(&mOutputLayer); @@ -732,10 +732,7 @@ TEST_F(LayerStateTest, updatePixelFormat) { sp newLayerFE = sp::make(); LayerFECompositionState layerFECompositionStateTwo; layerFECompositionStateTwo.buffer = - new GraphicBuffer(1, 1, PIXEL_FORMAT_RGBX_8888, - AHARDWAREBUFFER_USAGE_CPU_READ_OFTEN | - AHARDWAREBUFFER_USAGE_CPU_WRITE_OFTEN, - "buffer2"); + sp::make(1u, 1u, PIXEL_FORMAT_RGBX_8888, kUsageFlags, "buffer2"); setupMocksForLayer(newOutputLayer, *newLayerFE, outputLayerCompositionState, layerFECompositionStateTwo); ftl::Flags updates = mLayerState->update(&newOutputLayer); @@ -748,10 +745,7 @@ TEST_F(LayerStateTest, comparePixelFormat) { OutputLayerCompositionState outputLayerCompositionState; LayerFECompositionState layerFECompositionState; layerFECompositionState.buffer = - new GraphicBuffer(1, 1, PIXEL_FORMAT_RGBA_8888, - AHARDWAREBUFFER_USAGE_CPU_READ_OFTEN | - AHARDWAREBUFFER_USAGE_CPU_WRITE_OFTEN, - "buffer1"); + sp::make(1u, 1u, PIXEL_FORMAT_RGBA_8888, kUsageFlags, "buffer1"); setupMocksForLayer(mOutputLayer, *mLayerFE, outputLayerCompositionState, layerFECompositionState); mLayerState = std::make_unique(&mOutputLayer); @@ -759,10 +753,7 @@ TEST_F(LayerStateTest, comparePixelFormat) { sp newLayerFE = sp::make(); LayerFECompositionState layerFECompositionStateTwo; layerFECompositionStateTwo.buffer = - new GraphicBuffer(1, 1, PIXEL_FORMAT_RGBX_8888, - AHARDWAREBUFFER_USAGE_CPU_READ_OFTEN | - AHARDWAREBUFFER_USAGE_CPU_WRITE_OFTEN, - "buffer2"); + sp::make(1u, 1u, PIXEL_FORMAT_RGBX_8888, kUsageFlags, "buffer2"); setupMocksForLayer(newOutputLayer, *newLayerFE, outputLayerCompositionState, layerFECompositionStateTwo); auto otherLayerState = std::make_unique(&newOutputLayer); @@ -1003,6 +994,45 @@ TEST_F(LayerStateTest, hasBlurBehind_withBlurRegion_returnsTrue) { EXPECT_TRUE(mLayerState->hasBlurBehind()); } +TEST_F(LayerStateTest, updateCachingHint) { + OutputLayerCompositionState outputLayerCompositionState; + LayerFECompositionState layerFECompositionState; + layerFECompositionState.cachingHint = gui::CachingHint::Enabled; + setupMocksForLayer(mOutputLayer, *mLayerFE, outputLayerCompositionState, + layerFECompositionState); + mLayerState = std::make_unique(&mOutputLayer); + + mock::OutputLayer newOutputLayer; + sp newLayerFE = sp::make(); + LayerFECompositionState layerFECompositionStateTwo; + layerFECompositionStateTwo.cachingHint = gui::CachingHint::Disabled; + setupMocksForLayer(newOutputLayer, *newLayerFE, outputLayerCompositionState, + layerFECompositionStateTwo); + ftl::Flags updates = mLayerState->update(&newOutputLayer); + EXPECT_EQ(ftl::Flags(LayerStateField::CachingHint), updates); +} + +TEST_F(LayerStateTest, compareCachingHint) { + OutputLayerCompositionState outputLayerCompositionState; + LayerFECompositionState layerFECompositionState; + layerFECompositionState.cachingHint = gui::CachingHint::Enabled; + setupMocksForLayer(mOutputLayer, *mLayerFE, outputLayerCompositionState, + layerFECompositionState); + mLayerState = std::make_unique(&mOutputLayer); + mock::OutputLayer newOutputLayer; + sp newLayerFE = sp::make(); + LayerFECompositionState layerFECompositionStateTwo; + layerFECompositionStateTwo.cachingHint = gui::CachingHint::Disabled; + setupMocksForLayer(newOutputLayer, *newLayerFE, outputLayerCompositionState, + layerFECompositionStateTwo); + auto otherLayerState = std::make_unique(&newOutputLayer); + + verifyNonUniqueDifferingFields(*mLayerState, *otherLayerState, LayerStateField::CachingHint); + + EXPECT_TRUE(mLayerState->compare(*otherLayerState)); + EXPECT_TRUE(otherLayerState->compare(*mLayerState)); +} + TEST_F(LayerStateTest, dumpDoesNotCrash) { OutputLayerCompositionState outputLayerCompositionState; LayerFECompositionState layerFECompositionState; @@ -1069,7 +1099,7 @@ TEST_F(LayerStateTest, getNonBufferHash_isIdempotent) { TEST_F(LayerStateTest, getNonBufferHash_filtersOutBuffers) { OutputLayerCompositionState outputLayerCompositionState; LayerFECompositionState layerFECompositionState; - layerFECompositionState.buffer = new GraphicBuffer(); + layerFECompositionState.buffer = sp::make(); setupMocksForLayer(mOutputLayer, *mLayerFE, outputLayerCompositionState, layerFECompositionState); mLayerState = std::make_unique(&mOutputLayer); @@ -1077,7 +1107,7 @@ TEST_F(LayerStateTest, getNonBufferHash_filtersOutBuffers) { mock::OutputLayer newOutputLayer; sp newLayerFE = sp::make(); LayerFECompositionState layerFECompositionStateTwo; - layerFECompositionStateTwo.buffer = new GraphicBuffer(); + layerFECompositionStateTwo.buffer = sp::make(); setupMocksForLayer(newOutputLayer, *newLayerFE, outputLayerCompositionState, layerFECompositionStateTwo); auto otherLayerState = std::make_unique(&newOutputLayer); diff --git a/services/surfaceflinger/CompositionEngine/tests/planner/PredictorTest.cpp b/services/surfaceflinger/CompositionEngine/tests/planner/PredictorTest.cpp index 68c72e09451eb01c0cfc0bceda050067f9657c20..35d0ffb6e92abe04376afda94dfa7b1477e8157b 100644 --- a/services/surfaceflinger/CompositionEngine/tests/planner/PredictorTest.cpp +++ b/services/surfaceflinger/CompositionEngine/tests/planner/PredictorTest.cpp @@ -228,7 +228,7 @@ TEST_F(LayerStackTest, getApproximateMatch_doesNotMatchManyDifferences) { } TEST_F(LayerStackTest, getApproximateMatch_exactMatchesSameBuffer) { - sp buffer = new GraphicBuffer(); + sp buffer = sp::make(); mock::OutputLayer outputLayerOne; sp layerFEOne = sp::make(); OutputLayerCompositionState outputLayerCompositionStateOne; @@ -268,7 +268,7 @@ TEST_F(LayerStackTest, getApproximateMatch_alwaysMatchesClientComposition) { .dataspace = ui::Dataspace::SRGB, }; LayerFECompositionState layerFECompositionStateOne; - layerFECompositionStateOne.buffer = new GraphicBuffer(); + layerFECompositionStateOne.buffer = sp::make(); layerFECompositionStateOne.alpha = sAlphaOne; layerFECompositionStateOne.colorTransformIsIdentity = true; setupMocksForLayer(outputLayerOne, *layerFEOne, outputLayerCompositionStateOne, @@ -285,7 +285,7 @@ TEST_F(LayerStackTest, getApproximateMatch_alwaysMatchesClientComposition) { .dataspace = ui::Dataspace::DISPLAY_P3, }; LayerFECompositionState layerFECompositionStateTwo; - layerFECompositionStateTwo.buffer = new GraphicBuffer(); + layerFECompositionStateTwo.buffer = sp::make(); layerFECompositionStateTwo.alpha = sAlphaTwo; layerFECompositionStateTwo.colorTransformIsIdentity = false; layerFECompositionStateTwo.colorTransform = sMat4One; @@ -310,7 +310,7 @@ TEST_F(LayerStackTest, getApproximateMatch_doesNotMatchMultipleApproximations) { .sourceCrop = sFloatRectOne, }; LayerFECompositionState layerFECompositionStateOne; - layerFECompositionStateOne.buffer = new GraphicBuffer(); + layerFECompositionStateOne.buffer = sp::make(); setupMocksForLayer(outputLayerOne, *layerFEOne, outputLayerCompositionStateOne, layerFECompositionStateOne); LayerState layerStateOne(&outputLayerOne); @@ -321,7 +321,7 @@ TEST_F(LayerStackTest, getApproximateMatch_doesNotMatchMultipleApproximations) { .sourceCrop = sFloatRectTwo, }; LayerFECompositionState layerFECompositionStateTwo; - layerFECompositionStateTwo.buffer = new GraphicBuffer(); + layerFECompositionStateTwo.buffer = sp::make(); setupMocksForLayer(outputLayerTwo, *layerFETwo, outputLayerCompositionStateTwo, layerFECompositionStateTwo); LayerState layerStateTwo(&outputLayerTwo); diff --git a/services/surfaceflinger/ContainerLayer.cpp b/services/surfaceflinger/ContainerLayer.cpp deleted file mode 100644 index 3ccc229261a5dba29360e994972341d1fadfd198..0000000000000000000000000000000000000000 --- a/services/surfaceflinger/ContainerLayer.cpp +++ /dev/null @@ -1,47 +0,0 @@ -/* - * Copyright (C) 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. - */ - -// TODO(b/129481165): remove the #pragma below and fix conversion issues -#pragma clang diagnostic push -#pragma clang diagnostic ignored "-Wconversion" - -// #define LOG_NDEBUG 0 -#undef LOG_TAG -#define LOG_TAG "ContainerLayer" - -#include "ContainerLayer.h" - -namespace android { - -ContainerLayer::ContainerLayer(const LayerCreationArgs& args) : Layer(args) {} - -ContainerLayer::~ContainerLayer() = default; - -bool ContainerLayer::isVisible() const { - return false; -} - -sp ContainerLayer::createClone() { - sp layer = mFlinger->getFactory().createContainerLayer( - LayerCreationArgs(mFlinger.get(), nullptr, mName + " (Mirror)", 0, LayerMetadata())); - layer->setInitialValuesForClone(this); - return layer; -} - -} // namespace android - -// TODO(b/129481165): remove the #pragma below and fix conversion issues -#pragma clang diagnostic pop // ignored "-Wconversion" diff --git a/services/surfaceflinger/Display/DisplayMap.h b/services/surfaceflinger/Display/DisplayMap.h new file mode 100644 index 0000000000000000000000000000000000000000..0d5970676ed720dd7a050906296b7f20896ab14e --- /dev/null +++ b/services/surfaceflinger/Display/DisplayMap.h @@ -0,0 +1,35 @@ +/* + * 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::display { + +// The static capacities were chosen to exceed a typical number of physical and/or virtual displays. + +template +using DisplayMap = ftl::SmallMap; + +template +using PhysicalDisplayMap = ftl::SmallMap; + +template +using PhysicalDisplayVector = ftl::SmallVector; + +} // namespace android::display diff --git a/services/surfaceflinger/Display/DisplayModeRequest.h b/services/surfaceflinger/Display/DisplayModeRequest.h new file mode 100644 index 0000000000000000000000000000000000000000..d07cdf55d22d28a2630b6d892c41219b77963f40 --- /dev/null +++ b/services/surfaceflinger/Display/DisplayModeRequest.h @@ -0,0 +1,36 @@ +/* + * 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::display { + +struct DisplayModeRequest { + scheduler::FrameRateMode mode; + + // Whether to emit DisplayEventReceiver::DISPLAY_EVENT_MODE_CHANGE. + bool emitEvent = false; +}; + +inline bool operator==(const DisplayModeRequest& lhs, const DisplayModeRequest& rhs) { + return lhs.mode == rhs.mode && lhs.emitEvent == rhs.emitEvent; +} + +} // namespace android::display diff --git a/services/surfaceflinger/Display/DisplaySnapshot.cpp b/services/surfaceflinger/Display/DisplaySnapshot.cpp new file mode 100644 index 0000000000000000000000000000000000000000..0c7a58ee71e849294953d41c7a66e78f7bdaf86a --- /dev/null +++ b/services/surfaceflinger/Display/DisplaySnapshot.cpp @@ -0,0 +1,78 @@ +/* + * 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 "DisplaySnapshot.h" + +namespace android::display { + +DisplaySnapshot::DisplaySnapshot(PhysicalDisplayId displayId, + ui::DisplayConnectionType connectionType, + DisplayModes&& displayModes, ui::ColorModes&& colorModes, + std::optional&& deviceProductInfo) + : mDisplayId(displayId), + mConnectionType(connectionType), + mDisplayModes(std::move(displayModes)), + mColorModes(std::move(colorModes)), + mDeviceProductInfo(std::move(deviceProductInfo)) {} + +std::optional DisplaySnapshot::translateModeId(hal::HWConfigId hwcId) const { + return ftl::find_if(mDisplayModes, + [hwcId](const DisplayModes::value_type& pair) { + return pair.second->getHwcId() == hwcId; + }) + .transform(&ftl::to_key); +} + +ui::ColorModes DisplaySnapshot::filterColorModes(bool supportsWideColor) const { + ui::ColorModes modes = mColorModes; + + // If the display is internal and the configuration claims it's not wide color capable, filter + // out all wide color modes. The typical reason why this happens is that the hardware is not + // good enough to support GPU composition of wide color, and thus the OEMs choose to disable + // this capability. + if (mConnectionType == ui::DisplayConnectionType::Internal && !supportsWideColor) { + const auto it = std::remove_if(modes.begin(), modes.end(), ui::isWideColorMode); + modes.erase(it, modes.end()); + } + + return modes; +} + +void DisplaySnapshot::dump(utils::Dumper& dumper) const { + using namespace std::string_view_literals; + + dumper.dump("connectionType"sv, ftl::enum_string(mConnectionType)); + + dumper.dump("colorModes"sv); + { + utils::Dumper::Indent indent(dumper); + for (const auto mode : mColorModes) { + dumper.dump({}, decodeColorMode(mode)); + } + } + + dumper.dump("deviceProductInfo"sv, mDeviceProductInfo); +} + +} // namespace android::display diff --git a/services/surfaceflinger/Display/DisplaySnapshot.h b/services/surfaceflinger/Display/DisplaySnapshot.h new file mode 100644 index 0000000000000000000000000000000000000000..23471f5d8e9c18064695805f2db7af7453ad2cd3 --- /dev/null +++ b/services/surfaceflinger/Display/DisplaySnapshot.h @@ -0,0 +1,62 @@ +/* + * 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 +#include +#include + +#include "DisplayHardware/DisplayMode.h" +#include "Utils/Dumper.h" + +namespace android::display { + +// Immutable state of a physical display, captured on hotplug. +class DisplaySnapshot { +public: + DisplaySnapshot(PhysicalDisplayId, ui::DisplayConnectionType, DisplayModes&&, ui::ColorModes&&, + std::optional&&); + + DisplaySnapshot(const DisplaySnapshot&) = delete; + DisplaySnapshot(DisplaySnapshot&&) = default; + + PhysicalDisplayId displayId() const { return mDisplayId; } + ui::DisplayConnectionType connectionType() const { return mConnectionType; } + + std::optional translateModeId(hal::HWConfigId) const; + + const auto& displayModes() const { return mDisplayModes; } + const auto& colorModes() const { return mColorModes; } + const auto& deviceProductInfo() const { return mDeviceProductInfo; } + + ui::ColorModes filterColorModes(bool supportsWideColor) const; + + void dump(utils::Dumper&) const; + +private: + const PhysicalDisplayId mDisplayId; + const ui::DisplayConnectionType mConnectionType; + + // Effectively const except in move constructor. + DisplayModes mDisplayModes; + ui::ColorModes mColorModes; + std::optional mDeviceProductInfo; +}; + +} // namespace android::display diff --git a/services/surfaceflinger/Display/PhysicalDisplay.h b/services/surfaceflinger/Display/PhysicalDisplay.h new file mode 100644 index 0000000000000000000000000000000000000000..cba10146b708cdb80eebb9b15c73e7945791f2d1 --- /dev/null +++ b/services/surfaceflinger/Display/PhysicalDisplay.h @@ -0,0 +1,76 @@ +/* + * 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 + +#include +#include +#include + +#include "DisplayMap.h" +#include "DisplaySnapshot.h" + +namespace android::display { + +// TODO(b/229877597): Replace with AIDL type. +using DisplayToken = IBinder; + +class PhysicalDisplay { +public: + template + PhysicalDisplay(sp token, Args&&... args) + : mToken(std::move(token)), mSnapshot(std::forward(args)...) {} + + PhysicalDisplay(const PhysicalDisplay&) = delete; + PhysicalDisplay(PhysicalDisplay&&) = default; + + const sp& token() const { return mToken; } + const DisplaySnapshot& snapshot() const { return mSnapshot; } + + // Transformers for PhysicalDisplays::get. + + using SnapshotRef = std::reference_wrapper; + SnapshotRef snapshotRef() const { return std::cref(mSnapshot); } + + bool isInternal() const { + return mSnapshot.connectionType() == ui::DisplayConnectionType::Internal; + } + + // Predicate for ftl::find_if on PhysicalDisplays. + static constexpr auto hasToken(const sp& token) { + return [&token](const std::pair& pair) { + return pair.second.token() == token; + }; + } + +private: + const sp mToken; + + // Effectively const except in move constructor. + DisplaySnapshot mSnapshot; +}; + +using PhysicalDisplays = PhysicalDisplayMap; + +// Combinator for ftl::Optional::and_then. +constexpr auto getPhysicalDisplay(const PhysicalDisplays& displays) { + return [&](PhysicalDisplayId id) { return displays.get(id); }; +} + +} // namespace android::display diff --git a/services/surfaceflinger/DisplayDevice.cpp b/services/surfaceflinger/DisplayDevice.cpp index b49c95d677480176fbdf2fa7d4559ea840d21837..f6ca9e2856c47151f505c2dd66dd634f65a80830 100644 --- a/services/surfaceflinger/DisplayDevice.cpp +++ b/services/surfaceflinger/DisplayDevice.cpp @@ -39,7 +39,9 @@ #include #include +#include "Display/DisplaySnapshot.h" #include "DisplayDevice.h" +#include "FrontEnd/DisplayInfo.h" #include "Layer.h" #include "RefreshRateOverlay.h" #include "SurfaceFlinger.h" @@ -48,8 +50,6 @@ namespace android { namespace hal = hardware::graphics::composer::hal; -ui::Transform::RotationFlags DisplayDevice::sPrimaryDisplayRotationFlags = ui::Transform::ROT_0; - DisplayDeviceCreationArgs::DisplayDeviceCreationArgs( const sp& flinger, HWComposer& hwComposer, const wp& displayToken, std::shared_ptr compositionDisplay) @@ -63,14 +63,14 @@ DisplayDevice::DisplayDevice(DisplayDeviceCreationArgs& args) mHwComposer(args.hwComposer), mDisplayToken(args.displayToken), mSequenceId(args.sequenceId), - mConnectionType(args.connectionType), mCompositionDisplay{args.compositionDisplay}, mActiveModeFPSTrace("ActiveModeFPS -" + to_string(getId())), mActiveModeFPSHwcTrace("ActiveModeFPS_HWC -" + to_string(getId())), + mRenderFrameRateFPSTrace("RenderRateFPS -" + to_string(getId())), mPhysicalOrientation(args.physicalOrientation), - mSupportedModes(std::move(args.supportedModes)), mIsPrimary(args.isPrimary), - mRefreshRateConfigs(std::move(args.refreshRateConfigs)) { + mRequestedRefreshRate(args.requestedRefreshRate), + mRefreshRateSelector(std::move(args.refreshRateSelector)) { mCompositionDisplay->editState().isSecure = args.isSecure; mCompositionDisplay->createRenderSurface( compositionengine::RenderSurfaceCreationArgsBuilder() @@ -104,7 +104,9 @@ DisplayDevice::DisplayDevice(DisplayDeviceCreationArgs& args) mCompositionDisplay->getRenderSurface()->initialize(); - if (args.initialPowerMode.has_value()) setPowerMode(args.initialPowerMode.value()); + if (const auto powerModeOpt = args.initialPowerMode) { + setPowerMode(*powerModeOpt); + } // initialize the display orientation transform. setProjection(ui::ROTATION_0, Rect::INVALID_RECT, Rect::INVALID_RECT); @@ -132,15 +134,7 @@ void DisplayDevice::setDisplayName(const std::string& displayName) { } } -void DisplayDevice::setDeviceProductInfo(std::optional info) { - mDeviceProductInfo = std::move(info); -} - -uint32_t DisplayDevice::getPageFlipCount() const { - return mCompositionDisplay->getRenderSurface()->getPageFlipCount(); -} - -auto DisplayDevice::getInputInfo() const -> InputInfo { +auto DisplayDevice::getFrontEndInfo() const -> frontend::DisplayInfo { gui::DisplayInfo info; info.displayId = getLayerStack().id; @@ -169,7 +163,11 @@ auto DisplayDevice::getInputInfo() const -> InputInfo { return {.info = info, .transform = displayTransform, .receivesInput = receivesInput(), - .isSecure = isSecure()}; + .isSecure = isSecure(), + .isPrimary = isPrimary(), + .isVirtual = isVirtual(), + .rotationFlags = ui::Transform::toRotationFlags(mOrientation), + .transformHint = getTransformHint()}; } void DisplayDevice::setPowerMode(hal::PowerMode mode) { @@ -182,10 +180,21 @@ void DisplayDevice::setPowerMode(hal::PowerMode mode) { getCompositionDisplay()->applyDisplayBrightness(true); } - mPowerMode = mode; + if (mPowerMode) { + *mPowerMode = mode; + } else { + mPowerMode.emplace("PowerMode -" + to_string(getId()), mode); + } - getCompositionDisplay()->setCompositionEnabled(mPowerMode.has_value() && - *mPowerMode != hal::PowerMode::OFF); + getCompositionDisplay()->setCompositionEnabled(isPoweredOn()); +} + +void DisplayDevice::tracePowerMode() { + // assign the same value for tracing + if (mPowerMode) { + const hal::PowerMode powerMode = *mPowerMode; + *mPowerMode = powerMode; + } } void DisplayDevice::enableLayerCaching(bool enable) { @@ -200,56 +209,32 @@ bool DisplayDevice::isPoweredOn() const { return mPowerMode && *mPowerMode != hal::PowerMode::OFF; } -void DisplayDevice::setActiveMode(DisplayModeId id) { - const auto mode = getMode(id); - LOG_FATAL_IF(!mode, "Cannot set active mode which is not supported."); - ATRACE_INT(mActiveModeFPSTrace.c_str(), mode->getFps().getIntValue()); - mActiveMode = mode; - if (mRefreshRateConfigs) { - mRefreshRateConfigs->setActiveModeId(mActiveMode->getId()); - } +void DisplayDevice::setActiveMode(DisplayModeId modeId, Fps displayFps, Fps renderFps) { + ATRACE_INT(mActiveModeFPSTrace.c_str(), displayFps.getIntValue()); + ATRACE_INT(mRenderFrameRateFPSTrace.c_str(), renderFps.getIntValue()); + + mRefreshRateSelector->setActiveMode(modeId, renderFps); + if (mRefreshRateOverlay) { - mRefreshRateOverlay->changeRefreshRate(mActiveMode->getFps()); + mRefreshRateOverlay->changeRefreshRate(displayFps, renderFps); } } status_t DisplayDevice::initiateModeChange(const ActiveModeInfo& info, const hal::VsyncPeriodChangeConstraints& constraints, hal::VsyncPeriodChangeTimeline* outTimeline) { - if (!info.mode || info.mode->getPhysicalDisplayId() != getPhysicalId()) { + if (!info.modeOpt || info.modeOpt->modePtr->getPhysicalDisplayId() != getPhysicalId()) { ALOGE("Trying to initiate a mode change to invalid mode %s on display %s", - info.mode ? std::to_string(info.mode->getId().value()).c_str() : "null", + info.modeOpt ? std::to_string(info.modeOpt->modePtr->getId().value()).c_str() + : "null", to_string(getId()).c_str()); return BAD_VALUE; } - mNumModeSwitchesInPolicy++; mUpcomingActiveMode = info; - ATRACE_INT(mActiveModeFPSHwcTrace.c_str(), info.mode->getFps().getIntValue()); - return mHwComposer.setActiveModeWithConstraints(getPhysicalId(), info.mode->getHwcId(), - constraints, outTimeline); -} - -const DisplayModePtr& DisplayDevice::getActiveMode() const { - return mActiveMode; -} - -const DisplayModes& DisplayDevice::getSupportedModes() const { - return mSupportedModes; -} - -DisplayModePtr DisplayDevice::getMode(DisplayModeId modeId) const { - const DisplayModePtr nullMode; - return mSupportedModes.get(modeId).value_or(std::cref(nullMode)); -} - -std::optional DisplayDevice::translateModeId(hal::HWConfigId hwcId) const { - const auto it = - std::find_if(mSupportedModes.begin(), mSupportedModes.end(), - [hwcId](const auto& pair) { return pair.second->getHwcId() == hwcId; }); - if (it != mSupportedModes.end()) { - return it->second->getId(); - } - return {}; + ATRACE_INT(mActiveModeFPSHwcTrace.c_str(), info.modeOpt->modePtr->getFps().getIntValue()); + return mHwComposer.setActiveModeWithConstraints(getPhysicalId(), + info.modeOpt->modePtr->getHwcId(), constraints, + outTimeline); } nsecs_t DisplayDevice::getVsyncPeriodFromHWC() const { @@ -264,27 +249,17 @@ nsecs_t DisplayDevice::getVsyncPeriodFromHWC() const { return vsyncPeriod; } - return getActiveMode()->getFps().getPeriodNsecs(); -} - -nsecs_t DisplayDevice::getRefreshTimestamp() const { - const nsecs_t now = systemTime(CLOCK_MONOTONIC); - const auto vsyncPeriodNanos = getVsyncPeriodFromHWC(); - return now - ((now - mLastHwVsync) % vsyncPeriodNanos); -} - -void DisplayDevice::onVsync(nsecs_t timestamp) { - mLastHwVsync = timestamp; + return refreshRateSelector().getActiveMode().modePtr->getVsyncPeriod(); } ui::Dataspace DisplayDevice::getCompositionDataSpace() const { return mCompositionDisplay->getState().dataspace; } -void DisplayDevice::setLayerStack(ui::LayerStack stack) { - mCompositionDisplay->setLayerFilter({stack, isInternal()}); +void DisplayDevice::setLayerFilter(ui::LayerFilter filter) { + mCompositionDisplay->setLayerFilter(filter); if (mRefreshRateOverlay) { - mRefreshRateOverlay->setLayerStack(stack); + mRefreshRateOverlay->setLayerStack(filter.layerStack); } } @@ -305,10 +280,6 @@ void DisplayDevice::setProjection(ui::Rotation orientation, Rect layerStackSpace Rect orientedDisplaySpaceRect) { mOrientation = orientation; - if (isPrimary()) { - sPrimaryDisplayRotationFlags = ui::Transform::toRotationFlags(orientation); - } - // We need to take care of display rotation for globalTransform for case if the panel is not // installed aligned with device orientation. const auto transformOrientation = orientation + mPhysicalOrientation; @@ -349,48 +320,14 @@ std::optional DisplayDevice::getStagedBrightness() const { return mStagedBrightness; } -ui::Transform::RotationFlags DisplayDevice::getPrimaryDisplayRotationFlags() { - return sPrimaryDisplayRotationFlags; -} - -std::string DisplayDevice::getDebugName() const { - using namespace std::string_literals; - - std::string name = "Display "s + to_string(getId()) + " ("s; +void DisplayDevice::dump(utils::Dumper& dumper) const { + using namespace std::string_view_literals; - if (mConnectionType) { - name += isInternal() ? "internal"s : "external"s; - } else { - name += "virtual"s; - } + dumper.dump("name"sv, '"' + mDisplayName + '"'); + dumper.dump("powerMode"sv, mPowerMode); - if (isPrimary()) { - name += ", primary"s; - } - - return name + ", \""s + mDisplayName + "\")"s; -} - -void DisplayDevice::dump(std::string& result) const { - using namespace std::string_literals; - - result += getDebugName(); - - if (!isVirtual()) { - result += "\n deviceProductInfo="s; - if (mDeviceProductInfo) { - mDeviceProductInfo->dump(result); - } else { - result += "{}"s; - } - } - - result += "\n powerMode="s; - result += mPowerMode.has_value() ? to_string(mPowerMode.value()) : "OFF(reset)"; - result += '\n'; - - if (mRefreshRateConfigs) { - mRefreshRateConfigs->dump(result); + if (mRefreshRateSelector) { + mRefreshRateSelector->dump(dumper); } } @@ -414,10 +351,6 @@ const Region& DisplayDevice::getUndefinedRegion() const { return mCompositionDisplay->getState().undefinedRegion; } -bool DisplayDevice::needsFiltering() const { - return mCompositionDisplay->getState().needsFiltering; -} - ui::LayerStack DisplayDevice::getLayerStack() const { return mCompositionDisplay->getState().layerFilter.layerStack; } @@ -478,26 +411,51 @@ HdrCapabilities DisplayDevice::getHdrCapabilities() const { capabilities.getDesiredMinLuminance()); } -void DisplayDevice::enableRefreshRateOverlay(bool enable, bool showSpinnner) { +void DisplayDevice::enableRefreshRateOverlay(bool enable, bool setByHwc, bool showSpinner, + bool showRenderRate, bool showInMiddle) { if (!enable) { mRefreshRateOverlay.reset(); return; } - const auto fpsRange = mRefreshRateConfigs->getSupportedRefreshRateRange(); - mRefreshRateOverlay = std::make_unique(fpsRange, showSpinnner); + ftl::Flags features; + if (showSpinner) { + features |= RefreshRateOverlay::Features::Spinner; + } + + if (showRenderRate) { + features |= RefreshRateOverlay::Features::RenderRate; + } + + if (showInMiddle) { + features |= RefreshRateOverlay::Features::ShowInMiddle; + } + + if (setByHwc) { + features |= RefreshRateOverlay::Features::SetByHwc; + } + + const auto fpsRange = mRefreshRateSelector->getSupportedRefreshRateRange(); + mRefreshRateOverlay = std::make_unique(fpsRange, features); mRefreshRateOverlay->setLayerStack(getLayerStack()); mRefreshRateOverlay->setViewport(getSize()); - mRefreshRateOverlay->changeRefreshRate(getActiveMode()->getFps()); + updateRefreshRateOverlayRate(getActiveMode().modePtr->getFps(), getActiveMode().fps); +} + +void DisplayDevice::updateRefreshRateOverlayRate(Fps displayFps, Fps renderFps, bool setByHwc) { + ATRACE_CALL(); + if (mRefreshRateOverlay && (!mRefreshRateOverlay->isSetByHwc() || setByHwc)) { + mRefreshRateOverlay->changeRefreshRate(displayFps, renderFps); + } } bool DisplayDevice::onKernelTimerChanged(std::optional desiredModeId, bool timerExpired) { - if (mRefreshRateConfigs && mRefreshRateOverlay) { - const auto newRefreshRate = - mRefreshRateConfigs->onKernelTimerChanged(desiredModeId, timerExpired); - if (newRefreshRate) { - mRefreshRateOverlay->changeRefreshRate(*newRefreshRate); + if (mRefreshRateSelector && mRefreshRateOverlay) { + const auto newMode = + mRefreshRateSelector->onKernelTimerChanged(desiredModeId, timerExpired); + if (newMode) { + updateRefreshRateOverlayRate(newMode->modePtr->getFps(), newMode->fps); return true; } } @@ -511,13 +469,15 @@ void DisplayDevice::animateRefreshRateOverlay() { } } -bool DisplayDevice::setDesiredActiveMode(const ActiveModeInfo& info, bool force) { +auto DisplayDevice::setDesiredActiveMode(const ActiveModeInfo& info, bool force) + -> DesiredActiveModeAction { ATRACE_CALL(); - LOG_ALWAYS_FATAL_IF(!info.mode, "desired mode not provided"); - LOG_ALWAYS_FATAL_IF(getPhysicalId() != info.mode->getPhysicalDisplayId(), "DisplayId mismatch"); + LOG_ALWAYS_FATAL_IF(!info.modeOpt, "desired mode not provided"); + LOG_ALWAYS_FATAL_IF(getPhysicalId() != info.modeOpt->modePtr->getPhysicalDisplayId(), + "DisplayId mismatch"); - ALOGV("%s(%s)", __func__, to_string(*info.mode).c_str()); + ALOGV("%s(%s)", __func__, to_string(*info.modeOpt->modePtr).c_str()); std::scoped_lock lock(mActiveModeLock); if (mDesiredActiveModeChanged) { @@ -525,18 +485,31 @@ bool DisplayDevice::setDesiredActiveMode(const ActiveModeInfo& info, bool force) const auto prevConfig = mDesiredActiveMode.event; mDesiredActiveMode = info; mDesiredActiveMode.event = mDesiredActiveMode.event | prevConfig; - return false; + return DesiredActiveModeAction::None; } + const auto& desiredMode = *info.modeOpt->modePtr; + // Check if we are already at the desired mode - if (!force && getActiveMode()->getId() == info.mode->getId()) { - return false; + const auto currentMode = refreshRateSelector().getActiveMode(); + if (!force && currentMode.modePtr->getId() == desiredMode.getId()) { + if (currentMode == info.modeOpt) { + return DesiredActiveModeAction::None; + } + + setActiveMode(desiredMode.getId(), desiredMode.getFps(), info.modeOpt->fps); + return DesiredActiveModeAction::InitiateRenderRateSwitch; } + // Set the render frame rate to the current physical refresh rate to schedule the next + // frame as soon as possible. + setActiveMode(currentMode.modePtr->getId(), currentMode.modePtr->getFps(), + currentMode.modePtr->getFps()); + // Initiate a mode change. mDesiredActiveModeChanged = true; mDesiredActiveMode = info; - return true; + return DesiredActiveModeAction::InitiateDisplayModeSwitch; } std::optional DisplayDevice::getDesiredActiveMode() const { @@ -551,25 +524,26 @@ void DisplayDevice::clearDesiredActiveModeState() { mDesiredActiveModeChanged = false; } -status_t DisplayDevice::setRefreshRatePolicy( - const std::optional& policy, bool overridePolicy) { - const auto oldPolicy = mRefreshRateConfigs->getCurrentPolicy(); - const status_t setPolicyResult = overridePolicy - ? mRefreshRateConfigs->setOverridePolicy(policy) - : mRefreshRateConfigs->setDisplayManagerPolicy(*policy); +void DisplayDevice::adjustRefreshRate(Fps pacesetterDisplayRefreshRate) { + using fps_approx_ops::operator<=; + if (mRequestedRefreshRate <= 0_Hz) { + return; + } - if (setPolicyResult == OK) { - const int numModeChanges = mNumModeSwitchesInPolicy.exchange(0); + using fps_approx_ops::operator>; + if (mRequestedRefreshRate > pacesetterDisplayRefreshRate) { + mAdjustedRefreshRate = pacesetterDisplayRefreshRate; + return; + } - ALOGI("Display %s policy changed\n" - "Previous: {%s}\n" - "Current: {%s}\n" - "%d mode changes were performed under the previous policy", - to_string(getId()).c_str(), oldPolicy.toString().c_str(), - policy ? policy->toString().c_str() : "null", numModeChanges); + unsigned divisor = static_cast( + std::floor(pacesetterDisplayRefreshRate.getValue() / mRequestedRefreshRate.getValue())); + if (divisor == 0) { + mAdjustedRefreshRate = 0_Hz; + return; } - return setPolicyResult; + mAdjustedRefreshRate = pacesetterDisplayRefreshRate / divisor; } std::atomic DisplayDeviceState::sNextSequenceId(1); diff --git a/services/surfaceflinger/DisplayDevice.h b/services/surfaceflinger/DisplayDevice.h index b91dece9093db22cf8ac049d8c9145670f8d8f3d..dc5f8a85af44a16bbd19f2b554d7e6d0b73151db 100644 --- a/services/surfaceflinger/DisplayDevice.h +++ b/services/surfaceflinger/DisplayDevice.h @@ -24,6 +24,7 @@ #include #include #include +#include #include #include #include @@ -41,13 +42,15 @@ #include #include +#include "Display/DisplayModeRequest.h" #include "DisplayHardware/DisplayMode.h" #include "DisplayHardware/Hal.h" #include "DisplayHardware/PowerAdvisor.h" -#include "Scheduler/RefreshRateConfigs.h" +#include "FrontEnd/DisplayInfo.h" +#include "Scheduler/RefreshRateSelector.h" #include "ThreadContext.h" #include "TracedOrdinal.h" - +#include "Utils/Dumper.h" namespace android { class Fence; @@ -65,6 +68,10 @@ class Display; class DisplaySurface; } // namespace compositionengine +namespace display { +class DisplaySnapshot; +} // namespace display + class DisplayDevice : public RefBase { public: constexpr static float sDefaultMinLumiance = 0.0; @@ -80,11 +87,8 @@ public: return mCompositionDisplay; } - std::optional getConnectionType() const { return mConnectionType; } - - bool isVirtual() const { return !mConnectionType; } + bool isVirtual() const { return VirtualDisplayId::tryCast(getId()).has_value(); } bool isPrimary() const { return mIsPrimary; } - bool isInternal() const { return mConnectionType == ui::DisplayConnectionType::Internal; } // isSecure indicates whether this display can be trusted to display // secure surfaces. @@ -94,7 +98,7 @@ public: int getHeight() const; ui::Size getSize() const { return {getWidth(), getHeight()}; } - void setLayerStack(ui::LayerStack); + void setLayerFilter(ui::LayerFilter); void setDisplaySize(int width, int height); void setProjection(ui::Rotation orientation, Rect viewport, Rect frame); void stageBrightness(float brightness) REQUIRES(kMainThreadContext); @@ -105,14 +109,11 @@ public: ui::Rotation getPhysicalOrientation() const { return mPhysicalOrientation; } ui::Rotation getOrientation() const { return mOrientation; } - static ui::Transform::RotationFlags getPrimaryDisplayRotationFlags(); - std::optional getStagedBrightness() const REQUIRES(kMainThreadContext); ui::Transform::RotationFlags getTransformHint() const; const ui::Transform& getTransform() const; const Rect& getLayerStackSpaceRect() const; const Rect& getOrientedDisplaySpaceRect() const; - bool needsFiltering() const; ui::LayerStack getLayerStack() const; bool receivesInput() const { return mFlags & eReceivesInput; } @@ -164,19 +165,7 @@ public: void setDisplayName(const std::string& displayName); const std::string& getDisplayName() const { return mDisplayName; } - void setDeviceProductInfo(std::optional info); - const std::optional& getDeviceProductInfo() const { - return mDeviceProductInfo; - } - - struct InputInfo { - gui::DisplayInfo info; - ui::Transform transform; - bool receivesInput; - bool isSecure; - }; - - InputInfo getInputInfo() const; + surfaceflinger::frontend::DisplayInfo getFrontEndInfo() const; /* ------------------------------------------------------------------------ * Display power mode management. @@ -184,6 +173,7 @@ public: std::optional getPowerMode() const; void setPowerMode(hardware::graphics::composer::hal::PowerMode mode); bool isPoweredOn() const; + void tracePowerMode(); // Enables layer caching on this DisplayDevice void enableLayerCaching(bool enable); @@ -193,135 +183,136 @@ public: /* ------------------------------------------------------------------------ * Display mode management. */ - const DisplayModePtr& getActiveMode() const; + // TODO(b/241285876): Replace ActiveModeInfo and DisplayModeEvent with DisplayModeRequest. struct ActiveModeInfo { - DisplayModePtr mode; - scheduler::DisplayModeEvent event = scheduler::DisplayModeEvent::None; + using Event = scheduler::DisplayModeEvent; + + ActiveModeInfo() = default; + ActiveModeInfo(scheduler::FrameRateMode mode, Event event) + : modeOpt(std::move(mode)), event(event) {} + + explicit ActiveModeInfo(display::DisplayModeRequest&& request) + : ActiveModeInfo(std::move(request.mode), + request.emitEvent ? Event::Changed : Event::None) {} + + ftl::Optional modeOpt; + Event event = Event::None; bool operator!=(const ActiveModeInfo& other) const { - return mode != other.mode || event != other.event; + return modeOpt != other.modeOpt || event != other.event; } }; - bool setDesiredActiveMode(const ActiveModeInfo&, bool force = false) EXCLUDES(mActiveModeLock); + enum class DesiredActiveModeAction { + None, + InitiateDisplayModeSwitch, + InitiateRenderRateSwitch + }; + DesiredActiveModeAction setDesiredActiveMode(const ActiveModeInfo&, bool force = false) + EXCLUDES(mActiveModeLock); std::optional getDesiredActiveMode() const EXCLUDES(mActiveModeLock); void clearDesiredActiveModeState() EXCLUDES(mActiveModeLock); ActiveModeInfo getUpcomingActiveMode() const REQUIRES(kMainThreadContext) { return mUpcomingActiveMode; } - void setActiveMode(DisplayModeId) REQUIRES(kMainThreadContext); + scheduler::FrameRateMode getActiveMode() const REQUIRES(kMainThreadContext) { + return mRefreshRateSelector->getActiveMode(); + } + + void setActiveMode(DisplayModeId, Fps displayFps, Fps renderFps); + status_t initiateModeChange(const ActiveModeInfo&, const hal::VsyncPeriodChangeConstraints& constraints, hal::VsyncPeriodChangeTimeline* outTimeline) REQUIRES(kMainThreadContext); - // Return the immutable list of supported display modes. The HWC may report different modes - // after a hotplug reconnect event, in which case the DisplayDevice object will be recreated. - // Hotplug reconnects are common for external displays. - const DisplayModes& getSupportedModes() const; - - // Returns nullptr if the given mode ID is not supported. A previously - // supported mode may be no longer supported for some devices like TVs and - // set-top boxes after a hotplug reconnect. - DisplayModePtr getMode(DisplayModeId) const; - - std::optional translateModeId(hal::HWConfigId) const; - - // Returns the refresh rate configs for this display. - scheduler::RefreshRateConfigs& refreshRateConfigs() const { return *mRefreshRateConfigs; } + scheduler::RefreshRateSelector& refreshRateSelector() const { return *mRefreshRateSelector; } - // Returns a shared pointer to the refresh rate configs for this display. - // Clients can store this refresh rate configs and use it even if the DisplayDevice - // is destroyed. - std::shared_ptr holdRefreshRateConfigs() const { - return mRefreshRateConfigs; + // Extends the lifetime of the RefreshRateSelector, so it can outlive this DisplayDevice. + std::shared_ptr holdRefreshRateSelector() const { + return mRefreshRateSelector; } // Enables an overlay to be displayed with the current refresh rate - void enableRefreshRateOverlay(bool enable, bool showSpinner); + void enableRefreshRateOverlay(bool enable, bool setByHwc, bool showSpinner, bool showRenderRate, + bool showInMiddle) REQUIRES(kMainThreadContext); + void updateRefreshRateOverlayRate(Fps displayFps, Fps renderFps, bool setByHwc = false); bool isRefreshRateOverlayEnabled() const { return mRefreshRateOverlay != nullptr; } bool onKernelTimerChanged(std::optional, bool timerExpired); void animateRefreshRateOverlay(); - void onVsync(nsecs_t timestamp); nsecs_t getVsyncPeriodFromHWC() const; - nsecs_t getRefreshTimestamp() const; - status_t setRefreshRatePolicy( - const std::optional& policy, - bool overridePolicy); + Fps getAdjustedRefreshRate() const { return mAdjustedRefreshRate; } + + // Round the requested refresh rate to match a divisor of the pacesetter + // display's refresh rate. Only supported for virtual displays. + void adjustRefreshRate(Fps pacesetterDisplayRefreshRate); // release HWC resources (if any) for removable displays void disconnect(); - /* ------------------------------------------------------------------------ - * Debugging - */ - uint32_t getPageFlipCount() const; - std::string getDebugName() const; - void dump(std::string& result) const; + void dump(utils::Dumper&) const; private: const sp mFlinger; HWComposer& mHwComposer; const wp mDisplayToken; const int32_t mSequenceId; - const std::optional mConnectionType; const std::shared_ptr mCompositionDisplay; std::string mDisplayName; std::string mActiveModeFPSTrace; std::string mActiveModeFPSHwcTrace; + std::string mRenderFrameRateFPSTrace; const ui::Rotation mPhysicalOrientation; ui::Rotation mOrientation = ui::ROTATION_0; - static ui::Transform::RotationFlags sPrimaryDisplayRotationFlags; + // Allow nullopt as initial power mode. + using TracedPowerMode = TracedOrdinal; + std::optional mPowerMode; - // allow initial power mode as null. - std::optional mPowerMode; - DisplayModePtr mActiveMode; std::optional mStagedBrightness; std::optional mBrightness; - const DisplayModes mSupportedModes; - - std::atomic mLastHwVsync = 0; // TODO(b/182939859): Remove special cases for primary display. const bool mIsPrimary; uint32_t mFlags = 0; - std::optional mDeviceProductInfo; + // Requested refresh rate in fps, supported only for virtual displays. + // when this value is non zero, SurfaceFlinger will try to drop frames + // for virtual displays to match this requested refresh rate. + const Fps mRequestedRefreshRate; + + // Adjusted refresh rate, rounded to match a divisor of the pacesetter + // display's refresh rate. Only supported for virtual displays. + Fps mAdjustedRefreshRate = 0_Hz; std::vector mOverrideHdrTypes; - std::shared_ptr mRefreshRateConfigs; + std::shared_ptr mRefreshRateSelector; std::unique_ptr mRefreshRateOverlay; mutable std::mutex mActiveModeLock; ActiveModeInfo mDesiredActiveMode GUARDED_BY(mActiveModeLock); - TracedOrdinal mDesiredActiveModeChanged - GUARDED_BY(mActiveModeLock) = {"DesiredActiveModeChanged", false}; + TracedOrdinal mDesiredActiveModeChanged GUARDED_BY(mActiveModeLock) = + {ftl::Concat("DesiredActiveModeChanged-", getId().value).c_str(), false}; ActiveModeInfo mUpcomingActiveMode GUARDED_BY(kMainThreadContext); - - std::atomic_int mNumModeSwitchesInPolicy = 0; }; struct DisplayDeviceState { struct Physical { PhysicalDisplayId id; - ui::DisplayConnectionType type; hardware::graphics::composer::hal::HWDisplayId hwcDisplayId; - std::optional deviceProductInfo; - DisplayModes supportedModes; DisplayModePtr activeMode; bool operator==(const Physical& other) const { - return id == other.id && type == other.type && hwcDisplayId == other.hwcDisplayId; + return id == other.id && hwcDisplayId == other.hwcDisplayId; } }; @@ -339,6 +330,8 @@ struct DisplayDeviceState { uint32_t height = 0; std::string displayName; bool isSecure = false; + // Refer to DisplayDevice::mRequestedRefreshRate, for virtual display only + Fps requestedRefreshRate; private: static std::atomic sNextSequenceId; @@ -354,10 +347,9 @@ struct DisplayDeviceCreationArgs { HWComposer& hwComposer; const wp displayToken; const std::shared_ptr compositionDisplay; - std::shared_ptr refreshRateConfigs; + std::shared_ptr refreshRateSelector; int32_t sequenceId{0}; - std::optional connectionType; bool isSecure{false}; sp nativeWindow; sp displaySurface; @@ -368,8 +360,9 @@ struct DisplayDeviceCreationArgs { std::unordered_map> hwcColorModes; std::optional initialPowerMode; bool isPrimary{false}; - DisplayModes supportedModes; DisplayModeId activeModeId; + // Refer to DisplayDevice::mRequestedRefreshRate, for virtual display only + Fps requestedRefreshRate; }; } // namespace android diff --git a/services/surfaceflinger/DisplayHardware/AidlComposerHal.cpp b/services/surfaceflinger/DisplayHardware/AidlComposerHal.cpp index 36512311d15928158507fba3786646bce373eca8..c0eb36dc02cba1fd31dd38bdeb661a192e9ec00d 100644 --- a/services/surfaceflinger/DisplayHardware/AidlComposerHal.cpp +++ b/services/surfaceflinger/DisplayHardware/AidlComposerHal.cpp @@ -20,9 +20,11 @@ #include "AidlComposerHal.h" +#include #include #include #include +#include #include #include @@ -55,6 +57,10 @@ using AidlDisplayContentSample = aidl::android::hardware::graphics::composer3::D using AidlDisplayAttribute = aidl::android::hardware::graphics::composer3::DisplayAttribute; using AidlDisplayCapability = aidl::android::hardware::graphics::composer3::DisplayCapability; using AidlHdrCapabilities = aidl::android::hardware::graphics::composer3::HdrCapabilities; +using AidlHdrConversionCapability = + aidl::android::hardware::graphics::common::HdrConversionCapability; +using AidlHdrConversionStrategy = aidl::android::hardware::graphics::common::HdrConversionStrategy; +using AidlOverlayProperties = aidl::android::hardware::graphics::composer3::OverlayProperties; using AidlPerFrameMetadata = aidl::android::hardware::graphics::composer3::PerFrameMetadata; using AidlPerFrameMetadataKey = aidl::android::hardware::graphics::composer3::PerFrameMetadataKey; using AidlPerFrameMetadataBlob = aidl::android::hardware::graphics::composer3::PerFrameMetadataBlob; @@ -203,6 +209,12 @@ public: return ::ndk::ScopedAStatus::ok(); } + ::ndk::ScopedAStatus onRefreshRateChangedDebug( + const RefreshRateChangedDebugData& refreshRateChangedDebugData) override { + mCallback.onRefreshRateChangedDebug(refreshRateChangedDebugData); + return ::ndk::ScopedAStatus::ok(); + } + private: HWC2::ComposerCallback& mCallback; }; @@ -229,6 +241,30 @@ AidlComposer::AidlComposer(const std::string& serviceName) { return; } + addReader(translate(kSingleReaderKey)); + + // If unable to read interface version, then become backwards compatible. + int32_t version = 1; + const auto status = mAidlComposerClient->getInterfaceVersion(&version); + if (!status.isOk()) { + ALOGE("getInterfaceVersion for AidlComposer constructor failed %s", + status.getDescription().c_str()); + } + mSupportsBufferSlotsToClear = version > 1; + if (!mSupportsBufferSlotsToClear) { + if (sysprop::clear_slots_with_set_layer_buffer(false)) { + mClearSlotBuffer = sp::make(1, 1, PIXEL_FORMAT_RGBX_8888, + GraphicBuffer::USAGE_HW_COMPOSER | + GraphicBuffer::USAGE_SW_READ_OFTEN | + GraphicBuffer::USAGE_SW_WRITE_OFTEN, + "AidlComposer"); + if (!mClearSlotBuffer || mClearSlotBuffer->initCheck() != ::android::OK) { + LOG_ALWAYS_FATAL("Failed to allocate a buffer for clearing layer buffer slots"); + return; + } + } + } + ALOGI("Loaded AIDL composer3 HAL service"); } @@ -297,12 +333,11 @@ void AidlComposer::registerCallback(HWC2::ComposerCallback& callback) { } } -void AidlComposer::resetCommands() { - mWriter.reset(); -} - -Error AidlComposer::executeCommands() { - return execute(); +Error AidlComposer::executeCommands(Display display) { + mMutex.lock_shared(); + auto error = execute(display); + mMutex.unlock_shared(); + return error; } uint32_t AidlComposer::getMaxVirtualDisplayCount() { @@ -333,6 +368,7 @@ Error AidlComposer::createVirtualDisplay(uint32_t width, uint32_t height, PixelF *outDisplay = translate(virtualDisplay.display); *format = static_cast(virtualDisplay.format); + addDisplay(translate(virtualDisplay.display)); return Error::NONE; } @@ -342,12 +378,20 @@ Error AidlComposer::destroyVirtualDisplay(Display display) { ALOGE("destroyVirtualDisplay failed %s", status.getDescription().c_str()); return static_cast(status.getServiceSpecificError()); } + removeDisplay(display); return Error::NONE; } Error AidlComposer::acceptDisplayChanges(Display display) { - mWriter.acceptDisplayChanges(translate(display)); - return Error::NONE; + Error error = Error::NONE; + mMutex.lock_shared(); + if (auto writer = getWriter(display)) { + writer->get().acceptDisplayChanges(translate(display)); + } else { + error = Error::BAD_DISPLAY; + } + mMutex.unlock_shared(); + return error; } Error AidlComposer::createLayer(Display display, Layer* outLayer) { @@ -387,7 +431,17 @@ Error AidlComposer::getActiveConfig(Display display, Config* outConfig) { Error AidlComposer::getChangedCompositionTypes( Display display, std::vector* outLayers, std::vector* outTypes) { - const auto changedLayers = mReader.takeChangedCompositionTypes(translate(display)); + std::vector changedLayers; + Error error = Error::NONE; + { + mMutex.lock_shared(); + if (auto reader = getReader(display)) { + changedLayers = reader->get().takeChangedCompositionTypes(translate(display)); + } else { + error = Error::BAD_DISPLAY; + } + mMutex.unlock_shared(); + } outLayers->reserve(changedLayers.size()); outTypes->reserve(changedLayers.size()); @@ -395,7 +449,7 @@ Error AidlComposer::getChangedCompositionTypes( outLayers->emplace_back(translate(layer.layer)); outTypes->emplace_back(layer.composition); } - return Error::NONE; + return error; } Error AidlComposer::getColorModes(Display display, std::vector* outModes) { @@ -447,7 +501,17 @@ Error AidlComposer::getDisplayName(Display display, std::string* outName) { Error AidlComposer::getDisplayRequests(Display display, uint32_t* outDisplayRequestMask, std::vector* outLayers, std::vector* outLayerRequestMasks) { - const auto displayRequests = mReader.takeDisplayRequests(translate(display)); + Error error = Error::NONE; + DisplayRequest displayRequests; + { + mMutex.lock_shared(); + if (auto reader = getReader(display)) { + displayRequests = reader->get().takeDisplayRequests(translate(display)); + } else { + error = Error::BAD_DISPLAY; + } + mMutex.unlock_shared(); + } *outDisplayRequestMask = translate(displayRequests.mask); outLayers->reserve(displayRequests.layerRequests.size()); outLayerRequestMasks->reserve(displayRequests.layerRequests.size()); @@ -456,7 +520,7 @@ Error AidlComposer::getDisplayRequests(Display display, uint32_t* outDisplayRequ outLayers->emplace_back(translate(layer.layer)); outLayerRequestMasks->emplace_back(translate(layer.mask)); } - return Error::NONE; + return error; } Error AidlComposer::getDozeSupport(Display display, bool* outSupport) { @@ -496,16 +560,35 @@ Error AidlComposer::getHdrCapabilities(Display display, std::vector* outTyp return static_cast(status.getServiceSpecificError()); } - *outTypes = translate(capabilities.types); + *outTypes = capabilities.types; *outMaxLuminance = capabilities.maxLuminance; *outMaxAverageLuminance = capabilities.maxAverageLuminance; *outMinLuminance = capabilities.minLuminance; return Error::NONE; } +Error AidlComposer::getOverlaySupport(AidlOverlayProperties* outProperties) { + const auto status = mAidlComposerClient->getOverlaySupport(outProperties); + if (!status.isOk()) { + ALOGE("getOverlaySupport failed %s", status.getDescription().c_str()); + return static_cast(status.getServiceSpecificError()); + } + return Error::NONE; +} + Error AidlComposer::getReleaseFences(Display display, std::vector* outLayers, std::vector* outReleaseFences) { - auto fences = mReader.takeReleaseFences(translate(display)); + Error error = Error::NONE; + std::vector fences; + { + mMutex.lock_shared(); + if (auto reader = getReader(display)) { + fences = reader->get().takeReleaseFences(translate(display)); + } else { + error = Error::BAD_DISPLAY; + } + mMutex.unlock_shared(); + } outLayers->reserve(fences.size()); outReleaseFences->reserve(fences.size()); @@ -516,19 +599,31 @@ Error AidlComposer::getReleaseFences(Display display, std::vector* outLay *fence.fence.getR() = -1; outReleaseFences->emplace_back(fenceOwner); } - return Error::NONE; + return error; } Error AidlComposer::presentDisplay(Display display, int* outPresentFence) { - ATRACE_NAME("HwcPresentDisplay"); - mWriter.presentDisplay(translate(display)); + const auto displayId = translate(display); + ATRACE_FORMAT("HwcPresentDisplay %" PRId64, displayId); + + Error error = Error::NONE; + mMutex.lock_shared(); + auto writer = getWriter(display); + auto reader = getReader(display); + if (writer && reader) { + writer->get().presentDisplay(displayId); + error = execute(display); + } else { + error = Error::BAD_DISPLAY; + } - Error error = execute(); if (error != Error::NONE) { + mMutex.unlock_shared(); return error; } - auto fence = mReader.takePresentFence(translate(display)); + auto fence = reader->get().takePresentFence(displayId); + mMutex.unlock_shared(); // take ownership *outPresentFence = fence.get(); *fence.getR() = -1; @@ -553,11 +648,19 @@ Error AidlComposer::setClientTarget(Display display, uint32_t slot, const spgetNativeBuffer()->handle; } - mWriter.setClientTarget(translate(display), slot, handle, acquireFence, - translate( - dataspace), - translate(damage)); - return Error::NONE; + Error error = Error::NONE; + mMutex.lock_shared(); + if (auto writer = getWriter(display)) { + writer->get() + .setClientTarget(translate(display), slot, handle, acquireFence, + translate( + dataspace), + translate(damage)); + } else { + error = Error::BAD_DISPLAY; + } + mMutex.unlock_shared(); + return error; } Error AidlComposer::setColorMode(Display display, ColorMode mode, RenderIntent renderIntent) { @@ -573,14 +676,28 @@ Error AidlComposer::setColorMode(Display display, ColorMode mode, RenderIntent r } Error AidlComposer::setColorTransform(Display display, const float* matrix) { - mWriter.setColorTransform(translate(display), matrix); - return Error::NONE; + auto error = Error::NONE; + mMutex.lock_shared(); + if (auto writer = getWriter(display)) { + writer->get().setColorTransform(translate(display), matrix); + } else { + error = Error::BAD_DISPLAY; + } + mMutex.unlock_shared(); + return error; } Error AidlComposer::setOutputBuffer(Display display, const native_handle_t* buffer, int releaseFence) { - mWriter.setOutputBuffer(translate(display), 0, buffer, dup(releaseFence)); - return Error::NONE; + auto error = Error::NONE; + mMutex.lock_shared(); + if (auto writer = getWriter(display)) { + writer->get().setOutputBuffer(translate(display), 0, buffer, dup(releaseFence)); + } else { + error = Error::BAD_DISPLAY; + } + mMutex.unlock_shared(); + return error; } Error AidlComposer::setPowerMode(Display display, IComposerClient::PowerMode mode) { @@ -617,57 +734,89 @@ Error AidlComposer::setClientTargetSlotCount(Display display) { Error AidlComposer::validateDisplay(Display display, nsecs_t expectedPresentTime, uint32_t* outNumTypes, uint32_t* outNumRequests) { - ATRACE_NAME("HwcValidateDisplay"); - mWriter.validateDisplay(translate(display), - ClockMonotonicTimestamp{expectedPresentTime}); + const auto displayId = translate(display); + ATRACE_FORMAT("HwcValidateDisplay %" PRId64, displayId); + + Error error = Error::NONE; + mMutex.lock_shared(); + auto writer = getWriter(display); + auto reader = getReader(display); + if (writer && reader) { + writer->get().validateDisplay(displayId, ClockMonotonicTimestamp{expectedPresentTime}); + error = execute(display); + } else { + error = Error::BAD_DISPLAY; + } - Error error = execute(); if (error != Error::NONE) { + mMutex.unlock_shared(); return error; } - mReader.hasChanges(translate(display), outNumTypes, outNumRequests); + reader->get().hasChanges(displayId, outNumTypes, outNumRequests); + mMutex.unlock_shared(); return Error::NONE; } Error AidlComposer::presentOrValidateDisplay(Display display, nsecs_t expectedPresentTime, uint32_t* outNumTypes, uint32_t* outNumRequests, int* outPresentFence, uint32_t* state) { - ATRACE_NAME("HwcPresentOrValidateDisplay"); - mWriter.presentOrvalidateDisplay(translate(display), - ClockMonotonicTimestamp{expectedPresentTime}); + const auto displayId = translate(display); + ATRACE_FORMAT("HwcPresentOrValidateDisplay %" PRId64, displayId); + + Error error = Error::NONE; + mMutex.lock_shared(); + auto writer = getWriter(display); + auto reader = getReader(display); + if (writer && reader) { + writer->get().presentOrvalidateDisplay(displayId, + ClockMonotonicTimestamp{expectedPresentTime}); + error = execute(display); + } else { + error = Error::BAD_DISPLAY; + } - Error error = execute(); if (error != Error::NONE) { + mMutex.unlock_shared(); return error; } - const auto result = mReader.takePresentOrValidateStage(translate(display)); + const auto result = reader->get().takePresentOrValidateStage(displayId); if (!result.has_value()) { *state = translate(-1); + mMutex.unlock_shared(); return Error::NO_RESOURCES; } *state = translate(*result); if (*result == PresentOrValidate::Result::Presented) { - auto fence = mReader.takePresentFence(translate(display)); + auto fence = reader->get().takePresentFence(displayId); // take ownership *outPresentFence = fence.get(); *fence.getR() = -1; } if (*result == PresentOrValidate::Result::Validated) { - mReader.hasChanges(translate(display), outNumTypes, outNumRequests); + reader->get().hasChanges(displayId, outNumTypes, outNumRequests); } + mMutex.unlock_shared(); return Error::NONE; } Error AidlComposer::setCursorPosition(Display display, Layer layer, int32_t x, int32_t y) { - mWriter.setLayerCursorPosition(translate(display), translate(layer), x, y); - return Error::NONE; + Error error = Error::NONE; + mMutex.lock_shared(); + if (auto writer = getWriter(display)) { + writer->get().setLayerCursorPosition(translate(display), translate(layer), + x, y); + } else { + error = Error::BAD_DISPLAY; + } + mMutex.unlock_shared(); + return error; } Error AidlComposer::setLayerBuffer(Display display, Layer layer, uint32_t slot, @@ -677,90 +826,232 @@ Error AidlComposer::setLayerBuffer(Display display, Layer layer, uint32_t slot, handle = buffer->getNativeBuffer()->handle; } - mWriter.setLayerBuffer(translate(display), translate(layer), slot, handle, - acquireFence); - return Error::NONE; + Error error = Error::NONE; + mMutex.lock_shared(); + if (auto writer = getWriter(display)) { + writer->get().setLayerBuffer(translate(display), translate(layer), slot, + handle, acquireFence); + } else { + error = Error::BAD_DISPLAY; + } + mMutex.unlock_shared(); + return error; +} + +Error AidlComposer::setLayerBufferSlotsToClear(Display display, Layer layer, + const std::vector& slotsToClear, + uint32_t activeBufferSlot) { + if (slotsToClear.empty()) { + return Error::NONE; + } + + Error error = Error::NONE; + mMutex.lock_shared(); + if (auto writer = getWriter(display)) { + if (mSupportsBufferSlotsToClear) { + writer->get().setLayerBufferSlotsToClear(translate(display), + translate(layer), slotsToClear); + // Backwards compatible way of clearing buffer slots is to set the layer buffer with a + // placeholder buffer, using the slot that needs to cleared... tricky. + } else if (mClearSlotBuffer != nullptr) { + for (uint32_t slot : slotsToClear) { + // Don't clear the active buffer slot because we need to restore the active buffer + // after clearing the requested buffer slots with a placeholder buffer. + if (slot != activeBufferSlot) { + writer->get().setLayerBufferWithNewCommand(translate(display), + translate(layer), slot, + mClearSlotBuffer->handle, + /*fence*/ -1); + } + } + // Since we clear buffers by setting them to a placeholder buffer, we want to make + // sure that the last setLayerBuffer command is sent with the currently active + // buffer, not the placeholder buffer, so that there is no perceptual change when + // buffers are discarded. + writer->get().setLayerBufferWithNewCommand(translate(display), + translate(layer), activeBufferSlot, + // The active buffer is still cached in + // its slot and doesn't need a fence. + /*buffer*/ nullptr, /*fence*/ -1); + } + } else { + error = Error::BAD_DISPLAY; + } + mMutex.unlock_shared(); + return error; } Error AidlComposer::setLayerSurfaceDamage(Display display, Layer layer, const std::vector& damage) { - mWriter.setLayerSurfaceDamage(translate(display), translate(layer), - translate(damage)); - return Error::NONE; + Error error = Error::NONE; + mMutex.lock_shared(); + if (auto writer = getWriter(display)) { + writer->get().setLayerSurfaceDamage(translate(display), translate(layer), + translate(damage)); + } else { + error = Error::BAD_DISPLAY; + } + mMutex.unlock_shared(); + return error; } Error AidlComposer::setLayerBlendMode(Display display, Layer layer, IComposerClient::BlendMode mode) { - mWriter.setLayerBlendMode(translate(display), translate(layer), - translate(mode)); - return Error::NONE; + Error error = Error::NONE; + mMutex.lock_shared(); + if (auto writer = getWriter(display)) { + writer->get().setLayerBlendMode(translate(display), translate(layer), + translate(mode)); + } else { + error = Error::BAD_DISPLAY; + } + mMutex.unlock_shared(); + return error; } Error AidlComposer::setLayerColor(Display display, Layer layer, const Color& color) { - mWriter.setLayerColor(translate(display), translate(layer), color); - return Error::NONE; + Error error = Error::NONE; + mMutex.lock_shared(); + if (auto writer = getWriter(display)) { + writer->get().setLayerColor(translate(display), translate(layer), color); + } else { + error = Error::BAD_DISPLAY; + } + mMutex.unlock_shared(); + return error; } Error AidlComposer::setLayerCompositionType( Display display, Layer layer, aidl::android::hardware::graphics::composer3::Composition type) { - mWriter.setLayerCompositionType(translate(display), translate(layer), type); - return Error::NONE; + Error error = Error::NONE; + mMutex.lock_shared(); + if (auto writer = getWriter(display)) { + writer->get().setLayerCompositionType(translate(display), + translate(layer), type); + } else { + error = Error::BAD_DISPLAY; + } + mMutex.unlock_shared(); + return error; } Error AidlComposer::setLayerDataspace(Display display, Layer layer, Dataspace dataspace) { - mWriter.setLayerDataspace(translate(display), translate(layer), - translate(dataspace)); - return Error::NONE; + Error error = Error::NONE; + mMutex.lock_shared(); + if (auto writer = getWriter(display)) { + writer->get().setLayerDataspace(translate(display), translate(layer), + translate(dataspace)); + } else { + error = Error::BAD_DISPLAY; + } + mMutex.unlock_shared(); + return error; } Error AidlComposer::setLayerDisplayFrame(Display display, Layer layer, const IComposerClient::Rect& frame) { - mWriter.setLayerDisplayFrame(translate(display), translate(layer), - translate(frame)); - return Error::NONE; + Error error = Error::NONE; + mMutex.lock_shared(); + if (auto writer = getWriter(display)) { + writer->get().setLayerDisplayFrame(translate(display), translate(layer), + translate(frame)); + } else { + error = Error::BAD_DISPLAY; + } + mMutex.unlock_shared(); + return error; } Error AidlComposer::setLayerPlaneAlpha(Display display, Layer layer, float alpha) { - mWriter.setLayerPlaneAlpha(translate(display), translate(layer), alpha); - return Error::NONE; + Error error = Error::NONE; + mMutex.lock_shared(); + if (auto writer = getWriter(display)) { + writer->get().setLayerPlaneAlpha(translate(display), translate(layer), + alpha); + } else { + error = Error::BAD_DISPLAY; + } + mMutex.unlock_shared(); + return error; } Error AidlComposer::setLayerSidebandStream(Display display, Layer layer, const native_handle_t* stream) { - mWriter.setLayerSidebandStream(translate(display), translate(layer), stream); - return Error::NONE; + Error error = Error::NONE; + mMutex.lock_shared(); + if (auto writer = getWriter(display)) { + writer->get().setLayerSidebandStream(translate(display), translate(layer), + stream); + } else { + error = Error::BAD_DISPLAY; + } + mMutex.unlock_shared(); + return error; } Error AidlComposer::setLayerSourceCrop(Display display, Layer layer, const IComposerClient::FRect& crop) { - mWriter.setLayerSourceCrop(translate(display), translate(layer), - translate(crop)); - return Error::NONE; + Error error = Error::NONE; + mMutex.lock_shared(); + if (auto writer = getWriter(display)) { + writer->get().setLayerSourceCrop(translate(display), translate(layer), + translate(crop)); + } else { + error = Error::BAD_DISPLAY; + } + mMutex.unlock_shared(); + return error; } Error AidlComposer::setLayerTransform(Display display, Layer layer, Transform transform) { - mWriter.setLayerTransform(translate(display), translate(layer), - translate(transform)); - return Error::NONE; + Error error = Error::NONE; + mMutex.lock_shared(); + if (auto writer = getWriter(display)) { + writer->get().setLayerTransform(translate(display), translate(layer), + translate(transform)); + } else { + error = Error::BAD_DISPLAY; + } + mMutex.unlock_shared(); + return error; } Error AidlComposer::setLayerVisibleRegion(Display display, Layer layer, const std::vector& visible) { - mWriter.setLayerVisibleRegion(translate(display), translate(layer), - translate(visible)); - return Error::NONE; + Error error = Error::NONE; + mMutex.lock_shared(); + if (auto writer = getWriter(display)) { + writer->get().setLayerVisibleRegion(translate(display), translate(layer), + translate(visible)); + } else { + error = Error::BAD_DISPLAY; + } + mMutex.unlock_shared(); + return error; } Error AidlComposer::setLayerZOrder(Display display, Layer layer, uint32_t z) { - mWriter.setLayerZOrder(translate(display), translate(layer), z); - return Error::NONE; + Error error = Error::NONE; + mMutex.lock_shared(); + if (auto writer = getWriter(display)) { + writer->get().setLayerZOrder(translate(display), translate(layer), z); + } else { + error = Error::BAD_DISPLAY; + } + mMutex.unlock_shared(); + return error; } -Error AidlComposer::execute() { - const auto& commands = mWriter.getPendingCommands(); +Error AidlComposer::execute(Display display) { + auto writer = getWriter(display); + auto reader = getReader(display); + if (!writer || !reader) { + return Error::BAD_DISPLAY; + } + + auto commands = writer->get().takePendingCommands(); if (commands.empty()) { - mWriter.reset(); return Error::NONE; } @@ -772,9 +1063,9 @@ Error AidlComposer::execute() { return static_cast(status.getServiceSpecificError()); } - mReader.parse(std::move(results)); + reader->get().parse(std::move(results)); } - const auto commandErrors = mReader.takeErrors(); + const auto commandErrors = reader->get().takeErrors(); Error error = Error::NONE; for (const auto& cmdErr : commandErrors) { const auto index = static_cast(cmdErr.commandIndex); @@ -792,17 +1083,23 @@ Error AidlComposer::execute() { } } - mWriter.reset(); - return error; } Error AidlComposer::setLayerPerFrameMetadata( Display display, Layer layer, const std::vector& perFrameMetadatas) { - mWriter.setLayerPerFrameMetadata(translate(display), translate(layer), - translate(perFrameMetadatas)); - return Error::NONE; + Error error = Error::NONE; + mMutex.lock_shared(); + if (auto writer = getWriter(display)) { + writer->get().setLayerPerFrameMetadata(translate(display), + translate(layer), + translate(perFrameMetadatas)); + } else { + error = Error::BAD_DISPLAY; + } + mMutex.unlock_shared(); + return error; } std::vector AidlComposer::getPerFrameMetadataKeys( @@ -862,8 +1159,16 @@ Error AidlComposer::getDisplayIdentificationData(Display display, uint8_t* outPo } Error AidlComposer::setLayerColorTransform(Display display, Layer layer, const float* matrix) { - mWriter.setLayerColorTransform(translate(display), translate(layer), matrix); - return Error::NONE; + Error error = Error::NONE; + mMutex.lock_shared(); + if (auto writer = getWriter(display)) { + writer->get().setLayerColorTransform(translate(display), translate(layer), + matrix); + } else { + error = Error::BAD_DISPLAY; + } + mMutex.unlock_shared(); + return error; } Error AidlComposer::getDisplayedContentSamplingAttributes(Display display, PixelFormat* outFormat, @@ -926,20 +1231,36 @@ Error AidlComposer::getDisplayedContentSample(Display display, uint64_t maxFrame Error AidlComposer::setLayerPerFrameMetadataBlobs( Display display, Layer layer, const std::vector& metadata) { - mWriter.setLayerPerFrameMetadataBlobs(translate(display), translate(layer), - translate(metadata)); - return Error::NONE; + Error error = Error::NONE; + mMutex.lock_shared(); + if (auto writer = getWriter(display)) { + writer->get().setLayerPerFrameMetadataBlobs(translate(display), + translate(layer), + translate(metadata)); + } else { + error = Error::BAD_DISPLAY; + } + mMutex.unlock_shared(); + return error; } Error AidlComposer::setDisplayBrightness(Display display, float brightness, float brightnessNits, const DisplayBrightnessOptions& options) { - mWriter.setDisplayBrightness(translate(display), brightness, brightnessNits); - - if (options.applyImmediately) { - return execute(); + Error error = Error::NONE; + mMutex.lock_shared(); + if (auto writer = getWriter(display)) { + writer->get().setDisplayBrightness(translate(display), brightness, brightnessNits); + + if (options.applyImmediately) { + error = execute(display); + mMutex.unlock_shared(); + return error; + } + } else { + error = Error::BAD_DISPLAY; } - - return Error::NONE; + mMutex.unlock_shared(); + return error; } Error AidlComposer::getDisplayCapabilities(Display display, @@ -1077,22 +1398,81 @@ Error AidlComposer::getPreferredBootDisplayConfig(Display display, Config* confi return Error::NONE; } +Error AidlComposer::getHdrConversionCapabilities( + std::vector* hdrConversionCapabilities) { + const auto status = + mAidlComposerClient->getHdrConversionCapabilities(hdrConversionCapabilities); + if (!status.isOk()) { + hdrConversionCapabilities = {}; + ALOGE("getHdrConversionCapabilities failed %s", status.getDescription().c_str()); + return static_cast(status.getServiceSpecificError()); + } + return Error::NONE; +} + +Error AidlComposer::setHdrConversionStrategy(AidlHdrConversionStrategy hdrConversionStrategy, + Hdr* outPreferredHdrOutputType) { + const auto status = mAidlComposerClient->setHdrConversionStrategy(hdrConversionStrategy, + outPreferredHdrOutputType); + if (!status.isOk()) { + ALOGE("setHdrConversionStrategy failed %s", status.getDescription().c_str()); + return static_cast(status.getServiceSpecificError()); + } + return Error::NONE; +} + +Error AidlComposer::setRefreshRateChangedCallbackDebugEnabled(Display displayId, bool enabled) { + const auto status = + mAidlComposerClient->setRefreshRateChangedCallbackDebugEnabled(translate( + displayId), + enabled); + if (!status.isOk()) { + ALOGE("setRefreshRateChangedCallbackDebugEnabled failed %s", + status.getDescription().c_str()); + return static_cast(status.getServiceSpecificError()); + } + return Error::NONE; +} + Error AidlComposer::getClientTargetProperty( Display display, ClientTargetPropertyWithBrightness* outClientTargetProperty) { - *outClientTargetProperty = mReader.takeClientTargetProperty(translate(display)); - return Error::NONE; + Error error = Error::NONE; + mMutex.lock_shared(); + if (auto reader = getReader(display)) { + *outClientTargetProperty = + reader->get().takeClientTargetProperty(translate(display)); + } else { + error = Error::BAD_DISPLAY; + } + mMutex.unlock_shared(); + return error; } Error AidlComposer::setLayerBrightness(Display display, Layer layer, float brightness) { - mWriter.setLayerBrightness(translate(display), translate(layer), brightness); - return Error::NONE; + Error error = Error::NONE; + mMutex.lock_shared(); + if (auto writer = getWriter(display)) { + writer->get().setLayerBrightness(translate(display), translate(layer), + brightness); + } else { + error = Error::BAD_DISPLAY; + } + mMutex.unlock_shared(); + return error; } Error AidlComposer::setLayerBlockingRegion(Display display, Layer layer, const std::vector& blocking) { - mWriter.setLayerBlockingRegion(translate(display), translate(layer), - translate(blocking)); - return Error::NONE; + Error error = Error::NONE; + mMutex.lock_shared(); + if (auto writer = getWriter(display)) { + writer->get().setLayerBlockingRegion(translate(display), translate(layer), + translate(blocking)); + } else { + error = Error::BAD_DISPLAY; + } + mMutex.unlock_shared(); + return error; } Error AidlComposer::getDisplayDecorationSupport(Display display, @@ -1130,5 +1510,94 @@ Error AidlComposer::getPhysicalDisplayOrientation(Display displayId, return Error::NONE; } +ftl::Optional> AidlComposer::getWriter(Display display) + REQUIRES_SHARED(mMutex) { + return mWriters.get(display); +} + +ftl::Optional> AidlComposer::getReader(Display display) + REQUIRES_SHARED(mMutex) { + if (mSingleReader) { + display = translate(kSingleReaderKey); + } + return mReaders.get(display); +} + +void AidlComposer::removeDisplay(Display display) { + mMutex.lock(); + bool wasErased = mWriters.erase(display); + ALOGW_IF(!wasErased, + "Attempting to remove writer for display %" PRId64 " which is not connected", + translate(display)); + if (!mSingleReader) { + removeReader(display); + } + mMutex.unlock(); +} + +void AidlComposer::onHotplugDisconnect(Display display) { + removeDisplay(display); +} + +bool AidlComposer::hasMultiThreadedPresentSupport(Display display) { +#if 0 + // TODO (b/259132483): Reenable + const auto displayId = translate(display); + std::vector capabilities; + const auto status = mAidlComposerClient->getDisplayCapabilities(displayId, &capabilities); + if (!status.isOk()) { + ALOGE("getDisplayCapabilities failed %s", status.getDescription().c_str()); + return false; + } + return std::find(capabilities.begin(), capabilities.end(), + AidlDisplayCapability::MULTI_THREADED_PRESENT) != capabilities.end(); +#else + (void) display; + return false; +#endif +} + +void AidlComposer::addReader(Display display) { + const auto displayId = translate(display); + std::optional displayOpt; + if (displayId != kSingleReaderKey) { + displayOpt.emplace(displayId); + } + auto [it, added] = mReaders.try_emplace(display, std::move(displayOpt)); + ALOGW_IF(!added, "Attempting to add writer for display %" PRId64 " which is already connected", + displayId); +} + +void AidlComposer::removeReader(Display display) { + bool wasErased = mReaders.erase(display); + ALOGW_IF(!wasErased, + "Attempting to remove reader for display %" PRId64 " which is not connected", + translate(display)); +} + +void AidlComposer::addDisplay(Display display) { + const auto displayId = translate(display); + mMutex.lock(); + auto [it, added] = mWriters.try_emplace(display, displayId); + ALOGW_IF(!added, "Attempting to add writer for display %" PRId64 " which is already connected", + displayId); + if (mSingleReader) { + if (hasMultiThreadedPresentSupport(display)) { + mSingleReader = false; + removeReader(translate(kSingleReaderKey)); + // Note that this includes the new display. + for (const auto& [existingDisplay, _] : mWriters) { + addReader(existingDisplay); + } + } + } else { + addReader(display); + } + mMutex.unlock(); +} + +void AidlComposer::onHotplugConnect(Display display) { + addDisplay(display); +} } // namespace Hwc2 } // namespace android diff --git a/services/surfaceflinger/DisplayHardware/AidlComposerHal.h b/services/surfaceflinger/DisplayHardware/AidlComposerHal.h index 18d2242c7ed5a228fed9034909e99ea4ffcdde06..b8ae26f87923a0d0d2071cc6c1901c552205520d 100644 --- a/services/surfaceflinger/DisplayHardware/AidlComposerHal.h +++ b/services/surfaceflinger/DisplayHardware/AidlComposerHal.h @@ -17,10 +17,12 @@ #pragma once #include "ComposerHal.h" +#include +#include +#include #include #include -#include #include #include @@ -46,8 +48,11 @@ namespace android::Hwc2 { using aidl::android::hardware::graphics::common::DisplayDecorationSupport; +using aidl::android::hardware::graphics::common::HdrConversionCapability; +using aidl::android::hardware::graphics::common::HdrConversionStrategy; using aidl::android::hardware::graphics::composer3::ComposerClientReader; using aidl::android::hardware::graphics::composer3::ComposerClientWriter; +using aidl::android::hardware::graphics::composer3::OverlayProperties; class AidlIComposerCallbackWrapper; @@ -67,12 +72,8 @@ public: void registerCallback(HWC2::ComposerCallback& callback) override; - // Reset all pending commands in the command buffer. Useful if you want to - // skip a frame but have already queued some commands. - void resetCommands() override; - // Explicitly flush all pending commands in the command buffer. - Error executeCommands() override; + Error executeCommands(Display) override; uint32_t getMaxVirtualDisplayCount() override; Error createVirtualDisplay(uint32_t width, uint32_t height, PixelFormat* format, @@ -101,8 +102,9 @@ public: Error getDozeSupport(Display display, bool* outSupport) override; Error hasDisplayIdleTimerCapability(Display display, bool* outSupport) override; - Error getHdrCapabilities(Display display, std::vector* outTypes, float* outMaxLuminance, + Error getHdrCapabilities(Display display, std::vector* outHdrTypes, float* outMaxLuminance, float* outMaxAverageLuminance, float* outMinLuminance) override; + Error getOverlaySupport(OverlayProperties* outProperties) override; Error getReleaseFences(Display display, std::vector* outLayers, std::vector* outReleaseFences) override; @@ -139,6 +141,9 @@ public: /* see setClientTarget for the purpose of slot */ Error setLayerBuffer(Display display, Layer layer, uint32_t slot, const sp& buffer, int acquireFence) override; + Error setLayerBufferSlotsToClear(Display display, Layer layer, + const std::vector& slotsToClear, + uint32_t activeBufferSlot) override; Error setLayerSurfaceDamage(Display display, Layer layer, const std::vector& damage) override; Error setLayerBlendMode(Display display, Layer layer, IComposerClient::BlendMode mode) override; @@ -226,16 +231,32 @@ public: Error getPhysicalDisplayOrientation(Display displayId, AidlTransform* outDisplayOrientation) override; + void onHotplugConnect(Display) override; + void onHotplugDisconnect(Display) override; + Error getHdrConversionCapabilities(std::vector*) override; + Error setHdrConversionStrategy(HdrConversionStrategy, Hdr*) override; + Error setRefreshRateChangedCallbackDebugEnabled(Display, bool) override; private: // Many public functions above simply write a command into the command // queue to batch the calls. validateDisplay and presentDisplay will call // this function to execute the command queue. - Error execute(); + Error execute(Display) REQUIRES_SHARED(mMutex); // returns the default instance name for the given service static std::string instance(const std::string& serviceName); + ftl::Optional> getWriter(Display) + REQUIRES_SHARED(mMutex); + ftl::Optional> getReader(Display) + REQUIRES_SHARED(mMutex); + void addDisplay(Display) EXCLUDES(mMutex); + void removeDisplay(Display) EXCLUDES(mMutex); + void addReader(Display) REQUIRES(mMutex); + void removeReader(Display) REQUIRES(mMutex); + + bool hasMultiThreadedPresentSupport(Display); + // 64KiB minus a small space for metadata such as read/write pointers static constexpr size_t kWriterInitialSize = 64 * 1024 / sizeof(uint32_t) - 16; // Max number of buffers that may be cached for a given layer @@ -243,8 +264,30 @@ private: // 1. Tightly coupling this cache to the max size of BufferQueue // 2. Adding an additional slot for the layer caching feature in SurfaceFlinger (see: Planner.h) static const constexpr uint32_t kMaxLayerBufferCount = BufferQueue::NUM_BUFFER_SLOTS + 1; - ComposerClientWriter mWriter; - ComposerClientReader mReader; + + // Without DisplayCapability::MULTI_THREADED_PRESENT, we use a single reader + // for all displays. With the capability, we use a separate reader for each + // display. + bool mSingleReader = true; + // Invalid displayId used as a key to mReaders when mSingleReader is true. + static constexpr int64_t kSingleReaderKey = 0; + + // TODO (b/256881188): Use display::PhysicalDisplayMap instead of hard-coded `3` + ftl::SmallMap mWriters GUARDED_BY(mMutex); + ftl::SmallMap mReaders GUARDED_BY(mMutex); + // Protect access to mWriters and mReaders with a shared_mutex. Adding and + // removing a display require exclusive access, since the iterator or the + // writer/reader may be invalidated. Other calls need shared access while + // using the writer/reader, so they can use their display's writer/reader + // without it being deleted or the iterator being invalidated. + // TODO (b/257958323): Use std::shared_mutex and RAII once they support + // threading annotations. + ftl::SharedMutex mMutex; + + // Whether or not explicitly clearing buffer slots is supported. + bool mSupportsBufferSlotsToClear; + // Buffer slots for layers are cleared by setting the slot buffer to this buffer. + sp mClearSlotBuffer; // Aidl interface using AidlIComposer = aidl::android::hardware::graphics::composer3::IComposer; diff --git a/services/surfaceflinger/DisplayHardware/ComposerHal.h b/services/surfaceflinger/DisplayHardware/ComposerHal.h index d266d942fb75ac6db4588a2820babd63f25046d6..cf677955bf073626713cdddbba305eca8064d61b 100644 --- a/services/surfaceflinger/DisplayHardware/ComposerHal.h +++ b/services/surfaceflinger/DisplayHardware/ComposerHal.h @@ -32,12 +32,15 @@ #include #include +#include +#include #include #include #include #include #include #include +#include #include #include @@ -65,7 +68,6 @@ using types::V1_0::Transform; using types::V1_1::RenderIntent; using types::V1_2::ColorMode; using types::V1_2::Dataspace; -using types::V1_2::Hdr; using types::V1_2::PixelFormat; using V2_1::Config; @@ -83,6 +85,7 @@ using PerFrameMetadata = IComposerClient::PerFrameMetadata; using PerFrameMetadataKey = IComposerClient::PerFrameMetadataKey; using PerFrameMetadataBlob = IComposerClient::PerFrameMetadataBlob; using AidlTransform = ::aidl::android::hardware::graphics::common::Transform; +using aidl::android::hardware::graphics::common::Hdr; class Composer { public: @@ -107,12 +110,8 @@ public: virtual void registerCallback(HWC2::ComposerCallback& callback) = 0; - // Reset all pending commands in the command buffer. Useful if you want to - // skip a frame but have already queued some commands. - virtual void resetCommands() = 0; - // Explicitly flush all pending commands in the command buffer. - virtual Error executeCommands() = 0; + virtual Error executeCommands(Display) = 0; virtual uint32_t getMaxVirtualDisplayCount() = 0; virtual Error createVirtualDisplay(uint32_t width, uint32_t height, PixelFormat*, @@ -139,7 +138,7 @@ public: virtual Error getDozeSupport(Display display, bool* outSupport) = 0; virtual Error hasDisplayIdleTimerCapability(Display display, bool* outSupport) = 0; - virtual Error getHdrCapabilities(Display display, std::vector* outTypes, + virtual Error getHdrCapabilities(Display display, std::vector* outHdrTypes, float* outMaxLuminance, float* outMaxAverageLuminance, float* outMinLuminance) = 0; @@ -178,6 +177,9 @@ public: /* see setClientTarget for the purpose of slot */ virtual Error setLayerBuffer(Display display, Layer layer, uint32_t slot, const sp& buffer, int acquireFence) = 0; + virtual Error setLayerBufferSlotsToClear(Display display, Layer layer, + const std::vector& slotsToClear, + uint32_t activeBufferSlot) = 0; virtual Error setLayerSurfaceDamage(Display display, Layer layer, const std::vector& damage) = 0; virtual Error setLayerBlendMode(Display display, Layer layer, @@ -281,6 +283,14 @@ public: virtual Error setIdleTimerEnabled(Display displayId, std::chrono::milliseconds timeout) = 0; virtual Error getPhysicalDisplayOrientation(Display displayId, AidlTransform* outDisplayOrientation) = 0; + virtual Error getOverlaySupport(V3_0::OverlayProperties* outProperties) = 0; + virtual void onHotplugConnect(Display) = 0; + virtual void onHotplugDisconnect(Display) = 0; + virtual Error getHdrConversionCapabilities( + std::vector<::aidl::android::hardware::graphics::common::HdrConversionCapability>*) = 0; + virtual Error setHdrConversionStrategy( + ::aidl::android::hardware::graphics::common::HdrConversionStrategy, Hdr*) = 0; + virtual Error setRefreshRateChangedCallbackDebugEnabled(Display, bool) = 0; }; } // namespace Hwc2 diff --git a/services/surfaceflinger/DisplayHardware/FramebufferSurface.cpp b/services/surfaceflinger/DisplayHardware/FramebufferSurface.cpp index eb14933a61eca6aef1262dba6a63d6c3a2d28fd4..ce602a8ad90a21e797fc3ba22007216dcb7fee93 100644 --- a/services/surfaceflinger/DisplayHardware/FramebufferSurface.cpp +++ b/services/surfaceflinger/DisplayHardware/FramebufferSurface.cpp @@ -72,6 +72,10 @@ FramebufferSurface::FramebufferSurface(HWComposer& hwc, PhysicalDisplayId displa mConsumer->setDefaultBufferSize(limitedSize.width, limitedSize.height); mConsumer->setMaxAcquiredBufferCount( SurfaceFlinger::maxFrameBufferAcquiredBuffers - 1); + + for (size_t i = 0; i < sizeof(mHwcBufferIds) / sizeof(mHwcBufferIds[0]); ++i) { + mHwcBufferIds[i] = UINT64_MAX; + } } void FramebufferSurface::resizeBuffers(const ui::Size& newSize) { @@ -88,31 +92,16 @@ status_t FramebufferSurface::prepareFrame(CompositionType /*compositionType*/) { } status_t FramebufferSurface::advanceFrame() { - uint32_t slot = 0; - sp buf; - sp acquireFence(Fence::NO_FENCE); - Dataspace dataspace = Dataspace::UNKNOWN; - status_t result = nextBuffer(slot, buf, acquireFence, dataspace); - mDataSpace = dataspace; - if (result != NO_ERROR) { - ALOGE("error latching next FramebufferSurface buffer: %s (%d)", - strerror(-result), result); - } - return result; -} - -status_t FramebufferSurface::nextBuffer(uint32_t& outSlot, - sp& outBuffer, sp& outFence, - Dataspace& outDataspace) { Mutex::Autolock lock(mMutex); BufferItem item; status_t err = acquireBufferLocked(&item, 0); if (err == BufferQueue::NO_BUFFER_AVAILABLE) { - mHwcBufferCache.getHwcBuffer(mCurrentBufferSlot, mCurrentBuffer, &outSlot, &outBuffer); + mDataspace = Dataspace::UNKNOWN; return NO_ERROR; } else if (err != NO_ERROR) { ALOGE("error acquiring buffer: %s (%d)", strerror(-err), err); + mDataspace = Dataspace::UNKNOWN; return err; } @@ -133,13 +122,18 @@ status_t FramebufferSurface::nextBuffer(uint32_t& outSlot, mCurrentBufferSlot = item.mSlot; mCurrentBuffer = mSlots[mCurrentBufferSlot].mGraphicBuffer; mCurrentFence = item.mFence; + mDataspace = static_cast(item.mDataSpace); - outFence = item.mFence; - mHwcBufferCache.getHwcBuffer(mCurrentBufferSlot, mCurrentBuffer, &outSlot, &outBuffer); - outDataspace = static_cast(item.mDataSpace); - status_t result = mHwc.setClientTarget(mDisplayId, outSlot, outFence, outBuffer, outDataspace); + // assume HWC has previously seen the buffer in this slot + sp hwcBuffer = sp(nullptr); + if (mCurrentBuffer->getId() != mHwcBufferIds[mCurrentBufferSlot]) { + mHwcBufferIds[mCurrentBufferSlot] = mCurrentBuffer->getId(); + hwcBuffer = mCurrentBuffer; // HWC hasn't previously seen this buffer in this slot + } + status_t result = mHwc.setClientTarget(mDisplayId, mCurrentBufferSlot, mCurrentFence, hwcBuffer, + mDataspace); if (result != NO_ERROR) { - ALOGE("error posting framebuffer: %d", result); + ALOGE("error posting framebuffer: %s (%d)", strerror(-result), result); return result; } @@ -190,7 +184,7 @@ ui::Size FramebufferSurface::limitSizeInternal(const ui::Size& size, const ui::S limitedSize.width = maxSize.height * aspectRatio; wasLimited = true; } - ALOGI_IF(wasLimited, "framebuffer size has been limited to [%dx%d] from [%dx%d]", + ALOGI_IF(wasLimited, "Framebuffer size has been limited to [%dx%d] from [%dx%d]", limitedSize.width, limitedSize.height, size.width, size.height); return limitedSize; } @@ -198,9 +192,9 @@ ui::Size FramebufferSurface::limitSizeInternal(const ui::Size& size, const ui::S void FramebufferSurface::dumpAsString(String8& result) const { Mutex::Autolock lock(mMutex); result.append(" FramebufferSurface\n"); - result.appendFormat(" mDataSpace=%s (%d)\n", - dataspaceDetails(static_cast(mDataSpace)).c_str(), - mDataSpace); + result.appendFormat(" mDataspace=%s (%d)\n", + dataspaceDetails(static_cast(mDataspace)).c_str(), + mDataspace); ConsumerBase::dumpLocked(result, " "); } diff --git a/services/surfaceflinger/DisplayHardware/FramebufferSurface.h b/services/surfaceflinger/DisplayHardware/FramebufferSurface.h index d41a856e68245d13285588dbbb40f56e2226f5c1..0b863daf475d936dba91a7d7e740f881c71c8d21 100644 --- a/services/surfaceflinger/DisplayHardware/FramebufferSurface.h +++ b/services/surfaceflinger/DisplayHardware/FramebufferSurface.h @@ -21,7 +21,7 @@ #include #include -#include +#include #include #include #include @@ -69,12 +69,6 @@ private: virtual void dumpLocked(String8& result, const char* prefix) const; - // nextBuffer waits for and then latches the next buffer from the - // BufferQueue and releases the previously latched buffer to the - // BufferQueue. The new buffer is returned in the 'buffer' argument. - status_t nextBuffer(uint32_t& outSlot, sp& outBuffer, - sp& outFence, ui::Dataspace& outDataspace); - const PhysicalDisplayId mDisplayId; // Framebuffer size has a dimension limitation in pixels based on the graphics capabilities of @@ -91,7 +85,7 @@ private: // compositing. Otherwise it will display the dataspace of the buffer // use for compositing which can change as wide-color content is // on/off. - ui::Dataspace mDataSpace; + ui::Dataspace mDataspace; // mCurrentBuffer is the current buffer or nullptr to indicate that there is // no current buffer. @@ -103,7 +97,9 @@ private: // Hardware composer, owned by SurfaceFlinger. HWComposer& mHwc; - compositionengine::impl::HwcBufferCache mHwcBufferCache; + // Buffers that HWC has seen before, indexed by slot number. + // NOTE: The BufferQueue slot number is the same as the HWC slot number. + uint64_t mHwcBufferIds[BufferQueue::NUM_BUFFER_SLOTS]; // Previous buffer to release after getting an updated retire fence bool mHasPendingRelease; diff --git a/services/surfaceflinger/DisplayHardware/HWC2.cpp b/services/surfaceflinger/DisplayHardware/HWC2.cpp index 8364ed9d28ffd7636a290529a54cce60a35ca46d..aaf2523338b31ff1531b032e85e30910e728abcd 100644 --- a/services/surfaceflinger/DisplayHardware/HWC2.cpp +++ b/services/surfaceflinger/DisplayHardware/HWC2.cpp @@ -40,6 +40,7 @@ using aidl::android::hardware::graphics::composer3::Color; using aidl::android::hardware::graphics::composer3::Composition; using AidlCapability = aidl::android::hardware::graphics::composer3::Capability; using aidl::android::hardware::graphics::composer3::DisplayCapability; +using aidl::android::hardware::graphics::composer3::OverlayProperties; namespace android { @@ -319,20 +320,25 @@ Error Display::getHdrCapabilities(HdrCapabilities* outCapabilities) const float maxLuminance = -1.0f; float maxAverageLuminance = -1.0f; float minLuminance = -1.0f; - std::vector types; - auto intError = mComposer.getHdrCapabilities(mId, &types, - &maxLuminance, &maxAverageLuminance, &minLuminance); + std::vector hdrTypes; + auto intError = mComposer.getHdrCapabilities(mId, &hdrTypes, &maxLuminance, + &maxAverageLuminance, &minLuminance); auto error = static_cast(intError); if (error != Error::NONE) { return error; } - *outCapabilities = HdrCapabilities(std::move(types), - maxLuminance, maxAverageLuminance, minLuminance); + *outCapabilities = + HdrCapabilities(std::move(hdrTypes), maxLuminance, maxAverageLuminance, minLuminance); return Error::NONE; } +Error Display::getOverlaySupport(OverlayProperties* outProperties) const { + auto intError = mComposer.getOverlaySupport(outProperties); + return static_cast(intError); +} + Error Display::getDisplayedContentSamplingAttributes(hal::PixelFormat* outFormat, Dataspace* outDataspace, uint8_t* outComponentMask) const { @@ -369,7 +375,7 @@ Error Display::getReleaseFences(std::unordered_map>* out for (uint32_t element = 0; element < numElements; ++element) { auto layer = getLayerById(layerIds[element]); if (layer) { - sp fence(new Fence(fenceFds[element])); + sp fence(sp::make(fenceFds[element])); releaseFences.emplace(layer.get(), fence); } else { ALOGE("getReleaseFences: invalid layer %" PRIu64 @@ -394,7 +400,7 @@ Error Display::present(sp* outPresentFence) return error; } - *outPresentFence = new Fence(presentFenceFd); + *outPresentFence = sp::make(presentFenceFd); return Error::NONE; } @@ -532,7 +538,7 @@ Error Display::presentOrValidate(nsecs_t expectedPresentTime, uint32_t* outNumTy } if (*state == 1) { - *outPresentFence = new Fence(presentFenceFd); + *outPresentFence = sp::make(presentFenceFd); } if (*state == 0) { @@ -711,6 +717,16 @@ Error Layer::setBuffer(uint32_t slot, const sp& buffer, return static_cast(intError); } +Error Layer::setBufferSlotsToClear(const std::vector& slotsToClear, + uint32_t activeBufferSlot) { + if (CC_UNLIKELY(!mDisplay)) { + return Error::BAD_DISPLAY; + } + auto intError = mComposer.setLayerBufferSlotsToClear(mDisplay->getId(), mId, slotsToClear, + activeBufferSlot); + return static_cast(intError); +} + Error Layer::setSurfaceDamage(const Region& damage) { if (CC_UNLIKELY(!mDisplay)) { diff --git a/services/surfaceflinger/DisplayHardware/HWC2.h b/services/surfaceflinger/DisplayHardware/HWC2.h index 24aef9b73c29f577a46d490e660166aac1dbb87e..23dd3e5016ed41feb09417811792df93d40cdb99 100644 --- a/services/surfaceflinger/DisplayHardware/HWC2.h +++ b/services/surfaceflinger/DisplayHardware/HWC2.h @@ -43,6 +43,8 @@ #include #include #include +#include +#include namespace android { @@ -62,6 +64,8 @@ class Layer; namespace hal = android::hardware::graphics::composer::hal; +using aidl::android::hardware::graphics::composer3::RefreshRateChangedDebugData; + // Implement this interface to receive hardware composer events. // // These callback functions will generally be called on a hwbinder thread, but @@ -70,12 +74,13 @@ namespace hal = android::hardware::graphics::composer::hal; struct ComposerCallback { virtual void onComposerHalHotplug(hal::HWDisplayId, hal::Connection) = 0; virtual void onComposerHalRefresh(hal::HWDisplayId) = 0; - virtual void onComposerHalVsync(hal::HWDisplayId, int64_t timestamp, + virtual void onComposerHalVsync(hal::HWDisplayId, nsecs_t timestamp, std::optional) = 0; virtual void onComposerHalVsyncPeriodTimingChanged(hal::HWDisplayId, const hal::VsyncPeriodChangeTimeline&) = 0; virtual void onComposerHalSeamlessPossible(hal::HWDisplayId) = 0; virtual void onComposerHalVsyncIdle(hal::HWDisplayId) = 0; + virtual void onRefreshRateChangedDebug(const RefreshRateChangedDebugData&) = 0; protected: ~ComposerCallback() = default; @@ -88,7 +93,7 @@ public: virtual hal::HWDisplayId getId() const = 0; virtual bool isConnected() const = 0; - virtual void setConnected(bool connected) = 0; // For use by Device only + virtual void setConnected(bool connected) = 0; // For use by HWComposer only virtual bool hasCapability( aidl::android::hardware::graphics::composer3::DisplayCapability) const = 0; virtual bool isVsyncPeriodSwitchSupported() const = 0; @@ -117,6 +122,9 @@ public: [[nodiscard]] virtual hal::Error supportsDoze(bool* outSupport) const = 0; [[nodiscard]] virtual hal::Error getHdrCapabilities( android::HdrCapabilities* outCapabilities) const = 0; + [[nodiscard]] virtual hal::Error getOverlaySupport( + aidl::android::hardware::graphics::composer3::OverlayProperties* outProperties) + const = 0; [[nodiscard]] virtual hal::Error getDisplayedContentSamplingAttributes( hal::PixelFormat* outFormat, hal::Dataspace* outDataspace, uint8_t* outComponentMask) const = 0; @@ -204,6 +212,8 @@ public: hal::Error getConnectionType(ui::DisplayConnectionType*) const override; hal::Error supportsDoze(bool* outSupport) const override EXCLUDES(mDisplayCapabilitiesMutex); hal::Error getHdrCapabilities(android::HdrCapabilities* outCapabilities) const override; + hal::Error getOverlaySupport(aidl::android::hardware::graphics::composer3::OverlayProperties* + outProperties) const override; hal::Error getDisplayedContentSamplingAttributes(hal::PixelFormat* outFormat, hal::Dataspace* outDataspace, uint8_t* outComponentMask) const override; @@ -253,7 +263,7 @@ public: // Other Display methods hal::HWDisplayId getId() const override { return mId; } bool isConnected() const override { return mIsConnected; } - void setConnected(bool connected) override; // For use by Device only + void setConnected(bool connected) override; bool hasCapability(aidl::android::hardware::graphics::composer3::DisplayCapability) const override EXCLUDES(mDisplayCapabilitiesMutex); bool isVsyncPeriodSwitchSupported() const override; @@ -271,7 +281,7 @@ private: // Member variables - // These are references to data owned by HWC2::Device, which will outlive + // These are references to data owned by HWComposer, which will outlive // this HWC2::Display, so these references are guaranteed to be valid for // the lifetime of this object. android::Hwc2::Composer& mComposer; @@ -304,6 +314,8 @@ public: [[nodiscard]] virtual hal::Error setBuffer(uint32_t slot, const android::sp& buffer, const android::sp& acquireFence) = 0; + [[nodiscard]] virtual hal::Error setBufferSlotsToClear( + const std::vector& slotsToClear, uint32_t activeBufferSlot) = 0; [[nodiscard]] virtual hal::Error setSurfaceDamage(const android::Region& damage) = 0; [[nodiscard]] virtual hal::Error setBlendMode(hal::BlendMode mode) = 0; @@ -354,6 +366,8 @@ public: hal::Error setCursorPosition(int32_t x, int32_t y) override; hal::Error setBuffer(uint32_t slot, const android::sp& buffer, const android::sp& acquireFence) override; + hal::Error setBufferSlotsToClear(const std::vector& slotsToClear, + uint32_t activeBufferSlot) override; hal::Error setSurfaceDamage(const android::Region& damage) override; hal::Error setBlendMode(hal::BlendMode mode) override; @@ -383,7 +397,7 @@ public: hal::Error setBlockingRegion(const android::Region& region) override; private: - // These are references to data owned by HWC2::Device, which will outlive + // These are references to data owned by HWComposer, which will outlive // this HWC2::Layer, so these references are guaranteed to be valid for // the lifetime of this object. android::Hwc2::Composer& mComposer; diff --git a/services/surfaceflinger/DisplayHardware/HWComposer.cpp b/services/surfaceflinger/DisplayHardware/HWComposer.cpp index a6aee1f2f59a720483205bca02b727ad44b070b5..f350eba7ca495c2758cd272aad38bad2a2d88a3f 100644 --- a/services/surfaceflinger/DisplayHardware/HWComposer.cpp +++ b/services/surfaceflinger/DisplayHardware/HWComposer.cpp @@ -30,6 +30,7 @@ #include #include #include +#include #include #include #include @@ -70,6 +71,8 @@ #define RETURN_IF_HWC_ERROR(error, displayId, ...) \ RETURN_IF_HWC_ERROR_FOR(__FUNCTION__, error, displayId, __VA_ARGS__) +using aidl::android::hardware::graphics::common::HdrConversionCapability; +using aidl::android::hardware::graphics::common::HdrConversionStrategy; using aidl::android::hardware::graphics::composer3::Capability; using aidl::android::hardware::graphics::composer3::DisplayCapability; namespace hal = android::hardware::graphics::composer::hal; @@ -96,6 +99,8 @@ HWComposer::~HWComposer() { void HWComposer::setCallback(HWC2::ComposerCallback& callback) { loadCapabilities(); loadLayerMetadataSupport(); + loadOverlayProperties(); + loadHdrConversionCapabilities(); if (mRegisteredCallback) { ALOGW("Callback already registered. Ignored extra registration attempt."); @@ -144,36 +149,37 @@ bool HWComposer::updatesDeviceProductInfoOnHotplugReconnect() const { return mUpdateDeviceProductInfoOnHotplugReconnect; } -bool HWComposer::onVsync(hal::HWDisplayId hwcDisplayId, int64_t timestamp) { - const auto displayId = toPhysicalDisplayId(hwcDisplayId); - if (!displayId) { +std::optional HWComposer::onVsync(hal::HWDisplayId hwcDisplayId, + nsecs_t timestamp) { + const auto displayIdOpt = toPhysicalDisplayId(hwcDisplayId); + if (!displayIdOpt) { LOG_HWC_DISPLAY_ERROR(hwcDisplayId, "Invalid HWC display"); - return false; + return {}; } - RETURN_IF_INVALID_DISPLAY(*displayId, false); + RETURN_IF_INVALID_DISPLAY(*displayIdOpt, {}); - auto& displayData = mDisplayData[*displayId]; + auto& displayData = mDisplayData[*displayIdOpt]; { // There have been reports of HWCs that signal several vsync events // with the same timestamp when turning the display off and on. This // is a bug in the HWC implementation, but filter the extra events // out here so they don't cause havoc downstream. - if (timestamp == displayData.lastHwVsync) { + if (timestamp == displayData.lastPresentTimestamp) { ALOGW("Ignoring duplicate VSYNC event from HWC for display %s (t=%" PRId64 ")", - to_string(*displayId).c_str(), timestamp); - return false; + to_string(*displayIdOpt).c_str(), timestamp); + return {}; } - displayData.lastHwVsync = timestamp; + displayData.lastPresentTimestamp = timestamp; } - const auto tag = "HW_VSYNC_" + to_string(*displayId); - ATRACE_INT(tag.c_str(), displayData.vsyncTraceToggle); + ATRACE_INT(ftl::Concat("HW_VSYNC_", displayIdOpt->value).c_str(), + displayData.vsyncTraceToggle); displayData.vsyncTraceToggle = !displayData.vsyncTraceToggle; - return true; + return displayIdOpt; } size_t HWComposer::getMaxVirtualDisplayCount() const { @@ -252,11 +258,7 @@ std::shared_ptr HWComposer::createLayer(HalDisplayId displayId) { } bool HWComposer::isConnected(PhysicalDisplayId displayId) const { - if (mDisplayData.count(displayId)) { - return mDisplayData.at(displayId).hwcDisplay->isConnected(); - } - - return false; + return mDisplayData.count(displayId) && mDisplayData.at(displayId).hwcDisplay->isConnected(); } std::vector HWComposer::getModes(PhysicalDisplayId displayId) const { @@ -286,17 +288,12 @@ std::vector HWComposer::getModes(PhysicalDisplayId d std::optional HWComposer::getActiveMode(PhysicalDisplayId displayId) const { RETURN_IF_INVALID_DISPLAY(displayId, std::nullopt); - const auto hwcId = *fromPhysicalDisplayId(displayId); - ALOGV("[%" PRIu64 "] getActiveMode", hwcId); - hal::HWConfigId configId; - auto error = static_cast(mComposer->getActiveConfig(hwcId, &configId)); - if (error == hal::Error::BAD_CONFIG) { - LOG_DISPLAY_ERROR(displayId, "No active mode"); - return std::nullopt; - } + hal::HWConfigId configId; + const auto error = static_cast(mComposer->getActiveConfig(hwcId, &configId)); + RETURN_IF_HWC_ERROR_FOR("getActiveConfig", error, displayId, std::nullopt); return configId; } @@ -380,8 +377,8 @@ void HWComposer::setVsyncEnabled(PhysicalDisplayId displayId, hal::Vsync enabled displayData.vsyncEnabled = enabled; - const auto tag = "HW_VSYNC_ON_" + to_string(displayId); - ATRACE_INT(tag.c_str(), enabled == hal::Vsync::ENABLE ? 1 : 0); + ATRACE_INT(ftl::Concat("HW_VSYNC_ON_", displayId.value).c_str(), + enabled == hal::Vsync::ENABLE ? 1 : 0); } status_t HWComposer::setClientTarget(HalDisplayId displayId, uint32_t slot, @@ -398,8 +395,8 @@ status_t HWComposer::setClientTarget(HalDisplayId displayId, uint32_t slot, status_t HWComposer::getDeviceCompositionChanges( HalDisplayId displayId, bool frameUsesClientComposition, - std::chrono::steady_clock::time_point earliestPresentTime, - const std::shared_ptr& previousPresentFence, nsecs_t expectedPresentTime, + std::optional earliestPresentTime, + nsecs_t expectedPresentTime, std::optional* outChanges) { ATRACE_CALL(); @@ -429,14 +426,13 @@ status_t HWComposer::getDeviceCompositionChanges( // If composer supports getting the expected present time, we can skip // as composer will make sure to prevent early presentation - if (mComposer->isSupported(Hwc2::Composer::OptionalFeature::ExpectedPresentTime)) { + if (!earliestPresentTime) { return true; } // composer doesn't support getting the expected present time. We can only // skip validate if we know that we are not going to present early. - return std::chrono::steady_clock::now() >= earliestPresentTime || - previousPresentFence->getSignalTime() == Fence::SIGNAL_TIME_PENDING; + return std::chrono::steady_clock::now() >= *earliestPresentTime; }(); displayData.validateWasSkipped = false; @@ -494,6 +490,11 @@ sp HWComposer::getPresentFence(HalDisplayId displayId) const { return mDisplayData.at(displayId).lastPresentFence; } +nsecs_t HWComposer::getPresentTimestamp(PhysicalDisplayId displayId) const { + RETURN_IF_INVALID_DISPLAY(displayId, 0); + return mDisplayData.at(displayId).lastPresentTimestamp; +} + sp HWComposer::getLayerReleaseFence(HalDisplayId displayId, HWC2::Layer* layer) const { RETURN_IF_INVALID_DISPLAY(displayId, Fence::NO_FENCE); const auto& displayFences = mDisplayData.at(displayId).releaseFences; @@ -506,8 +507,8 @@ sp HWComposer::getLayerReleaseFence(HalDisplayId displayId, HWC2::Layer* } status_t HWComposer::presentAndGetReleaseFences( - HalDisplayId displayId, std::chrono::steady_clock::time_point earliestPresentTime, - const std::shared_ptr& previousPresentFence) { + HalDisplayId displayId, + std::optional earliestPresentTime) { ATRACE_CALL(); RETURN_IF_INVALID_DISPLAY(displayId, BAD_INDEX); @@ -517,19 +518,15 @@ status_t HWComposer::presentAndGetReleaseFences( if (displayData.validateWasSkipped) { // explicitly flush all pending commands - auto error = static_cast(mComposer->executeCommands()); + auto error = static_cast(mComposer->executeCommands(hwcDisplay->getId())); RETURN_IF_HWC_ERROR_FOR("executeCommands", error, displayId, UNKNOWN_ERROR); RETURN_IF_HWC_ERROR_FOR("present", displayData.presentError, displayId, UNKNOWN_ERROR); return NO_ERROR; } - const bool waitForEarliestPresent = - !mComposer->isSupported(Hwc2::Composer::OptionalFeature::ExpectedPresentTime) && - previousPresentFence->getSignalTime() != Fence::SIGNAL_TIME_PENDING; - - if (waitForEarliestPresent) { + if (earliestPresentTime) { ATRACE_NAME("wait for earliest present time"); - std::this_thread::sleep_until(earliestPresentTime); + std::this_thread::sleep_until(*earliestPresentTime); } auto error = hwcDisplay->present(&displayData.lastPresentFence); @@ -656,6 +653,11 @@ status_t HWComposer::getHdrCapabilities(HalDisplayId displayId, HdrCapabilities* return NO_ERROR; } +const aidl::android::hardware::graphics::composer3::OverlayProperties& +HWComposer::getOverlaySupport() const { + return mOverlayProperties; +} + int32_t HWComposer::getSupportedPerFrameMetadata(HalDisplayId displayId) const { RETURN_IF_INVALID_DISPLAY(displayId, 0); return mDisplayData.at(displayId).hwcDisplay->getSupportedPerFrameMetadata(); @@ -785,6 +787,37 @@ std::optional HWComposer::getPreferredBootDisplayMode( return displayModeId; } +std::vector HWComposer::getHdrConversionCapabilities() const { + return mHdrConversionCapabilities; +} + +status_t HWComposer::setHdrConversionStrategy( + HdrConversionStrategy hdrConversionStrategy, + aidl::android::hardware::graphics::common::Hdr* outPreferredHdrOutputType) { + const auto error = + mComposer->setHdrConversionStrategy(hdrConversionStrategy, outPreferredHdrOutputType); + if (error != hal::Error::NONE) { + ALOGE("Error in setting HDR conversion strategy %s", to_string(error).c_str()); + return INVALID_OPERATION; + } + return NO_ERROR; +} + +status_t HWComposer::setRefreshRateChangedCallbackDebugEnabled(PhysicalDisplayId displayId, + bool enabled) { + RETURN_IF_INVALID_DISPLAY(displayId, BAD_INDEX); + const auto error = + mComposer->setRefreshRateChangedCallbackDebugEnabled(mDisplayData[displayId] + .hwcDisplay->getId(), + enabled); + if (error != hal::Error::NONE) { + ALOGE("Error in setting refresh refresh rate change callback debug enabled %s", + to_string(error).c_str()); + return INVALID_OPERATION; + } + return NO_ERROR; +} + status_t HWComposer::getDisplayDecorationSupport( PhysicalDisplayId displayId, std::optional* @@ -932,6 +965,8 @@ std::optional HWComposer::onHotplugConnect( : "Secondary display", .deviceProductInfo = std::nullopt}; }(); + + mComposer->onHotplugConnect(hwcDisplayId); } if (!isConnected(info->id)) { @@ -951,18 +986,16 @@ std::optional HWComposer::onHotplugDisconnect( return {}; } - // The display will later be destroyed by a call to - // destroyDisplay(). For now we just mark it disconnected. - if (isConnected(*displayId)) { - mDisplayData[*displayId].hwcDisplay->setConnected(false); - } else { + if (!isConnected(*displayId)) { LOG_HWC_DISPLAY_ERROR(hwcDisplayId, "Already disconnected"); + return {}; } - // The cleanup of Disconnect is handled through HWComposer::disconnectDisplay - // via SurfaceFlinger's onHotplugReceived callback handling - return DisplayIdentificationInfo{.id = *displayId, - .name = std::string(), - .deviceProductInfo = std::nullopt}; + + // The display will later be destroyed by a call to HWComposer::disconnectDisplay. For now, mark + // it as disconnected. + mDisplayData.at(*displayId).hwcDisplay->setConnected(false); + mComposer->onHotplugDisconnect(hwcDisplayId); + return DisplayIdentificationInfo{.id = *displayId}; } void HWComposer::loadCapabilities() { @@ -973,6 +1006,18 @@ void HWComposer::loadCapabilities() { } } +void HWComposer::loadOverlayProperties() { + mComposer->getOverlaySupport(&mOverlayProperties); +} + +void HWComposer::loadHdrConversionCapabilities() { + const auto error = mComposer->getHdrConversionCapabilities(&mHdrConversionCapabilities); + if (error != hal::Error::NONE) { + ALOGE("Error in fetching HDR conversion capabilities %s", to_string(error).c_str()); + mHdrConversionCapabilities = {}; + } +} + status_t HWComposer::setIdleTimerEnabled(PhysicalDisplayId displayId, std::chrono::milliseconds timeout) { ATRACE_CALL(); diff --git a/services/surfaceflinger/DisplayHardware/HWComposer.h b/services/surfaceflinger/DisplayHardware/HWComposer.h index 92a8f30f1b403bd38dc2366ed2e864a93c177bbf..3702c62b651e941fd3c235510d612ab7eae999f1 100644 --- a/services/surfaceflinger/DisplayHardware/HWComposer.h +++ b/services/surfaceflinger/DisplayHardware/HWComposer.h @@ -44,10 +44,14 @@ #include "Hal.h" #include +#include +#include +#include #include #include #include #include +#include namespace android { @@ -139,17 +143,16 @@ public: // expected. virtual status_t getDeviceCompositionChanges( HalDisplayId, bool frameUsesClientComposition, - std::chrono::steady_clock::time_point earliestPresentTime, - const std::shared_ptr& previousPresentFence, nsecs_t expectedPresentTime, - std::optional* outChanges) = 0; + std::optional earliestPresentTime, + nsecs_t expectedPresentTime, std::optional* outChanges) = 0; virtual status_t setClientTarget(HalDisplayId, uint32_t slot, const sp& acquireFence, const sp& target, ui::Dataspace) = 0; // Present layers to the display and read releaseFences. virtual status_t presentAndGetReleaseFences( - HalDisplayId, std::chrono::steady_clock::time_point earliestPresentTime, - const std::shared_ptr& previousPresentFence) = 0; + HalDisplayId, + std::optional earliestPresentTime) = 0; // set power mode virtual status_t setPowerMode(PhysicalDisplayId, hal::PowerMode) = 0; @@ -160,8 +163,9 @@ public: // reset state when a display is disconnected virtual void disconnectDisplay(HalDisplayId) = 0; - // get the present fence received from the last call to present. + // Get the present fence/timestamp received from the last call to present. virtual sp getPresentFence(HalDisplayId) const = 0; + virtual nsecs_t getPresentTimestamp(PhysicalDisplayId) const = 0; // Get last release fence for the given layer virtual sp getLayerReleaseFence(HalDisplayId, HWC2::Layer*) const = 0; @@ -177,6 +181,9 @@ public: // Fetches the HDR capabilities of the given display virtual status_t getHdrCapabilities(HalDisplayId, HdrCapabilities* outCapabilities) = 0; + virtual const aidl::android::hardware::graphics::composer3::OverlayProperties& + getOverlaySupport() const = 0; + virtual int32_t getSupportedPerFrameMetadata(HalDisplayId) const = 0; // Returns the available RenderIntent of the given display. @@ -214,7 +221,10 @@ public: // TODO(b/157555476): Remove when the framework has proper support for headless mode virtual bool updatesDeviceProductInfoOnHotplugReconnect() const = 0; - virtual bool onVsync(hal::HWDisplayId, int64_t timestamp) = 0; + // Called when a vsync happens. If the vsync is valid, returns the + // corresponding PhysicalDisplayId. Otherwise returns nullopt. + virtual std::optional onVsync(hal::HWDisplayId, nsecs_t timestamp) = 0; + virtual void setVsyncEnabled(PhysicalDisplayId, hal::Vsync enabled) = 0; virtual bool isConnected(PhysicalDisplayId) const = 0; @@ -281,6 +291,12 @@ public: virtual status_t setIdleTimerEnabled(PhysicalDisplayId, std::chrono::milliseconds timeout) = 0; virtual bool hasDisplayIdleTimerCapability(PhysicalDisplayId) const = 0; virtual Hwc2::AidlTransform getPhysicalDisplayOrientation(PhysicalDisplayId) const = 0; + virtual std::vector + getHdrConversionCapabilities() const = 0; + virtual status_t setHdrConversionStrategy( + aidl::android::hardware::graphics::common::HdrConversionStrategy, + aidl::android::hardware::graphics::common::Hdr*) = 0; + virtual status_t setRefreshRateChangedCallbackDebugEnabled(PhysicalDisplayId, bool enabled) = 0; }; static inline bool operator==(const android::HWComposer::DeviceRequestedChanges& lhs, @@ -322,8 +338,8 @@ public: status_t getDeviceCompositionChanges( HalDisplayId, bool frameUsesClientComposition, - std::chrono::steady_clock::time_point earliestPresentTime, - const std::shared_ptr& previousPresentFence, nsecs_t expectedPresentTime, + std::optional earliestPresentTime, + nsecs_t expectedPresentTime, std::optional* outChanges) override; status_t setClientTarget(HalDisplayId, uint32_t slot, const sp& acquireFence, @@ -331,8 +347,8 @@ public: // Present layers to the display and read releaseFences. status_t presentAndGetReleaseFences( - HalDisplayId, std::chrono::steady_clock::time_point earliestPresentTime, - const std::shared_ptr& previousPresentFence) override; + HalDisplayId, + std::optional earliestPresentTime) override; // set power mode status_t setPowerMode(PhysicalDisplayId, hal::PowerMode mode) override; @@ -343,8 +359,9 @@ public: // reset state when a display is disconnected void disconnectDisplay(HalDisplayId) override; - // get the present fence received from the last call to present. + // Get the present fence/timestamp received from the last call to present. sp getPresentFence(HalDisplayId) const override; + nsecs_t getPresentTimestamp(PhysicalDisplayId) const override; // Get last release fence for the given layer sp getLayerReleaseFence(HalDisplayId, HWC2::Layer*) const override; @@ -360,6 +377,9 @@ public: // Fetches the HDR capabilities of the given display status_t getHdrCapabilities(HalDisplayId, HdrCapabilities* outCapabilities) override; + const aidl::android::hardware::graphics::composer3::OverlayProperties& getOverlaySupport() + const override; + int32_t getSupportedPerFrameMetadata(HalDisplayId) const override; // Returns the available RenderIntent of the given display. @@ -387,7 +407,7 @@ public: bool updatesDeviceProductInfoOnHotplugReconnect() const override; - bool onVsync(hal::HWDisplayId, int64_t timestamp) override; + std::optional onVsync(hal::HWDisplayId, nsecs_t timestamp) override; void setVsyncEnabled(PhysicalDisplayId, hal::Vsync enabled) override; bool isConnected(PhysicalDisplayId) const override; @@ -428,6 +448,12 @@ public: status_t setIdleTimerEnabled(PhysicalDisplayId, std::chrono::milliseconds timeout) override; bool hasDisplayIdleTimerCapability(PhysicalDisplayId) const override; Hwc2::AidlTransform getPhysicalDisplayOrientation(PhysicalDisplayId) const override; + std::vector + getHdrConversionCapabilities() const override; + status_t setHdrConversionStrategy( + aidl::android::hardware::graphics::common::HdrConversionStrategy, + aidl::android::hardware::graphics::common::Hdr*) override; + status_t setRefreshRateChangedCallbackDebugEnabled(PhysicalDisplayId, bool enabled) override; // for debugging ---------------------------------------------------------- void dump(std::string& out) const override; @@ -456,7 +482,10 @@ private: struct DisplayData { std::unique_ptr hwcDisplay; + sp lastPresentFence = Fence::NO_FENCE; // signals when the last set op retires + nsecs_t lastPresentTimestamp = 0; + std::unordered_map> releaseFences; bool validateWasSkipped; @@ -466,8 +495,6 @@ private: std::mutex vsyncEnabledLock; hal::Vsync vsyncEnabled GUARDED_BY(vsyncEnabledLock) = hal::Vsync::DISABLE; - - nsecs_t lastHwVsync = 0; }; std::optional onHotplugConnect(hal::HWDisplayId); @@ -479,11 +506,17 @@ private: void loadCapabilities(); void loadLayerMetadataSupport(); + void loadOverlayProperties(); + void loadHdrConversionCapabilities(); std::unordered_map mDisplayData; std::unique_ptr mComposer; std::unordered_set mCapabilities; + aidl::android::hardware::graphics::composer3::OverlayProperties mOverlayProperties; + std::vector + mHdrConversionCapabilities = {}; + std::unordered_map mSupportedLayerGenericMetadata; bool mRegisteredCallback = false; diff --git a/services/surfaceflinger/DisplayHardware/Hal.h b/services/surfaceflinger/DisplayHardware/Hal.h index 473703423ebfd61ac5e7ea5e41703e33e87acc2a..bf3089f04060ec84879417df38ba4f29dc919bfc 100644 --- a/services/surfaceflinger/DisplayHardware/Hal.h +++ b/services/surfaceflinger/DisplayHardware/Hal.h @@ -20,6 +20,7 @@ #include #include +#include #include #include @@ -39,7 +40,6 @@ using types::V1_0::Transform; using types::V1_1::RenderIntent; using types::V1_2::ColorMode; using types::V1_2::Dataspace; -using types::V1_2::Hdr; using types::V1_2::PixelFormat; using V2_1::Error; @@ -69,6 +69,7 @@ using PerFrameMetadataBlob = IComposerClient::PerFrameMetadataBlob; using PowerMode = IComposerClient::PowerMode; using Vsync = IComposerClient::Vsync; using VsyncPeriodChangeConstraints = IComposerClient::VsyncPeriodChangeConstraints; +using Hdr = aidl::android::hardware::graphics::common::Hdr; } // namespace hardware::graphics::composer::hal @@ -112,6 +113,8 @@ inline std::string to_string( return "Sideband"; case aidl::android::hardware::graphics::composer3::Composition::DISPLAY_DECORATION: return "DisplayDecoration"; + case aidl::android::hardware::graphics::composer3::Composition::REFRESH_RATE_INDICATOR: + return "RefreshRateIndicator"; default: return "Unknown"; } @@ -177,6 +180,10 @@ inline std::string to_string(hardware::graphics::composer::hal::Error error) { return to_string(static_cast(error)); } +// For utils::Dumper ADL. +namespace hardware::graphics::composer { +namespace V2_2 { + inline std::string to_string(hardware::graphics::composer::hal::PowerMode mode) { switch (mode) { case hardware::graphics::composer::hal::PowerMode::OFF: @@ -194,6 +201,10 @@ inline std::string to_string(hardware::graphics::composer::hal::PowerMode mode) } } +} // namespace V2_2 + +namespace V2_1 { + inline std::string to_string(hardware::graphics::composer::hal::Vsync vsync) { switch (vsync) { case hardware::graphics::composer::hal::Vsync::ENABLE: @@ -205,4 +216,6 @@ inline std::string to_string(hardware::graphics::composer::hal::Vsync vsync) { } } +} // namespace V2_1 +} // namespace hardware::graphics::composer } // namespace android diff --git a/services/surfaceflinger/DisplayHardware/HidlComposerHal.cpp b/services/surfaceflinger/DisplayHardware/HidlComposerHal.cpp index 2597ae6091b408b65625b2b3c13e5a244d47514e..9b41da57547e514ec9064042693bf1432d90bb67 100644 --- a/services/surfaceflinger/DisplayHardware/HidlComposerHal.cpp +++ b/services/surfaceflinger/DisplayHardware/HidlComposerHal.cpp @@ -24,22 +24,27 @@ #include "HidlComposerHal.h" +#include #include #include #include #include #include #include + #include "HWC2.h" #include "Hal.h" #include #include +using aidl::android::hardware::graphics::common::HdrConversionCapability; +using aidl::android::hardware::graphics::common::HdrConversionStrategy; using aidl::android::hardware::graphics::composer3::Capability; using aidl::android::hardware::graphics::composer3::ClientTargetPropertyWithBrightness; using aidl::android::hardware::graphics::composer3::DimmingStage; using aidl::android::hardware::graphics::composer3::DisplayCapability; +using aidl::android::hardware::graphics::composer3::OverlayProperties; namespace android { @@ -185,9 +190,25 @@ std::vector translate(const hidl_vec& in) { return out; } +sp allocateClearSlotBuffer() { + if (!sysprop::clear_slots_with_set_layer_buffer(false)) { + return nullptr; + } + sp buffer = sp::make(1, 1, PIXEL_FORMAT_RGBX_8888, + GraphicBuffer::USAGE_HW_COMPOSER | + GraphicBuffer::USAGE_SW_READ_OFTEN | + GraphicBuffer::USAGE_SW_WRITE_OFTEN, + "HidlComposer"); + if (!buffer || buffer->initCheck() != ::android::OK) { + return nullptr; + } + return std::move(buffer); +} + } // anonymous namespace -HidlComposer::HidlComposer(const std::string& serviceName) : mWriter(kWriterInitialSize) { +HidlComposer::HidlComposer(const std::string& serviceName) + : mClearSlotBuffer(allocateClearSlotBuffer()), mWriter(kWriterInitialSize) { mComposer = V2_1::IComposer::getService(serviceName); if (mComposer == nullptr) { @@ -229,6 +250,11 @@ HidlComposer::HidlComposer(const std::string& serviceName) : mWriter(kWriterInit if (mClient == nullptr) { LOG_ALWAYS_FATAL("failed to create composer client"); } + + if (!mClearSlotBuffer && sysprop::clear_slots_with_set_layer_buffer(false)) { + LOG_ALWAYS_FATAL("Failed to allocate a buffer for clearing layer buffer slots"); + return; + } } bool HidlComposer::isSupported(OptionalFeature feature) const { @@ -272,11 +298,7 @@ void HidlComposer::registerCallback(const sp& callback) { } } -void HidlComposer::resetCommands() { - mWriter.reset(); -} - -Error HidlComposer::executeCommands() { +Error HidlComposer::executeCommands(Display) { return execute(); } @@ -495,13 +517,13 @@ Error HidlComposer::hasDisplayIdleTimerCapability(Display, bool*) { "OptionalFeature::KernelIdleTimer is not supported on HIDL"); } -Error HidlComposer::getHdrCapabilities(Display display, std::vector* outTypes, +Error HidlComposer::getHdrCapabilities(Display display, std::vector* outHdrTypes, float* outMaxLuminance, float* outMaxAverageLuminance, float* outMinLuminance) { Error error = kDefaultError; if (mClient_2_3) { mClient_2_3->getHdrCapabilities_2_3(display, - [&](const auto& tmpError, const auto& tmpTypes, + [&](const auto& tmpError, const auto& tmpHdrTypes, const auto& tmpMaxLuminance, const auto& tmpMaxAverageLuminance, const auto& tmpMinLuminance) { @@ -509,15 +531,15 @@ Error HidlComposer::getHdrCapabilities(Display display, std::vector* outTyp if (error != Error::NONE) { return; } + *outHdrTypes = translate(tmpHdrTypes); - *outTypes = tmpTypes; *outMaxLuminance = tmpMaxLuminance; *outMaxAverageLuminance = tmpMaxAverageLuminance; *outMinLuminance = tmpMinLuminance; }); } else { mClient->getHdrCapabilities(display, - [&](const auto& tmpError, const auto& tmpTypes, + [&](const auto& tmpError, const auto& tmpHdrTypes, const auto& tmpMaxLuminance, const auto& tmpMaxAverageLuminance, const auto& tmpMinLuminance) { @@ -525,11 +547,7 @@ Error HidlComposer::getHdrCapabilities(Display display, std::vector* outTyp if (error != Error::NONE) { return; } - - outTypes->clear(); - for (auto type : tmpTypes) { - outTypes->push_back(static_cast(type)); - } + *outHdrTypes = translate(tmpHdrTypes); *outMaxLuminance = tmpMaxLuminance; *outMaxAverageLuminance = tmpMaxAverageLuminance; @@ -540,6 +558,10 @@ Error HidlComposer::getHdrCapabilities(Display display, std::vector* outTyp return error; } +Error HidlComposer::getOverlaySupport(OverlayProperties* /*outProperties*/) { + return Error::NONE; +} + Error HidlComposer::getReleaseFences(Display display, std::vector* outLayers, std::vector* outReleaseFences) { mReader.takeReleaseFences(display, outLayers, outReleaseFences); @@ -693,6 +715,36 @@ Error HidlComposer::setLayerBuffer(Display display, Layer layer, uint32_t slot, return Error::NONE; } +Error HidlComposer::setLayerBufferSlotsToClear(Display display, Layer layer, + const std::vector& slotsToClear, + uint32_t activeBufferSlot) { + if (slotsToClear.empty()) { + return Error::NONE; + } + // This can be null when the HAL hasn't explicitly enabled this feature. + if (mClearSlotBuffer == nullptr) { + return Error::NONE; + } + // Backwards compatible way of clearing buffer is to set the layer buffer with a placeholder + // buffer, using the slot that needs to cleared... tricky. + for (uint32_t slot : slotsToClear) { + // Don't clear the active buffer slot because we need to restore the active buffer after + // setting the requested buffer slots with a placeholder buffer. + if (slot != activeBufferSlot) { + mWriter.selectDisplay(display); + mWriter.selectLayer(layer); + mWriter.setLayerBuffer(slot, mClearSlotBuffer->handle, /*fence*/ -1); + } + } + // Since we clear buffers by setting them to a placeholder buffer, we want to make sure that the + // last setLayerBuffer command is sent with the currently active buffer, not the placeholder + // buffer, so that there is no perceptual change. + mWriter.selectDisplay(display); + mWriter.selectLayer(layer); + mWriter.setLayerBuffer(activeBufferSlot, /*buffer*/ nullptr, /*fence*/ -1); + return Error::NONE; +} + Error HidlComposer::setLayerSurfaceDamage(Display display, Layer layer, const std::vector& damage) { mWriter.selectDisplay(display); @@ -1303,6 +1355,18 @@ Error HidlComposer::getPreferredBootDisplayConfig(Display /*displayId*/, Config* return Error::UNSUPPORTED; } +Error HidlComposer::getHdrConversionCapabilities(std::vector*) { + return Error::UNSUPPORTED; +} + +Error HidlComposer::setHdrConversionStrategy(HdrConversionStrategy, Hdr*) { + return Error::UNSUPPORTED; +} + +Error HidlComposer::setRefreshRateChangedCallbackDebugEnabled(Display, bool) { + return Error::UNSUPPORTED; +} + Error HidlComposer::getClientTargetProperty( Display display, ClientTargetPropertyWithBrightness* outClientTargetProperty) { IComposerClient::ClientTargetProperty property; @@ -1352,6 +1416,9 @@ void HidlComposer::registerCallback(ComposerCallback& callback) { registerCallback(sp::make(callback, vsyncSwitchingSupported)); } +void HidlComposer::onHotplugConnect(Display) {} +void HidlComposer::onHotplugDisconnect(Display) {} + CommandReader::~CommandReader() { resetData(); } diff --git a/services/surfaceflinger/DisplayHardware/HidlComposerHal.h b/services/surfaceflinger/DisplayHardware/HidlComposerHal.h index d0d3c2e6d7d2ef128306ba5ce4234f4e2d514eaf..0521acf9c43ec064d45f9b5d7700586dcb47c868 100644 --- a/services/surfaceflinger/DisplayHardware/HidlComposerHal.h +++ b/services/surfaceflinger/DisplayHardware/HidlComposerHal.h @@ -56,7 +56,6 @@ using types::V1_0::Transform; using types::V1_1::RenderIntent; using types::V1_2::ColorMode; using types::V1_2::Dataspace; -using types::V1_2::Hdr; using types::V1_2::PixelFormat; using V2_1::Config; @@ -175,12 +174,8 @@ public: void registerCallback(HWC2::ComposerCallback& callback) override; - // Reset all pending commands in the command buffer. Useful if you want to - // skip a frame but have already queued some commands. - void resetCommands() override; - // Explicitly flush all pending commands in the command buffer. - Error executeCommands() override; + Error executeCommands(Display) override; uint32_t getMaxVirtualDisplayCount() override; Error createVirtualDisplay(uint32_t width, uint32_t height, PixelFormat* format, @@ -209,8 +204,10 @@ public: Error getDozeSupport(Display display, bool* outSupport) override; Error hasDisplayIdleTimerCapability(Display display, bool* outSupport) override; - Error getHdrCapabilities(Display display, std::vector* outTypes, float* outMaxLuminance, + Error getHdrCapabilities(Display display, std::vector* outHdrTypes, float* outMaxLuminance, float* outMaxAverageLuminance, float* outMinLuminance) override; + Error getOverlaySupport(aidl::android::hardware::graphics::composer3::OverlayProperties* + outProperties) override; Error getReleaseFences(Display display, std::vector* outLayers, std::vector* outReleaseFences) override; @@ -247,6 +244,9 @@ public: /* see setClientTarget for the purpose of slot */ Error setLayerBuffer(Display display, Layer layer, uint32_t slot, const sp& buffer, int acquireFence) override; + Error setLayerBufferSlotsToClear(Display display, Layer layer, + const std::vector& slotsToClear, + uint32_t activeBufferSlot) override; Error setLayerSurfaceDamage(Display display, Layer layer, const std::vector& damage) override; Error setLayerBlendMode(Display display, Layer layer, IComposerClient::BlendMode mode) override; @@ -337,6 +337,14 @@ public: Error getPhysicalDisplayOrientation(Display displayId, AidlTransform* outDisplayOrientation) override; + void onHotplugConnect(Display) override; + void onHotplugDisconnect(Display) override; + Error getHdrConversionCapabilities( + std::vector*) + override; + Error setHdrConversionStrategy(aidl::android::hardware::graphics::common::HdrConversionStrategy, + Hdr*) override; + Error setRefreshRateChangedCallbackDebugEnabled(Display, bool) override; private: class CommandWriter : public CommandWriterBase { @@ -359,6 +367,9 @@ private: sp mClient_2_3; sp mClient_2_4; + // Buffer slots for layers are cleared by setting the slot buffer to this buffer. + sp mClearSlotBuffer; + // 64KiB minus a small space for metadata such as read/write pointers static constexpr size_t kWriterInitialSize = 64 * 1024 / sizeof(uint32_t) - 16; // Max number of buffers that may be cached for a given layer diff --git a/services/surfaceflinger/DisplayHardware/PowerAdvisor.cpp b/services/surfaceflinger/DisplayHardware/PowerAdvisor.cpp index a0350b717ab4f75a05cbe988c5f6aeb1976febc0..f8b466c93c5ffad24cd16abc25958ecfaa9cf48e 100644 --- a/services/surfaceflinger/DisplayHardware/PowerAdvisor.cpp +++ b/services/surfaceflinger/DisplayHardware/PowerAdvisor.cpp @@ -31,7 +31,7 @@ #include #include -#include +#include #include #include @@ -49,18 +49,12 @@ PowerAdvisor::~PowerAdvisor() = default; namespace impl { -namespace V1_0 = android::hardware::power::V1_0; -namespace V1_3 = android::hardware::power::V1_3; -using V1_3::PowerHint; - using android::hardware::power::Boost; -using android::hardware::power::IPower; using android::hardware::power::IPowerHintSession; using android::hardware::power::Mode; +using android::hardware::power::SessionHint; using android::hardware::power::WorkDuration; -using scheduler::OneShotTimer; - PowerAdvisor::~PowerAdvisor() = default; namespace { @@ -81,7 +75,8 @@ void traceExpensiveRendering(bool enabled) { } // namespace -PowerAdvisor::PowerAdvisor(SurfaceFlinger& flinger) : mFlinger(flinger) { +PowerAdvisor::PowerAdvisor(SurfaceFlinger& flinger) + : mPowerHal(std::make_unique()), mFlinger(flinger) { if (getUpdateTimeout() > 0ms) { mScreenUpdateTimer.emplace("UpdateImminentTimer", getUpdateTimeout(), /* resetCallback */ nullptr, @@ -118,6 +113,10 @@ void PowerAdvisor::onBootFinished() { } void PowerAdvisor::setExpensiveRenderingExpected(DisplayId displayId, bool expected) { + if (!mHasExpensiveRendering) { + ALOGV("Skipped sending EXPENSIVE_RENDERING because HAL doesn't support it"); + return; + } if (expected) { mExpensiveDisplays.insert(displayId); } else { @@ -126,23 +125,20 @@ void PowerAdvisor::setExpensiveRenderingExpected(DisplayId displayId, bool expec const bool expectsExpensiveRendering = !mExpensiveDisplays.empty(); if (mNotifiedExpensiveRendering != expectsExpensiveRendering) { - std::lock_guard lock(mPowerHalMutex); - HalWrapper* const halWrapper = getPowerHal(); - if (halWrapper == nullptr) { - return; - } - - if (!halWrapper->setExpensiveRendering(expectsExpensiveRendering)) { - // The HAL has become unavailable; attempt to reconnect later - mReconnectPowerHal = true; + auto ret = getPowerHal().setMode(Mode::EXPENSIVE_RENDERING, expectsExpensiveRendering); + if (!ret.isOk()) { + if (ret.isUnsupported()) { + mHasExpensiveRendering = false; + } return; } mNotifiedExpensiveRendering = expectsExpensiveRendering; + traceExpensiveRendering(mNotifiedExpensiveRendering); } } -void PowerAdvisor::notifyDisplayUpdateImminent() { +void PowerAdvisor::notifyDisplayUpdateImminentAndCpuReset() { // Only start sending this notification once the system has booted so we don't introduce an // early-boot dependency on Power HAL if (!mBootFinished.load()) { @@ -150,16 +146,22 @@ void PowerAdvisor::notifyDisplayUpdateImminent() { } if (mSendUpdateImminent.exchange(false)) { - std::lock_guard lock(mPowerHalMutex); - HalWrapper* const halWrapper = getPowerHal(); - if (halWrapper == nullptr) { - return; + ALOGV("AIDL notifyDisplayUpdateImminentAndCpuReset"); + if (usePowerHintSession() && ensurePowerHintSessionRunning()) { + std::lock_guard lock(mHintSessionMutex); + auto ret = mHintSession->sendHint(SessionHint::CPU_LOAD_RESET); + if (!ret.isOk()) { + mHintSessionRunning = false; + } } - if (!halWrapper->notifyDisplayUpdateImminent()) { - // The HAL has become unavailable; attempt to reconnect later - mReconnectPowerHal = true; - return; + if (!mHasDisplayUpdateImminent) { + ALOGV("Skipped sending DISPLAY_UPDATE_IMMINENT because HAL doesn't support it"); + } else { + auto ret = getPowerHal().setBoost(Boost::DISPLAY_UPDATE_IMMINENT, 0); + if (ret.isUnsupported()) { + mHasDisplayUpdateImminent = false; + } } if (mScreenUpdateTimer) { @@ -179,88 +181,123 @@ void PowerAdvisor::notifyDisplayUpdateImminent() { // checks both if it supports and if it's enabled bool PowerAdvisor::usePowerHintSession() { // uses cached value since the underlying support and flag are unlikely to change at runtime - return mPowerHintEnabled.value_or(false) && supportsPowerHintSession(); + return mHintSessionEnabled.value_or(false) && supportsPowerHintSession(); } bool PowerAdvisor::supportsPowerHintSession() { // cache to avoid needing lock every time - if (!mSupportsPowerHint.has_value()) { - std::lock_guard lock(mPowerHalMutex); - HalWrapper* const halWrapper = getPowerHal(); - mSupportsPowerHint = halWrapper && halWrapper->supportsPowerHintSession(); + if (!mSupportsHintSession.has_value()) { + mSupportsHintSession = getPowerHal().getHintSessionPreferredRate().isOk(); } - return *mSupportsPowerHint; + return *mSupportsHintSession; } -bool PowerAdvisor::isPowerHintSessionRunning() { - return mPowerHintSessionRunning; +bool PowerAdvisor::ensurePowerHintSessionRunning() { + if (!mHintSessionRunning && !mHintSessionThreadIds.empty() && usePowerHintSession()) { + startPowerHintSession(mHintSessionThreadIds); + } + return mHintSessionRunning; } -void PowerAdvisor::setTargetWorkDuration(int64_t targetDuration) { +void PowerAdvisor::updateTargetWorkDuration(Duration targetDuration) { if (!usePowerHintSession()) { ALOGV("Power hint session target duration cannot be set, skipping"); return; } + ATRACE_CALL(); { - std::lock_guard lock(mPowerHalMutex); - HalWrapper* const halWrapper = getPowerHal(); - if (halWrapper != nullptr) { - halWrapper->setTargetWorkDuration(targetDuration); + mTargetDuration = targetDuration; + if (sTraceHintSessionData) ATRACE_INT64("Time target", targetDuration.ns()); + if (ensurePowerHintSessionRunning() && (targetDuration != mLastTargetDurationSent)) { + ALOGV("Sending target time: %" PRId64 "ns", targetDuration.ns()); + mLastTargetDurationSent = targetDuration; + std::lock_guard lock(mHintSessionMutex); + auto ret = mHintSession->updateTargetWorkDuration(targetDuration.ns()); + if (!ret.isOk()) { + ALOGW("Failed to set power hint target work duration with error: %s", + ret.exceptionMessage().c_str()); + mHintSessionRunning = false; + } } } } -void PowerAdvisor::sendActualWorkDuration() { - if (!mBootFinished || !usePowerHintSession()) { +void PowerAdvisor::reportActualWorkDuration() { + if (!mBootFinished || !sUseReportActualDuration || !usePowerHintSession()) { ALOGV("Actual work duration power hint cannot be sent, skipping"); return; } - const std::optional actualDuration = estimateWorkDuration(false); - if (actualDuration.has_value()) { - std::lock_guard lock(mPowerHalMutex); - HalWrapper* const halWrapper = getPowerHal(); - if (halWrapper != nullptr) { - halWrapper->sendActualWorkDuration(*actualDuration + kTargetSafetyMargin.count(), - systemTime()); - } + ATRACE_CALL(); + std::optional actualDuration = estimateWorkDuration(); + if (!actualDuration.has_value() || actualDuration < 0ns || !ensurePowerHintSessionRunning()) { + ALOGV("Failed to send actual work duration, skipping"); + return; } -} + actualDuration = std::make_optional(*actualDuration + sTargetSafetyMargin); + mActualDuration = actualDuration; + WorkDuration duration; + duration.durationNanos = actualDuration->ns(); + duration.timeStampNanos = TimePoint::now().ns(); + mHintSessionQueue.push_back(duration); -void PowerAdvisor::sendPredictedWorkDuration() { - if (!mBootFinished || !usePowerHintSession()) { - ALOGV("Actual work duration power hint cannot be sent, skipping"); - return; + if (sTraceHintSessionData) { + ATRACE_INT64("Measured duration", actualDuration->ns()); + ATRACE_INT64("Target error term", Duration{*actualDuration - mTargetDuration}.ns()); + ATRACE_INT64("Reported duration", actualDuration->ns()); + ATRACE_INT64("Reported target", mLastTargetDurationSent.ns()); + ATRACE_INT64("Reported target error term", + Duration{*actualDuration - mLastTargetDurationSent}.ns()); } - const std::optional predictedDuration = estimateWorkDuration(true); + ALOGV("Sending actual work duration of: %" PRId64 " on reported target: %" PRId64 + " with error: %" PRId64, + actualDuration->ns(), mLastTargetDurationSent.ns(), + Duration{*actualDuration - mLastTargetDurationSent}.ns()); - if (predictedDuration.has_value()) { - std::lock_guard lock(mPowerHalMutex); - HalWrapper* const halWrapper = getPowerHal(); - if (halWrapper != nullptr) { - halWrapper->sendActualWorkDuration(*predictedDuration + kTargetSafetyMargin.count(), - systemTime()); + { + std::lock_guard lock(mHintSessionMutex); + auto ret = mHintSession->reportActualWorkDuration(mHintSessionQueue); + if (!ret.isOk()) { + ALOGW("Failed to report actual work durations with error: %s", + ret.exceptionMessage().c_str()); + mHintSessionRunning = false; + return; } } + mHintSessionQueue.clear(); } -void PowerAdvisor::enablePowerHint(bool enabled) { - mPowerHintEnabled = enabled; +void PowerAdvisor::enablePowerHintSession(bool enabled) { + mHintSessionEnabled = enabled; } bool PowerAdvisor::startPowerHintSession(const std::vector& threadIds) { + if (!mBootFinished.load()) { + return false; + } if (!usePowerHintSession()) { - ALOGI("Power hint session cannot be started, skipping"); + ALOGI("Cannot start power hint session: disabled or unsupported"); + return false; + } + if (mHintSessionRunning) { + ALOGE("Cannot start power hint session: already running"); + return false; } + LOG_ALWAYS_FATAL_IF(threadIds.empty(), "No thread IDs provided to power hint session!"); { - std::lock_guard lock(mPowerHalMutex); - HalWrapper* halWrapper = getPowerHal(); - if (halWrapper != nullptr && usePowerHintSession()) { - halWrapper->setPowerHintSessionThreadIds(threadIds); - mPowerHintSessionRunning = halWrapper->startPowerHintSession(); + std::lock_guard lock(mHintSessionMutex); + mHintSession = nullptr; + mHintSessionThreadIds = threadIds; + + auto ret = getPowerHal().createHintSession(getpid(), static_cast(getuid()), + threadIds, mTargetDuration.ns()); + + if (ret.isOk()) { + mHintSessionRunning = true; + mHintSession = ret.value(); } } - return mPowerHintSessionRunning; + return mHintSessionRunning; } void PowerAdvisor::setGpuFenceTime(DisplayId displayId, std::unique_ptr&& fenceTime) { @@ -281,22 +318,22 @@ void PowerAdvisor::setGpuFenceTime(DisplayId displayId, std::unique_ptr& displayIds) { mDisplayIds = displayIds; } -void PowerAdvisor::setTotalFrameTargetWorkDuration(nsecs_t targetDuration) { +void PowerAdvisor::setTotalFrameTargetWorkDuration(Duration targetDuration) { mTotalFrameTargetDuration = targetDuration; } std::vector PowerAdvisor::getOrderedDisplayIds( - std::optional DisplayTimingData::*sortBy) { + std::optional DisplayTimingData::*sortBy) { std::vector sortedDisplays; std::copy_if(mDisplayIds.begin(), mDisplayIds.end(), std::back_inserter(sortedDisplays), [&](DisplayId id) { @@ -360,39 +395,30 @@ std::vector PowerAdvisor::getOrderedDisplayIds( return sortedDisplays; } -std::optional PowerAdvisor::estimateWorkDuration(bool earlyHint) { - if (earlyHint && (!mExpectedPresentTimes.isFull() || !mCommitStartTimes.isFull())) { +std::optional PowerAdvisor::estimateWorkDuration() { + if (!mExpectedPresentTimes.isFull() || !mCommitStartTimes.isFull()) { return std::nullopt; } // Tracks when we finish presenting to hwc - nsecs_t estimatedEndTime = mCommitStartTimes[0]; + TimePoint estimatedHwcEndTime = mCommitStartTimes[0]; // How long we spent this frame not doing anything, waiting for fences or vsync - nsecs_t idleDuration = 0; + Duration idleDuration = 0ns; // Most recent previous gpu end time in the current frame, probably from a prior display, used // as the start time for the next gpu operation if it ran over time since it probably blocked - std::optional previousValidGpuEndTime; + std::optional previousValidGpuEndTime; // The currently estimated gpu end time for the frame, // used to accumulate gpu time as we iterate over the active displays - std::optional estimatedGpuEndTime; - - // If we're predicting at the start of the frame, we use last frame as our reference point - // If we're predicting at the end of the frame, we use the current frame as a reference point - nsecs_t referenceFrameStartTime = (earlyHint ? mCommitStartTimes[-1] : mCommitStartTimes[0]); - - // When the prior frame should be presenting to the display - // If we're predicting at the start of the frame, we use last frame's expected present time - // If we're predicting at the end of the frame, the present fence time is already known - nsecs_t lastFramePresentTime = (earlyHint ? mExpectedPresentTimes[-1] : mLastPresentFenceTime); + std::optional estimatedGpuEndTime; // The timing info for the previously calculated display, if there was one - std::optional previousDisplayReferenceTiming; + std::optional previousDisplayTiming; std::vector&& displayIds = getOrderedDisplayIds(&DisplayTimingData::hwcPresentStartTime); - DisplayTimeline referenceTiming, estimatedTiming; + DisplayTimeline displayTiming; // Iterate over the displays that use hwc in the same order they are presented for (DisplayId displayId : displayIds) { @@ -402,35 +428,26 @@ std::optional PowerAdvisor::estimateWorkDuration(bool earlyHint) { auto& displayData = mDisplayTimingData.at(displayId); - // mLastPresentFenceTime should always be the time of the reference frame, since it will be - // the previous frame's present fence if called at the start, and current frame's if called - // at the end - referenceTiming = displayData.calculateDisplayTimeline(mLastPresentFenceTime); + displayTiming = displayData.calculateDisplayTimeline(mLastPresentFenceTime); // If this is the first display, include the duration before hwc present starts - if (!previousDisplayReferenceTiming.has_value()) { - estimatedEndTime += referenceTiming.hwcPresentStartTime - referenceFrameStartTime; + if (!previousDisplayTiming.has_value()) { + estimatedHwcEndTime += displayTiming.hwcPresentStartTime - mCommitStartTimes[0]; } else { // Otherwise add the time since last display's hwc present finished - estimatedEndTime += referenceTiming.hwcPresentStartTime - - previousDisplayReferenceTiming->hwcPresentEndTime; + estimatedHwcEndTime += + displayTiming.hwcPresentStartTime - previousDisplayTiming->hwcPresentEndTime; } - // Late hint can re-use reference timing here since it's estimating its own reference frame - estimatedTiming = earlyHint - ? referenceTiming.estimateTimelineFromReference(lastFramePresentTime, - estimatedEndTime) - : referenceTiming; - // Update predicted present finish time with this display's present time - estimatedEndTime = estimatedTiming.hwcPresentEndTime; + estimatedHwcEndTime = displayTiming.hwcPresentEndTime; // Track how long we spent waiting for the fence, can be excluded from the timing estimate - idleDuration += estimatedTiming.probablyWaitsForPresentFence - ? lastFramePresentTime - estimatedTiming.presentFenceWaitStartTime - : 0; + idleDuration += displayTiming.probablyWaitsForPresentFence + ? mLastPresentFenceTime - displayTiming.presentFenceWaitStartTime + : 0ns; // Track how long we spent waiting to present, can be excluded from the timing estimate - idleDuration += earlyHint ? 0 : referenceTiming.hwcPresentDelayDuration; + idleDuration += displayTiming.hwcPresentDelayDuration; // Estimate the reference frame's gpu timing auto gpuTiming = displayData.estimateGpuTiming(previousValidGpuEndTime); @@ -438,76 +455,54 @@ std::optional PowerAdvisor::estimateWorkDuration(bool earlyHint) { previousValidGpuEndTime = gpuTiming->startTime + gpuTiming->duration; // Estimate the prediction frame's gpu end time from the reference frame - estimatedGpuEndTime = - std::max(estimatedTiming.hwcPresentStartTime, estimatedGpuEndTime.value_or(0)) + + estimatedGpuEndTime = std::max(displayTiming.hwcPresentStartTime, + estimatedGpuEndTime.value_or(TimePoint{0ns})) + gpuTiming->duration; } - previousDisplayReferenceTiming = referenceTiming; + previousDisplayTiming = displayTiming; } - ATRACE_INT64("Idle duration", idleDuration); + ATRACE_INT64("Idle duration", idleDuration.ns()); - nsecs_t estimatedFlingerEndTime = earlyHint ? estimatedEndTime : mLastSfPresentEndTime; + TimePoint estimatedFlingerEndTime = mLastSfPresentEndTime; // Don't count time spent idly waiting in the estimate as we could do more work in that time - estimatedEndTime -= idleDuration; + estimatedHwcEndTime -= idleDuration; estimatedFlingerEndTime -= idleDuration; // We finish the frame when both present and the gpu are done, so wait for the later of the two // Also add the frame delay duration since the target did not move while we were delayed - nsecs_t totalDuration = mFrameDelayDuration + - std::max(estimatedEndTime, estimatedGpuEndTime.value_or(0)) - mCommitStartTimes[0]; + Duration totalDuration = mFrameDelayDuration + + std::max(estimatedHwcEndTime, estimatedGpuEndTime.value_or(TimePoint{0ns})) - + mCommitStartTimes[0]; // We finish SurfaceFlinger when post-composition finishes, so add that in here - nsecs_t flingerDuration = + Duration flingerDuration = estimatedFlingerEndTime + mLastPostcompDuration - mCommitStartTimes[0]; // Combine the two timings into a single normalized one - nsecs_t combinedDuration = combineTimingEstimates(totalDuration, flingerDuration); + Duration combinedDuration = combineTimingEstimates(totalDuration, flingerDuration); return std::make_optional(combinedDuration); } -nsecs_t PowerAdvisor::combineTimingEstimates(nsecs_t totalDuration, nsecs_t flingerDuration) { - nsecs_t targetDuration; - { - std::lock_guard lock(mPowerHalMutex); - targetDuration = *getPowerHal()->getTargetWorkDuration(); - } +Duration PowerAdvisor::combineTimingEstimates(Duration totalDuration, Duration flingerDuration) { + Duration targetDuration{0ns}; + targetDuration = mTargetDuration; if (!mTotalFrameTargetDuration.has_value()) return flingerDuration; // Normalize total to the flinger target (vsync period) since that's how often we actually send // hints - nsecs_t normalizedTotalDuration = (targetDuration * totalDuration) / *mTotalFrameTargetDuration; + Duration normalizedTotalDuration = Duration::fromNs((targetDuration.ns() * totalDuration.ns()) / + mTotalFrameTargetDuration->ns()); return std::max(flingerDuration, normalizedTotalDuration); } -PowerAdvisor::DisplayTimeline PowerAdvisor::DisplayTimeline::estimateTimelineFromReference( - nsecs_t fenceTime, nsecs_t displayStartTime) { - DisplayTimeline estimated; - estimated.hwcPresentStartTime = displayStartTime; - - // We don't predict waiting for vsync alignment yet - estimated.hwcPresentDelayDuration = 0; - - // How long we expect to run before we start waiting for the fence - // For now just re-use last frame's post-present duration and assume it will not change much - // Excludes time spent waiting for vsync since that's not going to be consistent - estimated.presentFenceWaitStartTime = estimated.hwcPresentStartTime + - (presentFenceWaitStartTime - (hwcPresentStartTime + hwcPresentDelayDuration)); - estimated.probablyWaitsForPresentFence = fenceTime > estimated.presentFenceWaitStartTime; - estimated.hwcPresentEndTime = postPresentFenceHwcPresentDuration + - (estimated.probablyWaitsForPresentFence ? fenceTime - : estimated.presentFenceWaitStartTime); - return estimated; -} - PowerAdvisor::DisplayTimeline PowerAdvisor::DisplayTimingData::calculateDisplayTimeline( - nsecs_t fenceTime) { + TimePoint fenceTime) { DisplayTimeline timeline; // How long between calling hwc present and trying to wait on the fence - const nsecs_t fenceWaitStartDelay = - (skippedValidate ? kFenceWaitStartDelaySkippedValidate : kFenceWaitStartDelayValidated) - .count(); + const Duration fenceWaitStartDelay = + (skippedValidate ? kFenceWaitStartDelaySkippedValidate : kFenceWaitStartDelayValidated); // Did our reference frame wait for an appropriate vsync before calling into hwc const bool waitedOnHwcPresentTime = hwcPresentDelayedTime.has_value() && @@ -522,7 +517,7 @@ PowerAdvisor::DisplayTimeline PowerAdvisor::DisplayTimingData::calculateDisplayT // How long hwc present was delayed waiting for the next appropriate vsync timeline.hwcPresentDelayDuration = - (waitedOnHwcPresentTime ? *hwcPresentDelayedTime - *hwcPresentStartTime : 0); + (waitedOnHwcPresentTime ? *hwcPresentDelayedTime - *hwcPresentStartTime : 0ns); // When we started waiting for the present fence after calling into hwc present timeline.presentFenceWaitStartTime = timeline.hwcPresentStartTime + timeline.hwcPresentDelayDuration + fenceWaitStartDelay; @@ -537,366 +532,45 @@ PowerAdvisor::DisplayTimeline PowerAdvisor::DisplayTimingData::calculateDisplayT } std::optional PowerAdvisor::DisplayTimingData::estimateGpuTiming( - std::optional previousEnd) { + std::optional previousEndTime) { if (!(usedClientComposition && lastValidGpuStartTime.has_value() && gpuEndFenceTime)) { return std::nullopt; } - const nsecs_t latestGpuStartTime = std::max(previousEnd.value_or(0), *gpuStartTime); - const nsecs_t latestGpuEndTime = gpuEndFenceTime->getSignalTime(); - nsecs_t gpuDuration = 0; - if (latestGpuEndTime != Fence::SIGNAL_TIME_INVALID && - latestGpuEndTime != Fence::SIGNAL_TIME_PENDING) { + const TimePoint latestGpuStartTime = + std::max(previousEndTime.value_or(TimePoint{0ns}), *gpuStartTime); + const nsecs_t gpuEndFenceSignal = gpuEndFenceTime->getSignalTime(); + Duration gpuDuration{0ns}; + if (gpuEndFenceSignal != Fence::SIGNAL_TIME_INVALID && + gpuEndFenceSignal != Fence::SIGNAL_TIME_PENDING) { + const TimePoint latestGpuEndTime = TimePoint::fromNs(gpuEndFenceSignal); + // If we know how long the most recent gpu duration was, use that gpuDuration = latestGpuEndTime - latestGpuStartTime; } else if (lastValidGpuEndTime.has_value()) { // If we don't have the fence data, use the most recent information we do have gpuDuration = *lastValidGpuEndTime - *lastValidGpuStartTime; - if (latestGpuEndTime == Fence::SIGNAL_TIME_PENDING) { + if (gpuEndFenceSignal == Fence::SIGNAL_TIME_PENDING) { // If pending but went over the previous duration, use current time as the end - gpuDuration = std::max(gpuDuration, systemTime() - latestGpuStartTime); + gpuDuration = std::max(gpuDuration, Duration{TimePoint::now() - latestGpuStartTime}); } } return GpuTimeline{.duration = gpuDuration, .startTime = latestGpuStartTime}; } -class HidlPowerHalWrapper : public PowerAdvisor::HalWrapper { -public: - HidlPowerHalWrapper(sp powerHal) : mPowerHal(std::move(powerHal)) {} - - ~HidlPowerHalWrapper() override = default; - - static std::unique_ptr connect() { - // Power HAL 1.3 is not guaranteed to be available, thus we need to query - // Power HAL 1.0 first and try to cast it to Power HAL 1.3. - sp powerHal = nullptr; - sp powerHal_1_0 = V1_0::IPower::getService(); - if (powerHal_1_0 != nullptr) { - // Try to cast to Power HAL 1.3 - powerHal = V1_3::IPower::castFrom(powerHal_1_0); - if (powerHal == nullptr) { - ALOGW("No Power HAL 1.3 service in system, disabling PowerAdvisor"); - } else { - ALOGI("Loaded Power HAL 1.3 service"); - } - } else { - ALOGW("No Power HAL found, disabling PowerAdvisor"); - } - - if (powerHal == nullptr) { - return nullptr; - } - - return std::make_unique(std::move(powerHal)); - } - - bool setExpensiveRendering(bool enabled) override { - ALOGV("HIDL setExpensiveRendering %s", enabled ? "T" : "F"); - auto ret = mPowerHal->powerHintAsync_1_3(PowerHint::EXPENSIVE_RENDERING, enabled); - if (ret.isOk()) { - traceExpensiveRendering(enabled); - } - return ret.isOk(); - } - - bool notifyDisplayUpdateImminent() override { - // Power HAL 1.x doesn't have a notification for this - ALOGV("HIDL notifyUpdateImminent received but can't send"); - return true; - } - - bool supportsPowerHintSession() override { return false; } - - bool isPowerHintSessionRunning() override { return false; } - - void restartPowerHintSession() override {} - - void setPowerHintSessionThreadIds(const std::vector&) override {} - - bool startPowerHintSession() override { return false; } - - void setTargetWorkDuration(int64_t) override {} - - void sendActualWorkDuration(int64_t, nsecs_t) override {} - - bool shouldReconnectHAL() override { return false; } - - std::vector getPowerHintSessionThreadIds() override { return std::vector{}; } - - std::optional getTargetWorkDuration() override { return std::nullopt; } - -private: - const sp mPowerHal = nullptr; -}; - -AidlPowerHalWrapper::AidlPowerHalWrapper(sp powerHal) : mPowerHal(std::move(powerHal)) { - auto ret = mPowerHal->isModeSupported(Mode::EXPENSIVE_RENDERING, &mHasExpensiveRendering); - if (!ret.isOk()) { - mHasExpensiveRendering = false; - } - - ret = mPowerHal->isBoostSupported(Boost::DISPLAY_UPDATE_IMMINENT, &mHasDisplayUpdateImminent); - if (!ret.isOk()) { - mHasDisplayUpdateImminent = false; - } - - mSupportsPowerHint = checkPowerHintSessionSupported(); - - // Currently set to 0 to disable rate limiter by default - mAllowedActualDeviation = base::GetIntProperty("debug.sf.allowed_actual_deviation", 0); -} - -AidlPowerHalWrapper::~AidlPowerHalWrapper() { - if (mPowerHintSession != nullptr) { - mPowerHintSession->close(); - mPowerHintSession = nullptr; - } -} - -std::unique_ptr AidlPowerHalWrapper::connect() { - // This only waits if the service is actually declared - sp powerHal = waitForVintfService(); - if (powerHal == nullptr) { - return nullptr; - } - ALOGI("Loaded AIDL Power HAL service"); - - return std::make_unique(std::move(powerHal)); -} - -bool AidlPowerHalWrapper::setExpensiveRendering(bool enabled) { - ALOGV("AIDL setExpensiveRendering %s", enabled ? "T" : "F"); - if (!mHasExpensiveRendering) { - ALOGV("Skipped sending EXPENSIVE_RENDERING because HAL doesn't support it"); - return true; - } - - auto ret = mPowerHal->setMode(Mode::EXPENSIVE_RENDERING, enabled); - if (ret.isOk()) { - traceExpensiveRendering(enabled); - } - return ret.isOk(); -} - -bool AidlPowerHalWrapper::notifyDisplayUpdateImminent() { - ALOGV("AIDL notifyDisplayUpdateImminent"); - if (!mHasDisplayUpdateImminent) { - ALOGV("Skipped sending DISPLAY_UPDATE_IMMINENT because HAL doesn't support it"); - return true; - } - - auto ret = mPowerHal->setBoost(Boost::DISPLAY_UPDATE_IMMINENT, 0); - return ret.isOk(); -} - -// Only version 2+ of the aidl supports power hint sessions, hidl has no support -bool AidlPowerHalWrapper::supportsPowerHintSession() { - return mSupportsPowerHint; -} - -bool AidlPowerHalWrapper::checkPowerHintSessionSupported() { - int64_t unused; - // Try to get preferred rate to determine if hint sessions are supported - // We check for isOk not EX_UNSUPPORTED_OPERATION to lump together errors - return mPowerHal->getHintSessionPreferredRate(&unused).isOk(); -} - -bool AidlPowerHalWrapper::isPowerHintSessionRunning() { - return mPowerHintSession != nullptr; -} - -void AidlPowerHalWrapper::closePowerHintSession() { - if (mPowerHintSession != nullptr) { - mPowerHintSession->close(); - mPowerHintSession = nullptr; - } -} - -void AidlPowerHalWrapper::restartPowerHintSession() { - closePowerHintSession(); - startPowerHintSession(); -} - -void AidlPowerHalWrapper::setPowerHintSessionThreadIds(const std::vector& threadIds) { - if (threadIds != mPowerHintThreadIds) { - mPowerHintThreadIds = threadIds; - if (isPowerHintSessionRunning()) { - restartPowerHintSession(); - } - } -} - -bool AidlPowerHalWrapper::startPowerHintSession() { - if (mPowerHintSession != nullptr || mPowerHintThreadIds.empty()) { - ALOGV("Cannot start power hint session, skipping"); - return false; - } - auto ret = - mPowerHal->createHintSession(getpid(), static_cast(getuid()), - mPowerHintThreadIds, mTargetDuration, &mPowerHintSession); - if (!ret.isOk()) { - ALOGW("Failed to start power hint session with error: %s", - ret.exceptionToString(ret.exceptionCode()).c_str()); - } else { - mLastTargetDurationSent = mTargetDuration; - } - return isPowerHintSessionRunning(); -} - -void AidlPowerHalWrapper::setTargetWorkDuration(int64_t targetDuration) { - ATRACE_CALL(); - mTargetDuration = targetDuration; - if (sTraceHintSessionData) ATRACE_INT64("Time target", targetDuration); - if (isPowerHintSessionRunning() && (targetDuration != mLastTargetDurationSent)) { - ALOGV("Sending target time: %" PRId64 "ns", targetDuration); - mLastTargetDurationSent = targetDuration; - auto ret = mPowerHintSession->updateTargetWorkDuration(targetDuration); - if (!ret.isOk()) { - ALOGW("Failed to set power hint target work duration with error: %s", - ret.exceptionMessage().c_str()); - mShouldReconnectHal = true; - } - } -} - -bool AidlPowerHalWrapper::shouldReportActualDurations() { - // Report if we have never reported before or are approaching a stale session - if (!mLastActualDurationSent.has_value() || - (systemTime() - mLastActualReportTimestamp) > kStaleTimeout.count()) { - return true; - } - - if (!mActualDuration.has_value()) { - return false; - } - // Report if the change in actual duration exceeds the threshold - return abs(*mActualDuration - *mLastActualDurationSent) > mAllowedActualDeviation; -} - -void AidlPowerHalWrapper::sendActualWorkDuration(int64_t actualDuration, nsecs_t timestamp) { - ATRACE_CALL(); - - if (actualDuration < 0 || !isPowerHintSessionRunning()) { - ALOGV("Failed to send actual work duration, skipping"); - return; - } - const nsecs_t reportedDuration = actualDuration; - - mActualDuration = reportedDuration; - WorkDuration duration; - duration.durationNanos = reportedDuration; - duration.timeStampNanos = timestamp; - mPowerHintQueue.push_back(duration); - - if (sTraceHintSessionData) { - ATRACE_INT64("Measured duration", actualDuration); - ATRACE_INT64("Target error term", actualDuration - mTargetDuration); - - ATRACE_INT64("Reported duration", reportedDuration); - ATRACE_INT64("Reported target", mLastTargetDurationSent); - ATRACE_INT64("Reported target error term", reportedDuration - mLastTargetDurationSent); - } - - ALOGV("Sending actual work duration of: %" PRId64 " on reported target: %" PRId64 - " with error: %" PRId64, - reportedDuration, mLastTargetDurationSent, reportedDuration - mLastTargetDurationSent); - - // This rate limiter queues similar duration reports to the powerhal into - // batches to avoid excessive binder calls. The criteria to send a given batch - // are outlined in shouldReportActualDurationsNow() - if (shouldReportActualDurations()) { - ALOGV("Sending hint update batch"); - mLastActualReportTimestamp = systemTime(); - auto ret = mPowerHintSession->reportActualWorkDuration(mPowerHintQueue); - if (!ret.isOk()) { - ALOGW("Failed to report actual work durations with error: %s", - ret.exceptionMessage().c_str()); - mShouldReconnectHal = true; - } - mPowerHintQueue.clear(); - // We save the actual duration here for rate limiting - mLastActualDurationSent = actualDuration; - } -} - -bool AidlPowerHalWrapper::shouldReconnectHAL() { - return mShouldReconnectHal; -} - -std::vector AidlPowerHalWrapper::getPowerHintSessionThreadIds() { - return mPowerHintThreadIds; -} - -std::optional AidlPowerHalWrapper::getTargetWorkDuration() { - return mTargetDuration; -} - -void AidlPowerHalWrapper::setAllowedActualDeviation(nsecs_t allowedDeviation) { - mAllowedActualDeviation = allowedDeviation; -} - -const bool AidlPowerHalWrapper::sTraceHintSessionData = +const bool PowerAdvisor::sTraceHintSessionData = base::GetBoolProperty(std::string("debug.sf.trace_hint_sessions"), false); -PowerAdvisor::HalWrapper* PowerAdvisor::getPowerHal() { - if (!mHasHal) { - return nullptr; - } - - // Grab old hint session values before we destroy any existing wrapper - std::vector oldPowerHintSessionThreadIds; - std::optional oldTargetWorkDuration; - - if (mHalWrapper != nullptr) { - oldPowerHintSessionThreadIds = mHalWrapper->getPowerHintSessionThreadIds(); - oldTargetWorkDuration = mHalWrapper->getTargetWorkDuration(); - } - - // If we used to have a HAL, but it stopped responding, attempt to reconnect - if (mReconnectPowerHal) { - mHalWrapper = nullptr; - mReconnectPowerHal = false; - } +const Duration PowerAdvisor::sTargetSafetyMargin = std::chrono::microseconds( + base::GetIntProperty("debug.sf.hint_margin_us", + ticks(PowerAdvisor::kDefaultTargetSafetyMargin))); - if (mHalWrapper != nullptr) { - auto wrapper = mHalWrapper.get(); - // If the wrapper is fine, return it, but if it indicates a reconnect, remake it - if (!wrapper->shouldReconnectHAL()) { - return wrapper; - } - ALOGD("Reconnecting Power HAL"); - mHalWrapper = nullptr; - } - - // At this point, we know for sure there is no running session - mPowerHintSessionRunning = false; - - // First attempt to connect to the AIDL Power HAL - mHalWrapper = AidlPowerHalWrapper::connect(); - - // If that didn't succeed, attempt to connect to the HIDL Power HAL - if (mHalWrapper == nullptr) { - mHalWrapper = HidlPowerHalWrapper::connect(); - } else { - ALOGD("Successfully connecting AIDL Power HAL"); - // If AIDL, pass on any existing hint session values - mHalWrapper->setPowerHintSessionThreadIds(oldPowerHintSessionThreadIds); - // Only set duration and start if duration is defined - if (oldTargetWorkDuration.has_value()) { - mHalWrapper->setTargetWorkDuration(*oldTargetWorkDuration); - // Only start if possible to run and both threadids and duration are defined - if (usePowerHintSession() && !oldPowerHintSessionThreadIds.empty()) { - mPowerHintSessionRunning = mHalWrapper->startPowerHintSession(); - } - } - } - - // If we make it to this point and still don't have a HAL, it's unlikely we - // will, so stop trying - if (mHalWrapper == nullptr) { - mHasHal = false; - } +const bool PowerAdvisor::sUseReportActualDuration = + base::GetBoolProperty(std::string("debug.adpf.use_report_actual_duration"), true); - return mHalWrapper.get(); +power::PowerHalController& PowerAdvisor::getPowerHal() { + static std::once_flag halFlag; + std::call_once(halFlag, [this] { mPowerHal->init(); }); + return *mPowerHal; } } // namespace impl diff --git a/services/surfaceflinger/DisplayHardware/PowerAdvisor.h b/services/surfaceflinger/DisplayHardware/PowerAdvisor.h index 6e25f787d7c119ee6e0360a5ed33248a50fa1fdc..f0d3fd851845c9eff5239383c68c23689851b25a 100644 --- a/services/surfaceflinger/DisplayHardware/PowerAdvisor.h +++ b/services/surfaceflinger/DisplayHardware/PowerAdvisor.h @@ -27,6 +27,8 @@ #include #include +#include +#include #include #include "../Scheduler/OneShotTimer.h" @@ -47,51 +49,50 @@ public: virtual void onBootFinished() = 0; virtual void setExpensiveRenderingExpected(DisplayId displayId, bool expected) = 0; virtual bool isUsingExpensiveRendering() = 0; - virtual void notifyDisplayUpdateImminent() = 0; + virtual void notifyDisplayUpdateImminentAndCpuReset() = 0; // Checks both if it supports and if it's enabled virtual bool usePowerHintSession() = 0; virtual bool supportsPowerHintSession() = 0; - virtual bool isPowerHintSessionRunning() = 0; + + virtual bool ensurePowerHintSessionRunning() = 0; // Sends a power hint that updates to the target work duration for the frame - virtual void setTargetWorkDuration(nsecs_t targetDuration) = 0; + virtual void updateTargetWorkDuration(Duration targetDuration) = 0; // Sends a power hint for the actual known work duration at the end of the frame - virtual void sendActualWorkDuration() = 0; - // Sends a power hint for the upcoming frame predicted from previous frame timing - virtual void sendPredictedWorkDuration() = 0; + virtual void reportActualWorkDuration() = 0; // Sets whether the power hint session is enabled - virtual void enablePowerHint(bool enabled) = 0; + virtual void enablePowerHintSession(bool enabled) = 0; // Initializes the power hint session virtual bool startPowerHintSession(const std::vector& threadIds) = 0; // Provides PowerAdvisor with a copy of the gpu fence so it can determine the gpu end time virtual void setGpuFenceTime(DisplayId displayId, std::unique_ptr&& fenceTime) = 0; // Reports the start and end times of a hwc validate call this frame for a given display - virtual void setHwcValidateTiming(DisplayId displayId, nsecs_t validateStartTime, - nsecs_t validateEndTime) = 0; + virtual void setHwcValidateTiming(DisplayId displayId, TimePoint validateStartTime, + TimePoint validateEndTime) = 0; // Reports the start and end times of a hwc present call this frame for a given display - virtual void setHwcPresentTiming(DisplayId displayId, nsecs_t presentStartTime, - nsecs_t presentEndTime) = 0; + virtual void setHwcPresentTiming(DisplayId displayId, TimePoint presentStartTime, + TimePoint presentEndTime) = 0; // Reports the expected time that the current frame will present to the display - virtual void setExpectedPresentTime(nsecs_t expectedPresentTime) = 0; + virtual void setExpectedPresentTime(TimePoint expectedPresentTime) = 0; // Reports the most recent present fence time and end time once known - virtual void setSfPresentTiming(nsecs_t presentFenceTime, nsecs_t presentEndTime) = 0; + virtual void setSfPresentTiming(TimePoint presentFenceTime, TimePoint presentEndTime) = 0; // Reports whether a display used client composition this frame virtual void setRequiresClientComposition(DisplayId displayId, bool requiresClientComposition) = 0; // Reports whether a given display skipped validation this frame virtual void setSkippedValidate(DisplayId displayId, bool skipped) = 0; // Reports when a hwc present is delayed, and the time that it will resume - virtual void setHwcPresentDelayedTime( - DisplayId displayId, std::chrono::steady_clock::time_point earliestFrameStartTime) = 0; + virtual void setHwcPresentDelayedTime(DisplayId displayId, + TimePoint earliestFrameStartTime) = 0; // Reports the start delay for SurfaceFlinger this frame - virtual void setFrameDelay(nsecs_t frameDelayDuration) = 0; + virtual void setFrameDelay(Duration frameDelayDuration) = 0; // Reports the SurfaceFlinger commit start time this frame - virtual void setCommitStart(nsecs_t commitStartTime) = 0; + virtual void setCommitStart(TimePoint commitStartTime) = 0; // Reports the SurfaceFlinger composite end time this frame - virtual void setCompositeEnd(nsecs_t compositeEndTime) = 0; + virtual void setCompositeEnd(TimePoint compositeEndTime) = 0; // Reports the list of the currently active displays virtual void setDisplays(std::vector& displayIds) = 0; // Sets the target duration for the entire pipeline including the gpu - virtual void setTotalFrameTargetWorkDuration(nsecs_t targetDuration) = 0; + virtual void setTotalFrameTargetWorkDuration(Duration targetDuration) = 0; }; namespace impl { @@ -100,24 +101,6 @@ namespace impl { // full state of the system when sending out power hints to things like the GPU. class PowerAdvisor final : public Hwc2::PowerAdvisor { public: - class HalWrapper { - public: - virtual ~HalWrapper() = default; - - virtual bool setExpensiveRendering(bool enabled) = 0; - virtual bool notifyDisplayUpdateImminent() = 0; - virtual bool supportsPowerHintSession() = 0; - virtual bool isPowerHintSessionRunning() = 0; - virtual void restartPowerHintSession() = 0; - virtual void setPowerHintSessionThreadIds(const std::vector& threadIds) = 0; - virtual bool startPowerHintSession() = 0; - virtual void setTargetWorkDuration(nsecs_t targetDuration) = 0; - virtual void sendActualWorkDuration(nsecs_t actualDuration, nsecs_t timestamp) = 0; - virtual bool shouldReconnectHAL() = 0; - virtual std::vector getPowerHintSessionThreadIds() = 0; - virtual std::optional getTargetWorkDuration() = 0; - }; - PowerAdvisor(SurfaceFlinger& flinger); ~PowerAdvisor() override; @@ -125,46 +108,35 @@ public: void onBootFinished() override; void setExpensiveRenderingExpected(DisplayId displayId, bool expected) override; bool isUsingExpensiveRendering() override { return mNotifiedExpensiveRendering; }; - void notifyDisplayUpdateImminent() override; + void notifyDisplayUpdateImminentAndCpuReset() override; bool usePowerHintSession() override; bool supportsPowerHintSession() override; - bool isPowerHintSessionRunning() override; - void setTargetWorkDuration(nsecs_t targetDuration) override; - void sendActualWorkDuration() override; - void sendPredictedWorkDuration() override; - void enablePowerHint(bool enabled) override; + bool ensurePowerHintSessionRunning() override; + void updateTargetWorkDuration(Duration targetDuration) override; + void reportActualWorkDuration() override; + void enablePowerHintSession(bool enabled) override; bool startPowerHintSession(const std::vector& threadIds) override; void setGpuFenceTime(DisplayId displayId, std::unique_ptr&& fenceTime); - void setHwcValidateTiming(DisplayId displayId, nsecs_t valiateStartTime, - nsecs_t validateEndTime) override; - void setHwcPresentTiming(DisplayId displayId, nsecs_t presentStartTime, - nsecs_t presentEndTime) override; + void setHwcValidateTiming(DisplayId displayId, TimePoint validateStartTime, + TimePoint validateEndTime) override; + void setHwcPresentTiming(DisplayId displayId, TimePoint presentStartTime, + TimePoint presentEndTime) override; void setSkippedValidate(DisplayId displayId, bool skipped) override; void setRequiresClientComposition(DisplayId displayId, bool requiresClientComposition) override; - void setExpectedPresentTime(nsecs_t expectedPresentTime) override; - void setSfPresentTiming(nsecs_t presentFenceTime, nsecs_t presentEndTime) override; - void setHwcPresentDelayedTime( - DisplayId displayId, - std::chrono::steady_clock::time_point earliestFrameStartTime) override; - - void setFrameDelay(nsecs_t frameDelayDuration) override; - void setCommitStart(nsecs_t commitStartTime) override; - void setCompositeEnd(nsecs_t compositeEndTime) override; + void setExpectedPresentTime(TimePoint expectedPresentTime) override; + void setSfPresentTiming(TimePoint presentFenceTime, TimePoint presentEndTime) override; + void setHwcPresentDelayedTime(DisplayId displayId, TimePoint earliestFrameStartTime) override; + + void setFrameDelay(Duration frameDelayDuration) override; + void setCommitStart(TimePoint commitStartTime) override; + void setCompositeEnd(TimePoint compositeEndTime) override; void setDisplays(std::vector& displayIds) override; - void setTotalFrameTargetWorkDuration(nsecs_t targetDuration) override; + void setTotalFrameTargetWorkDuration(Duration targetDuration) override; private: friend class PowerAdvisorTest; - // Tracks if powerhal exists - bool mHasHal = true; - // Holds the hal wrapper for getPowerHal - std::unique_ptr mHalWrapper GUARDED_BY(mPowerHalMutex) = nullptr; - - HalWrapper* getPowerHal() REQUIRES(mPowerHalMutex); - bool mReconnectPowerHal GUARDED_BY(mPowerHalMutex) = false; - std::mutex mPowerHalMutex; - + std::unique_ptr mPowerHal; std::atomic_bool mBootFinished = false; std::unordered_set mExpensiveDisplays; @@ -178,44 +150,42 @@ private: // Higher-level timing data used for estimation struct DisplayTimeline { // The start of hwc present, or the start of validate if it happened there instead - nsecs_t hwcPresentStartTime = -1; + TimePoint hwcPresentStartTime; // The end of hwc present or validate, whichever one actually presented - nsecs_t hwcPresentEndTime = -1; + TimePoint hwcPresentEndTime; // How long the actual hwc present was delayed after hwcPresentStartTime - nsecs_t hwcPresentDelayDuration = 0; + Duration hwcPresentDelayDuration{0ns}; // When we think we started waiting for the present fence after calling into hwc present and // after potentially waiting for the earliest present time - nsecs_t presentFenceWaitStartTime = -1; + TimePoint presentFenceWaitStartTime; // How long we ran after we finished waiting for the fence but before hwc present finished - nsecs_t postPresentFenceHwcPresentDuration = 0; + Duration postPresentFenceHwcPresentDuration{0ns}; // Are we likely to have waited for the present fence during composition bool probablyWaitsForPresentFence = false; - // Estimate one frame's timeline from that of a previous frame - DisplayTimeline estimateTimelineFromReference(nsecs_t fenceTime, nsecs_t displayStartTime); }; struct GpuTimeline { - nsecs_t duration = 0; - nsecs_t startTime = -1; + Duration duration{0ns}; + TimePoint startTime; }; // Power hint session data recorded from the pipeline struct DisplayTimingData { std::unique_ptr gpuEndFenceTime; - std::optional gpuStartTime; - std::optional lastValidGpuEndTime; - std::optional lastValidGpuStartTime; - std::optional hwcPresentStartTime; - std::optional hwcPresentEndTime; - std::optional hwcValidateStartTime; - std::optional hwcValidateEndTime; - std::optional hwcPresentDelayedTime; + std::optional gpuStartTime; + std::optional lastValidGpuEndTime; + std::optional lastValidGpuStartTime; + std::optional hwcPresentStartTime; + std::optional hwcPresentEndTime; + std::optional hwcValidateStartTime; + std::optional hwcValidateEndTime; + std::optional hwcPresentDelayedTime; bool usedClientComposition = false; bool skippedValidate = false; // Calculate high-level timing milestones from more granular display timing data - DisplayTimeline calculateDisplayTimeline(nsecs_t fenceTime); + DisplayTimeline calculateDisplayTimeline(TimePoint fenceTime); // Estimate the gpu duration for a given display from previous gpu timing data - std::optional estimateGpuTiming(std::optional previousEnd); + std::optional estimateGpuTiming(std::optional previousEndTime); }; template @@ -240,107 +210,71 @@ private: }; // Filter and sort the display ids by a given property - std::vector getOrderedDisplayIds(std::optional DisplayTimingData::*sortBy); + std::vector getOrderedDisplayIds( + std::optional DisplayTimingData::*sortBy); // Estimates a frame's total work duration including gpu time. - // Runs either at the beginning or end of a frame, using the most recent data available - std::optional estimateWorkDuration(bool earlyHint); + std::optional estimateWorkDuration(); // There are two different targets and actual work durations we care about, // this normalizes them together and takes the max of the two - nsecs_t combineTimingEstimates(nsecs_t totalDuration, nsecs_t flingerDuration); + Duration combineTimingEstimates(Duration totalDuration, Duration flingerDuration); std::unordered_map mDisplayTimingData; // Current frame's delay - nsecs_t mFrameDelayDuration = 0; + Duration mFrameDelayDuration{0ns}; // Last frame's post-composition duration - nsecs_t mLastPostcompDuration = 0; + Duration mLastPostcompDuration{0ns}; // Buffer of recent commit start times - RingBuffer mCommitStartTimes; + RingBuffer mCommitStartTimes; // Buffer of recent expected present times - RingBuffer mExpectedPresentTimes; - // Most recent present fence time, set at the end of the frame once known - nsecs_t mLastPresentFenceTime = -1; - // Most recent present fence time, set at the end of the frame once known - nsecs_t mLastSfPresentEndTime = -1; - // Target for the entire pipeline including gpu - std::optional mTotalFrameTargetDuration; + RingBuffer mExpectedPresentTimes; + // Most recent present fence time, provided by SF after composition engine finishes presenting + TimePoint mLastPresentFenceTime; + // Most recent composition engine present end time, returned with the present fence from SF + TimePoint mLastSfPresentEndTime; + // Target duration for the entire pipeline including gpu + std::optional mTotalFrameTargetDuration; // Updated list of display IDs std::vector mDisplayIds; - std::optional mPowerHintEnabled; - std::optional mSupportsPowerHint; - bool mPowerHintSessionRunning = false; + // Ensure powerhal connection is initialized + power::PowerHalController& getPowerHal(); - // An adjustable safety margin which pads the "actual" value sent to PowerHAL, - // encouraging more aggressive boosting to give SurfaceFlinger a larger margin for error - static constexpr const std::chrono::nanoseconds kTargetSafetyMargin = 1ms; + std::optional mHintSessionEnabled; + std::optional mSupportsHintSession; + bool mHintSessionRunning = false; - // How long we expect hwc to run after the present call until it waits for the fence - static constexpr const std::chrono::nanoseconds kFenceWaitStartDelayValidated = 150us; - static constexpr const std::chrono::nanoseconds kFenceWaitStartDelaySkippedValidate = 250us; -}; + std::mutex mHintSessionMutex; + sp mHintSession GUARDED_BY(mHintSessionMutex) = nullptr; -class AidlPowerHalWrapper : public PowerAdvisor::HalWrapper { -public: - explicit AidlPowerHalWrapper(sp powerHal); - ~AidlPowerHalWrapper() override; - - static std::unique_ptr connect(); - - bool setExpensiveRendering(bool enabled) override; - bool notifyDisplayUpdateImminent() override; - bool supportsPowerHintSession() override; - bool isPowerHintSessionRunning() override; - void restartPowerHintSession() override; - void setPowerHintSessionThreadIds(const std::vector& threadIds) override; - bool startPowerHintSession() override; - void setTargetWorkDuration(nsecs_t targetDuration) override; - void sendActualWorkDuration(nsecs_t actualDuration, nsecs_t timestamp) override; - bool shouldReconnectHAL() override; - std::vector getPowerHintSessionThreadIds() override; - std::optional getTargetWorkDuration() override; - -private: - friend class AidlPowerHalWrapperTest; - - bool checkPowerHintSessionSupported(); - void closePowerHintSession(); - bool shouldReportActualDurations(); - - // Used for testing - void setAllowedActualDeviation(nsecs_t); - - const sp mPowerHal = nullptr; - bool mHasExpensiveRendering = false; - bool mHasDisplayUpdateImminent = false; - // Used to indicate an error state and need for reconstruction - bool mShouldReconnectHal = false; - - // Power hint session data - - // Concurrent access for this is protected by mPowerHalMutex - sp mPowerHintSession = nullptr; + // Initialize to true so we try to call, to check if it's supported + bool mHasExpensiveRendering = true; + bool mHasDisplayUpdateImminent = true; // Queue of actual durations saved to report - std::vector mPowerHintQueue; + std::vector mHintSessionQueue; // The latest values we have received for target and actual - nsecs_t mTargetDuration = kDefaultTarget.count(); - std::optional mActualDuration; + Duration mTargetDuration = kDefaultTargetDuration; + std::optional mActualDuration; // The list of thread ids, stored so we can restart the session from this class if needed - std::vector mPowerHintThreadIds; - bool mSupportsPowerHint = false; - // Keep track of the last messages sent for rate limiter change detection - std::optional mLastActualDurationSent; - // Timestamp of the last report we sent, used to avoid stale sessions - nsecs_t mLastActualReportTimestamp = 0; - nsecs_t mLastTargetDurationSent = kDefaultTarget.count(); - // Max amount the error term can vary without causing an actual value report - nsecs_t mAllowedActualDeviation = -1; + std::vector mHintSessionThreadIds; + Duration mLastTargetDurationSent = kDefaultTargetDuration; // Whether we should emit ATRACE_INT data for hint sessions static const bool sTraceHintSessionData; - static constexpr const std::chrono::nanoseconds kDefaultTarget = 16ms; - // Amount of time after the last message was sent before the session goes stale - // actually 100ms but we use 80 here to ideally avoid going stale - static constexpr const std::chrono::nanoseconds kStaleTimeout = 80ms; + + // Default target duration for the hint session + static constexpr const Duration kDefaultTargetDuration{16ms}; + + // An adjustable safety margin which pads the "actual" value sent to PowerHAL, + // encouraging more aggressive boosting to give SurfaceFlinger a larger margin for error + static const Duration sTargetSafetyMargin; + static constexpr const Duration kDefaultTargetSafetyMargin{1ms}; + + // Whether we should send reportActualWorkDuration calls + static const bool sUseReportActualDuration; + + // 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}; }; } // namespace impl diff --git a/services/surfaceflinger/DisplayHardware/VirtualDisplaySurface.cpp b/services/surfaceflinger/DisplayHardware/VirtualDisplaySurface.cpp index 3803a78670681aa463b301e6943d5e819d075aef..d62075ec65ddccbeb577ea8745c9d0b862f221c6 100644 --- a/services/surfaceflinger/DisplayHardware/VirtualDisplaySurface.cpp +++ b/services/surfaceflinger/DisplayHardware/VirtualDisplaySurface.cpp @@ -103,6 +103,10 @@ VirtualDisplaySurface::VirtualDisplaySurface(HWComposer& hwc, VirtualDisplayId d sink->setAsyncMode(true); IGraphicBufferProducer::QueueBufferOutput output; mSource[SOURCE_SCRATCH]->connect(nullptr, NATIVE_WINDOW_API_EGL, false, &output); + + for (size_t i = 0; i < sizeof(mHwcBufferIds) / sizeof(mHwcBufferIds[0]); ++i) { + mHwcBufferIds[i] = UINT64_MAX; + } } VirtualDisplaySurface::~VirtualDisplaySurface() { @@ -197,9 +201,9 @@ status_t VirtualDisplaySurface::advanceFrame() { return NO_MEMORY; } - sp fbBuffer = mFbProducerSlot >= 0 ? - mProducerBuffers[mFbProducerSlot] : sp(nullptr); - sp outBuffer = mProducerBuffers[mOutputProducerSlot]; + sp const& fbBuffer = + mFbProducerSlot >= 0 ? mProducerBuffers[mFbProducerSlot] : sp(nullptr); + sp const& outBuffer = mProducerBuffers[mOutputProducerSlot]; VDS_LOGV("%s: fb=%d(%p) out=%d(%p)", __func__, mFbProducerSlot, fbBuffer.get(), mOutputProducerSlot, outBuffer.get()); @@ -211,12 +215,14 @@ status_t VirtualDisplaySurface::advanceFrame() { status_t result = NO_ERROR; if (fbBuffer != nullptr) { - uint32_t hwcSlot = 0; - sp hwcBuffer; - mHwcBufferCache.getHwcBuffer(mFbProducerSlot, fbBuffer, &hwcSlot, &hwcBuffer); - + // assume that HWC has previously seen the buffer in this slot + sp hwcBuffer = sp(nullptr); + if (fbBuffer->getId() != mHwcBufferIds[mFbProducerSlot]) { + mHwcBufferIds[mFbProducerSlot] = fbBuffer->getId(); + hwcBuffer = fbBuffer; // HWC hasn't previously seen this buffer in this slot + } // TODO: Correctly propagate the dataspace from GL composition - result = mHwc.setClientTarget(*halDisplayId, hwcSlot, mFbFence, hwcBuffer, + result = mHwc.setClientTarget(*halDisplayId, mFbProducerSlot, mFbFence, hwcBuffer, ui::Dataspace::UNKNOWN); } diff --git a/services/surfaceflinger/DisplayHardware/VirtualDisplaySurface.h b/services/surfaceflinger/DisplayHardware/VirtualDisplaySurface.h index e21095aa88630f878af91d28c80716d0f0123186..be06e2bb10c31be0b999960e8d45c90fb8f05c1f 100644 --- a/services/surfaceflinger/DisplayHardware/VirtualDisplaySurface.h +++ b/services/surfaceflinger/DisplayHardware/VirtualDisplaySurface.h @@ -20,7 +20,7 @@ #include #include -#include +#include #include #include #include @@ -164,6 +164,10 @@ private: sp mSource[2]; // indexed by SOURCE_* uint32_t mDefaultOutputFormat; + // Buffers that HWC has seen before, indexed by HWC slot number. + // NOTE: The BufferQueue slot number is the same as the HWC slot number. + uint64_t mHwcBufferIds[BufferQueue::NUM_BUFFER_SLOTS]; + // // Inter-frame state // @@ -260,8 +264,6 @@ private: bool mMustRecompose = false; - compositionengine::impl::HwcBufferCache mHwcBufferCache; - bool mForceHwcCopy; }; diff --git a/services/surfaceflinger/DisplayRenderArea.cpp b/services/surfaceflinger/DisplayRenderArea.cpp index 20486e0aa3333726a46de5b3423314e495b62504..e55cd3ea420b0b207caa3e9adb614e45c16b3542 100644 --- a/services/surfaceflinger/DisplayRenderArea.cpp +++ b/services/surfaceflinger/DisplayRenderArea.cpp @@ -35,20 +35,23 @@ std::unique_ptr DisplayRenderArea::create(wp di const Rect& sourceCrop, ui::Size reqSize, ui::Dataspace reqDataSpace, bool useIdentityTransform, + bool hintForSeamlessTransition, bool allowSecureLayers) { if (auto display = displayWeak.promote()) { // Using new to access a private constructor. return std::unique_ptr( new DisplayRenderArea(std::move(display), sourceCrop, reqSize, reqDataSpace, - useIdentityTransform, allowSecureLayers)); + useIdentityTransform, hintForSeamlessTransition, + allowSecureLayers)); } return nullptr; } DisplayRenderArea::DisplayRenderArea(sp display, const Rect& sourceCrop, ui::Size reqSize, ui::Dataspace reqDataSpace, - bool useIdentityTransform, bool allowSecureLayers) - : RenderArea(reqSize, CaptureFill::OPAQUE, reqDataSpace, display->getLayerStackSpaceRect(), + bool useIdentityTransform, bool hintForSeamlessTransition, + bool allowSecureLayers) + : RenderArea(reqSize, CaptureFill::OPAQUE, reqDataSpace, hintForSeamlessTransition, allowSecureLayers, applyDeviceOrientation(useIdentityTransform, *display)), mDisplay(std::move(display)), mSourceCrop(sourceCrop) {} @@ -57,18 +60,6 @@ const ui::Transform& DisplayRenderArea::getTransform() const { return mTransform; } -Rect DisplayRenderArea::getBounds() const { - return mDisplay->getBounds(); -} - -int DisplayRenderArea::getHeight() const { - return mDisplay->getHeight(); -} - -int DisplayRenderArea::getWidth() const { - return mDisplay->getWidth(); -} - bool DisplayRenderArea::isSecure() const { return mAllowSecureLayers && mDisplay->isSecure(); } @@ -77,18 +68,6 @@ sp DisplayRenderArea::getDisplayDevice() const { return mDisplay; } -bool DisplayRenderArea::needsFiltering() const { - // check if the projection from the logical render area - // to the physical render area requires filtering - const Rect& sourceCrop = getSourceCrop(); - int width = sourceCrop.width(); - int height = sourceCrop.height(); - if (getRotationFlags() & ui::Transform::ROT_90) { - std::swap(width, height); - } - return width != getReqWidth() || height != getReqHeight(); -} - Rect DisplayRenderArea::getSourceCrop() const { // use the projected display viewport by default. if (mSourceCrop.isEmpty()) { @@ -107,4 +86,4 @@ Rect DisplayRenderArea::getSourceCrop() const { return rotation.transform(mSourceCrop); } -} // namespace android \ No newline at end of file +} // namespace android diff --git a/services/surfaceflinger/DisplayRenderArea.h b/services/surfaceflinger/DisplayRenderArea.h index 3478fc16c4c68c5cae4cad7d59f6efee6817e56a..9a4981c881f9c32308e6f2a26bb7c7fb919bd0b8 100644 --- a/services/surfaceflinger/DisplayRenderArea.h +++ b/services/surfaceflinger/DisplayRenderArea.h @@ -30,24 +30,22 @@ public: static std::unique_ptr create(wp, const Rect& sourceCrop, ui::Size reqSize, ui::Dataspace, bool useIdentityTransform, + bool hintForSeamlessTransition, bool allowSecureLayers = true); const ui::Transform& getTransform() const override; - Rect getBounds() const override; - int getHeight() const override; - int getWidth() const override; bool isSecure() const override; sp getDisplayDevice() const override; - bool needsFiltering() const override; Rect getSourceCrop() const override; private: DisplayRenderArea(sp, const Rect& sourceCrop, ui::Size reqSize, - ui::Dataspace, bool useIdentityTransform, bool allowSecureLayers = true); + ui::Dataspace, bool useIdentityTransform, bool hintForSeamlessTransition, + bool allowSecureLayers = true); const sp mDisplay; const Rect mSourceCrop; const ui::Transform mTransform; }; -} // namespace android \ No newline at end of file +} // namespace android diff --git a/services/surfaceflinger/EffectLayer.cpp b/services/surfaceflinger/EffectLayer.cpp deleted file mode 100644 index e8c590e3c43df56f0830d3037427d86a4238c7aa..0000000000000000000000000000000000000000 --- a/services/surfaceflinger/EffectLayer.cpp +++ /dev/null @@ -1,163 +0,0 @@ -/* - * Copyright (C) 2007 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 -#pragma clang diagnostic push -#pragma clang diagnostic ignored "-Wconversion" - -// #define LOG_NDEBUG 0 -#undef LOG_TAG -#define LOG_TAG "EffectLayer" - -#include "EffectLayer.h" - -#include -#include -#include - -#include -#include -#include -#include -#include -#include - -#include "DisplayDevice.h" -#include "SurfaceFlinger.h" - -namespace android { -// --------------------------------------------------------------------------- - -EffectLayer::EffectLayer(const LayerCreationArgs& args) - : Layer(args), - mCompositionState{mFlinger->getCompositionEngine().createLayerFECompositionState()} {} - -EffectLayer::~EffectLayer() = default; - -std::vector EffectLayer::prepareClientCompositionList( - compositionengine::LayerFE::ClientCompositionTargetSettings& targetSettings) { - std::vector results; - std::optional layerSettings = - prepareClientComposition(targetSettings); - // Nothing to render. - if (!layerSettings) { - return {}; - } - - // set the shadow for the layer if needed - prepareShadowClientComposition(*layerSettings, targetSettings.viewport); - - // If fill bounds are occluded or the fill color is invalid skip the fill settings. - if (targetSettings.realContentIsVisible && fillsColor()) { - // Set color for color fill settings. - layerSettings->source.solidColor = getColor().rgb; - results.push_back(*layerSettings); - } else if (hasBlur() || drawShadows()) { - layerSettings->skipContentDraw = true; - results.push_back(*layerSettings); - } - - return results; -} - -bool EffectLayer::isVisible() const { - return !isHiddenByPolicy() && (getAlpha() > 0.0_hf || hasBlur()) && hasSomethingToDraw(); -} - -bool EffectLayer::setColor(const half3& color) { - if (mDrawingState.color.r == color.r && mDrawingState.color.g == color.g && - mDrawingState.color.b == color.b) { - return false; - } - - mDrawingState.sequence++; - mDrawingState.color.r = color.r; - mDrawingState.color.g = color.g; - mDrawingState.color.b = color.b; - mDrawingState.modified = true; - setTransactionFlags(eTransactionNeeded); - return true; -} - -bool EffectLayer::setDataspace(ui::Dataspace dataspace) { - if (mDrawingState.dataspace == dataspace) { - return false; - } - - mDrawingState.sequence++; - mDrawingState.dataspace = dataspace; - mDrawingState.modified = true; - setTransactionFlags(eTransactionNeeded); - return true; -} - -void EffectLayer::preparePerFrameCompositionState() { - Layer::preparePerFrameCompositionState(); - - auto* compositionState = editCompositionState(); - compositionState->color = getColor(); - compositionState->compositionType = - aidl::android::hardware::graphics::composer3::Composition::SOLID_COLOR; -} - -sp EffectLayer::getCompositionEngineLayerFE() const { - // There's no need to get a CE Layer if the EffectLayer isn't going to draw anything. In that - // case, it acts more like a ContainerLayer so returning a null CE Layer makes more sense - if (hasSomethingToDraw()) { - return asLayerFE(); - } else { - return nullptr; - } -} - -compositionengine::LayerFECompositionState* EffectLayer::editCompositionState() { - return mCompositionState.get(); -} - -const compositionengine::LayerFECompositionState* EffectLayer::getCompositionState() const { - return mCompositionState.get(); -} - -bool EffectLayer::isOpaque(const Layer::State& s) const { - // Consider the layer to be opaque if its opaque flag is set or its effective - // alpha (considering the alpha of its parents as well) is 1.0; - return (s.flags & layer_state_t::eLayerOpaque) != 0 || (fillsColor() && getAlpha() == 1.0_hf); -} - -ui::Dataspace EffectLayer::getDataSpace() const { - return mDrawingState.dataspace; -} - -sp EffectLayer::createClone() { - sp layer = mFlinger->getFactory().createEffectLayer( - LayerCreationArgs(mFlinger.get(), nullptr, mName + " (Mirror)", 0, LayerMetadata())); - layer->setInitialValuesForClone(this); - return layer; -} - -bool EffectLayer::fillsColor() const { - return mDrawingState.color.r >= 0.0_hf && mDrawingState.color.g >= 0.0_hf && - mDrawingState.color.b >= 0.0_hf; -} - -bool EffectLayer::hasBlur() const { - return getBackgroundBlurRadius() > 0 || getDrawingState().blurRegions.size() > 0; -} - -} // namespace android - -// TODO(b/129481165): remove the #pragma below and fix conversion issues -#pragma clang diagnostic pop // ignored "-Wconversion" diff --git a/services/surfaceflinger/EffectLayer.h b/services/surfaceflinger/EffectLayer.h deleted file mode 100644 index 1dcb633251237c775c6f26bc75d9dd33e3359b70..0000000000000000000000000000000000000000 --- a/services/surfaceflinger/EffectLayer.h +++ /dev/null @@ -1,70 +0,0 @@ -/* - * Copyright (C) 2007 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 "Layer.h" - -namespace android { - -// A layer that can render a combination of the following effects. -// * fill the bounds of the layer with a color -// * render a shadow cast by the bounds of the layer -// If no effects are enabled, the layer is considered to be invisible. -class EffectLayer : public Layer { -public: - explicit EffectLayer(const LayerCreationArgs&); - ~EffectLayer() override; - - sp getCompositionEngineLayerFE() const override; - compositionengine::LayerFECompositionState* editCompositionState() override; - - const char* getType() const override { return "EffectLayer"; } - bool isVisible() const override; - - bool setColor(const half3& color) override; - - bool setDataspace(ui::Dataspace dataspace) override; - - ui::Dataspace getDataSpace() const override; - - bool isOpaque(const Layer::State& s) const override; - -protected: - /* - * compositionengine::LayerFE overrides - */ - const compositionengine::LayerFECompositionState* getCompositionState() const override; - void preparePerFrameCompositionState() override; - std::vector prepareClientCompositionList( - compositionengine::LayerFE::ClientCompositionTargetSettings& targetSettings) override; - - std::unique_ptr mCompositionState; - - sp createClone() override; - -private: - // Returns true if there is a valid color to fill. - bool fillsColor() const; - // Returns true if this layer has a blur value. - bool hasBlur() const; - bool hasSomethingToDraw() const { return fillsColor() || drawShadows() || hasBlur(); } -}; - -} // namespace android diff --git a/services/surfaceflinger/FpsReporter.cpp b/services/surfaceflinger/FpsReporter.cpp index e12835f0f6667c3a2773b2d326e5dc8c06c60659..155cf4da5853edf1d59e20901c181f9c425d6fed 100644 --- a/services/surfaceflinger/FpsReporter.cpp +++ b/services/surfaceflinger/FpsReporter.cpp @@ -56,14 +56,15 @@ void FpsReporter::dispatchLayerFps() { mFlinger.mCurrentState.traverse([&](Layer* layer) { auto& currentState = layer->getDrawingState(); - if (currentState.metadata.has(METADATA_TASK_ID)) { - int32_t taskId = currentState.metadata.getInt32(METADATA_TASK_ID, 0); + if (currentState.metadata.has(gui::METADATA_TASK_ID)) { + int32_t taskId = currentState.metadata.getInt32(gui::METADATA_TASK_ID, 0); if (seenTasks.count(taskId) == 0) { // localListeners is expected to be tiny for (TrackedListener& listener : localListeners) { if (listener.taskId == taskId) { seenTasks.insert(taskId); - listenersAndLayersToReport.push_back({listener, sp(layer)}); + listenersAndLayersToReport.push_back( + {listener, sp::fromExisting(layer)}); break; } } @@ -90,7 +91,7 @@ void FpsReporter::binderDied(const wp& who) { void FpsReporter::addListener(const sp& listener, int32_t taskId) { sp asBinder = IInterface::asBinder(listener); - asBinder->linkToDeath(this); + asBinder->linkToDeath(sp::fromExisting(this)); std::lock_guard lock(mMutex); mListeners.emplace(wp(asBinder), TrackedListener{listener, taskId}); } diff --git a/services/surfaceflinger/FrameTimeline/FrameTimeline.cpp b/services/surfaceflinger/FrameTimeline/FrameTimeline.cpp index 66beff295371e51b21b7c86356158f9ed1486977..ded734efad36482e960e5885b53cd57666376dd3 100644 --- a/services/surfaceflinger/FrameTimeline/FrameTimeline.cpp +++ b/services/surfaceflinger/FrameTimeline/FrameTimeline.cpp @@ -109,11 +109,11 @@ std::string jankTypeBitmaskToString(int32_t jankType) { jankType &= ~JankType::DisplayHAL; } if (jankType & JankType::SurfaceFlingerCpuDeadlineMissed) { - janks.emplace_back("SurfaceFlinger CPU Deadline Missed"); + janks.emplace_back("SurfaceFlinger deadline missed (while in HWC)"); jankType &= ~JankType::SurfaceFlingerCpuDeadlineMissed; } if (jankType & JankType::SurfaceFlingerGpuDeadlineMissed) { - janks.emplace_back("SurfaceFlinger GPU Deadline Missed"); + janks.emplace_back("SurfaceFlinger deadline missed (while in GPU comp)"); jankType &= ~JankType::SurfaceFlingerGpuDeadlineMissed; } if (jankType & JankType::AppDeadlineMissed) { @@ -289,7 +289,7 @@ nsecs_t getMinTime(PredictionState predictionState, TimelineItem predictions, minTime = std::min(minTime, actuals.endTime); } if (actuals.presentTime != 0) { - minTime = std::min(minTime, actuals.endTime); + minTime = std::min(minTime, actuals.presentTime); } return minTime; } @@ -885,13 +885,19 @@ void FrameTimeline::DisplayFrame::setGpuFence(const std::shared_ptr& void FrameTimeline::DisplayFrame::classifyJank(nsecs_t& deadlineDelta, nsecs_t& deltaToVsync, nsecs_t previousPresentTime) { - if (mPredictionState == PredictionState::Expired || - mSurfaceFlingerActuals.presentTime == Fence::SIGNAL_TIME_INVALID) { + const bool presentTimeValid = + mSurfaceFlingerActuals.presentTime >= mSurfaceFlingerActuals.startTime; + if (mPredictionState == PredictionState::Expired || !presentTimeValid) { // Cannot do jank classification with expired predictions or invalid signal times. Set the // deltas to 0 as both negative and positive deltas are used as real values. mJankType = JankType::Unknown; deadlineDelta = 0; deltaToVsync = 0; + if (!presentTimeValid) { + mSurfaceFlingerActuals.presentTime = mSurfaceFlingerActuals.endTime; + mJankType |= JankType::DisplayHAL; + } + return; } @@ -986,11 +992,8 @@ void FrameTimeline::DisplayFrame::classifyJank(nsecs_t& deadlineDelta, nsecs_t& mJankClassificationThresholds.presentThreshold) { // Classify CPU vs GPU if SF wasn't stuffed or if SF was stuffed but this frame // was presented more than a vsync late. - if (mGpuFence != FenceTime::NO_FENCE && - mSurfaceFlingerActuals.endTime - mSurfaceFlingerActuals.startTime < - mRefreshRate.getPeriodNsecs()) { - // If SF was in GPU composition and the CPU work finished before the vsync - // period, classify it as GPU deadline missed. + if (mGpuFence != FenceTime::NO_FENCE) { + // If SF was in GPU composition, classify it as GPU deadline missed. mJankType = JankType::SurfaceFlingerGpuDeadlineMissed; } else { mJankType = JankType::SurfaceFlingerCpuDeadlineMissed; @@ -1094,6 +1097,12 @@ void FrameTimeline::DisplayFrame::traceActuals(pid_t surfaceFlingerPid, } void FrameTimeline::DisplayFrame::trace(pid_t surfaceFlingerPid, nsecs_t monoBootOffset) const { + if (mSurfaceFrames.empty()) { + // We don't want to trace display frames without any surface frames updates as this cannot + // be janky + return; + } + if (mToken == FrameTimelineInfo::INVALID_VSYNC_ID) { // DisplayFrame should not have an invalid token. ALOGE("Cannot trace DisplayFrame with invalid token"); @@ -1171,22 +1180,50 @@ float FrameTimeline::computeFps(const std::unordered_set& layerIds) { static_cast(totalPresentToPresentWalls); } +std::optional FrameTimeline::getFirstSignalFenceIndex() const { + for (size_t i = 0; i < mPendingPresentFences.size(); i++) { + const auto& [fence, _] = mPendingPresentFences[i]; + if (fence && fence->getSignalTime() != Fence::SIGNAL_TIME_PENDING) { + return i; + } + } + + return {}; +} + void FrameTimeline::flushPendingPresentFences() { + const auto firstSignaledFence = getFirstSignalFenceIndex(); + if (!firstSignaledFence.has_value()) { + return; + } + // Perfetto is using boottime clock to void drifts when the device goes // to suspend. const auto monoBootOffset = mUseBootTimeClock ? (systemTime(SYSTEM_TIME_BOOTTIME) - systemTime(SYSTEM_TIME_MONOTONIC)) : 0; + // Present fences are expected to be signaled in order. Mark all the previous + // pending fences as errors. + for (size_t i = 0; i < firstSignaledFence.value(); i++) { + const auto& pendingPresentFence = *mPendingPresentFences.begin(); + const nsecs_t signalTime = Fence::SIGNAL_TIME_INVALID; + auto& displayFrame = pendingPresentFence.second; + displayFrame->onPresent(signalTime, mPreviousPresentTime); + displayFrame->trace(mSurfaceFlingerPid, monoBootOffset); + mPendingPresentFences.erase(mPendingPresentFences.begin()); + } + for (size_t i = 0; i < mPendingPresentFences.size(); i++) { const auto& pendingPresentFence = mPendingPresentFences[i]; nsecs_t signalTime = Fence::SIGNAL_TIME_INVALID; if (pendingPresentFence.first && pendingPresentFence.first->isValid()) { signalTime = pendingPresentFence.first->getSignalTime(); if (signalTime == Fence::SIGNAL_TIME_PENDING) { - continue; + break; } } + auto& displayFrame = pendingPresentFence.second; displayFrame->onPresent(signalTime, mPreviousPresentTime); displayFrame->trace(mSurfaceFlingerPid, monoBootOffset); diff --git a/services/surfaceflinger/FrameTimeline/FrameTimeline.h b/services/surfaceflinger/FrameTimeline/FrameTimeline.h index 3611deabcee810e5eaf24b6f62cf49a84b755bfd..d54d22d53b33d390bd68c5a4e02214e532d29aa7 100644 --- a/services/surfaceflinger/FrameTimeline/FrameTimeline.h +++ b/services/surfaceflinger/FrameTimeline/FrameTimeline.h @@ -474,6 +474,7 @@ private: friend class android::frametimeline::FrameTimelineTest; void flushPendingPresentFences() REQUIRES(mMutex); + std::optional getFirstSignalFenceIndex() const REQUIRES(mMutex); void finalizeCurrentDisplayFrame() REQUIRES(mMutex); void dumpAll(std::string& result); void dumpJank(std::string& result); diff --git a/services/surfaceflinger/FrontEnd/DisplayInfo.h b/services/surfaceflinger/FrontEnd/DisplayInfo.h new file mode 100644 index 0000000000000000000000000000000000000000..218a64a8d65d1c6c5c79957fdc8c883678d069ba --- /dev/null +++ b/services/surfaceflinger/FrontEnd/DisplayInfo.h @@ -0,0 +1,47 @@ +/* + * 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::surfaceflinger::frontend { + +// Display information needed to populate input and calculate layer geometry. +struct DisplayInfo { + gui::DisplayInfo info; + ui::Transform transform; + bool receivesInput; + bool isSecure; + // TODO(b/259407931): can eliminate once SurfaceFlinger::sActiveDisplayRotationFlags is removed. + bool isPrimary; + bool isVirtual; + ui::Transform::RotationFlags rotationFlags; + ui::Transform::RotationFlags transformHint; + std::string getDebugString() const { + std::stringstream debug; + debug << "DisplayInfo {displayId=" << info.displayId << " lw=" << info.logicalWidth + << " lh=" << info.logicalHeight << " transform={" << transform.dsdx() << " ," + << transform.dsdy() << " ," << transform.dtdx() << " ," << transform.dtdy() + << "} isSecure=" << isSecure << " isPrimary=" << isPrimary + << " rotationFlags=" << rotationFlags << " transformHint=" << transformHint << "}"; + return debug.str(); + } +}; + +} // namespace android::surfaceflinger::frontend diff --git a/services/surfaceflinger/FrontEnd/LayerCreationArgs.cpp b/services/surfaceflinger/FrontEnd/LayerCreationArgs.cpp new file mode 100644 index 0000000000000000000000000000000000000000..97af445513a78a93a9364358605062c796d23b2d --- /dev/null +++ b/services/surfaceflinger/FrontEnd/LayerCreationArgs.cpp @@ -0,0 +1,95 @@ +/* + * 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 "LayerCreationArgs.h" +#include +#include +#include "Client.h" +#include "gui/LayerMetadata.h" + +namespace android::surfaceflinger { + +std::atomic LayerCreationArgs::sSequence{1}; +std::atomic LayerCreationArgs::sInternalSequence{1}; + +uint32_t LayerCreationArgs::getInternalLayerId(uint32_t id) { + return id | INTERNAL_LAYER_PREFIX; +} + +LayerCreationArgs::LayerCreationArgs(SurfaceFlinger* flinger, sp client, std::string name, + uint32_t flags, gui::LayerMetadata metadataArg, + std::optional id, bool internalLayer) + : flinger(flinger), + client(std::move(client)), + name(std::move(name)), + flags(flags), + metadata(std::move(metadataArg)) { + IPCThreadState* ipc = IPCThreadState::self(); + ownerPid = ipc->getCallingPid(); + uid_t callingUid = ipc->getCallingUid(); + metadata.setInt32(gui::METADATA_CALLING_UID, static_cast(callingUid)); + ownerUid = callingUid; + if (ownerUid == AID_GRAPHICS || ownerUid == AID_SYSTEM) { + // System can override the calling UID/PID since it can create layers on behalf of apps. + ownerPid = metadata.getInt32(gui::METADATA_OWNER_PID, ownerPid); + ownerUid = static_cast( + metadata.getInt32(gui::METADATA_OWNER_UID, static_cast(ownerUid))); + } + + if (internalLayer) { + sequence = id.value_or(getInternalLayerId(sInternalSequence++)); + } else if (id) { + sequence = *id; + sSequence = *id + 1; + } else { + sequence = sSequence++; + if (sequence >= INTERNAL_LAYER_PREFIX) { + sSequence = 1; + ALOGW("Layer sequence id rolled over."); + sequence = sSequence++; + } + } +} + +LayerCreationArgs::LayerCreationArgs(std::optional id, bool internalLayer) + : LayerCreationArgs(nullptr, nullptr, /*name=*/"", /*flags=*/0, /*metadata=*/{}, id, + internalLayer) {} + +LayerCreationArgs LayerCreationArgs::fromOtherArgs(const LayerCreationArgs& other) { + // returns a new instance of LayerCreationArgs with a unique id. + return LayerCreationArgs(other.flinger, other.client, other.name, other.flags, other.metadata); +} + +std::string LayerCreationArgs::getDebugString() const { + std::stringstream stream; + stream << "LayerCreationArgs{" << name << "[" << sequence << "] flags=" << flags + << " pid=" << ownerPid << " uid=" << ownerUid; + if (addToRoot) { + stream << " addToRoot=" << addToRoot; + } + if (parentId != UNASSIGNED_LAYER_ID) { + stream << " parentId=" << parentId; + } + if (layerIdToMirror != UNASSIGNED_LAYER_ID) { + stream << " layerIdToMirror=" << layerIdToMirror; + } + if (layerStackToMirror != ui::INVALID_LAYER_STACK) { + stream << " layerStackToMirror=" << layerStackToMirror.id; + } + return stream.str(); +} + +} // namespace android::surfaceflinger diff --git a/services/surfaceflinger/FrontEnd/LayerCreationArgs.h b/services/surfaceflinger/FrontEnd/LayerCreationArgs.h new file mode 100644 index 0000000000000000000000000000000000000000..c26edb5d222ad6a0d87388557f344cc57490eba3 --- /dev/null +++ b/services/surfaceflinger/FrontEnd/LayerCreationArgs.h @@ -0,0 +1,67 @@ +/* + * 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 +#include +#include +#include +#include +#include + +constexpr uint32_t UNASSIGNED_LAYER_ID = std::numeric_limits::max(); +constexpr uint32_t INTERNAL_LAYER_PREFIX = 1u << 31; + +namespace android { +class SurfaceFlinger; +class Client; +} // namespace android + +namespace android::surfaceflinger { + +struct LayerCreationArgs { + static std::atomic sSequence; + static std::atomic sInternalSequence; + static uint32_t getInternalLayerId(uint32_t id); + static LayerCreationArgs fromOtherArgs(const LayerCreationArgs& other); + + LayerCreationArgs(android::SurfaceFlinger*, sp, std::string name, + uint32_t flags, gui::LayerMetadata, std::optional id = std::nullopt, + bool internalLayer = false); + LayerCreationArgs(std::optional id, bool internalLayer = false); + LayerCreationArgs() = default; // for tracing + std::string getDebugString() const; + + android::SurfaceFlinger* flinger; + sp client; + std::string name; + uint32_t flags; // ISurfaceComposerClient flags + gui::LayerMetadata metadata; + pid_t ownerPid; + uid_t ownerUid; + uint32_t textureName; + uint32_t sequence; + bool addToRoot = true; + wp parentHandle = nullptr; + wp mirrorLayerHandle = nullptr; + ui::LayerStack layerStackToMirror = ui::INVALID_LAYER_STACK; + uint32_t parentId = UNASSIGNED_LAYER_ID; + uint32_t layerIdToMirror = UNASSIGNED_LAYER_ID; +}; + +} // namespace android::surfaceflinger diff --git a/services/surfaceflinger/FrontEnd/LayerHandle.cpp b/services/surfaceflinger/FrontEnd/LayerHandle.cpp new file mode 100644 index 0000000000000000000000000000000000000000..75e4e3ae115f830bd7d5fbf16cadb86a74fb2594 --- /dev/null +++ b/services/surfaceflinger/FrontEnd/LayerHandle.cpp @@ -0,0 +1,62 @@ +/* + * 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 "LayerHandle.h" +#include +#include "Layer.h" +#include "LayerCreationArgs.h" +#include "SurfaceFlinger.h" + +namespace android::surfaceflinger { + +LayerHandle::LayerHandle(const sp& flinger, + const sp& layer) + : mFlinger(flinger), mLayer(layer), mLayerId(static_cast(layer->getSequence())) {} + +LayerHandle::~LayerHandle() { + if (mFlinger) { + mFlinger->onHandleDestroyed(this, mLayer, mLayerId); + } +} + +const String16 LayerHandle::kDescriptor = String16("android.Layer.LayerHandle"); + +sp LayerHandle::fromIBinder(const sp& binder) { + if (binder == nullptr) { + return nullptr; + } + + BBinder* b = binder->localBinder(); + if (b == nullptr || b->getInterfaceDescriptor() != LayerHandle::kDescriptor) { + ALOGD("handle does not have a valid descriptor"); + return nullptr; + } + + // We can safely cast this binder since its local and we verified its interface descriptor. + return sp::cast(binder); +} + +sp LayerHandle::getLayer(const sp& binder) { + sp handle = LayerHandle::fromIBinder(binder); + return handle ? handle->mLayer : nullptr; +} + +uint32_t LayerHandle::getLayerId(const sp& binder) { + sp handle = LayerHandle::fromIBinder(binder); + return handle ? handle->mLayerId : UNASSIGNED_LAYER_ID; +} + +} // namespace android::surfaceflinger diff --git a/services/surfaceflinger/FrontEnd/LayerHandle.h b/services/surfaceflinger/FrontEnd/LayerHandle.h new file mode 100644 index 0000000000000000000000000000000000000000..5d0f7835153d94f15208948c80069ad203bf5af3 --- /dev/null +++ b/services/surfaceflinger/FrontEnd/LayerHandle.h @@ -0,0 +1,58 @@ +/* + * 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 { +class SurfaceFlinger; +class Layer; +} // namespace android + +namespace android::surfaceflinger { + +/* + * The layer handle is just a BBinder object passed to the client + * (remote process) -- we don't keep any reference on our side such that + * the dtor is called when the remote side let go of its reference. + * + * ~LayerHandle ensures that mFlinger->onLayerDestroyed() is called for + * this layer when the handle is destroyed. + */ +class LayerHandle : public BBinder { +public: + LayerHandle(const sp& flinger, const sp& layer); + // for testing + LayerHandle(uint32_t layerId) : mFlinger(nullptr), mLayer(nullptr), mLayerId(layerId) {} + ~LayerHandle(); + + // Static functions to access the layer and layer id safely from an incoming binder. + static sp fromIBinder(const sp& handle); + static sp getLayer(const sp& handle); + static uint32_t getLayerId(const sp& handle); + static const String16 kDescriptor; + + const String16& getInterfaceDescriptor() const override { return kDescriptor; } + +private: + sp mFlinger; + sp mLayer; + const uint32_t mLayerId; +}; + +} // namespace android::surfaceflinger diff --git a/services/surfaceflinger/FrontEnd/LayerHierarchy.cpp b/services/surfaceflinger/FrontEnd/LayerHierarchy.cpp new file mode 100644 index 0000000000000000000000000000000000000000..5913d4b589002821efbc4f6e0e27fe813c64b3f7 --- /dev/null +++ b/services/surfaceflinger/FrontEnd/LayerHierarchy.cpp @@ -0,0 +1,487 @@ +/* + * 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. + */ + +#define ATRACE_TAG ATRACE_TAG_GRAPHICS +#undef LOG_TAG +#define LOG_TAG "LayerHierarchy" + +#include "LayerHierarchy.h" +#include "LayerLog.h" +#include "SwapErase.h" + +namespace android::surfaceflinger::frontend { + +namespace { +auto layerZCompare = [](const std::pair& lhs, + const std::pair& rhs) { + auto lhsLayer = lhs.first->getLayer(); + auto rhsLayer = rhs.first->getLayer(); + if (lhsLayer->layerStack.id != rhsLayer->layerStack.id) { + return lhsLayer->layerStack.id < rhsLayer->layerStack.id; + } + if (lhsLayer->z != rhsLayer->z) { + return lhsLayer->z < rhsLayer->z; + } + return lhsLayer->id < rhsLayer->id; +}; + +void insertSorted(std::vector>& vec, + std::pair value) { + auto it = std::upper_bound(vec.begin(), vec.end(), value, layerZCompare); + vec.insert(it, std::move(value)); +} +} // namespace + +LayerHierarchy::LayerHierarchy(RequestedLayerState* layer) : mLayer(layer) {} + +LayerHierarchy::LayerHierarchy(const LayerHierarchy& hierarchy, bool childrenOnly) { + mLayer = (childrenOnly) ? nullptr : hierarchy.mLayer; + mChildren = hierarchy.mChildren; +} + +void LayerHierarchy::traverse(const Visitor& visitor, + LayerHierarchy::TraversalPath& traversalPath) const { + if (mLayer) { + bool breakTraversal = !visitor(*this, traversalPath); + if (breakTraversal) { + return; + } + } + if (traversalPath.hasRelZLoop()) { + LOG_ALWAYS_FATAL("Found relative z loop layerId:%d", traversalPath.invalidRelativeRootId); + } + for (auto& [child, childVariant] : mChildren) { + ScopedAddToTraversalPath addChildToTraversalPath(traversalPath, child->mLayer->id, + childVariant); + child->traverse(visitor, traversalPath); + } +} + +void LayerHierarchy::traverseInZOrder(const Visitor& visitor, + LayerHierarchy::TraversalPath& traversalPath) const { + bool traverseThisLayer = (mLayer != nullptr); + for (auto it = mChildren.begin(); it < mChildren.end(); it++) { + auto& [child, childVariant] = *it; + if (traverseThisLayer && child->getLayer()->z >= 0) { + traverseThisLayer = false; + bool breakTraversal = !visitor(*this, traversalPath); + if (breakTraversal) { + return; + } + } + if (childVariant == LayerHierarchy::Variant::Detached) { + continue; + } + ScopedAddToTraversalPath addChildToTraversalPath(traversalPath, child->mLayer->id, + childVariant); + child->traverseInZOrder(visitor, traversalPath); + } + + if (traverseThisLayer) { + visitor(*this, traversalPath); + } +} + +void LayerHierarchy::addChild(LayerHierarchy* child, LayerHierarchy::Variant variant) { + insertSorted(mChildren, {child, variant}); +} + +void LayerHierarchy::removeChild(LayerHierarchy* child) { + auto it = std::find_if(mChildren.begin(), mChildren.end(), + [child](const std::pair& x) { + return x.first == child; + }); + if (it == mChildren.end()) { + LOG_ALWAYS_FATAL("Could not find child!"); + } + mChildren.erase(it); +} + +void LayerHierarchy::sortChildrenByZOrder() { + std::sort(mChildren.begin(), mChildren.end(), layerZCompare); +} + +void LayerHierarchy::updateChild(LayerHierarchy* hierarchy, LayerHierarchy::Variant variant) { + auto it = std::find_if(mChildren.begin(), mChildren.end(), + [hierarchy](std::pair& child) { + return child.first == hierarchy; + }); + if (it == mChildren.end()) { + LOG_ALWAYS_FATAL("Could not find child!"); + } else { + it->second = variant; + } +} + +const RequestedLayerState* LayerHierarchy::getLayer() const { + return mLayer; +} + +const LayerHierarchy* LayerHierarchy::getRelativeParent() const { + return mRelativeParent; +} + +const LayerHierarchy* LayerHierarchy::getParent() const { + return mParent; +} + +std::string LayerHierarchy::getDebugStringShort() const { + std::string debug = "LayerHierarchy{"; + debug += ((mLayer) ? mLayer->getDebugString() : "root") + " "; + if (mChildren.empty()) { + debug += "no children"; + } else { + debug += std::to_string(mChildren.size()) + " children"; + } + return debug + "}"; +} + +std::string LayerHierarchy::getDebugString(const char* prefix) const { + std::string debug = prefix + getDebugStringShort(); + for (auto& [child, childVariant] : mChildren) { + std::string childPrefix = " " + std::string(prefix) + " " + std::to_string(childVariant); + debug += "\n" + child->getDebugString(childPrefix.c_str()); + } + return debug; +} + +bool LayerHierarchy::hasRelZLoop(uint32_t& outInvalidRelativeRoot) const { + outInvalidRelativeRoot = UNASSIGNED_LAYER_ID; + traverse([&outInvalidRelativeRoot](const LayerHierarchy&, + const LayerHierarchy::TraversalPath& traversalPath) -> bool { + if (traversalPath.hasRelZLoop()) { + outInvalidRelativeRoot = traversalPath.invalidRelativeRootId; + return false; + } + return true; + }); + return outInvalidRelativeRoot != UNASSIGNED_LAYER_ID; +} + +LayerHierarchyBuilder::LayerHierarchyBuilder( + const std::vector>& layers) { + mHierarchies.reserve(layers.size()); + mLayerIdToHierarchy.reserve(layers.size()); + for (auto& layer : layers) { + mHierarchies.emplace_back(std::make_unique(layer.get())); + mLayerIdToHierarchy[layer->id] = mHierarchies.back().get(); + } + for (const auto& layer : layers) { + onLayerAdded(layer.get()); + } + detachHierarchyFromRelativeParent(&mOffscreenRoot); +} + +void LayerHierarchyBuilder::attachToParent(LayerHierarchy* hierarchy) { + auto layer = hierarchy->mLayer; + LayerHierarchy::Variant type = layer->hasValidRelativeParent() + ? LayerHierarchy::Variant::Detached + : LayerHierarchy::Variant::Attached; + + LayerHierarchy* parent; + + if (layer->parentId != UNASSIGNED_LAYER_ID) { + parent = getHierarchyFromId(layer->parentId); + } else if (layer->canBeRoot) { + parent = &mRoot; + } else { + parent = &mOffscreenRoot; + } + parent->addChild(hierarchy, type); + hierarchy->mParent = parent; +} + +void LayerHierarchyBuilder::detachFromParent(LayerHierarchy* hierarchy) { + hierarchy->mParent->removeChild(hierarchy); + hierarchy->mParent = nullptr; +} + +void LayerHierarchyBuilder::attachToRelativeParent(LayerHierarchy* hierarchy) { + auto layer = hierarchy->mLayer; + if (!layer->hasValidRelativeParent() || hierarchy->mRelativeParent) { + return; + } + + if (layer->relativeParentId != UNASSIGNED_LAYER_ID) { + hierarchy->mRelativeParent = getHierarchyFromId(layer->relativeParentId); + } else { + hierarchy->mRelativeParent = &mOffscreenRoot; + } + hierarchy->mRelativeParent->addChild(hierarchy, LayerHierarchy::Variant::Relative); + hierarchy->mParent->updateChild(hierarchy, LayerHierarchy::Variant::Detached); +} + +void LayerHierarchyBuilder::detachFromRelativeParent(LayerHierarchy* hierarchy) { + if (hierarchy->mRelativeParent) { + hierarchy->mRelativeParent->removeChild(hierarchy); + } + hierarchy->mRelativeParent = nullptr; + 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); + } + } +} + +void LayerHierarchyBuilder::detachHierarchyFromRelativeParent(LayerHierarchy* root) { + if (root->mLayer) { + detachFromRelativeParent(root); + } + for (auto& [child, childVariant] : root->mChildren) { + if (childVariant == LayerHierarchy::Variant::Detached || + childVariant == LayerHierarchy::Variant::Attached) { + detachHierarchyFromRelativeParent(child); + } + } +} + +void LayerHierarchyBuilder::onLayerAdded(RequestedLayerState* layer) { + LayerHierarchy* hierarchy = getHierarchyFromId(layer->id); + attachToParent(hierarchy); + attachToRelativeParent(hierarchy); + + for (uint32_t mirrorId : layer->mirrorIds) { + LayerHierarchy* mirror = getHierarchyFromId(mirrorId); + hierarchy->addChild(mirror, LayerHierarchy::Variant::Mirror); + } +} + +void LayerHierarchyBuilder::onLayerDestroyed(RequestedLayerState* layer) { + LLOGV(layer->id, ""); + LayerHierarchy* hierarchy = getHierarchyFromId(layer->id, /*crashOnFailure=*/false); + if (!hierarchy) { + // Layer was never part of the hierarchy if it was created and destroyed in the same + // transaction. + return; + } + // detach from parent + detachFromRelativeParent(hierarchy); + detachFromParent(hierarchy); + + // detach children + for (auto& [child, variant] : hierarchy->mChildren) { + if (variant == LayerHierarchy::Variant::Attached || + variant == LayerHierarchy::Variant::Detached) { + mOffscreenRoot.addChild(child, LayerHierarchy::Variant::Attached); + child->mParent = &mOffscreenRoot; + } else if (variant == LayerHierarchy::Variant::Relative) { + mOffscreenRoot.addChild(child, LayerHierarchy::Variant::Attached); + child->mRelativeParent = &mOffscreenRoot; + } + } + + swapErase(mHierarchies, [hierarchy](std::unique_ptr& layerHierarchy) { + return layerHierarchy.get() == hierarchy; + }); + mLayerIdToHierarchy.erase(layer->id); +} + +void LayerHierarchyBuilder::updateMirrorLayer(RequestedLayerState* layer) { + LayerHierarchy* hierarchy = getHierarchyFromId(layer->id); + auto it = hierarchy->mChildren.begin(); + while (it != hierarchy->mChildren.end()) { + if (it->second == LayerHierarchy::Variant::Mirror) { + it = hierarchy->mChildren.erase(it); + } else { + it++; + } + } + + for (uint32_t mirrorId : layer->mirrorIds) { + hierarchy->addChild(getHierarchyFromId(mirrorId), LayerHierarchy::Variant::Mirror); + } +} + +void LayerHierarchyBuilder::update( + const std::vector>& layers, + const std::vector>& destroyedLayers) { + // rebuild map + for (auto& layer : layers) { + if (layer->changes.test(RequestedLayerState::Changes::Created)) { + mHierarchies.emplace_back(std::make_unique(layer.get())); + mLayerIdToHierarchy[layer->id] = mHierarchies.back().get(); + } + } + + for (auto& layer : layers) { + if (layer->changes.get() == 0) { + continue; + } + if (layer->changes.test(RequestedLayerState::Changes::Created)) { + onLayerAdded(layer.get()); + continue; + } + LayerHierarchy* hierarchy = getHierarchyFromId(layer->id); + if (layer->changes.test(RequestedLayerState::Changes::Parent)) { + detachFromParent(hierarchy); + attachToParent(hierarchy); + } + if (layer->changes.test(RequestedLayerState::Changes::RelativeParent)) { + detachFromRelativeParent(hierarchy); + attachToRelativeParent(hierarchy); + } + if (layer->changes.test(RequestedLayerState::Changes::Z)) { + hierarchy->mParent->sortChildrenByZOrder(); + if (hierarchy->mRelativeParent) { + hierarchy->mRelativeParent->sortChildrenByZOrder(); + } + } + if (layer->changes.test(RequestedLayerState::Changes::Mirror)) { + updateMirrorLayer(layer.get()); + } + } + + for (auto& layer : destroyedLayers) { + onLayerDestroyed(layer.get()); + } + // When moving from onscreen to offscreen and vice versa, we need to attach and detach + // from our relative parents. This walks down both trees to do so. We can optimize this + // further by tracking onscreen, offscreen state in LayerHierarchy. + detachHierarchyFromRelativeParent(&mOffscreenRoot); + attachHierarchyToRelativeParent(&mRoot); +} + +const LayerHierarchy& LayerHierarchyBuilder::getHierarchy() const { + return mRoot; +} + +const LayerHierarchy& LayerHierarchyBuilder::getOffscreenHierarchy() const { + return mOffscreenRoot; +} + +std::string LayerHierarchyBuilder::getDebugString(uint32_t layerId, uint32_t depth) const { + if (depth > 10) return "too deep, loop?"; + if (layerId == UNASSIGNED_LAYER_ID) return ""; + auto it = mLayerIdToHierarchy.find(layerId); + if (it == mLayerIdToHierarchy.end()) return "not found"; + + LayerHierarchy* hierarchy = it->second; + if (!hierarchy->mLayer) return "none"; + + std::string debug = + "[" + std::to_string(hierarchy->mLayer->id) + "] " + hierarchy->mLayer->name; + if (hierarchy->mRelativeParent) { + debug += " Relative:" + hierarchy->mRelativeParent->getDebugStringShort(); + } + if (hierarchy->mParent) { + debug += " Parent:" + hierarchy->mParent->getDebugStringShort(); + } + return debug; +} + +LayerHierarchy LayerHierarchyBuilder::getPartialHierarchy(uint32_t layerId, + bool childrenOnly) const { + auto it = mLayerIdToHierarchy.find(layerId); + if (it == mLayerIdToHierarchy.end()) return {nullptr}; + + LayerHierarchy hierarchy(*it->second, childrenOnly); + return hierarchy; +} + +LayerHierarchy* LayerHierarchyBuilder::getHierarchyFromId(uint32_t layerId, bool crashOnFailure) { + auto it = mLayerIdToHierarchy.find(layerId); + if (it == mLayerIdToHierarchy.end()) { + if (crashOnFailure) { + LOG_ALWAYS_FATAL("Could not find hierarchy for layer id %d", layerId); + } + return nullptr; + }; + + return it->second; +} + +const LayerHierarchy::TraversalPath LayerHierarchy::TraversalPath::ROOT = + {.id = UNASSIGNED_LAYER_ID, .variant = LayerHierarchy::Attached}; + +std::string LayerHierarchy::TraversalPath::toString() const { + if (id == UNASSIGNED_LAYER_ID) { + return "TraversalPath{ROOT}"; + } + std::stringstream ss; + ss << "TraversalPath{.id = " << id; + + if (mirrorRootId != UNASSIGNED_LAYER_ID) { + ss << ", .mirrorRootId=" << mirrorRootId; + } + + if (!relativeRootIds.empty()) { + ss << ", .relativeRootIds="; + for (auto rootId : relativeRootIds) { + ss << rootId << ","; + } + } + + if (hasRelZLoop()) { + ss << "hasRelZLoop=true invalidRelativeRootId=" << invalidRelativeRootId << ","; + } + ss << "}"; + return ss.str(); +} + +LayerHierarchy::TraversalPath LayerHierarchy::TraversalPath::getMirrorRoot() const { + LOG_ALWAYS_FATAL_IF(!isClone(), "Cannot get mirror root of a non cloned node"); + TraversalPath mirrorRootPath = *this; + mirrorRootPath.id = mirrorRootId; + return mirrorRootPath; +} + +// Helper class to update a passed in TraversalPath when visiting a child. When the object goes out +// of scope the TraversalPath is reset to its original state. +LayerHierarchy::ScopedAddToTraversalPath::ScopedAddToTraversalPath(TraversalPath& traversalPath, + uint32_t layerId, + LayerHierarchy::Variant variant) + : mTraversalPath(traversalPath), mParentPath(traversalPath) { + // Update the traversal id with the child layer id and variant. Parent id and variant are + // stored to reset the id upon destruction. + traversalPath.id = layerId; + traversalPath.variant = variant; + if (variant == LayerHierarchy::Variant::Mirror) { + traversalPath.mirrorRootId = mParentPath.id; + } else if (variant == LayerHierarchy::Variant::Relative) { + if (std::find(traversalPath.relativeRootIds.begin(), traversalPath.relativeRootIds.end(), + layerId) != traversalPath.relativeRootIds.end()) { + traversalPath.invalidRelativeRootId = layerId; + } + traversalPath.relativeRootIds.emplace_back(layerId); + } else if (variant == LayerHierarchy::Variant::Detached) { + traversalPath.detached = true; + } +} +LayerHierarchy::ScopedAddToTraversalPath::~ScopedAddToTraversalPath() { + // Reset the traversal id to its original parent state using the state that was saved in + // the constructor. + if (mTraversalPath.variant == LayerHierarchy::Variant::Mirror) { + mTraversalPath.mirrorRootId = mParentPath.mirrorRootId; + } else if (mTraversalPath.variant == LayerHierarchy::Variant::Relative) { + mTraversalPath.relativeRootIds.pop_back(); + } + if (mTraversalPath.invalidRelativeRootId == mTraversalPath.id) { + mTraversalPath.invalidRelativeRootId = UNASSIGNED_LAYER_ID; + } + mTraversalPath.id = mParentPath.id; + mTraversalPath.variant = mParentPath.variant; + mTraversalPath.detached = mParentPath.detached; +} + +} // namespace android::surfaceflinger::frontend diff --git a/services/surfaceflinger/FrontEnd/LayerHierarchy.h b/services/surfaceflinger/FrontEnd/LayerHierarchy.h new file mode 100644 index 0000000000000000000000000000000000000000..b25b7313562562282fae30ace93bb6868e866d97 --- /dev/null +++ b/services/surfaceflinger/FrontEnd/LayerHierarchy.h @@ -0,0 +1,212 @@ +/* + * 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 "FrontEnd/LayerCreationArgs.h" +#include "RequestedLayerState.h" +#include "ftl/small_vector.h" + +namespace android::surfaceflinger::frontend { +class LayerHierarchyBuilder; + +// LayerHierarchy allows us to navigate the layer hierarchy in z-order, or depth first traversal. +// The hierarchy is created from a set of RequestedLayerStates. The hierarchy itself does not +// contain additional states. Instead, it is a representation of RequestedLayerStates as a graph. +// +// Each node in the hierarchy can be visited by multiple parents (making this a graph). While +// traversing the hierarchy, a new concept called Variant can be used to understand the +// relationship of the layer to its parent. The following variants are possible: +// Attached - child of the parent +// Detached - child of the parent but currently relative parented to another layer +// Relative - relative child of the parent +// Mirror - mirrored from another layer +// +// By representing the hierarchy as a graph, we can represent mirrored layer hierarchies without +// cloning the layer requested state. The mirrored hierarchy and its corresponding +// RequestedLayerStates are kept in sync because the mirrored hierarchy does not clone any +// states. +class LayerHierarchy { +public: + enum Variant : uint32_t { + Attached, + Detached, + Relative, + Mirror, + ftl_first = Attached, + ftl_last = Mirror, + }; + // Represents a unique path to a node. + // The layer hierarchy is represented as a graph. Each node can be visited by multiple parents. + // This allows us to represent mirroring in an efficient way. See the example below: + // root + // ├─ A {Traversal path id = 1} + // ├─ B {Traversal path id = 2} + // │ ├─ C {Traversal path id = 3} + // │ ├─ D {Traversal path id = 4} + // │ └─ E {Traversal path id = 5} + // ├─ F (Mirrors B) {Traversal path id = 6} + // └─ G (Mirrors F) {Traversal path id = 7} + // + // C, D and E can be traversed via B or via F then B or via G then F then B. + // Depending on how the node is reached, its properties such as geometry or visibility might be + // different. And we can uniquely identify the node by keeping track of the nodes leading up to + // it. But to be more efficient we only need to track the nodes id and the top mirror root path. + // So C for example, would have the following unique traversal paths: + // - {Traversal path id = 3} + // - {Traversal path id = 3, mirrorRootId = 6} + // - {Traversal path id = 3, mirrorRootId = 7} + + struct TraversalPath { + uint32_t id; + LayerHierarchy::Variant variant; + // Mirrored layers can have a different geometry than their parents so we need to track + // the mirror roots in the traversal. + uint32_t mirrorRootId = UNASSIGNED_LAYER_ID; + // Relative layers can be visited twice, once by their parent and then once again by + // their relative parent. We keep track of the roots here to detect any loops in the + // hierarchy. If a relative root already exists in the list while building the + // TraversalPath, it means that somewhere in the hierarchy two layers are relatively + // parented to each other. + ftl::SmallVector relativeRootIds; + // First duplicate relative root id found. If this is a valid layer id that means we are + // in a loop. + uint32_t invalidRelativeRootId = UNASSIGNED_LAYER_ID; + // See isAttached() + bool detached = false; + bool hasRelZLoop() const { return invalidRelativeRootId != UNASSIGNED_LAYER_ID; } + // Returns true if this node is reached via one or more relative parents. + bool isRelative() const { return !relativeRootIds.empty(); } + // Returns true if the node or its parents are not Detached. + bool isAttached() const { return !detached; } + // Returns true if the node is a clone. + bool isClone() const { return mirrorRootId != UNASSIGNED_LAYER_ID; } + TraversalPath getMirrorRoot() const; + + bool operator==(const TraversalPath& other) const { + return id == other.id && mirrorRootId == other.mirrorRootId; + } + std::string toString() const; + + static const TraversalPath ROOT; + }; + + struct TraversalPathHash { + std::size_t operator()(const LayerHierarchy::TraversalPath& key) const { + uint32_t hashCode = key.id * 31; + if (key.mirrorRootId != UNASSIGNED_LAYER_ID) { + hashCode += key.mirrorRootId * 31; + } + return std::hash{}(hashCode); + } + }; + + // Helper class to add nodes to an existing traversal id and removes the + // node when it goes out of scope. + class ScopedAddToTraversalPath { + public: + ScopedAddToTraversalPath(TraversalPath& traversalPath, uint32_t layerId, + LayerHierarchy::Variant variantArg); + ~ScopedAddToTraversalPath(); + + private: + TraversalPath& mTraversalPath; + TraversalPath mParentPath; + }; + LayerHierarchy(RequestedLayerState* layer); + + // Visitor function that provides the hierarchy node and a traversal id which uniquely + // identifies how was visited. The hierarchy contains a pointer to the RequestedLayerState. + // Return false to stop traversing down the hierarchy. + typedef std::function + Visitor; + + // Traverse the hierarchy and visit all child variants. + void traverse(const Visitor& visitor) const { + TraversalPath root = TraversalPath::ROOT; + if (mLayer) { + root.id = mLayer->id; + } + traverse(visitor, root); + } + + // Traverse the hierarchy in z-order, skipping children that have relative parents. + void traverseInZOrder(const Visitor& visitor) const { + TraversalPath root = TraversalPath::ROOT; + if (mLayer) { + root.id = mLayer->id; + } + traverseInZOrder(visitor, root); + } + + const RequestedLayerState* getLayer() const; + const LayerHierarchy* getRelativeParent() const; + const LayerHierarchy* getParent() const; + std::string getDebugString(const char* prefix = "") const; + std::string getDebugStringShort() const; + // Traverse the hierarchy and return true if loops are found. The outInvalidRelativeRoot + // will contain the first relative root that was visited twice in a traversal. + bool hasRelZLoop(uint32_t& outInvalidRelativeRoot) const; + std::vector> mChildren; + +private: + friend LayerHierarchyBuilder; + LayerHierarchy(const LayerHierarchy& hierarchy, bool childrenOnly); + void addChild(LayerHierarchy*, LayerHierarchy::Variant); + void removeChild(LayerHierarchy*); + 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; + + const RequestedLayerState* mLayer; + LayerHierarchy* mParent = nullptr; + LayerHierarchy* mRelativeParent = nullptr; +}; + +// Given a list of RequestedLayerState, this class will build a root hierarchy and an +// offscreen hierarchy. The builder also has an update method which can update an existing +// hierarchy from a list of RequestedLayerState and associated change flags. +class LayerHierarchyBuilder { +public: + LayerHierarchyBuilder(const std::vector>&); + void update(const std::vector>& layers, + const std::vector>& destroyedLayers); + LayerHierarchy getPartialHierarchy(uint32_t, bool childrenOnly) const; + const LayerHierarchy& getHierarchy() const; + const LayerHierarchy& getOffscreenHierarchy() const; + std::string getDebugString(uint32_t layerId, uint32_t depth = 0) const; + +private: + void onLayerAdded(RequestedLayerState* layer); + void attachToParent(LayerHierarchy*); + void detachFromParent(LayerHierarchy*); + void attachToRelativeParent(LayerHierarchy*); + void detachFromRelativeParent(LayerHierarchy*); + void attachHierarchyToRelativeParent(LayerHierarchy*); + void detachHierarchyFromRelativeParent(LayerHierarchy*); + + void onLayerDestroyed(RequestedLayerState* layer); + void updateMirrorLayer(RequestedLayerState* layer); + LayerHierarchy* getHierarchyFromId(uint32_t layerId, bool crashOnFailure = true); + std::unordered_map mLayerIdToHierarchy; + std::vector> mHierarchies; + LayerHierarchy mRoot{nullptr}; + LayerHierarchy mOffscreenRoot{nullptr}; +}; + +} // namespace android::surfaceflinger::frontend diff --git a/services/surfaceflinger/FrontEnd/LayerLifecycleManager.cpp b/services/surfaceflinger/FrontEnd/LayerLifecycleManager.cpp new file mode 100644 index 0000000000000000000000000000000000000000..cd9515cd02cfb8c2511a13f0fdd530def00f31d4 --- /dev/null +++ b/services/surfaceflinger/FrontEnd/LayerLifecycleManager.cpp @@ -0,0 +1,406 @@ +/* + * 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. + */ + +#define ATRACE_TAG ATRACE_TAG_GRAPHICS + +#undef LOG_TAG +#define LOG_TAG "LayerLifecycleManager" + +#include "LayerLifecycleManager.h" +#include "Client.h" // temporarily needed for LayerCreationArgs +#include "LayerLog.h" +#include "SwapErase.h" + +namespace android::surfaceflinger::frontend { + +using namespace ftl::flag_operators; + +void LayerLifecycleManager::addLayers(std::vector> newLayers) { + if (newLayers.empty()) { + return; + } + + mGlobalChanges |= RequestedLayerState::Changes::Hierarchy; + for (auto& newLayer : newLayers) { + RequestedLayerState& layer = *newLayer.get(); + auto [it, inserted] = mIdToLayer.try_emplace(layer.id, References{.owner = layer}); + if (!inserted) { + LOG_ALWAYS_FATAL("Duplicate layer id found. New layer: %s Existing layer: %s", + layer.getDebugString().c_str(), + it->second.owner.getDebugString().c_str()); + } + mAddedLayers.push_back(newLayer.get()); + layer.parentId = linkLayer(layer.parentId, layer.id); + layer.relativeParentId = linkLayer(layer.relativeParentId, layer.id); + if (layer.layerStackToMirror != ui::INVALID_LAYER_STACK) { + // if this layer is mirroring a display, then walk though all the existing root layers + // for the layer stack and add them as children to be mirrored. + mDisplayMirroringLayers.emplace_back(layer.id); + for (auto& rootLayer : mLayers) { + if (rootLayer->isRoot() && rootLayer->layerStack == layer.layerStackToMirror) { + layer.mirrorIds.emplace_back(rootLayer->id); + linkLayer(rootLayer->id, layer.id); + } + } + } else { + // Check if we are mirroring a single layer, and if so add it to the list of children + // to be mirrored. + layer.layerIdToMirror = linkLayer(layer.layerIdToMirror, layer.id); + if (layer.layerIdToMirror != UNASSIGNED_LAYER_ID) { + layer.mirrorIds.emplace_back(layer.layerIdToMirror); + } + } + layer.touchCropId = linkLayer(layer.touchCropId, layer.id); + if (layer.isRoot()) { + updateDisplayMirrorLayers(layer); + } + LLOGV(layer.id, "%s", layer.getDebugString().c_str()); + mLayers.emplace_back(std::move(newLayer)); + } +} + +void LayerLifecycleManager::onHandlesDestroyed(const std::vector& destroyedHandles, + bool ignoreUnknownHandles) { + std::vector layersToBeDestroyed; + for (const auto& layerId : destroyedHandles) { + auto it = mIdToLayer.find(layerId); + if (it == mIdToLayer.end()) { + LOG_ALWAYS_FATAL_IF(!ignoreUnknownHandles, "%s Layerid not found %d", __func__, + layerId); + continue; + } + RequestedLayerState& layer = it->second.owner; + LLOGV(layer.id, "%s", layer.getDebugString().c_str()); + layer.handleAlive = false; + if (!layer.canBeDestroyed()) { + continue; + } + layer.changes |= RequestedLayerState::Changes::Destroyed; + layersToBeDestroyed.emplace_back(layerId); + } + + if (layersToBeDestroyed.empty()) { + return; + } + + mGlobalChanges |= RequestedLayerState::Changes::Hierarchy; + for (size_t i = 0; i < layersToBeDestroyed.size(); i++) { + uint32_t layerId = layersToBeDestroyed[i]; + auto it = mIdToLayer.find(layerId); + if (it == mIdToLayer.end()) { + LOG_ALWAYS_FATAL("%s Layer with id %d not found", __func__, layerId); + continue; + } + + RequestedLayerState& layer = it->second.owner; + + layer.parentId = unlinkLayer(layer.parentId, layer.id); + layer.relativeParentId = unlinkLayer(layer.relativeParentId, layer.id); + if (layer.layerStackToMirror != ui::INVALID_LAYER_STACK) { + layer.mirrorIds = unlinkLayers(layer.mirrorIds, layer.id); + swapErase(mDisplayMirroringLayers, layer.id); + } else { + layer.layerIdToMirror = unlinkLayer(layer.layerIdToMirror, layer.id); + layer.mirrorIds.clear(); + } + + layer.touchCropId = unlinkLayer(layer.touchCropId, layer.id); + + auto& references = it->second.references; + for (uint32_t linkedLayerId : references) { + RequestedLayerState* linkedLayer = getLayerFromId(linkedLayerId); + if (!linkedLayer) { + LOG_ALWAYS_FATAL("%s Layerid reference %d not found for %d", __func__, + linkedLayerId, layer.id); + continue; + }; + if (linkedLayer->parentId == layer.id) { + linkedLayer->parentId = UNASSIGNED_LAYER_ID; + if (linkedLayer->canBeDestroyed()) { + linkedLayer->changes |= RequestedLayerState::Changes::Destroyed; + layersToBeDestroyed.emplace_back(linkedLayer->id); + } + } + if (linkedLayer->relativeParentId == layer.id) { + linkedLayer->relativeParentId = UNASSIGNED_LAYER_ID; + } + if (swapErase(linkedLayer->mirrorIds, layer.id)) { + linkedLayer->changes |= RequestedLayerState::Changes::Mirror; + } + if (linkedLayer->touchCropId == layer.id) { + linkedLayer->touchCropId = UNASSIGNED_LAYER_ID; + } + } + mIdToLayer.erase(it); + } + + auto it = mLayers.begin(); + while (it != mLayers.end()) { + RequestedLayerState* layer = it->get(); + if (layer->changes.test(RequestedLayerState::Changes::Destroyed)) { + LLOGV(layer->id, "destroyed %s", layer->getDebugStringShort().c_str()); + std::iter_swap(it, mLayers.end() - 1); + mDestroyedLayers.emplace_back(std::move(mLayers.back())); + if (it == mLayers.end() - 1) { + it = mLayers.erase(mLayers.end() - 1); + } else { + mLayers.erase(mLayers.end() - 1); + } + } else { + it++; + } + } +} + +void LayerLifecycleManager::applyTransactions(const std::vector& transactions, + bool ignoreUnknownLayers) { + for (const auto& transaction : transactions) { + for (const auto& resolvedComposerState : transaction.states) { + const auto& clientState = resolvedComposerState.state; + uint32_t layerId = resolvedComposerState.layerId; + if (layerId == UNASSIGNED_LAYER_ID) { + ALOGW("%s Handle %p is not valid", __func__, clientState.surface.get()); + continue; + } + + RequestedLayerState* layer = getLayerFromId(layerId); + if (layer == nullptr) { + LOG_ALWAYS_FATAL_IF(!ignoreUnknownLayers, "%s Layer with layerid=%d not found", + __func__, layerId); + continue; + } + + if (!layer->handleAlive) { + LOG_ALWAYS_FATAL("%s Layer's with layerid=%d) is not alive. Possible out of " + "order LayerLifecycleManager updates", + __func__, layerId); + continue; + } + + if (transaction.flags & ISurfaceComposer::eAnimation) { + layer->changes |= RequestedLayerState::Changes::Animation; + } + + uint32_t oldParentId = layer->parentId; + uint32_t oldRelativeParentId = layer->relativeParentId; + uint32_t oldTouchCropId = layer->touchCropId; + layer->merge(resolvedComposerState); + + if (layer->what & layer_state_t::eBackgroundColorChanged) { + if (layer->bgColorLayerId == UNASSIGNED_LAYER_ID && layer->bgColor.a != 0) { + LayerCreationArgs + backgroundLayerArgs(LayerCreationArgs::getInternalLayerId( + LayerCreationArgs::sInternalSequence++), + /*internalLayer=*/true); + backgroundLayerArgs.parentId = layer->id; + backgroundLayerArgs.name = layer->name + "BackgroundColorLayer"; + backgroundLayerArgs.flags = ISurfaceComposerClient::eFXSurfaceEffect; + std::vector> newLayers; + newLayers.emplace_back( + std::make_unique(backgroundLayerArgs)); + RequestedLayerState* backgroundLayer = newLayers.back().get(); + backgroundLayer->bgColorLayer = true; + backgroundLayer->handleAlive = false; + backgroundLayer->parentId = layer->id; + backgroundLayer->z = std::numeric_limits::min(); + backgroundLayer->color = layer->bgColor; + backgroundLayer->dataspace = layer->bgColorDataspace; + layer->bgColorLayerId = backgroundLayer->id; + addLayers({std::move(newLayers)}); + } else if (layer->bgColorLayerId != UNASSIGNED_LAYER_ID && layer->bgColor.a == 0) { + RequestedLayerState* bgColorLayer = getLayerFromId(layer->bgColorLayerId); + layer->bgColorLayerId = UNASSIGNED_LAYER_ID; + bgColorLayer->parentId = unlinkLayer(bgColorLayer->parentId, bgColorLayer->id); + onHandlesDestroyed({bgColorLayer->id}); + } else if (layer->bgColorLayerId != UNASSIGNED_LAYER_ID) { + RequestedLayerState* bgColorLayer = getLayerFromId(layer->bgColorLayerId); + bgColorLayer->color = layer->bgColor; + bgColorLayer->dataspace = layer->bgColorDataspace; + bgColorLayer->what |= layer_state_t::eColorChanged | + layer_state_t::eDataspaceChanged | layer_state_t::eAlphaChanged; + bgColorLayer->changes |= RequestedLayerState::Changes::Content; + mGlobalChanges |= RequestedLayerState::Changes::Content; + } + } + + if (oldParentId != layer->parentId) { + unlinkLayer(oldParentId, layer->id); + layer->parentId = linkLayer(layer->parentId, layer->id); + if (oldParentId == UNASSIGNED_LAYER_ID) { + updateDisplayMirrorLayers(*layer); + } + } + if (layer->what & layer_state_t::eLayerStackChanged && layer->isRoot()) { + updateDisplayMirrorLayers(*layer); + } + if (oldRelativeParentId != layer->relativeParentId) { + unlinkLayer(oldRelativeParentId, layer->id); + layer->relativeParentId = linkLayer(layer->relativeParentId, layer->id); + } + if (oldTouchCropId != layer->touchCropId) { + unlinkLayer(oldTouchCropId, layer->id); + layer->touchCropId = linkLayer(layer->touchCropId, layer->id); + } + + mGlobalChanges |= layer->changes; + } + } +} + +void LayerLifecycleManager::commitChanges() { + for (auto layer : mAddedLayers) { + for (auto& listener : mListeners) { + listener->onLayerAdded(*layer); + } + } + mAddedLayers.clear(); + + for (auto& layer : mLayers) { + layer->clearChanges(); + } + + for (auto& destroyedLayer : mDestroyedLayers) { + for (auto& listener : mListeners) { + listener->onLayerDestroyed(*destroyedLayer); + } + } + mDestroyedLayers.clear(); + mGlobalChanges.clear(); +} + +void LayerLifecycleManager::addLifecycleListener(std::shared_ptr listener) { + mListeners.emplace_back(std::move(listener)); +} + +void LayerLifecycleManager::removeLifecycleListener(std::shared_ptr listener) { + swapErase(mListeners, listener); +} + +const std::vector>& LayerLifecycleManager::getLayers() const { + return mLayers; +} + +const std::vector>& LayerLifecycleManager::getDestroyedLayers() + const { + return mDestroyedLayers; +} + +const ftl::Flags LayerLifecycleManager::getGlobalChanges() const { + return mGlobalChanges; +} + +RequestedLayerState* LayerLifecycleManager::getLayerFromId(uint32_t id) { + if (id == UNASSIGNED_LAYER_ID) { + return nullptr; + } + auto it = mIdToLayer.find(id); + if (it == mIdToLayer.end()) { + return nullptr; + } + return &it->second.owner; +} + +std::vector* LayerLifecycleManager::getLinkedLayersFromId(uint32_t id) { + if (id == UNASSIGNED_LAYER_ID) { + return nullptr; + } + auto it = mIdToLayer.find(id); + if (it == mIdToLayer.end()) { + return nullptr; + } + return &it->second.references; +} + +uint32_t LayerLifecycleManager::linkLayer(uint32_t layerId, uint32_t layerToLink) { + if (layerId == UNASSIGNED_LAYER_ID) { + return UNASSIGNED_LAYER_ID; + } + + std::vector* linkedLayers = getLinkedLayersFromId(layerId); + if (!linkedLayers) { + ALOGV("Could not find layer id %d to link %d. Parent is probably destroyed", layerId, + layerToLink); + return UNASSIGNED_LAYER_ID; + } + linkedLayers->emplace_back(layerToLink); + return layerId; +} + +uint32_t LayerLifecycleManager::unlinkLayer(uint32_t layerId, uint32_t linkedLayer) { + std::vector* linkedLayers = getLinkedLayersFromId(layerId); + if (!linkedLayers) { + return UNASSIGNED_LAYER_ID; + } + swapErase(*linkedLayers, linkedLayer); + return UNASSIGNED_LAYER_ID; +} + +std::vector LayerLifecycleManager::unlinkLayers(const std::vector& layerIds, + uint32_t linkedLayer) { + for (uint32_t layerId : layerIds) { + unlinkLayer(layerId, linkedLayer); + } + return {}; +} + +std::string LayerLifecycleManager::References::getDebugString() const { + std::string debugInfo = owner.name + "[" + std::to_string(owner.id) + "] refs:"; + std::for_each(references.begin(), references.end(), + [&debugInfo = debugInfo](const uint32_t& reference) mutable { + debugInfo += std::to_string(reference) + ","; + }); + return debugInfo; +} + +void LayerLifecycleManager::fixRelativeZLoop(uint32_t relativeRootId) { + auto it = mIdToLayer.find(relativeRootId); + if (it == mIdToLayer.end()) { + return; + } + RequestedLayerState& layer = it->second.owner; + layer.relativeParentId = unlinkLayer(layer.relativeParentId, layer.id); + layer.changes |= + RequestedLayerState::Changes::Hierarchy | RequestedLayerState::Changes::RelativeParent; + mGlobalChanges |= RequestedLayerState::Changes::Hierarchy; +} + +// Some layers mirror the entire display stack. Since we don't have a single root layer per display +// we have to track all these layers and update what they mirror when the list of root layers +// on a display changes. This function walks through the list of display mirroring layers +// and updates its list of layers that its mirroring. This function should be called when a new +// root layer is added, removed or moved to another display. +void LayerLifecycleManager::updateDisplayMirrorLayers(RequestedLayerState& rootLayer) { + for (uint32_t mirrorLayerId : mDisplayMirroringLayers) { + RequestedLayerState* mirrorLayer = getLayerFromId(mirrorLayerId); + bool canBeMirrored = + rootLayer.isRoot() && rootLayer.layerStack == mirrorLayer->layerStackToMirror; + bool currentlyMirrored = + std::find(mirrorLayer->mirrorIds.begin(), mirrorLayer->mirrorIds.end(), + rootLayer.id) != mirrorLayer->mirrorIds.end(); + + if (canBeMirrored && !currentlyMirrored) { + mirrorLayer->mirrorIds.emplace_back(rootLayer.id); + linkLayer(rootLayer.id, mirrorLayer->id); + mirrorLayer->changes |= RequestedLayerState::Changes::Mirror; + } else if (!canBeMirrored && currentlyMirrored) { + swapErase(mirrorLayer->mirrorIds, rootLayer.id); + unlinkLayer(rootLayer.id, mirrorLayer->id); + mirrorLayer->changes |= RequestedLayerState::Changes::Mirror; + } + } +} + +} // namespace android::surfaceflinger::frontend diff --git a/services/surfaceflinger/FrontEnd/LayerLifecycleManager.h b/services/surfaceflinger/FrontEnd/LayerLifecycleManager.h new file mode 100644 index 0000000000000000000000000000000000000000..f0d2c22e5a6db711ed1ca144da16102213059153 --- /dev/null +++ b/services/surfaceflinger/FrontEnd/LayerLifecycleManager.h @@ -0,0 +1,116 @@ +/* + * 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 "RequestedLayerState.h" +#include "TransactionState.h" + +namespace android::surfaceflinger::frontend { + +// Owns a collection of RequestedLayerStates and manages their lifecycle +// and state changes. +// +// RequestedLayerStates are tracked and destroyed if they have no parent and +// no handle left to keep them alive. The handle does not keep a reference to +// the RequestedLayerState but a layer id associated with the RequestedLayerState. +// If the handle is destroyed and the RequestedLayerState does not have a parent, +// the LayerLifecycleManager destroys the RequestedLayerState. +// +// Threading: This class is not thread safe, it requires external synchronization. +// +// Typical usage: Input states (new layers, transactions, destroyed layer handles) +// are collected in the background passed into the LayerLifecycleManager to update +// layer lifecycle and layer state at start of composition. +class LayerLifecycleManager { +public: + // External state changes should be updated in the following order: + void addLayers(std::vector>); + // Ignore unknown layers when interoping with legacy front end. In legacy we destroy + // the layers it is unreachable. When using the LayerLifecycleManager for layer trace + // generation we may encounter layers which are known because we don't have an explicit + // lifecycle. Ignore these errors while we have to interop with legacy. + void applyTransactions(const std::vector&, bool ignoreUnknownLayers = false); + // Ignore unknown handles when iteroping with legacy front end. In the old world, we + // would create child layers which are not necessary with the new front end. This means + // we will get notified for handle changes that don't exist in the new front end. + void onHandlesDestroyed(const std::vector&, bool ignoreUnknownHandles = false); + + // Detaches the layer from its relative parent to prevent a loop in the + // layer hierarchy. This overrides the RequestedLayerState and leaves + // the system in an invalid state. This is always a client error that + // needs to be fixed but overriding the state allows us to fail gracefully. + void fixRelativeZLoop(uint32_t relativeRootId); + + // Destroys RequestedLayerStates that are marked to be destroyed. Invokes all + // ILifecycleListener callbacks and clears any change flags from previous state + // updates. This function should be called outside the hot path since it's not + // critical to composition. + void commitChanges(); + + class ILifecycleListener { + public: + virtual ~ILifecycleListener() = default; + // Called on commitChanges when a layer is added. The callback includes + // the layer state the client was created with as well as any state updates + // until changes were committed. + virtual void onLayerAdded(const RequestedLayerState&) = 0; + // Called on commitChanges when a layer has been destroyed. The callback + // includes the final state before the layer was destroyed. + virtual void onLayerDestroyed(const RequestedLayerState&) = 0; + }; + void addLifecycleListener(std::shared_ptr); + void removeLifecycleListener(std::shared_ptr); + const std::vector>& getLayers() const; + const std::vector>& getDestroyedLayers() const; + const ftl::Flags getGlobalChanges() const; + +private: + friend class LayerLifecycleManagerTest; + friend class HierarchyBuilderTest; + friend class android::SurfaceFlinger; + + RequestedLayerState* getLayerFromId(uint32_t); + std::vector* getLinkedLayersFromId(uint32_t); + uint32_t linkLayer(uint32_t layerId, uint32_t layerToLink); + uint32_t unlinkLayer(uint32_t layerId, uint32_t linkedLayer); + std::vector unlinkLayers(const std::vector& layerIds, uint32_t linkedLayer); + + void updateDisplayMirrorLayers(RequestedLayerState& rootLayer); + + struct References { + // Lifetime tied to mLayers + RequestedLayerState& owner; + std::vector references; + std::string getDebugString() const; + }; + std::unordered_map mIdToLayer; + // Listeners are invoked once changes are committed. + std::vector> mListeners; + // Layers that mirror a display stack (see updateDisplayMirrorLayers) + std::vector mDisplayMirroringLayers; + + // Aggregation of changes since last commit. + ftl::Flags mGlobalChanges; + std::vector> mLayers; + // Layers pending destruction. Layers will be destroyed once changes are committed. + std::vector> mDestroyedLayers; + // Keeps track of all the layers that were added in order. Changes will be cleared once + // committed. + std::vector mAddedLayers; +}; + +} // namespace android::surfaceflinger::frontend diff --git a/services/surfaceflinger/FrontEnd/LayerLog.h b/services/surfaceflinger/FrontEnd/LayerLog.h new file mode 100644 index 0000000000000000000000000000000000000000..4943483e6270f21cae7cb2cf03f5e63c8c2b8bcd --- /dev/null +++ b/services/surfaceflinger/FrontEnd/LayerLog.h @@ -0,0 +1,29 @@ +/* + * 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 + +// Uncomment to trace layer updates for a single layer +// #define LOG_LAYER 1 + +#ifdef LOG_LAYER +#define LLOGV(LAYER_ID, x, ...) \ + ALOGV_IF(((LAYER_ID) == LOG_LAYER), "[%d] %s " x, LOG_LAYER, __func__, ##__VA_ARGS__); +#else +#define LLOGV(LAYER_ID, x, ...) ALOGV("[%d] %s " x, (LAYER_ID), __func__, ##__VA_ARGS__); +#endif + +#define LLOGD(LAYER_ID, x, ...) ALOGD("[%d] %s " x, (LAYER_ID), __func__, ##__VA_ARGS__); diff --git a/services/surfaceflinger/FrontEnd/LayerSnapshot.cpp b/services/surfaceflinger/FrontEnd/LayerSnapshot.cpp new file mode 100644 index 0000000000000000000000000000000000000000..a9925843df0f03ba4baae6ad32c4498aa3d762a6 --- /dev/null +++ b/services/surfaceflinger/FrontEnd/LayerSnapshot.cpp @@ -0,0 +1,206 @@ +/* + * 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. + */ + +#define ATRACE_TAG ATRACE_TAG_GRAPHICS +#undef LOG_TAG +#define LOG_TAG "LayerSnapshot" + +#include "LayerSnapshot.h" + +namespace android::surfaceflinger::frontend { + +using namespace ftl::flag_operators; + +LayerSnapshot::LayerSnapshot(const RequestedLayerState& state, + const LayerHierarchy::TraversalPath& path) + : path(path) { + // Provide a unique id for all snapshots. + // A front end layer can generate multiple snapshots if its mirrored. + // Additionally, if the layer is not reachable, we may choose to destroy + // and recreate the snapshot in which case the unique sequence id will + // change. The consumer shouldn't tie any lifetimes to this unique id but + // register a LayerLifecycleManager::ILifecycleListener or get a list of + // destroyed layers from LayerLifecycleManager. + if (path.isClone()) { + uniqueSequence = + LayerCreationArgs::getInternalLayerId(LayerCreationArgs::sInternalSequence++); + } else { + uniqueSequence = state.id; + } + sequence = static_cast(state.id); + name = state.name; + textureName = state.textureName; + premultipliedAlpha = state.premultipliedAlpha; + inputInfo.name = state.name; + inputInfo.id = static_cast(uniqueSequence); + inputInfo.ownerUid = static_cast(state.ownerUid); + inputInfo.ownerPid = state.ownerPid; + uid = state.ownerUid; + pid = state.ownerPid; + changes = RequestedLayerState::Changes::Created; + mirrorRootPath = path.variant == LayerHierarchy::Variant::Mirror + ? path + : LayerHierarchy::TraversalPath::ROOT; +} + +// As documented in libhardware header, formats in the range +// 0x100 - 0x1FF are specific to the HAL implementation, and +// are known to have no alpha channel +// TODO: move definition for device-specific range into +// hardware.h, instead of using hard-coded values here. +#define HARDWARE_IS_DEVICE_FORMAT(f) ((f) >= 0x100 && (f) <= 0x1FF) + +bool LayerSnapshot::isOpaqueFormat(PixelFormat format) { + if (HARDWARE_IS_DEVICE_FORMAT(format)) { + return true; + } + switch (format) { + case PIXEL_FORMAT_RGBA_8888: + case PIXEL_FORMAT_BGRA_8888: + case PIXEL_FORMAT_RGBA_FP16: + case PIXEL_FORMAT_RGBA_1010102: + case PIXEL_FORMAT_R_8: + return false; + } + // in all other case, we have no blending (also for unknown formats) + return true; +} + +bool LayerSnapshot::hasBufferOrSidebandStream() const { + return ((sidebandStream != nullptr) || (externalTexture != nullptr)); +} + +bool LayerSnapshot::drawShadows() const { + return shadowSettings.length > 0.f; +} + +bool LayerSnapshot::fillsColor() const { + return !hasBufferOrSidebandStream() && color.r >= 0.0_hf && color.g >= 0.0_hf && + color.b >= 0.0_hf; +} + +bool LayerSnapshot::hasBlur() const { + return backgroundBlurRadius > 0 || blurRegions.size() > 0; +} + +bool LayerSnapshot::hasEffect() const { + return fillsColor() || drawShadows() || hasBlur(); +} + +bool LayerSnapshot::hasSomethingToDraw() const { + return hasEffect() || hasBufferOrSidebandStream(); +} + +bool LayerSnapshot::isContentOpaque() const { + // if we don't have a buffer or sidebandStream yet, we're translucent regardless of the + // layer's opaque flag. + if (!hasSomethingToDraw()) { + return false; + } + + // if the layer has the opaque flag, then we're always opaque + if (layerOpaqueFlagSet) { + return true; + } + + // If the buffer has no alpha channel, then we are opaque + if (hasBufferOrSidebandStream() && + isOpaqueFormat(externalTexture ? externalTexture->getPixelFormat() : PIXEL_FORMAT_NONE)) { + return true; + } + + // Lastly consider the layer opaque if drawing a color with alpha == 1.0 + return fillsColor() && color.a == 1.0_hf; +} + +bool LayerSnapshot::isHiddenByPolicy() const { + return invalidTransform || isHiddenByPolicyFromParent || isHiddenByPolicyFromRelativeParent; +} + +bool LayerSnapshot::getIsVisible() const { + if (handleSkipScreenshotFlag & outputFilter.toInternalDisplay) { + return false; + } + + if (!hasSomethingToDraw()) { + return false; + } + + if (isHiddenByPolicy()) { + return false; + } + + return color.a > 0.0f || hasBlur(); +} + +std::string LayerSnapshot::getIsVisibleReason() const { + // not visible + if (handleSkipScreenshotFlag & outputFilter.toInternalDisplay) return "eLayerSkipScreenshot"; + if (!hasSomethingToDraw()) return "!hasSomethingToDraw"; + if (invalidTransform) return "invalidTransform"; + if (isHiddenByPolicyFromParent) return "hidden by parent or layer flag"; + if (isHiddenByPolicyFromRelativeParent) return "hidden by relative parent"; + if (color.a == 0.0f && !hasBlur()) return "alpha = 0 and no blur"; + + // visible + std::stringstream reason; + if (sidebandStream != nullptr) reason << " sidebandStream"; + if (externalTexture != nullptr) + reason << " buffer:" << externalTexture->getId() << " frame:" << frameNumber; + if (fillsColor() || color.a > 0.0f) reason << " color{" << color << "}"; + if (drawShadows()) reason << " shadowSettings.length=" << shadowSettings.length; + if (backgroundBlurRadius > 0) reason << " backgroundBlurRadius=" << backgroundBlurRadius; + if (blurRegions.size() > 0) reason << " blurRegions.size()=" << blurRegions.size(); + return reason.str(); +} + +bool LayerSnapshot::canReceiveInput() const { + return !isHiddenByPolicy() && (!hasBufferOrSidebandStream() || color.a > 0.0f); +} + +bool LayerSnapshot::isTransformValid(const ui::Transform& t) { + float transformDet = t.det(); + return transformDet != 0 && !isinf(transformDet) && !isnan(transformDet); +} + +bool LayerSnapshot::hasInputInfo() const { + return inputInfo.token != nullptr || + inputInfo.inputConfig.test(gui::WindowInfo::InputConfig::NO_INPUT_CHANNEL); +} + +std::string LayerSnapshot::getDebugString() const { + std::stringstream debug; + debug << "Snapshot{" << path.toString() << name << " isVisible=" << isVisible << " {" + << getIsVisibleReason() << "} changes=" << changes.string() + << " layerStack=" << outputFilter.layerStack.id << " geomLayerBounds={" + << geomLayerBounds.left << "," << geomLayerBounds.top << "," << geomLayerBounds.bottom + << "," << geomLayerBounds.right << "}" + << " geomLayerTransform={tx=" << geomLayerTransform.tx() + << ",ty=" << geomLayerTransform.ty() << "}" + << "}"; + debug << " input{ touchCropId=" << touchCropId + << " replaceTouchableRegionWithCrop=" << inputInfo.replaceTouchableRegionWithCrop << "}"; + return debug.str(); +} + +FloatRect LayerSnapshot::sourceBounds() const { + if (!externalTexture) { + return geomLayerBounds; + } + return geomBufferSize.toFloatRect(); +} + +} // namespace android::surfaceflinger::frontend diff --git a/services/surfaceflinger/FrontEnd/LayerSnapshot.h b/services/surfaceflinger/FrontEnd/LayerSnapshot.h new file mode 100644 index 0000000000000000000000000000000000000000..b167d3ea1b97d71acac023b60c41fcb66eee0b79 --- /dev/null +++ b/services/surfaceflinger/FrontEnd/LayerSnapshot.h @@ -0,0 +1,121 @@ +/* + * 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 +#include "LayerHierarchy.h" +#include "RequestedLayerState.h" +#include "Scheduler/LayerInfo.h" +#include "android-base/stringprintf.h" + +namespace android::surfaceflinger::frontend { + +struct RoundedCornerState { + RoundedCornerState() = default; + RoundedCornerState(const FloatRect& cropRect, const vec2& radius) + : cropRect(cropRect), radius(radius) {} + + // Rounded rectangle in local layer coordinate space. + FloatRect cropRect = FloatRect(); + // Radius of the rounded rectangle. + vec2 radius; + bool hasRoundedCorners() const { return radius.x > 0.0f && radius.y > 0.0f; } + bool operator==(RoundedCornerState const& rhs) const { + return cropRect == rhs.cropRect && radius == rhs.radius; + } +}; + +struct ChildState { + bool hasValidFrameRate = false; +}; + +// LayerSnapshot stores Layer state used by CompositionEngine and RenderEngine. Composition +// Engine uses a pointer to LayerSnapshot (as LayerFECompositionState*) and the LayerSettings +// passed to Render Engine are created using properties stored on this struct. +struct LayerSnapshot : public compositionengine::LayerFECompositionState { + LayerSnapshot() = default; + LayerSnapshot(const RequestedLayerState&, const LayerHierarchy::TraversalPath&); + + LayerHierarchy::TraversalPath path; + size_t globalZ = std::numeric_limits::max(); + bool invalidTransform = false; + bool isHiddenByPolicyFromParent = false; + bool isHiddenByPolicyFromRelativeParent = false; + ftl::Flags changes; + // Some consumers of this snapshot (input, layer traces) rely on each snapshot to be unique. + // For mirrored layers, snapshots will have the same sequence so this unique id provides + // an alternative identifier when needed. + uint32_t uniqueSequence; + // Layer id used to create this snapshot. Multiple snapshots will have the same sequence if they + // generated from the same layer, for example when mirroring. + int32_t sequence; + std::string name; + uint32_t textureName; + bool contentOpaque; + bool layerOpaqueFlagSet; + RoundedCornerState roundedCorner; + FloatRect transformedBounds; + Rect transformedBoundsWithoutTransparentRegion; + renderengine::ShadowSettings shadowSettings; + bool premultipliedAlpha; + bool isHdrY410; + ui::Transform parentTransform; + Rect bufferSize; + Rect croppedBufferSize; + std::shared_ptr externalTexture; + gui::LayerMetadata layerMetadata; + gui::LayerMetadata relativeLayerMetadata; + bool hasReadyFrame; + ui::Transform localTransformInverse; + gui::WindowInfo inputInfo; + ui::Transform localTransform; + gui::DropInputMode dropInputMode; + bool isTrustedOverlay; + gui::GameMode gameMode; + scheduler::LayerInfo::FrameRate frameRate; + ui::Transform::RotationFlags fixedTransformHint; + std::optional transformHint; + bool handleSkipScreenshotFlag = false; + int32_t frameRateSelectionPriority; + LayerHierarchy::TraversalPath mirrorRootPath; + bool unreachable = true; + uint32_t touchCropId; + uid_t uid; + pid_t pid; + ChildState childState; + + static bool isOpaqueFormat(PixelFormat format); + static bool isTransformValid(const ui::Transform& t); + + bool canReceiveInput() const; + bool drawShadows() const; + bool fillsColor() const; + bool getIsVisible() const; + bool hasBlur() const; + bool hasBufferOrSidebandStream() const; + bool hasEffect() const; + bool hasSomethingToDraw() const; + bool isContentOpaque() const; + bool isHiddenByPolicy() const; + std::string getDebugString() const; + std::string getIsVisibleReason() const; + bool hasInputInfo() const; + FloatRect sourceBounds() const; +}; + +} // namespace android::surfaceflinger::frontend diff --git a/services/surfaceflinger/FrontEnd/LayerSnapshotBuilder.cpp b/services/surfaceflinger/FrontEnd/LayerSnapshotBuilder.cpp new file mode 100644 index 0000000000000000000000000000000000000000..985c6f9fc9d8235f4a7f90a50780f74fde83d330 --- /dev/null +++ b/services/surfaceflinger/FrontEnd/LayerSnapshotBuilder.cpp @@ -0,0 +1,1197 @@ +/* + * 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. + */ + +// #define LOG_NDEBUG 0 +#define ATRACE_TAG ATRACE_TAG_GRAPHICS +#undef LOG_TAG +#define LOG_TAG "LayerSnapshotBuilder" + +#include "LayerSnapshotBuilder.h" +#include +#include + +#include +#include + +#include +#include "DisplayHardware/HWC2.h" +#include "DisplayHardware/Hal.h" +#include "LayerLog.h" +#include "LayerSnapshotBuilder.h" +#include "TimeStats/TimeStats.h" +#include "ftl/small_map.h" + +namespace android::surfaceflinger::frontend { + +using namespace ftl::flag_operators; + +namespace { +FloatRect getMaxDisplayBounds( + const display::DisplayMap& displays) { + const ui::Size maxSize = [&displays] { + if (displays.empty()) return ui::Size{5000, 5000}; + + return std::accumulate(displays.begin(), displays.end(), ui::kEmptySize, + [](ui::Size size, const auto& pair) -> ui::Size { + const auto& display = pair.second; + return {std::max(size.getWidth(), display.info.logicalWidth), + std::max(size.getHeight(), display.info.logicalHeight)}; + }); + }(); + + // Ignore display bounds for now since they will be computed later. Use a large Rect bound + // to ensure it's bigger than an actual display will be. + const float xMax = static_cast(maxSize.getWidth()) * 10.f; + const float yMax = static_cast(maxSize.getHeight()) * 10.f; + + return {-xMax, -yMax, xMax, yMax}; +} + +// Applies the given transform to the region, while protecting against overflows caused by any +// offsets. If applying the offset in the transform to any of the Rects in the region would result +// in an overflow, they are not added to the output Region. +Region transformTouchableRegionSafely(const ui::Transform& t, const Region& r, + const std::string& debugWindowName) { + // Round the translation using the same rounding strategy used by ui::Transform. + const auto tx = static_cast(t.tx() + 0.5); + const auto ty = static_cast(t.ty() + 0.5); + + ui::Transform transformWithoutOffset = t; + transformWithoutOffset.set(0.f, 0.f); + + const Region transformed = transformWithoutOffset.transform(r); + + // Apply the translation to each of the Rects in the region while discarding any that overflow. + Region ret; + for (const auto& rect : transformed) { + Rect newRect; + if (__builtin_add_overflow(rect.left, tx, &newRect.left) || + __builtin_add_overflow(rect.top, ty, &newRect.top) || + __builtin_add_overflow(rect.right, tx, &newRect.right) || + __builtin_add_overflow(rect.bottom, ty, &newRect.bottom)) { + ALOGE("Applying transform to touchable region of window '%s' resulted in an overflow.", + debugWindowName.c_str()); + continue; + } + ret.orSelf(newRect); + } + return ret; +} + +/* + * We don't want to send the layer's transform to input, but rather the + * parent's transform. This is because Layer's transform is + * information about how the buffer is placed on screen. The parent's + * transform makes more sense to send since it's information about how the + * layer is placed on screen. This transform is used by input to determine + * how to go from screen space back to window space. + */ +ui::Transform getInputTransform(const LayerSnapshot& snapshot) { + if (!snapshot.hasBufferOrSidebandStream()) { + return snapshot.geomLayerTransform; + } + return snapshot.parentTransform; +} + +/** + * Returns the bounds used to fill the input frame and the touchable region. + * + * Similar to getInputTransform, we need to update the bounds to include the transform. + * This is because bounds don't include the buffer transform, where the input assumes + * that's already included. + */ +std::pair getInputBounds(const LayerSnapshot& snapshot, bool fillParentBounds) { + FloatRect inputBounds = snapshot.croppedBufferSize.toFloatRect(); + if (snapshot.hasBufferOrSidebandStream() && snapshot.croppedBufferSize.isValid() && + snapshot.localTransform.getType() != ui::Transform::IDENTITY) { + inputBounds = snapshot.localTransform.transform(inputBounds); + } + + bool inputBoundsValid = snapshot.croppedBufferSize.isValid(); + if (!inputBoundsValid) { + /** + * Input bounds are based on the layer crop or buffer size. But if we are using + * the layer bounds as the input bounds (replaceTouchableRegionWithCrop flag) then + * we can use the parent bounds as the input bounds if the layer does not have buffer + * or a crop. We want to unify this logic but because of compat reasons we cannot always + * use the parent bounds. A layer without a buffer can get input. So when a window is + * initially added, its touchable region can fill its parent layer bounds and that can + * have negative consequences. + */ + inputBounds = fillParentBounds ? snapshot.geomLayerBounds : FloatRect{}; + } + + // Clamp surface inset to the input bounds. + const float inset = static_cast(snapshot.inputInfo.surfaceInset); + const float xSurfaceInset = std::clamp(inset, 0.f, inputBounds.getWidth() / 2.f); + const float ySurfaceInset = std::clamp(inset, 0.f, inputBounds.getHeight() / 2.f); + + // Apply the insets to the input bounds. + inputBounds.left += xSurfaceInset; + inputBounds.top += ySurfaceInset; + inputBounds.right -= xSurfaceInset; + inputBounds.bottom -= ySurfaceInset; + return {inputBounds, inputBoundsValid}; +} + +Rect getInputBoundsInDisplaySpace(const LayerSnapshot& snapshot, const FloatRect& insetBounds, + const ui::Transform& screenToDisplay) { + // InputDispatcher works in the display device's coordinate space. Here, we calculate the + // frame and transform used for the layer, which determines the bounds and the coordinate space + // within which the layer will receive input. + + // Coordinate space definitions: + // - display: The display device's coordinate space. Correlates to pixels on the display. + // - screen: The post-rotation coordinate space for the display, a.k.a. logical display space. + // - layer: The coordinate space of this layer. + // - input: The coordinate space in which this layer will receive input events. This could be + // different than layer space if a surfaceInset is used, which changes the origin + // of the input space. + + // Crop the input bounds to ensure it is within the parent's bounds. + const FloatRect croppedInsetBoundsInLayer = snapshot.geomLayerBounds.intersect(insetBounds); + + const ui::Transform layerToScreen = getInputTransform(snapshot); + const ui::Transform layerToDisplay = screenToDisplay * layerToScreen; + + return Rect{layerToDisplay.transform(croppedInsetBoundsInLayer)}; +} + +void fillInputFrameInfo(gui::WindowInfo& info, const ui::Transform& screenToDisplay, + const LayerSnapshot& snapshot) { + auto [inputBounds, inputBoundsValid] = getInputBounds(snapshot, /*fillParentBounds=*/false); + if (!inputBoundsValid) { + info.touchableRegion.clear(); + } + + const Rect roundedFrameInDisplay = + getInputBoundsInDisplaySpace(snapshot, inputBounds, screenToDisplay); + info.frameLeft = roundedFrameInDisplay.left; + info.frameTop = roundedFrameInDisplay.top; + info.frameRight = roundedFrameInDisplay.right; + info.frameBottom = roundedFrameInDisplay.bottom; + + ui::Transform inputToLayer; + inputToLayer.set(inputBounds.left, inputBounds.top); + const ui::Transform layerToScreen = getInputTransform(snapshot); + const ui::Transform inputToDisplay = screenToDisplay * layerToScreen * inputToLayer; + + // InputDispatcher expects a display-to-input transform. + info.transform = inputToDisplay.inverse(); + + // The touchable region is specified in the input coordinate space. Change it to display space. + info.touchableRegion = + transformTouchableRegionSafely(inputToDisplay, info.touchableRegion, snapshot.name); +} + +void handleDropInputMode(LayerSnapshot& snapshot, const LayerSnapshot& parentSnapshot) { + if (snapshot.inputInfo.inputConfig.test(gui::WindowInfo::InputConfig::NO_INPUT_CHANNEL)) { + return; + } + + // Check if we need to drop input unconditionally + const gui::DropInputMode dropInputMode = snapshot.dropInputMode; + if (dropInputMode == gui::DropInputMode::ALL) { + snapshot.inputInfo.inputConfig |= gui::WindowInfo::InputConfig::DROP_INPUT; + ALOGV("Dropping input for %s as requested by policy.", snapshot.name.c_str()); + return; + } + + // Check if we need to check if the window is obscured by parent + if (dropInputMode != gui::DropInputMode::OBSCURED) { + return; + } + + // Check if the parent has set an alpha on the layer + if (parentSnapshot.color.a != 1.0_hf) { + snapshot.inputInfo.inputConfig |= gui::WindowInfo::InputConfig::DROP_INPUT; + ALOGV("Dropping input for %s as requested by policy because alpha=%f", + snapshot.name.c_str(), static_cast(parentSnapshot.color.a)); + } + + // Check if the parent has cropped the buffer + Rect bufferSize = snapshot.croppedBufferSize; + if (!bufferSize.isValid()) { + snapshot.inputInfo.inputConfig |= gui::WindowInfo::InputConfig::DROP_INPUT_IF_OBSCURED; + return; + } + + // Screenbounds are the layer bounds cropped by parents, transformed to screenspace. + // To check if the layer has been cropped, we take the buffer bounds, apply the local + // layer crop and apply the same set of transforms to move to screenspace. If the bounds + // match then the layer has not been cropped by its parents. + Rect bufferInScreenSpace(snapshot.geomLayerTransform.transform(bufferSize)); + bool croppedByParent = bufferInScreenSpace != Rect{snapshot.transformedBounds}; + + if (croppedByParent) { + snapshot.inputInfo.inputConfig |= gui::WindowInfo::InputConfig::DROP_INPUT; + ALOGV("Dropping input for %s as requested by policy because buffer is cropped by parent", + snapshot.name.c_str()); + } else { + // If the layer is not obscured by its parents (by setting an alpha or crop), then only drop + // input if the window is obscured. This check should be done in surfaceflinger but the + // logic currently resides in inputflinger. So pass the if_obscured check to input to only + // drop input events if the window is obscured. + snapshot.inputInfo.inputConfig |= gui::WindowInfo::InputConfig::DROP_INPUT_IF_OBSCURED; + } +} + +auto getBlendMode(const LayerSnapshot& snapshot, const RequestedLayerState& requested) { + auto blendMode = Hwc2::IComposerClient::BlendMode::NONE; + if (snapshot.alpha != 1.0f || !snapshot.isContentOpaque()) { + blendMode = requested.premultipliedAlpha ? Hwc2::IComposerClient::BlendMode::PREMULTIPLIED + : Hwc2::IComposerClient::BlendMode::COVERAGE; + } + return blendMode; +} + +void updateSurfaceDamage(const RequestedLayerState& requested, bool hasReadyFrame, + bool forceFullDamage, Region& outSurfaceDamageRegion) { + if (!hasReadyFrame) { + outSurfaceDamageRegion.clear(); + return; + } + if (forceFullDamage) { + outSurfaceDamageRegion = Region::INVALID_REGION; + } else { + outSurfaceDamageRegion = requested.surfaceDamageRegion; + } +} + +void updateVisibility(LayerSnapshot& snapshot, bool visible) { + snapshot.isVisible = visible; + + // TODO(b/238781169) we are ignoring this compat for now, since we will have + // to remove any optimization based on visibility. + + // For compatibility reasons we let layers which can receive input + // receive input before they have actually submitted a buffer. Because + // of this we use canReceiveInput instead of isVisible to check the + // policy-visibility, ignoring the buffer state. However for layers with + // hasInputInfo()==false we can use the real visibility state. + // We are just using these layers for occlusion detection in + // InputDispatcher, and obviously if they aren't visible they can't occlude + // anything. + const bool visibleForInput = + snapshot.hasInputInfo() ? snapshot.canReceiveInput() : snapshot.isVisible; + snapshot.inputInfo.setInputConfig(gui::WindowInfo::InputConfig::NOT_VISIBLE, !visibleForInput); +} + +bool needsInputInfo(const LayerSnapshot& snapshot, const RequestedLayerState& requested) { + if (requested.potentialCursor) { + return false; + } + + if (snapshot.inputInfo.token != nullptr) { + return true; + } + + if (snapshot.hasBufferOrSidebandStream()) { + return true; + } + + return requested.windowInfoHandle && + requested.windowInfoHandle->getInfo()->inputConfig.test( + gui::WindowInfo::InputConfig::NO_INPUT_CHANNEL); +} + +void updateMetadata(LayerSnapshot& snapshot, const RequestedLayerState& requested, + const LayerSnapshotBuilder::Args& args) { + snapshot.metadata.clear(); + for (const auto& [key, mandatory] : args.supportedLayerGenericMetadata) { + auto compatIter = args.genericLayerMetadataKeyMap.find(key); + if (compatIter == std::end(args.genericLayerMetadataKeyMap)) { + continue; + } + const uint32_t id = compatIter->second; + auto it = requested.metadata.mMap.find(id); + if (it == std::end(requested.metadata.mMap)) { + continue; + } + + snapshot.metadata.emplace(key, + compositionengine::GenericLayerMetadataEntry{mandatory, + it->second}); + } +} + +void clearChanges(LayerSnapshot& snapshot) { + snapshot.changes.clear(); + snapshot.contentDirty = false; + snapshot.hasReadyFrame = false; + snapshot.sidebandStreamHasFrame = false; + snapshot.surfaceDamage.clear(); +} + +} // namespace + +LayerSnapshot LayerSnapshotBuilder::getRootSnapshot() { + LayerSnapshot snapshot; + snapshot.path = LayerHierarchy::TraversalPath::ROOT; + snapshot.changes = ftl::Flags(); + snapshot.isHiddenByPolicyFromParent = false; + snapshot.isHiddenByPolicyFromRelativeParent = false; + snapshot.parentTransform.reset(); + snapshot.geomLayerTransform.reset(); + snapshot.geomInverseLayerTransform.reset(); + snapshot.geomLayerBounds = getMaxDisplayBounds({}); + snapshot.roundedCorner = RoundedCornerState(); + snapshot.stretchEffect = {}; + snapshot.outputFilter.layerStack = ui::DEFAULT_LAYER_STACK; + snapshot.outputFilter.toInternalDisplay = false; + snapshot.isSecure = false; + snapshot.color.a = 1.0_hf; + snapshot.colorTransformIsIdentity = true; + snapshot.shadowRadius = 0.f; + snapshot.layerMetadata.mMap.clear(); + snapshot.relativeLayerMetadata.mMap.clear(); + snapshot.inputInfo.touchOcclusionMode = gui::TouchOcclusionMode::BLOCK_UNTRUSTED; + snapshot.dropInputMode = gui::DropInputMode::NONE; + snapshot.isTrustedOverlay = false; + snapshot.gameMode = gui::GameMode::Unsupported; + snapshot.frameRate = {}; + snapshot.fixedTransformHint = ui::Transform::ROT_INVALID; + return snapshot; +} + +LayerSnapshotBuilder::LayerSnapshotBuilder() : mRootSnapshot(getRootSnapshot()) {} + +LayerSnapshotBuilder::LayerSnapshotBuilder(Args args) : LayerSnapshotBuilder() { + args.forceUpdate = ForceUpdateFlags::ALL; + updateSnapshots(args); +} + +bool LayerSnapshotBuilder::tryFastUpdate(const Args& args) { + if (args.forceUpdate != ForceUpdateFlags::NONE || args.displayChanges) { + // force update requested, or we have display changes, so skip the fast path + return false; + } + + if (args.layerLifecycleManager.getGlobalChanges().get() == 0) { + return true; + } + + if (args.layerLifecycleManager.getGlobalChanges() != RequestedLayerState::Changes::Content) { + // We have changes that require us to walk the hierarchy and update child layers. + // No fast path for you. + return false; + } + + // There are only content changes which do not require any child layer snapshots to be updated. + ALOGV("%s", __func__); + ATRACE_NAME("FastPath"); + + // Collect layers with changes + ftl::SmallMap layersWithChanges; + for (auto& layer : args.layerLifecycleManager.getLayers()) { + if (layer->changes.test(RequestedLayerState::Changes::Content)) { + layersWithChanges.emplace_or_replace(layer->id, layer.get()); + } + } + + // Walk through the snapshots, clearing previous change flags and updating the snapshots + // if needed. + for (auto& snapshot : mSnapshots) { + auto it = layersWithChanges.find(snapshot->path.id); + if (it != layersWithChanges.end()) { + ALOGV("%s fast path snapshot changes = %s", __func__, + mRootSnapshot.changes.string().c_str()); + LayerHierarchy::TraversalPath root = LayerHierarchy::TraversalPath::ROOT; + updateSnapshot(*snapshot, args, *it->second, mRootSnapshot, root); + } + } + return true; +} + +void LayerSnapshotBuilder::updateSnapshots(const Args& args) { + ATRACE_NAME("UpdateSnapshots"); + if (args.parentCrop) { + mRootSnapshot.geomLayerBounds = *args.parentCrop; + } else if (args.forceUpdate == ForceUpdateFlags::ALL || args.displayChanges) { + mRootSnapshot.geomLayerBounds = getMaxDisplayBounds(args.displays); + } + if (args.displayChanges) { + mRootSnapshot.changes = RequestedLayerState::Changes::AffectsChildren | + RequestedLayerState::Changes::Geometry; + } + if (args.forceUpdate == ForceUpdateFlags::HIERARCHY) { + mRootSnapshot.changes |= + RequestedLayerState::Changes::Hierarchy | RequestedLayerState::Changes::Visibility; + } + LayerHierarchy::TraversalPath root = LayerHierarchy::TraversalPath::ROOT; + if (args.root.getLayer()) { + // The hierarchy can have a root layer when used for screenshots otherwise, it will have + // multiple children. + LayerHierarchy::ScopedAddToTraversalPath addChildToPath(root, args.root.getLayer()->id, + LayerHierarchy::Variant::Attached); + updateSnapshotsInHierarchy(args, args.root, root, mRootSnapshot); + } else { + for (auto& [childHierarchy, variant] : args.root.mChildren) { + LayerHierarchy::ScopedAddToTraversalPath addChildToPath(root, + childHierarchy->getLayer()->id, + variant); + updateSnapshotsInHierarchy(args, *childHierarchy, root, mRootSnapshot); + } + } + + // Update touchable region crops outside the main update pass. This is because a layer could be + // cropped by any other layer and it requires both snapshots to be updated. + updateTouchableRegionCrop(args); + + const bool hasUnreachableSnapshots = sortSnapshotsByZ(args); + clearChanges(mRootSnapshot); + + // Destroy unreachable snapshots for clone layers. And destroy snapshots for non-clone + // layers if the layer have been destroyed. + // TODO(b/238781169) consider making clone layer ids stable as well + if (!hasUnreachableSnapshots && args.layerLifecycleManager.getDestroyedLayers().empty()) { + return; + } + + std::unordered_set destroyedLayerIds; + for (auto& destroyedLayer : args.layerLifecycleManager.getDestroyedLayers()) { + destroyedLayerIds.insert(destroyedLayer->id); + } + + auto it = mSnapshots.begin(); + while (it < mSnapshots.end()) { + auto& traversalPath = it->get()->path; + if (!it->get()->unreachable && + destroyedLayerIds.find(traversalPath.id) == destroyedLayerIds.end()) { + it++; + continue; + } + + mIdToSnapshot.erase(traversalPath); + mNeedsTouchableRegionCrop.erase(traversalPath); + mSnapshots.back()->globalZ = it->get()->globalZ; + std::iter_swap(it, mSnapshots.end() - 1); + mSnapshots.erase(mSnapshots.end() - 1); + } +} + +void LayerSnapshotBuilder::update(const Args& args) { + for (auto& snapshot : mSnapshots) { + clearChanges(*snapshot); + } + + if (tryFastUpdate(args)) { + return; + } + updateSnapshots(args); +} + +const LayerSnapshot& LayerSnapshotBuilder::updateSnapshotsInHierarchy( + const Args& args, const LayerHierarchy& hierarchy, + LayerHierarchy::TraversalPath& traversalPath, const LayerSnapshot& parentSnapshot) { + const RequestedLayerState* layer = hierarchy.getLayer(); + LayerSnapshot* snapshot = getSnapshot(traversalPath); + const bool newSnapshot = snapshot == nullptr; + if (newSnapshot) { + snapshot = createSnapshot(traversalPath, *layer, parentSnapshot); + } + scheduler::LayerInfo::FrameRate oldFrameRate = snapshot->frameRate; + if (traversalPath.isRelative()) { + bool parentIsRelative = traversalPath.variant == LayerHierarchy::Variant::Relative; + updateRelativeState(*snapshot, parentSnapshot, parentIsRelative, args); + } else { + if (traversalPath.isAttached()) { + resetRelativeState(*snapshot); + } + updateSnapshot(*snapshot, args, *layer, parentSnapshot, traversalPath); + } + + for (auto& [childHierarchy, variant] : hierarchy.mChildren) { + LayerHierarchy::ScopedAddToTraversalPath addChildToPath(traversalPath, + childHierarchy->getLayer()->id, + variant); + const LayerSnapshot& childSnapshot = + updateSnapshotsInHierarchy(args, *childHierarchy, traversalPath, *snapshot); + updateChildState(*snapshot, childSnapshot, args); + } + + if (oldFrameRate == snapshot->frameRate) { + snapshot->changes.clear(RequestedLayerState::Changes::FrameRate); + } + return *snapshot; +} + +LayerSnapshot* LayerSnapshotBuilder::getSnapshot(uint32_t layerId) const { + if (layerId == UNASSIGNED_LAYER_ID) { + return nullptr; + } + LayerHierarchy::TraversalPath path{.id = layerId}; + return getSnapshot(path); +} + +LayerSnapshot* LayerSnapshotBuilder::getSnapshot(const LayerHierarchy::TraversalPath& id) const { + auto it = mIdToSnapshot.find(id); + return it == mIdToSnapshot.end() ? nullptr : it->second; +} + +LayerSnapshot* LayerSnapshotBuilder::createSnapshot(const LayerHierarchy::TraversalPath& path, + const RequestedLayerState& layer, + const LayerSnapshot& parentSnapshot) { + mSnapshots.emplace_back(std::make_unique(layer, path)); + LayerSnapshot* snapshot = mSnapshots.back().get(); + snapshot->globalZ = static_cast(mSnapshots.size()) - 1; + if (path.isClone() && path.variant != LayerHierarchy::Variant::Mirror) { + snapshot->mirrorRootPath = parentSnapshot.mirrorRootPath; + } + mIdToSnapshot[path] = snapshot; + return snapshot; +} + +bool LayerSnapshotBuilder::sortSnapshotsByZ(const Args& args) { + if (!mResortSnapshots && args.forceUpdate == ForceUpdateFlags::NONE && + !args.layerLifecycleManager.getGlobalChanges().any( + RequestedLayerState::Changes::Hierarchy | + RequestedLayerState::Changes::Visibility)) { + // We are not force updating and there are no hierarchy or visibility changes. Avoid sorting + // the snapshots. + return false; + } + mResortSnapshots = false; + + for (auto& snapshot : mSnapshots) { + snapshot->unreachable = snapshot->path.isClone(); + } + + size_t globalZ = 0; + args.root.traverseInZOrder( + [this, &globalZ](const LayerHierarchy&, + const LayerHierarchy::TraversalPath& traversalPath) -> bool { + LayerSnapshot* snapshot = getSnapshot(traversalPath); + if (!snapshot) { + return false; + } + + snapshot->unreachable = false; + if (snapshot->getIsVisible() || snapshot->hasInputInfo()) { + updateVisibility(*snapshot, snapshot->getIsVisible()); + size_t oldZ = snapshot->globalZ; + size_t newZ = globalZ++; + snapshot->globalZ = newZ; + if (oldZ == newZ) { + return true; + } + mSnapshots[newZ]->globalZ = oldZ; + LLOGV(snapshot->sequence, "Made visible z=%zu -> %zu %s", oldZ, newZ, + snapshot->getDebugString().c_str()); + std::iter_swap(mSnapshots.begin() + static_cast(oldZ), + mSnapshots.begin() + static_cast(newZ)); + } + return true; + }); + mNumInterestingSnapshots = (int)globalZ; + bool hasUnreachableSnapshots = false; + while (globalZ < mSnapshots.size()) { + mSnapshots[globalZ]->globalZ = globalZ; + /* mark unreachable snapshots as explicitly invisible */ + updateVisibility(*mSnapshots[globalZ], false); + if (mSnapshots[globalZ]->unreachable) { + hasUnreachableSnapshots = true; + } + globalZ++; + } + return hasUnreachableSnapshots; +} + +void LayerSnapshotBuilder::updateRelativeState(LayerSnapshot& snapshot, + const LayerSnapshot& parentSnapshot, + bool parentIsRelative, const Args& args) { + if (parentIsRelative) { + snapshot.isHiddenByPolicyFromRelativeParent = + parentSnapshot.isHiddenByPolicyFromParent || parentSnapshot.invalidTransform; + if (args.includeMetadata) { + snapshot.relativeLayerMetadata = parentSnapshot.layerMetadata; + } + } else { + snapshot.isHiddenByPolicyFromRelativeParent = + parentSnapshot.isHiddenByPolicyFromRelativeParent; + if (args.includeMetadata) { + snapshot.relativeLayerMetadata = parentSnapshot.relativeLayerMetadata; + } + } + snapshot.isVisible = snapshot.getIsVisible(); +} + +void LayerSnapshotBuilder::updateChildState(LayerSnapshot& snapshot, + const LayerSnapshot& childSnapshot, const Args& args) { + if (snapshot.childState.hasValidFrameRate) { + return; + } + if (args.forceUpdate == ForceUpdateFlags::ALL || + childSnapshot.changes.test(RequestedLayerState::Changes::FrameRate)) { + // We return whether this layer ot its children has a vote. We ignore ExactOrMultiple votes + // for the same reason we are allowing touch boost for those layers. See + // RefreshRateSelector::rankFrameRates for details. + using FrameRateCompatibility = scheduler::LayerInfo::FrameRateCompatibility; + const auto layerVotedWithDefaultCompatibility = childSnapshot.frameRate.rate.isValid() && + childSnapshot.frameRate.type == FrameRateCompatibility::Default; + const auto layerVotedWithNoVote = + childSnapshot.frameRate.type == FrameRateCompatibility::NoVote; + const auto layerVotedWithExactCompatibility = childSnapshot.frameRate.rate.isValid() && + childSnapshot.frameRate.type == FrameRateCompatibility::Exact; + + snapshot.childState.hasValidFrameRate |= layerVotedWithDefaultCompatibility || + layerVotedWithNoVote || layerVotedWithExactCompatibility; + + // If we don't have a valid frame rate, but the children do, we set this + // layer as NoVote to allow the children to control the refresh rate + if (!snapshot.frameRate.rate.isValid() && + snapshot.frameRate.type != FrameRateCompatibility::NoVote && + snapshot.childState.hasValidFrameRate) { + snapshot.frameRate = + scheduler::LayerInfo::FrameRate(Fps(), FrameRateCompatibility::NoVote); + snapshot.changes |= childSnapshot.changes & RequestedLayerState::Changes::FrameRate; + } + } +} + +void LayerSnapshotBuilder::resetRelativeState(LayerSnapshot& snapshot) { + snapshot.isHiddenByPolicyFromRelativeParent = false; + snapshot.relativeLayerMetadata.mMap.clear(); +} + +// TODO (b/259407931): Remove. +uint32_t getPrimaryDisplayRotationFlags( + const display::DisplayMap& displays) { + for (auto& [_, display] : displays) { + if (display.isPrimary) { + return display.rotationFlags; + } + } + return 0; +} + +void LayerSnapshotBuilder::updateSnapshot(LayerSnapshot& snapshot, const Args& args, + const RequestedLayerState& requested, + const LayerSnapshot& parentSnapshot, + const LayerHierarchy::TraversalPath& path) { + // Always update flags and visibility + ftl::Flags parentChanges = parentSnapshot.changes & + (RequestedLayerState::Changes::Hierarchy | RequestedLayerState::Changes::Geometry | + RequestedLayerState::Changes::Visibility | RequestedLayerState::Changes::Metadata | + RequestedLayerState::Changes::AffectsChildren | + RequestedLayerState::Changes::FrameRate); + snapshot.changes |= parentChanges | requested.changes; + snapshot.isHiddenByPolicyFromParent = parentSnapshot.isHiddenByPolicyFromParent || + parentSnapshot.invalidTransform || requested.isHiddenByPolicy() || + (args.excludeLayerIds.find(path.id) != args.excludeLayerIds.end()); + snapshot.contentDirty = requested.what & layer_state_t::CONTENT_DIRTY; + // TODO(b/238781169) scope down the changes to only buffer updates. + snapshot.hasReadyFrame = requested.hasReadyFrame(); + snapshot.sidebandStreamHasFrame = requested.hasSidebandStreamFrame(); + updateSurfaceDamage(requested, snapshot.hasReadyFrame, args.forceFullDamage, + snapshot.surfaceDamage); + snapshot.outputFilter.layerStack = parentSnapshot.path == LayerHierarchy::TraversalPath::ROOT + ? requested.layerStack + : parentSnapshot.outputFilter.layerStack; + + uint32_t primaryDisplayRotationFlags = getPrimaryDisplayRotationFlags(args.displays); + const bool forceUpdate = args.forceUpdate == ForceUpdateFlags::ALL || + snapshot.changes.any(RequestedLayerState::Changes::Visibility | + RequestedLayerState::Changes::Created); + + // always update the buffer regardless of visibility + if (forceUpdate || requested.what & layer_state_t::BUFFER_CHANGES || args.displayChanges) { + snapshot.acquireFence = + (requested.externalTexture && + requested.bufferData->flags.test(BufferData::BufferDataChange::fenceChanged)) + ? requested.bufferData->acquireFence + : Fence::NO_FENCE; + snapshot.buffer = + requested.externalTexture ? requested.externalTexture->getBuffer() : nullptr; + snapshot.bufferSize = requested.getBufferSize(primaryDisplayRotationFlags); + snapshot.geomBufferSize = snapshot.bufferSize; + snapshot.croppedBufferSize = requested.getCroppedBufferSize(snapshot.bufferSize); + snapshot.dataspace = requested.dataspace; + snapshot.externalTexture = requested.externalTexture; + snapshot.frameNumber = (requested.bufferData) ? requested.bufferData->frameNumber : 0; + snapshot.geomBufferTransform = requested.bufferTransform; + snapshot.geomBufferUsesDisplayInverseTransform = requested.transformToDisplayInverse; + snapshot.geomContentCrop = requested.getBufferCrop(); + snapshot.geomUsesSourceCrop = snapshot.hasBufferOrSidebandStream(); + snapshot.hasProtectedContent = requested.externalTexture && + requested.externalTexture->getUsage() & GRALLOC_USAGE_PROTECTED; + snapshot.isHdrY410 = requested.dataspace == ui::Dataspace::BT2020_ITU_PQ && + requested.api == NATIVE_WINDOW_API_MEDIA && + requested.bufferData->getPixelFormat() == HAL_PIXEL_FORMAT_RGBA_1010102; + snapshot.sidebandStream = requested.sidebandStream; + snapshot.transparentRegionHint = requested.transparentRegion; + snapshot.color.rgb = requested.getColor().rgb; + snapshot.currentHdrSdrRatio = requested.currentHdrSdrRatio; + snapshot.desiredHdrSdrRatio = requested.desiredHdrSdrRatio; + } + + if (snapshot.isHiddenByPolicyFromParent && + !snapshot.changes.test(RequestedLayerState::Changes::Created)) { + if (forceUpdate || + snapshot.changes.any(RequestedLayerState::Changes::Hierarchy | + RequestedLayerState::Changes::Geometry | + RequestedLayerState::Changes::Input)) { + updateInput(snapshot, requested, parentSnapshot, path, args); + } + return; + } + + if (forceUpdate || snapshot.changes.any(RequestedLayerState::Changes::AffectsChildren)) { + // If root layer, use the layer stack otherwise get the parent's layer stack. + snapshot.color.a = parentSnapshot.color.a * requested.color.a; + snapshot.alpha = snapshot.color.a; + snapshot.inputInfo.alpha = snapshot.color.a; + + snapshot.isSecure = + parentSnapshot.isSecure || (requested.flags & layer_state_t::eLayerSecure); + snapshot.isTrustedOverlay = parentSnapshot.isTrustedOverlay || requested.isTrustedOverlay; + snapshot.outputFilter.toInternalDisplay = parentSnapshot.outputFilter.toInternalDisplay || + (requested.flags & layer_state_t::eLayerSkipScreenshot); + snapshot.stretchEffect = (requested.stretchEffect.hasEffect()) + ? requested.stretchEffect + : parentSnapshot.stretchEffect; + if (!parentSnapshot.colorTransformIsIdentity) { + snapshot.colorTransform = parentSnapshot.colorTransform * requested.colorTransform; + snapshot.colorTransformIsIdentity = false; + } else { + snapshot.colorTransform = requested.colorTransform; + snapshot.colorTransformIsIdentity = !requested.hasColorTransform; + } + snapshot.gameMode = requested.metadata.has(gui::METADATA_GAME_MODE) + ? requested.gameMode + : parentSnapshot.gameMode; + // Display mirrors are always placed in a VirtualDisplay so we never want to capture layers + // marked as skip capture + snapshot.handleSkipScreenshotFlag = parentSnapshot.handleSkipScreenshotFlag || + (requested.layerStackToMirror != ui::INVALID_LAYER_STACK); + } + + if (forceUpdate || snapshot.changes.any(RequestedLayerState::Changes::AffectsChildren) || + args.displayChanges) { + snapshot.fixedTransformHint = requested.fixedTransformHint != ui::Transform::ROT_INVALID + ? requested.fixedTransformHint + : parentSnapshot.fixedTransformHint; + + if (snapshot.fixedTransformHint != ui::Transform::ROT_INVALID) { + snapshot.transformHint = snapshot.fixedTransformHint; + } else { + const auto display = args.displays.get(snapshot.outputFilter.layerStack); + snapshot.transformHint = display.has_value() + ? std::make_optional<>(display->get().transformHint) + : std::nullopt; + } + } + + if (forceUpdate || + snapshot.changes.any(RequestedLayerState::Changes::FrameRate | + RequestedLayerState::Changes::Hierarchy)) { + snapshot.frameRate = (requested.requestedFrameRate.rate.isValid() || + (requested.requestedFrameRate.type == + scheduler::LayerInfo::FrameRateCompatibility::NoVote)) + ? requested.requestedFrameRate + : parentSnapshot.frameRate; + } + + if (forceUpdate || requested.what & layer_state_t::eMetadataChanged) { + updateMetadata(snapshot, requested, args); + } + + if (forceUpdate || requested.changes.get() != 0) { + snapshot.compositionType = requested.getCompositionType(); + snapshot.dimmingEnabled = requested.dimmingEnabled; + snapshot.layerOpaqueFlagSet = + (requested.flags & layer_state_t::eLayerOpaque) == layer_state_t::eLayerOpaque; + snapshot.cachingHint = requested.cachingHint; + snapshot.frameRateSelectionPriority = requested.frameRateSelectionPriority; + } + + if (forceUpdate || snapshot.changes.any(RequestedLayerState::Changes::Content) || + snapshot.changes.any(RequestedLayerState::Changes::AffectsChildren)) { + snapshot.color.rgb = requested.getColor().rgb; + snapshot.isColorspaceAgnostic = requested.colorSpaceAgnostic; + snapshot.backgroundBlurRadius = args.supportsBlur + ? static_cast(parentSnapshot.color.a * (float)requested.backgroundBlurRadius) + : 0; + snapshot.blurRegions = requested.blurRegions; + for (auto& region : snapshot.blurRegions) { + region.alpha = region.alpha * snapshot.color.a; + } + snapshot.hdrMetadata = requested.hdrMetadata; + } + + if (forceUpdate || + snapshot.changes.any(RequestedLayerState::Changes::Hierarchy | + RequestedLayerState::Changes::Geometry)) { + updateLayerBounds(snapshot, requested, parentSnapshot, primaryDisplayRotationFlags); + updateRoundedCorner(snapshot, requested, parentSnapshot); + } + + if (forceUpdate || + snapshot.changes.any(RequestedLayerState::Changes::Hierarchy | + RequestedLayerState::Changes::Geometry | + RequestedLayerState::Changes::Input)) { + updateInput(snapshot, requested, parentSnapshot, path, args); + } + + // computed snapshot properties + updateShadows(snapshot, requested, args.globalShadowSettings); + if (args.includeMetadata) { + snapshot.layerMetadata = parentSnapshot.layerMetadata; + snapshot.layerMetadata.merge(requested.metadata); + } + snapshot.forceClientComposition = snapshot.isHdrY410 || snapshot.shadowSettings.length > 0 || + requested.blurRegions.size() > 0 || snapshot.stretchEffect.hasEffect(); + snapshot.contentOpaque = snapshot.isContentOpaque(); + snapshot.isOpaque = snapshot.contentOpaque && !snapshot.roundedCorner.hasRoundedCorners() && + snapshot.color.a == 1.f; + snapshot.blendMode = getBlendMode(snapshot, requested); + LLOGV(snapshot.sequence, + "%supdated %s changes:%s parent:%s requested:%s requested:%s from parent %s", + args.forceUpdate == ForceUpdateFlags::ALL ? "Force " : "", + snapshot.getDebugString().c_str(), snapshot.changes.string().c_str(), + parentSnapshot.changes.string().c_str(), requested.changes.string().c_str(), + std::to_string(requested.what).c_str(), parentSnapshot.getDebugString().c_str()); +} + +void LayerSnapshotBuilder::updateRoundedCorner(LayerSnapshot& snapshot, + const RequestedLayerState& requested, + const LayerSnapshot& parentSnapshot) { + snapshot.roundedCorner = RoundedCornerState(); + RoundedCornerState parentRoundedCorner; + if (parentSnapshot.roundedCorner.hasRoundedCorners()) { + parentRoundedCorner = parentSnapshot.roundedCorner; + ui::Transform t = snapshot.localTransform.inverse(); + parentRoundedCorner.cropRect = t.transform(parentRoundedCorner.cropRect); + parentRoundedCorner.radius.x *= t.getScaleX(); + parentRoundedCorner.radius.y *= t.getScaleY(); + } + + FloatRect layerCropRect = snapshot.croppedBufferSize.toFloatRect(); + const vec2 radius(requested.cornerRadius, requested.cornerRadius); + RoundedCornerState layerSettings(layerCropRect, radius); + const bool layerSettingsValid = layerSettings.hasRoundedCorners() && !layerCropRect.isEmpty(); + const bool parentRoundedCornerValid = parentRoundedCorner.hasRoundedCorners(); + if (layerSettingsValid && parentRoundedCornerValid) { + // If the parent and the layer have rounded corner settings, use the parent settings if + // the parent crop is entirely inside the layer crop. This has limitations and cause + // rendering artifacts. See b/200300845 for correct fix. + if (parentRoundedCorner.cropRect.left > layerCropRect.left && + parentRoundedCorner.cropRect.top > layerCropRect.top && + parentRoundedCorner.cropRect.right < layerCropRect.right && + parentRoundedCorner.cropRect.bottom < layerCropRect.bottom) { + snapshot.roundedCorner = parentRoundedCorner; + } else { + snapshot.roundedCorner = layerSettings; + } + } else if (layerSettingsValid) { + snapshot.roundedCorner = layerSettings; + } else if (parentRoundedCornerValid) { + snapshot.roundedCorner = parentRoundedCorner; + } +} + +void LayerSnapshotBuilder::updateLayerBounds(LayerSnapshot& snapshot, + const RequestedLayerState& requested, + const LayerSnapshot& parentSnapshot, + uint32_t primaryDisplayRotationFlags) { + snapshot.croppedBufferSize = requested.getCroppedBufferSize(snapshot.bufferSize); + snapshot.geomCrop = requested.crop; + snapshot.localTransform = requested.getTransform(primaryDisplayRotationFlags); + snapshot.localTransformInverse = snapshot.localTransform.inverse(); + snapshot.geomLayerTransform = parentSnapshot.geomLayerTransform * snapshot.localTransform; + const bool transformWasInvalid = snapshot.invalidTransform; + snapshot.invalidTransform = !LayerSnapshot::isTransformValid(snapshot.geomLayerTransform); + if (snapshot.invalidTransform) { + auto& t = snapshot.geomLayerTransform; + auto& requestedT = requested.requestedTransform; + std::string transformDebug = + base::StringPrintf(" transform={%f,%f,%f,%f} requestedTransform={%f,%f,%f,%f}", + t.dsdx(), t.dsdy(), t.dtdx(), t.dtdy(), requestedT.dsdx(), + requestedT.dsdy(), requestedT.dtdx(), requestedT.dtdy()); + std::string bufferDebug; + if (requested.externalTexture) { + auto unRotBuffer = requested.getUnrotatedBufferSize(primaryDisplayRotationFlags); + auto& destFrame = requested.destinationFrame; + bufferDebug = base::StringPrintf(" buffer={%d,%d} displayRot=%d" + " destFrame={%d,%d,%d,%d} unRotBuffer={%d,%d}", + requested.externalTexture->getWidth(), + requested.externalTexture->getHeight(), + primaryDisplayRotationFlags, destFrame.left, + destFrame.top, destFrame.right, destFrame.bottom, + unRotBuffer.getHeight(), unRotBuffer.getWidth()); + } + ALOGW("Resetting transform for %s because it is invalid.%s%s", + snapshot.getDebugString().c_str(), transformDebug.c_str(), bufferDebug.c_str()); + snapshot.geomLayerTransform.reset(); + } + if (transformWasInvalid != snapshot.invalidTransform) { + // If transform is invalid, the layer will be hidden. + mResortSnapshots = true; + } + snapshot.geomInverseLayerTransform = snapshot.geomLayerTransform.inverse(); + + FloatRect parentBounds = parentSnapshot.geomLayerBounds; + parentBounds = snapshot.localTransform.inverse().transform(parentBounds); + snapshot.geomLayerBounds = + (requested.externalTexture) ? snapshot.bufferSize.toFloatRect() : parentBounds; + if (!requested.crop.isEmpty()) { + snapshot.geomLayerBounds = snapshot.geomLayerBounds.intersect(requested.crop.toFloatRect()); + } + snapshot.geomLayerBounds = snapshot.geomLayerBounds.intersect(parentBounds); + snapshot.transformedBounds = snapshot.geomLayerTransform.transform(snapshot.geomLayerBounds); + const Rect geomLayerBoundsWithoutTransparentRegion = + RequestedLayerState::reduce(Rect(snapshot.geomLayerBounds), + requested.transparentRegion); + snapshot.transformedBoundsWithoutTransparentRegion = + snapshot.geomLayerTransform.transform(geomLayerBoundsWithoutTransparentRegion); + snapshot.parentTransform = parentSnapshot.geomLayerTransform; + + // Subtract the transparent region and snap to the bounds + const Rect bounds = + RequestedLayerState::reduce(snapshot.croppedBufferSize, requested.transparentRegion); + if (requested.potentialCursor) { + snapshot.cursorFrame = snapshot.geomLayerTransform.transform(bounds); + } +} + +void LayerSnapshotBuilder::updateShadows(LayerSnapshot& snapshot, + const RequestedLayerState& requested, + const renderengine::ShadowSettings& globalShadowSettings) { + snapshot.shadowRadius = requested.shadowRadius; + snapshot.shadowSettings.length = requested.shadowRadius; + if (snapshot.shadowRadius > 0.f) { + snapshot.shadowSettings = globalShadowSettings; + + // Note: this preserves existing behavior of shadowing the entire layer and not cropping + // it if transparent regions are present. This may not be necessary since shadows are + // typically cast by layers without transparent regions. + snapshot.shadowSettings.boundaries = snapshot.geomLayerBounds; + + // If the casting layer is translucent, we need to fill in the shadow underneath the + // layer. Otherwise the generated shadow will only be shown around the casting layer. + snapshot.shadowSettings.casterIsTranslucent = + !snapshot.isContentOpaque() || (snapshot.alpha < 1.0f); + snapshot.shadowSettings.ambientColor *= snapshot.alpha; + snapshot.shadowSettings.spotColor *= snapshot.alpha; + } +} + +void LayerSnapshotBuilder::updateInput(LayerSnapshot& snapshot, + const RequestedLayerState& requested, + const LayerSnapshot& parentSnapshot, + const LayerHierarchy::TraversalPath& path, + const Args& args) { + if (requested.windowInfoHandle) { + snapshot.inputInfo = *requested.windowInfoHandle->getInfo(); + } else { + snapshot.inputInfo = {}; + // b/271132344 revisit this and see if we can always use the layers uid/pid + snapshot.inputInfo.name = requested.name; + snapshot.inputInfo.ownerUid = static_cast(requested.ownerUid); + snapshot.inputInfo.ownerPid = requested.ownerPid; + } + snapshot.touchCropId = requested.touchCropId; + + snapshot.inputInfo.id = static_cast(snapshot.uniqueSequence); + snapshot.inputInfo.displayId = static_cast(snapshot.outputFilter.layerStack.id); + updateVisibility(snapshot, snapshot.isVisible); + if (!needsInputInfo(snapshot, requested)) { + return; + } + + static frontend::DisplayInfo sDefaultInfo = {.isSecure = false}; + const std::optional displayInfoOpt = + args.displays.get(snapshot.outputFilter.layerStack); + bool noValidDisplay = !displayInfoOpt.has_value(); + auto displayInfo = displayInfoOpt.value_or(sDefaultInfo); + + if (!requested.windowInfoHandle) { + snapshot.inputInfo.inputConfig = gui::WindowInfo::InputConfig::NO_INPUT_CHANNEL; + } + fillInputFrameInfo(snapshot.inputInfo, displayInfo.transform, snapshot); + + if (noValidDisplay) { + // Do not let the window receive touches if it is not associated with a valid display + // transform. We still allow the window to receive keys and prevent ANRs. + snapshot.inputInfo.inputConfig |= gui::WindowInfo::InputConfig::NOT_TOUCHABLE; + } + + snapshot.inputInfo.alpha = snapshot.color.a; + snapshot.inputInfo.touchOcclusionMode = requested.hasInputInfo() + ? requested.windowInfoHandle->getInfo()->touchOcclusionMode + : parentSnapshot.inputInfo.touchOcclusionMode; + if (requested.dropInputMode == gui::DropInputMode::ALL || + parentSnapshot.dropInputMode == gui::DropInputMode::ALL) { + snapshot.dropInputMode = gui::DropInputMode::ALL; + } else if (requested.dropInputMode == gui::DropInputMode::OBSCURED || + parentSnapshot.dropInputMode == gui::DropInputMode::OBSCURED) { + snapshot.dropInputMode = gui::DropInputMode::OBSCURED; + } else { + snapshot.dropInputMode = gui::DropInputMode::NONE; + } + + handleDropInputMode(snapshot, parentSnapshot); + + // If the window will be blacked out on a display because the display does not have the secure + // flag and the layer has the secure flag set, then drop input. + if (!displayInfo.isSecure && snapshot.isSecure) { + snapshot.inputInfo.inputConfig |= gui::WindowInfo::InputConfig::DROP_INPUT; + } + + auto cropLayerSnapshot = getSnapshot(requested.touchCropId); + if (cropLayerSnapshot) { + mNeedsTouchableRegionCrop.insert(path); + } else if (snapshot.inputInfo.replaceTouchableRegionWithCrop) { + FloatRect inputBounds = getInputBounds(snapshot, /*fillParentBounds=*/true).first; + Rect inputBoundsInDisplaySpace = + getInputBoundsInDisplaySpace(snapshot, inputBounds, displayInfo.transform); + snapshot.inputInfo.touchableRegion = Region(inputBoundsInDisplaySpace); + } + + // 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) { + snapshot.inputInfo.inputConfig |= gui::WindowInfo::InputConfig::TRUSTED_OVERLAY; + } + + // If the layer is a clone, we need to crop the input region to cloned root to prevent + // touches from going outside the cloned area. + if (path.isClone()) { + snapshot.inputInfo.inputConfig |= gui::WindowInfo::InputConfig::CLONE; + // Cloned layers shouldn't handle watch outside since their z order is not determined by + // WM or the client. + snapshot.inputInfo.inputConfig.clear(gui::WindowInfo::InputConfig::WATCH_OUTSIDE_TOUCH); + + mNeedsTouchableRegionCrop.insert(path); + } +} + +std::vector>& LayerSnapshotBuilder::getSnapshots() { + return mSnapshots; +} + +void LayerSnapshotBuilder::forEachVisibleSnapshot(const ConstVisitor& visitor) const { + for (int i = 0; i < mNumInterestingSnapshots; i++) { + LayerSnapshot& snapshot = *mSnapshots[(size_t)i]; + if (!snapshot.isVisible) continue; + visitor(snapshot); + } +} + +// Visit each visible snapshot in z-order +void LayerSnapshotBuilder::forEachVisibleSnapshot(const ConstVisitor& visitor, + const LayerHierarchy& root) const { + root.traverseInZOrder( + [this, visitor](const LayerHierarchy&, + const LayerHierarchy::TraversalPath& traversalPath) -> bool { + LayerSnapshot* snapshot = getSnapshot(traversalPath); + if (snapshot && snapshot->isVisible) { + visitor(*snapshot); + } + return true; + }); +} + +void LayerSnapshotBuilder::forEachVisibleSnapshot(const Visitor& visitor) { + for (int i = 0; i < mNumInterestingSnapshots; i++) { + std::unique_ptr& snapshot = mSnapshots.at((size_t)i); + if (!snapshot->isVisible) continue; + visitor(snapshot); + } +} + +void LayerSnapshotBuilder::forEachInputSnapshot(const ConstVisitor& visitor) const { + for (int i = mNumInterestingSnapshots - 1; i >= 0; i--) { + LayerSnapshot& snapshot = *mSnapshots[(size_t)i]; + if (!snapshot.hasInputInfo()) continue; + visitor(snapshot); + } +} + +void LayerSnapshotBuilder::updateTouchableRegionCrop(const Args& args) { + if (mNeedsTouchableRegionCrop.empty()) { + return; + } + + static constexpr ftl::Flags AFFECTS_INPUT = + RequestedLayerState::Changes::Visibility | RequestedLayerState::Changes::Created | + RequestedLayerState::Changes::Hierarchy | RequestedLayerState::Changes::Geometry | + RequestedLayerState::Changes::Input; + + if (args.forceUpdate != ForceUpdateFlags::ALL && + !args.layerLifecycleManager.getGlobalChanges().any(AFFECTS_INPUT)) { + return; + } + + for (auto& path : mNeedsTouchableRegionCrop) { + frontend::LayerSnapshot* snapshot = getSnapshot(path); + if (!snapshot) { + continue; + } + const std::optional displayInfoOpt = + args.displays.get(snapshot->outputFilter.layerStack); + static frontend::DisplayInfo sDefaultInfo = {.isSecure = false}; + auto displayInfo = displayInfoOpt.value_or(sDefaultInfo); + + bool needsUpdate = + args.forceUpdate == ForceUpdateFlags::ALL || snapshot->changes.any(AFFECTS_INPUT); + auto cropLayerSnapshot = getSnapshot(snapshot->touchCropId); + needsUpdate = + needsUpdate || (cropLayerSnapshot && cropLayerSnapshot->changes.any(AFFECTS_INPUT)); + auto clonedRootSnapshot = path.isClone() ? getSnapshot(snapshot->mirrorRootPath) : nullptr; + needsUpdate = needsUpdate || + (clonedRootSnapshot && clonedRootSnapshot->changes.any(AFFECTS_INPUT)); + + if (!needsUpdate) { + continue; + } + + if (snapshot->inputInfo.replaceTouchableRegionWithCrop) { + Rect inputBoundsInDisplaySpace; + if (!cropLayerSnapshot) { + FloatRect inputBounds = getInputBounds(*snapshot, /*fillParentBounds=*/true).first; + inputBoundsInDisplaySpace = + getInputBoundsInDisplaySpace(*snapshot, inputBounds, displayInfo.transform); + } else { + FloatRect inputBounds = + getInputBounds(*cropLayerSnapshot, /*fillParentBounds=*/true).first; + inputBoundsInDisplaySpace = + getInputBoundsInDisplaySpace(*cropLayerSnapshot, inputBounds, + displayInfo.transform); + } + snapshot->inputInfo.touchableRegion = Region(inputBoundsInDisplaySpace); + } else if (cropLayerSnapshot) { + FloatRect inputBounds = + getInputBounds(*cropLayerSnapshot, /*fillParentBounds=*/true).first; + Rect inputBoundsInDisplaySpace = + getInputBoundsInDisplaySpace(*cropLayerSnapshot, inputBounds, + displayInfo.transform); + snapshot->inputInfo.touchableRegion = snapshot->inputInfo.touchableRegion.intersect( + displayInfo.transform.transform(inputBoundsInDisplaySpace)); + } + + // If the layer is a clone, we need to crop the input region to cloned root to prevent + // touches from going outside the cloned area. + if (clonedRootSnapshot) { + const Rect rect = + displayInfo.transform.transform(Rect{clonedRootSnapshot->transformedBounds}); + snapshot->inputInfo.touchableRegion = + snapshot->inputInfo.touchableRegion.intersect(rect); + } + } +} + +} // namespace android::surfaceflinger::frontend diff --git a/services/surfaceflinger/FrontEnd/LayerSnapshotBuilder.h b/services/surfaceflinger/FrontEnd/LayerSnapshotBuilder.h new file mode 100644 index 0000000000000000000000000000000000000000..148c98e2b1b75c69c71791776c766b898f07fa62 --- /dev/null +++ b/services/surfaceflinger/FrontEnd/LayerSnapshotBuilder.h @@ -0,0 +1,136 @@ +/* + * 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 "Display/DisplayMap.h" +#include "FrontEnd/DisplayInfo.h" +#include "FrontEnd/LayerLifecycleManager.h" +#include "LayerHierarchy.h" +#include "LayerSnapshot.h" +#include "RequestedLayerState.h" + +namespace android::surfaceflinger::frontend { + +// Walks through the layer hierarchy to build an ordered list +// of LayerSnapshots that can be passed on to CompositionEngine. +// This builder does a minimum amount of work to update +// an existing set of snapshots based on hierarchy changes +// and RequestedLayerState changes. + +// The builder also uses a fast path to update +// snapshots when there are only buffer updates. +class LayerSnapshotBuilder { +public: + enum class ForceUpdateFlags { + NONE, + ALL, + HIERARCHY, + }; + struct Args { + LayerHierarchy root; + const LayerLifecycleManager& layerLifecycleManager; + ForceUpdateFlags forceUpdate = ForceUpdateFlags::NONE; + bool includeMetadata = false; + const display::DisplayMap& displays; + // Set to true if there were display changes since last update. + bool displayChanges = false; + const renderengine::ShadowSettings& globalShadowSettings; + bool supportsBlur = true; + bool forceFullDamage = false; + std::optional parentCrop = std::nullopt; + std::unordered_set excludeLayerIds; + const std::unordered_map& supportedLayerGenericMetadata; + const std::unordered_map& genericLayerMetadataKeyMap; + }; + LayerSnapshotBuilder(); + + // Rebuild the snapshots from scratch. + LayerSnapshotBuilder(Args); + + // Update an existing set of snapshot using change flags in RequestedLayerState + // and LayerLifecycleManager. This needs to be called before + // LayerLifecycleManager.commitChanges is called as that function will clear all + // change flags. + void update(const Args&); + std::vector>& getSnapshots(); + LayerSnapshot* getSnapshot(uint32_t layerId) const; + LayerSnapshot* getSnapshot(const LayerHierarchy::TraversalPath& id) const; + + typedef std::function ConstVisitor; + + // Visit each visible snapshot in z-order + void forEachVisibleSnapshot(const ConstVisitor& visitor) const; + + // Visit each visible snapshot in z-order + void forEachVisibleSnapshot(const ConstVisitor& visitor, const LayerHierarchy& root) const; + + typedef std::function& snapshot)> Visitor; + // Visit each visible snapshot in z-order and move the snapshot if needed + void forEachVisibleSnapshot(const Visitor& visitor); + + // Visit each snapshot interesting to input reverse z-order + void forEachInputSnapshot(const ConstVisitor& visitor) const; + +private: + friend class LayerSnapshotTest; + static LayerSnapshot getRootSnapshot(); + + // return true if we were able to successfully update the snapshots via + // the fast path. + bool tryFastUpdate(const Args& args); + + void updateSnapshots(const Args& args); + + const LayerSnapshot& updateSnapshotsInHierarchy(const Args&, const LayerHierarchy& hierarchy, + LayerHierarchy::TraversalPath& traversalPath, + const LayerSnapshot& parentSnapshot); + void updateSnapshot(LayerSnapshot&, const Args&, const RequestedLayerState&, + const LayerSnapshot& parentSnapshot, const LayerHierarchy::TraversalPath&); + static void updateRelativeState(LayerSnapshot& snapshot, const LayerSnapshot& parentSnapshot, + bool parentIsRelative, const Args& args); + static void resetRelativeState(LayerSnapshot& snapshot); + static void updateRoundedCorner(LayerSnapshot& snapshot, const RequestedLayerState& layerState, + const LayerSnapshot& parentSnapshot); + void updateLayerBounds(LayerSnapshot& snapshot, const RequestedLayerState& layerState, + const LayerSnapshot& parentSnapshot, uint32_t displayRotationFlags); + static void updateShadows(LayerSnapshot& snapshot, const RequestedLayerState& requested, + const renderengine::ShadowSettings& globalShadowSettings); + void updateInput(LayerSnapshot& snapshot, const RequestedLayerState& requested, + const LayerSnapshot& parentSnapshot, const LayerHierarchy::TraversalPath& path, + const Args& args); + // Return true if there are unreachable snapshots + bool sortSnapshotsByZ(const Args& args); + LayerSnapshot* createSnapshot(const LayerHierarchy::TraversalPath& id, + const RequestedLayerState& layer, + const LayerSnapshot& parentSnapshot); + void updateChildState(LayerSnapshot& snapshot, const LayerSnapshot& childSnapshot, + const Args& args); + void updateTouchableRegionCrop(const Args& args); + + std::unordered_map + mIdToSnapshot; + // Track snapshots that needs touchable region crop from other snapshots + std::unordered_set + mNeedsTouchableRegionCrop; + std::vector> mSnapshots; + LayerSnapshot mRootSnapshot; + bool mResortSnapshots = false; + int mNumInterestingSnapshots = 0; +}; + +} // namespace android::surfaceflinger::frontend diff --git a/services/surfaceflinger/FrontEnd/RequestedLayerState.cpp b/services/surfaceflinger/FrontEnd/RequestedLayerState.cpp new file mode 100644 index 0000000000000000000000000000000000000000..23bb54cc3640690a7faca6dc5e0fcedd53e1ea34 --- /dev/null +++ b/services/surfaceflinger/FrontEnd/RequestedLayerState.cpp @@ -0,0 +1,483 @@ +/* + * 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. + */ + +#define ATRACE_TAG ATRACE_TAG_GRAPHICS +#undef LOG_TAG +#define LOG_TAG "RequestedLayerState" + +#include +#include +#include + +#include "Layer.h" +#include "LayerCreationArgs.h" +#include "LayerLog.h" +#include "RequestedLayerState.h" + +namespace android::surfaceflinger::frontend { +using ftl::Flags; +using namespace ftl::flag_operators; + +namespace { +std::string layerIdsToString(const std::vector& layerIds) { + std::stringstream stream; + stream << "{"; + for (auto layerId : layerIds) { + stream << layerId << ","; + } + stream << "}"; + return stream.str(); +} + +} // namespace + +RequestedLayerState::RequestedLayerState(const LayerCreationArgs& args) + : id(args.sequence), + name(args.name + "#" + std::to_string(args.sequence)), + canBeRoot(args.addToRoot), + layerCreationFlags(args.flags), + textureName(args.textureName), + ownerUid(args.ownerUid), + ownerPid(args.ownerPid), + parentId(args.parentId), + layerIdToMirror(args.layerIdToMirror) { + layerId = static_cast(args.sequence); + changes |= RequestedLayerState::Changes::Created; + metadata.merge(args.metadata); + changes |= RequestedLayerState::Changes::Metadata; + handleAlive = true; + if (parentId != UNASSIGNED_LAYER_ID) { + canBeRoot = false; + } + if (layerIdToMirror != UNASSIGNED_LAYER_ID) { + changes |= RequestedLayerState::Changes::Mirror; + } else if (args.layerStackToMirror != ui::INVALID_LAYER_STACK) { + layerStackToMirror = args.layerStackToMirror; + changes |= RequestedLayerState::Changes::Mirror; + } + + flags = 0; + if (args.flags & ISurfaceComposerClient::eHidden) flags |= layer_state_t::eLayerHidden; + if (args.flags & ISurfaceComposerClient::eOpaque) flags |= layer_state_t::eLayerOpaque; + if (args.flags & ISurfaceComposerClient::eSecure) flags |= layer_state_t::eLayerSecure; + if (args.flags & ISurfaceComposerClient::eSkipScreenshot) { + flags |= layer_state_t::eLayerSkipScreenshot; + } + premultipliedAlpha = !(args.flags & ISurfaceComposerClient::eNonPremultiplied); + potentialCursor = args.flags & ISurfaceComposerClient::eCursorWindow; + protectedByApp = args.flags & ISurfaceComposerClient::eProtectedByApp; + if (args.flags & ISurfaceComposerClient::eNoColorFill) { + // Set an invalid color so there is no color fill. + // (b/259981098) use an explicit flag instead of relying on invalid values. + color.r = -1.0_hf; + color.g = -1.0_hf; + color.b = -1.0_hf; + } else { + color.rgb = {0.0_hf, 0.0_hf, 0.0_hf}; + } + LLOGV(layerId, "Created %s flags=%d", getDebugString().c_str(), flags); + color.a = 1.0f; + + crop.makeInvalid(); + z = 0; + layerStack = ui::DEFAULT_LAYER_STACK; + transformToDisplayInverse = false; + dataspace = ui::Dataspace::UNKNOWN; + desiredHdrSdrRatio = 1.f; + currentHdrSdrRatio = 1.f; + dataspaceRequested = false; + hdrMetadata.validTypes = 0; + surfaceDamageRegion = Region::INVALID_REGION; + cornerRadius = 0.0f; + backgroundBlurRadius = 0; + api = -1; + hasColorTransform = false; + bufferTransform = 0; + requestedTransform.reset(); + bufferData = std::make_shared(); + bufferData->frameNumber = 0; + bufferData->acquireFence = sp::make(-1); + acquireFenceTime = std::make_shared(bufferData->acquireFence); + colorSpaceAgnostic = false; + frameRateSelectionPriority = Layer::PRIORITY_UNSET; + shadowRadius = 0.f; + fixedTransformHint = ui::Transform::ROT_INVALID; + destinationFrame.makeInvalid(); + isTrustedOverlay = false; + dropInputMode = gui::DropInputMode::NONE; + dimmingEnabled = true; + defaultFrameRateCompatibility = + static_cast(scheduler::LayerInfo::FrameRateCompatibility::Default); + dataspace = ui::Dataspace::V0_SRGB; + gameMode = gui::GameMode::Unsupported; + requestedFrameRate = {}; + cachingHint = gui::CachingHint::Enabled; +} + +void RequestedLayerState::merge(const ResolvedComposerState& resolvedComposerState) { + const uint32_t oldFlags = flags; + const half oldAlpha = color.a; + const bool hadBuffer = externalTexture != nullptr; + const bool hadSideStream = sidebandStream != nullptr; + const layer_state_t& clientState = resolvedComposerState.state; + const bool hadBlur = hasBlur(); + uint64_t clientChanges = what | layer_state_t::diff(clientState); + layer_state_t::merge(clientState); + what = clientChanges; + + if (clientState.what & layer_state_t::eFlagsChanged) { + if ((oldFlags ^ flags) & layer_state_t::eLayerHidden) { + changes |= RequestedLayerState::Changes::Visibility | + RequestedLayerState::Changes::VisibleRegion; + } + if ((oldFlags ^ flags) & layer_state_t::eIgnoreDestinationFrame) { + changes |= RequestedLayerState::Changes::Geometry; + } + } + if (clientState.what & layer_state_t::eBufferChanged) { + externalTexture = resolvedComposerState.externalTexture; + barrierProducerId = std::max(bufferData->producerId, barrierProducerId); + barrierFrameNumber = std::max(bufferData->frameNumber, barrierFrameNumber); + // TODO(b/277265947) log and flush transaction trace when we detect out of order updates + + const bool hasBuffer = externalTexture != nullptr; + if (hasBuffer || hasBuffer != hadBuffer) { + changes |= RequestedLayerState::Changes::Buffer; + } + + if (hasBuffer != hadBuffer) { + changes |= RequestedLayerState::Changes::Geometry | + RequestedLayerState::Changes::VisibleRegion | + RequestedLayerState::Changes::Visibility | RequestedLayerState::Changes::Input; + } + } + + if (clientState.what & layer_state_t::eSidebandStreamChanged) { + changes |= RequestedLayerState::Changes::SidebandStream; + const bool hasSideStream = sidebandStream != nullptr; + if (hasSideStream != hadSideStream) { + changes |= RequestedLayerState::Changes::Geometry | + RequestedLayerState::Changes::VisibleRegion | + RequestedLayerState::Changes::Visibility | RequestedLayerState::Changes::Input; + } + } + if (what & (layer_state_t::eAlphaChanged)) { + if (oldAlpha == 0 || color.a == 0) { + changes |= RequestedLayerState::Changes::Visibility | + RequestedLayerState::Changes::VisibleRegion; + } + } + if (what & (layer_state_t::eBackgroundBlurRadiusChanged | layer_state_t::eBlurRegionsChanged)) { + if (hadBlur != hasBlur()) { + changes |= RequestedLayerState::Changes::Visibility | + RequestedLayerState::Changes::VisibleRegion; + } + } + if (clientChanges & layer_state_t::HIERARCHY_CHANGES) + changes |= RequestedLayerState::Changes::Hierarchy; + if (clientChanges & layer_state_t::CONTENT_CHANGES) + changes |= RequestedLayerState::Changes::Content; + if (clientChanges & layer_state_t::GEOMETRY_CHANGES) + changes |= RequestedLayerState::Changes::Geometry; + if (clientChanges & layer_state_t::AFFECTS_CHILDREN) + changes |= RequestedLayerState::Changes::AffectsChildren; + if (clientChanges & layer_state_t::INPUT_CHANGES) + changes |= RequestedLayerState::Changes::Input; + if (clientChanges & layer_state_t::VISIBLE_REGION_CHANGES) + changes |= RequestedLayerState::Changes::VisibleRegion; + if (clientState.what & layer_state_t::eColorTransformChanged) { + static const mat4 identityMatrix = mat4(); + hasColorTransform = colorTransform != identityMatrix; + } + if (clientState.what & + (layer_state_t::eLayerChanged | layer_state_t::eRelativeLayerChanged | + layer_state_t::eLayerStackChanged)) { + changes |= RequestedLayerState::Changes::Z; + } + if (clientState.what & layer_state_t::eReparent) { + changes |= RequestedLayerState::Changes::Parent; + parentId = resolvedComposerState.parentId; + parentSurfaceControlForChild = nullptr; + // Once a layer has be reparented, it cannot be placed at the root. It sounds odd + // but thats the existing logic and until we make this behavior more explicit, we need + // to maintain this logic. + canBeRoot = false; + } + if (clientState.what & layer_state_t::eRelativeLayerChanged) { + changes |= RequestedLayerState::Changes::RelativeParent; + relativeParentId = resolvedComposerState.relativeParentId; + isRelativeOf = true; + relativeLayerSurfaceControl = nullptr; + } + if ((clientState.what & layer_state_t::eLayerChanged || + (clientState.what & layer_state_t::eReparent && parentId == UNASSIGNED_LAYER_ID)) && + isRelativeOf) { + // clear out relz data + relativeParentId = UNASSIGNED_LAYER_ID; + isRelativeOf = false; + changes |= RequestedLayerState::Changes::RelativeParent; + } + if (clientState.what & layer_state_t::eReparent && parentId == relativeParentId) { + // provide a hint that we are are now a direct child and not a relative child. + changes |= RequestedLayerState::Changes::RelativeParent; + } + if (clientState.what & layer_state_t::eInputInfoChanged) { + touchCropId = resolvedComposerState.touchCropId; + windowInfoHandle->editInfo()->touchableRegionCropHandle.clear(); + } + if (clientState.what & layer_state_t::eStretchChanged) { + stretchEffect.sanitize(); + } + + if (clientState.what & layer_state_t::eHasListenerCallbacksChanged) { + // TODO(b/238781169) handle callbacks + } + + if (clientState.what & layer_state_t::ePositionChanged) { + requestedTransform.set(x, y); + } + + if (clientState.what & layer_state_t::eMatrixChanged) { + requestedTransform.set(matrix.dsdx, matrix.dtdy, matrix.dtdx, matrix.dsdy); + } + if (clientState.what & layer_state_t::eMetadataChanged) { + const int32_t requestedGameMode = + clientState.metadata.getInt32(gui::METADATA_GAME_MODE, -1); + if (requestedGameMode != -1) { + // The transaction will be received on the Task layer and needs to be applied to all + // child layers. + if (static_cast(gameMode) != requestedGameMode) { + gameMode = static_cast(requestedGameMode); + changes |= RequestedLayerState::Changes::AffectsChildren; + } + } + } + if (clientState.what & layer_state_t::eFrameRateChanged) { + const auto compatibility = + Layer::FrameRate::convertCompatibility(clientState.frameRateCompatibility); + const auto strategy = Layer::FrameRate::convertChangeFrameRateStrategy( + clientState.changeFrameRateStrategy); + requestedFrameRate = + Layer::FrameRate(Fps::fromValue(clientState.frameRate), compatibility, strategy); + changes |= RequestedLayerState::Changes::FrameRate; + } +} + +ui::Size RequestedLayerState::getUnrotatedBufferSize(uint32_t displayRotationFlags) const { + uint32_t bufferWidth = externalTexture->getWidth(); + uint32_t bufferHeight = externalTexture->getHeight(); + // Undo any transformations on the buffer. + if (bufferTransform & ui::Transform::ROT_90) { + std::swap(bufferWidth, bufferHeight); + } + if (transformToDisplayInverse) { + if (displayRotationFlags & ui::Transform::ROT_90) { + std::swap(bufferWidth, bufferHeight); + } + } + return {bufferWidth, bufferHeight}; +} + +ui::Transform RequestedLayerState::getTransform(uint32_t displayRotationFlags) const { + if ((flags & layer_state_t::eIgnoreDestinationFrame) || destinationFrame.isEmpty()) { + // If destination frame is not set, use the requested transform set via + // Transaction::setPosition and Transaction::setMatrix. + return requestedTransform; + } + + Rect destRect = destinationFrame; + int32_t destW = destRect.width(); + int32_t destH = destRect.height(); + if (destRect.left < 0) { + destRect.left = 0; + destRect.right = destW; + } + if (destRect.top < 0) { + destRect.top = 0; + destRect.bottom = destH; + } + + if (!externalTexture) { + ui::Transform transform; + transform.set(static_cast(destRect.left), static_cast(destRect.top)); + return transform; + } + + ui::Size bufferSize = getUnrotatedBufferSize(displayRotationFlags); + + float sx = static_cast(destW) / static_cast(bufferSize.width); + float sy = static_cast(destH) / static_cast(bufferSize.height); + ui::Transform transform; + transform.set(sx, 0, 0, sy); + transform.set(static_cast(destRect.left), static_cast(destRect.top)); + return transform; +} + +std::string RequestedLayerState::getDebugString() const { + std::stringstream debug; + debug << "RequestedLayerState{" << name; + if (parentId != UNASSIGNED_LAYER_ID) debug << " parentId=" << parentId; + if (relativeParentId != UNASSIGNED_LAYER_ID) debug << " relativeParentId=" << relativeParentId; + if (!mirrorIds.empty()) debug << " mirrorId=" << layerIdsToString(mirrorIds); + if (!handleAlive) debug << " !handle"; + if (z != 0) debug << " z=" << z; + if (layerStack.id != 0) debug << " layerStack=" << layerStack.id; + return debug.str(); +} + +std::string RequestedLayerState::getDebugStringShort() const { + return "[" + std::to_string(id) + "]" + name; +} + +bool RequestedLayerState::canBeDestroyed() const { + return !handleAlive && parentId == UNASSIGNED_LAYER_ID; +} +bool RequestedLayerState::isRoot() const { + return canBeRoot && parentId == UNASSIGNED_LAYER_ID; +} +bool RequestedLayerState::isHiddenByPolicy() const { + return (flags & layer_state_t::eLayerHidden) == layer_state_t::eLayerHidden; +}; +half4 RequestedLayerState::getColor() const { + if ((sidebandStream != nullptr) || (externalTexture != nullptr)) { + return {0._hf, 0._hf, 0._hf, color.a}; + } + return color; +} +Rect RequestedLayerState::getBufferSize(uint32_t displayRotationFlags) const { + // for buffer state layers we use the display frame size as the buffer size. + if (!externalTexture) { + return Rect::INVALID_RECT; + } + + uint32_t bufWidth = externalTexture->getWidth(); + uint32_t bufHeight = externalTexture->getHeight(); + + // Undo any transformations on the buffer and return the result. + if (bufferTransform & ui::Transform::ROT_90) { + std::swap(bufWidth, bufHeight); + } + + if (transformToDisplayInverse) { + uint32_t invTransform = displayRotationFlags; + if (invTransform & ui::Transform::ROT_90) { + std::swap(bufWidth, bufHeight); + } + } + + return Rect(0, 0, static_cast(bufWidth), static_cast(bufHeight)); +} + +Rect RequestedLayerState::getCroppedBufferSize(const Rect& bufferSize) const { + Rect size = bufferSize; + if (!crop.isEmpty() && size.isValid()) { + size.intersect(crop, &size); + } else if (!crop.isEmpty()) { + size = crop; + } + return size; +} + +Rect RequestedLayerState::getBufferCrop() const { + // this is the crop rectangle that applies to the buffer + // itself (as opposed to the window) + if (!bufferCrop.isEmpty()) { + // if the buffer crop is defined, we use that + return bufferCrop; + } else if (externalTexture != nullptr) { + // otherwise we use the whole buffer + return externalTexture->getBounds(); + } else { + // if we don't have a buffer yet, we use an empty/invalid crop + return Rect(); + } +} + +aidl::android::hardware::graphics::composer3::Composition RequestedLayerState::getCompositionType() + const { + using aidl::android::hardware::graphics::composer3::Composition; + // TODO(b/238781169) check about sidestream ready flag + if (sidebandStream.get()) { + return Composition::SIDEBAND; + } + if (!externalTexture) { + return Composition::SOLID_COLOR; + } + if (flags & layer_state_t::eLayerIsDisplayDecoration) { + return Composition::DISPLAY_DECORATION; + } + if (potentialCursor) { + return Composition::CURSOR; + } + return Composition::DEVICE; +} + +Rect RequestedLayerState::reduce(const Rect& win, const Region& exclude) { + if (CC_LIKELY(exclude.isEmpty())) { + return win; + } + if (exclude.isRect()) { + return win.reduce(exclude.getBounds()); + } + return Region(win).subtract(exclude).getBounds(); +} + +// Returns true if the layer has a relative parent that is not its own parent. This is an input +// error from the client, and this check allows us to handle it gracefully. If both parentId and +// relativeParentId is unassigned then the layer does not have a valid relative parent. +// If the relative parentid is unassigned, the layer will be considered relative but won't be +// reachable. +bool RequestedLayerState::hasValidRelativeParent() const { + return isRelativeOf && + (parentId != relativeParentId || relativeParentId == UNASSIGNED_LAYER_ID); +} + +bool RequestedLayerState::hasInputInfo() const { + if (!windowInfoHandle) { + return false; + } + const auto windowInfo = windowInfoHandle->getInfo(); + return windowInfo->token != nullptr || + windowInfo->inputConfig.test(gui::WindowInfo::InputConfig::NO_INPUT_CHANNEL); +} + +bool RequestedLayerState::hasBlur() const { + return backgroundBlurRadius > 0 || blurRegions.size() > 0; +} + +bool RequestedLayerState::hasFrameUpdate() const { + return what & layer_state_t::CONTENT_DIRTY && + (externalTexture || bgColorLayerId != UNASSIGNED_LAYER_ID); +} + +bool RequestedLayerState::hasReadyFrame() const { + return hasFrameUpdate() || changes.test(Changes::SidebandStream) || autoRefresh; +} + +bool RequestedLayerState::hasSidebandStreamFrame() const { + return hasFrameUpdate() && sidebandStream.get(); +} + +bool RequestedLayerState::willReleaseBufferOnLatch() const { + return changes.test(Changes::Buffer) && !externalTexture; +} + +void RequestedLayerState::clearChanges() { + what = 0; + changes.clear(); +} + +} // namespace android::surfaceflinger::frontend diff --git a/services/surfaceflinger/FrontEnd/RequestedLayerState.h b/services/surfaceflinger/FrontEnd/RequestedLayerState.h new file mode 100644 index 0000000000000000000000000000000000000000..0ef50bc60acf6144d3fb4c56e219871cefb9fb9d --- /dev/null +++ b/services/surfaceflinger/FrontEnd/RequestedLayerState.h @@ -0,0 +1,126 @@ +/* + * 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 +#include +#include +#include "Scheduler/LayerInfo.h" + +#include "LayerCreationArgs.h" +#include "TransactionState.h" + +namespace android::surfaceflinger::frontend { + +// Stores client requested states for a layer. +// This struct does not store any other states or states pertaining to +// other layers. Links to other layers that are part of the client +// requested state such as parent are translated to layer id so +// we can avoid extending the lifetime of layer handles. +struct RequestedLayerState : layer_state_t { + // Changes in state after merging with new state. This includes additional state + // changes found in layer_state_t::what. + enum class Changes : uint32_t { + Created = 1u << 0, + Destroyed = 1u << 1, + Hierarchy = 1u << 2, + Geometry = 1u << 3, + Content = 1u << 4, + Input = 1u << 5, + Z = 1u << 6, + Mirror = 1u << 7, + Parent = 1u << 8, + RelativeParent = 1u << 9, + Metadata = 1u << 10, + Visibility = 1u << 11, + AffectsChildren = 1u << 12, + FrameRate = 1u << 13, + VisibleRegion = 1u << 14, + Buffer = 1u << 15, + SidebandStream = 1u << 16, + Animation = 1u << 17, + }; + static Rect reduce(const Rect& win, const Region& exclude); + RequestedLayerState(const LayerCreationArgs&); + void merge(const ResolvedComposerState&); + void clearChanges(); + + // Currently we only care about the primary display + ui::Transform getTransform(uint32_t displayRotationFlags) const; + ui::Size getUnrotatedBufferSize(uint32_t displayRotationFlags) const; + bool canBeDestroyed() const; + bool isRoot() const; + bool isHiddenByPolicy() const; + half4 getColor() const; + Rect getBufferSize(uint32_t displayRotationFlags) const; + Rect getCroppedBufferSize(const Rect& bufferSize) const; + Rect getBufferCrop() const; + std::string getDebugString() const; + std::string getDebugStringShort() const; + aidl::android::hardware::graphics::composer3::Composition getCompositionType() const; + bool hasValidRelativeParent() const; + bool hasInputInfo() const; + bool hasBlur() const; + bool hasFrameUpdate() const; + bool hasReadyFrame() const; + bool hasSidebandStreamFrame() const; + bool willReleaseBufferOnLatch() const; + + // Layer serial number. This gives layers an explicit ordering, so we + // have a stable sort order when their layer stack and Z-order are + // the same. + const uint32_t id; + const std::string name; + bool canBeRoot = false; + const uint32_t layerCreationFlags; + const uint32_t textureName; + // The owner of the layer. If created from a non system process, it will be the calling uid. + // If created from a system process, the value can be passed in. + const uid_t ownerUid; + // The owner pid of the layer. If created from a non system process, it will be the calling pid. + // If created from a system process, the value can be passed in. + const pid_t ownerPid; + bool dataspaceRequested; + bool hasColorTransform; + bool premultipliedAlpha{true}; + // This layer can be a cursor on some displays. + bool potentialCursor{false}; + bool protectedByApp{false}; // application requires protected path to external sink + ui::Transform requestedTransform; + std::shared_ptr acquireFenceTime; + std::shared_ptr externalTexture; + gui::GameMode gameMode; + scheduler::LayerInfo::FrameRate requestedFrameRate; + uint32_t parentId = UNASSIGNED_LAYER_ID; + uint32_t relativeParentId = UNASSIGNED_LAYER_ID; + uint32_t layerIdToMirror = UNASSIGNED_LAYER_ID; + ui::LayerStack layerStackToMirror = ui::INVALID_LAYER_STACK; + uint32_t touchCropId = UNASSIGNED_LAYER_ID; + uint32_t bgColorLayerId = UNASSIGNED_LAYER_ID; + uint64_t barrierFrameNumber = 0; + uint32_t barrierProducerId = 0; + + // book keeping states + bool handleAlive = true; + bool isRelativeOf = false; + std::vector mirrorIds{}; + ftl::Flags changes; + bool bgColorLayer = false; +}; + +} // namespace android::surfaceflinger::frontend diff --git a/services/surfaceflinger/FrontEnd/SwapErase.h b/services/surfaceflinger/FrontEnd/SwapErase.h new file mode 100644 index 0000000000000000000000000000000000000000..0061c53e62d89d80d9beed443309f6946463f6c6 --- /dev/null +++ b/services/surfaceflinger/FrontEnd/SwapErase.h @@ -0,0 +1,48 @@ +/* + * 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::surfaceflinger::frontend { +// Erases the first element in vec that matches value. This is a more optimal way to +// remove an element from a vector that avoids relocating all the elements after the one +// that is erased. +template +bool swapErase(std::vector& vec, const T& value) { + bool found = false; + auto it = std::find(vec.begin(), vec.end(), value); + if (it != vec.end()) { + std::iter_swap(it, vec.end() - 1); + vec.erase(vec.end() - 1); + found = true; + } + return found; +} + +// Similar to swapErase(std::vector& vec, const T& value) but erases the first element +// that returns true for predicate. +template +void swapErase(std::vector& vec, P predicate) { + auto it = std::find_if(vec.begin(), vec.end(), predicate); + if (it != vec.end()) { + std::iter_swap(it, vec.end() - 1); + vec.erase(vec.end() - 1); + } +} + +} // namespace android::surfaceflinger::frontend diff --git a/services/surfaceflinger/FrontEnd/TransactionHandler.cpp b/services/surfaceflinger/FrontEnd/TransactionHandler.cpp new file mode 100644 index 0000000000000000000000000000000000000000..9cbe0bb22455612dea857032025bba9307a682e2 --- /dev/null +++ b/services/surfaceflinger/FrontEnd/TransactionHandler.cpp @@ -0,0 +1,206 @@ +/* + * 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. + */ + +// #define LOG_NDEBUG 0 +#undef LOG_TAG +#define LOG_TAG "TransactionHandler" +#define ATRACE_TAG ATRACE_TAG_GRAPHICS + +#include +#include +#include + +#include "TransactionHandler.h" + +namespace android::surfaceflinger::frontend { + +void TransactionHandler::queueTransaction(TransactionState&& state) { + mLocklessTransactionQueue.push(std::move(state)); + mPendingTransactionCount.fetch_add(1); + ATRACE_INT("TransactionQueue", static_cast(mPendingTransactionCount.load())); +} + +std::vector TransactionHandler::flushTransactions() { + while (!mLocklessTransactionQueue.isEmpty()) { + auto maybeTransaction = mLocklessTransactionQueue.pop(); + if (!maybeTransaction.has_value()) { + break; + } + auto transaction = maybeTransaction.value(); + mPendingTransactionQueues[transaction.applyToken].emplace(std::move(transaction)); + } + + // Collect transaction that are ready to be applied. + std::vector transactions; + TransactionFlushState flushState; + flushState.queueProcessTime = systemTime(); + // Transactions with a buffer pending on a barrier may be on a different applyToken + // than the transaction which satisfies our barrier. In fact this is the exact use case + // that the primitive is designed for. This means we may first process + // the barrier dependent transaction, determine it ineligible to complete + // and then satisfy in a later inner iteration of flushPendingTransactionQueues. + // The barrier dependent transaction was eligible to be presented in this frame + // but we would have prevented it without case. To fix this we continually + // loop through flushPendingTransactionQueues until we perform an iteration + // where the number of transactionsPendingBarrier doesn't change. This way + // we can continue to resolve dependency chains of barriers as far as possible. + int lastTransactionsPendingBarrier = 0; + int transactionsPendingBarrier = 0; + do { + lastTransactionsPendingBarrier = transactionsPendingBarrier; + // Collect transactions that are ready to be applied. + transactionsPendingBarrier = flushPendingTransactionQueues(transactions, flushState); + } while (lastTransactionsPendingBarrier != transactionsPendingBarrier); + + applyUnsignaledBufferTransaction(transactions, flushState); + + mPendingTransactionCount.fetch_sub(transactions.size()); + ATRACE_INT("TransactionQueue", static_cast(mPendingTransactionCount.load())); + return transactions; +} + +void TransactionHandler::applyUnsignaledBufferTransaction( + std::vector& transactions, TransactionFlushState& flushState) { + if (!flushState.queueWithUnsignaledBuffer) { + return; + } + + // only apply an unsignaled buffer transaction if it's the first one + if (!transactions.empty()) { + ATRACE_NAME("fence unsignaled"); + return; + } + + auto it = mPendingTransactionQueues.find(flushState.queueWithUnsignaledBuffer); + LOG_ALWAYS_FATAL_IF(it == mPendingTransactionQueues.end(), + "Could not find queue with unsignaled buffer!"); + + auto& queue = it->second; + popTransactionFromPending(transactions, flushState, queue); + if (queue.empty()) { + it = mPendingTransactionQueues.erase(it); + } +} + +void TransactionHandler::popTransactionFromPending(std::vector& transactions, + TransactionFlushState& flushState, + std::queue& queue) { + auto& transaction = queue.front(); + // Transaction is ready move it from the pending queue. + flushState.firstTransaction = false; + removeFromStalledTransactions(transaction.id); + transactions.emplace_back(std::move(transaction)); + queue.pop(); + + auto& readyToApplyTransaction = transactions.back(); + readyToApplyTransaction.traverseStatesWithBuffers([&](const layer_state_t& state) { + const bool frameNumberChanged = + state.bufferData->flags.test(BufferData::BufferDataChange::frameNumberChanged); + if (frameNumberChanged) { + flushState.bufferLayersReadyToPresent.emplace_or_replace(state.surface.get(), + state.bufferData->frameNumber); + } else { + // Barrier function only used for BBQ which always includes a frame number. + // This value only used for barrier logic. + flushState.bufferLayersReadyToPresent + .emplace_or_replace(state.surface.get(), std::numeric_limits::max()); + } + }); +} + +TransactionHandler::TransactionReadiness TransactionHandler::applyFilters( + TransactionFlushState& flushState) { + auto ready = TransactionReadiness::Ready; + for (auto& filter : mTransactionReadyFilters) { + auto perFilterReady = filter(flushState); + switch (perFilterReady) { + case TransactionReadiness::NotReady: + case TransactionReadiness::NotReadyBarrier: + return perFilterReady; + + case TransactionReadiness::NotReadyUnsignaled: + // If one of the filters allows latching an unsignaled buffer, latch this ready + // state. + ready = perFilterReady; + break; + case TransactionReadiness::Ready: + continue; + } + } + return ready; +} + +int TransactionHandler::flushPendingTransactionQueues(std::vector& transactions, + TransactionFlushState& flushState) { + int transactionsPendingBarrier = 0; + auto it = mPendingTransactionQueues.begin(); + while (it != mPendingTransactionQueues.end()) { + auto& [applyToken, queue] = *it; + while (!queue.empty()) { + auto& transaction = queue.front(); + flushState.transaction = &transaction; + auto ready = applyFilters(flushState); + if (ready == TransactionReadiness::NotReadyBarrier) { + transactionsPendingBarrier++; + break; + } else if (ready == TransactionReadiness::NotReady) { + break; + } else if (ready == TransactionReadiness::NotReadyUnsignaled) { + // We maybe able to latch this transaction if it's the only transaction + // ready to be applied. + flushState.queueWithUnsignaledBuffer = applyToken; + break; + } + // ready == TransactionReadiness::Ready + popTransactionFromPending(transactions, flushState, queue); + } + + if (queue.empty()) { + it = mPendingTransactionQueues.erase(it); + } else { + it = std::next(it, 1); + } + } + return transactionsPendingBarrier; +} + +void TransactionHandler::addTransactionReadyFilter(TransactionFilter&& filter) { + mTransactionReadyFilters.emplace_back(std::move(filter)); +} + +bool TransactionHandler::hasPendingTransactions() { + return !mPendingTransactionQueues.empty() || !mLocklessTransactionQueue.isEmpty(); +} + +void TransactionHandler::onTransactionQueueStalled(uint64_t transactionId, + sp& listener, + const std::string& reason) { + if (std::find(mStalledTransactions.begin(), mStalledTransactions.end(), transactionId) != + mStalledTransactions.end()) { + return; + } + + mStalledTransactions.push_back(transactionId); + listener->onTransactionQueueStalled(String8(reason.c_str())); +} + +void TransactionHandler::removeFromStalledTransactions(uint64_t id) { + auto it = std::find(mStalledTransactions.begin(), mStalledTransactions.end(), id); + if (it != mStalledTransactions.end()) { + mStalledTransactions.erase(it); + } +} +} // namespace android::surfaceflinger::frontend diff --git a/services/surfaceflinger/FrontEnd/TransactionHandler.h b/services/surfaceflinger/FrontEnd/TransactionHandler.h new file mode 100644 index 0000000000000000000000000000000000000000..865835f92d0e6dc16f935971b19bdafadd1206e4 --- /dev/null +++ b/services/surfaceflinger/FrontEnd/TransactionHandler.h @@ -0,0 +1,85 @@ +/* + * 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 +#include + +#include +#include +#include +#include +#include + +namespace android { + +class TestableSurfaceFlinger; +namespace surfaceflinger::frontend { + +class TransactionHandler { +public: + struct TransactionFlushState { + TransactionState* transaction; + bool firstTransaction = true; + nsecs_t queueProcessTime = 0; + // Layer handles that have transactions with buffers that are ready to be applied. + ftl::SmallMap + bufferLayersReadyToPresent = {}; + // Tracks the queue with an unsignaled buffer. This is used to handle + // LatchUnsignaledConfig::AutoSingleLayer to ensure we only apply an unsignaled buffer + // if it's the only transaction that is ready to be applied. + sp queueWithUnsignaledBuffer = nullptr; + }; + enum class TransactionReadiness { + // Transaction is ready to be applied + Ready, + // Transaction has unmet conditions (fence, present time, etc) and cannot be applied. + NotReady, + // Transaction is waiting on a barrier (another buffer to be latched first) + NotReadyBarrier, + // Transaction has an unsignaled fence but can be applied if it's the only transaction + NotReadyUnsignaled, + }; + using TransactionFilter = std::function; + + bool hasPendingTransactions(); + std::vector flushTransactions(); + void addTransactionReadyFilter(TransactionFilter&&); + void queueTransaction(TransactionState&&); + void onTransactionQueueStalled(uint64_t transactionId, sp&, + const std::string& reason); + void removeFromStalledTransactions(uint64_t transactionId); + +private: + // For unit tests + friend class ::android::TestableSurfaceFlinger; + + int flushPendingTransactionQueues(std::vector&, TransactionFlushState&); + void applyUnsignaledBufferTransaction(std::vector&, TransactionFlushState&); + void popTransactionFromPending(std::vector&, TransactionFlushState&, + std::queue&); + TransactionReadiness applyFilters(TransactionFlushState&); + std::unordered_map, std::queue, IListenerHash> + mPendingTransactionQueues; + LocklessQueue mLocklessTransactionQueue; + std::atomic mPendingTransactionCount = 0; + ftl::SmallVector mTransactionReadyFilters; + std::vector mStalledTransactions; +}; +} // namespace surfaceflinger::frontend +} // namespace android diff --git a/services/surfaceflinger/FrontEnd/Update.h b/services/surfaceflinger/FrontEnd/Update.h new file mode 100644 index 0000000000000000000000000000000000000000..e1449b6e1be97efec9032d77e81b5417b79ef33f --- /dev/null +++ b/services/surfaceflinger/FrontEnd/Update.h @@ -0,0 +1,52 @@ +/* + * 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. + */ + +#pragma once + +#include + +#include "FrontEnd/LayerCreationArgs.h" +#include "RequestedLayerState.h" +#include "TransactionState.h" + +namespace android { +struct LayerCreatedState { + LayerCreatedState(const wp& layer, const wp& parent, bool addToRoot) + : layer(layer), initialParent(parent), addToRoot(addToRoot) {} + wp layer; + // Indicates the initial parent of the created layer, only used for creating layer in + // SurfaceFlinger. If nullptr, it may add the created layer into the current root layers. + wp initialParent; + // Indicates whether the layer getting created should be added at root if there's no parent + // and has permission ACCESS_SURFACE_FLINGER. If set to false and no parent, the layer will + // be added offscreen. + bool addToRoot; +}; +} // namespace android + +namespace android::surfaceflinger::frontend { + +// Atomic set of changes affecting layer state. These changes are queued in binder threads and +// applied every vsync. +struct Update { + std::vector transactions; + std::vector layerCreatedStates; + std::vector> newLayers; + std::vector layerCreationArgs; + std::vector destroyedHandles; +}; + +} // namespace android::surfaceflinger::frontend diff --git a/services/surfaceflinger/FrontEnd/readme.md b/services/surfaceflinger/FrontEnd/readme.md new file mode 100644 index 0000000000000000000000000000000000000000..e5f51a57738049df697797b90b439924f680cbe5 --- /dev/null +++ b/services/surfaceflinger/FrontEnd/readme.md @@ -0,0 +1,110 @@ +# SurfaceFlinger FrontEnd + +SurfaceFlinger FrontEnd implements the client APIs that describe how buffers should be +composited on the screen. Layers are used to capture how the buffer should be composited +and each buffer is associated with a Layer. Transactions contain an atomic set of changes +to one or more of these layers. The FrontEnd consumes these transactions, maintains the +layer lifecycle, and provides a snapshot to the composition engine every frame that +describes how a set of buffers should be composited. + + + +## Layers +Layers are used to describe how a buffer should be placed on the display relative to other +buffers. They are represented as a hierarchy, similar to a scene graph. Child layers can +inherit some properties from their parents, which allows higher-level system components to +maintain policies at different levels without needing to understand the entire hierarchy. +This allows control to be delegated to different parts of the system - such as SystemServer, +SysUI and Apps. + +### Layer Lifecycle +Layer is created by a client. The client receives a strong binder reference to the layer +handle, which will keep the layer alive as long as the client holds the reference. The +layer can also be kept alive if the layer has a parent, since the parent will hold a +strong reference to the children. If the layer is not reachable but its handle is alive, +the layer will be offscreen and its resources will not be freed. Clients must explicitly +release all references to the handle as soon as it's done with the layer. It's strongly +recommended to explicitly release the layer in Java and not rely on the GC. + + + +## Transactions +Transactions contain a group of changes to one or more layers that are applied together. +Transactions can be merged to apply a set of changes atomically. Merges are associative, +meaning how you group the merges does not matter, but they are not commutative, meaning +that the order in which you merge them does. +For example: + +`Transaction a; a.setAlpha(sc, 2);` + +`Transaction b; b.setAlpha(sc, 4);` + +`a.merge(b)` is not the same as `b.merge(a)` + +

+ +`Transaction c; c.setAlpha(sc, 6);` + +`a.merge(b).merge(c)` is the same as `b.merge(c); a.merge(b);` + +Transactions are queued in SurfaceFlinger per ApplyToken so order is only guaranteed for +Transactions with the same applyToken. By default each process and each buffer producer +provides a unique ApplyToken. This prevents clients from affecting one another, and possibly +slowing each other down. + + + +## Architecture +SurfaceFlinger FrontEnd intends to optimize for predictability and performance because state +generation is on the hotpath. Simple buffer updates should be as fast as possible, and they +should be consistently fast. This means avoiding contention (e.g., locks) and context +switching. We also want to avoid doing anything that does not contribute to putting a pixel +on the display. + +The pipeline can be broken down into five stages: +- Queue and filter transactions that are ready to be committed. +- Handle layer lifecycles and update server-side state per layer. +- Generate and/or update the traversal trees. +- Generate a z-ordered list of snapshots. +- Emit callbacks back to clients + + +### TransactionHandler +TransactionHandler is responsible for queuing and filtering transactions that are ready to +be applied. On commit, we filter the transactions that are ready. We provide an interface +for other components to apply their own filter to determine if a transaction is ready to be +applied. + + +### LayerLifecycleManager +RequestedLayerState is a simple data class that stores the server side layer state. +Transactions are merged into this state, similar to how transactions can be merged on the +client side. The states can always be reconstructed from LayerCreationArgs and a list of +transactions. LayerLifecycleManager keeps track of Layer handle lifecycle and the layer +lifecycle itself. It consumes a list of transactions and generates a list of server side +states and change flags. Other components can register to listen to layer lifecycles. + + +### LayerHierarchyBuilder +LayerHierarchyBuilder consumes a list of RequestedLayerStates to generate a LayerHierarchy. +The hierarchy provides functions for breadth-first traversal and z-order traversal of the +entire tree or a subtree. Internally, the hierarchy is represented by a graph. Mirrored +layers are represented by the same node in the graph with multiple parents. This allows us +to implement mirroring without cloning Layers and maintaining complex hierarchies. + + +### LayerSnapshotBuilder +LayerSnapshotBuilder consumes a LayerHierarchy along with a list of RequestedLayerStates to +generate a flattened z-ordered list of LayerSnapshots. LayerSnapshots contain all the data +required for CompositionEngine and RenderEngine. It has no dependencies to FrontEnd, or the +LayerHierarchy used to create them. They can be cloned and consumed freely. Other consumers +like WindowInfo listeners (input and accessibility) also updated from these snapshots. + +Change flags are used to efficiently traverse this hierarchy where possible. This allows us +to support short circuiting parts of the hierarchy, partial hierarchy updates and fast paths +for buffer updates. + + +While they can be cloned, the current implementation moves the snapshot from FrontEnd to +CompositionEngine to avoid needless work in the hotpath. For snapshot consumers not critical +to composition, the goal is to clone the snapshots and consume them on a background thread. diff --git a/services/surfaceflinger/HdrLayerInfoReporter.cpp b/services/surfaceflinger/HdrLayerInfoReporter.cpp index c06e300cdce60a68c7ac5cb2b53884bab4036c9b..9eefbe463dde0f08e407359af2d53a184ad2b83a 100644 --- a/services/surfaceflinger/HdrLayerInfoReporter.cpp +++ b/services/surfaceflinger/HdrLayerInfoReporter.cpp @@ -40,7 +40,8 @@ void HdrLayerInfoReporter::dispatchHdrLayerInfo(const HdrLayerInfo& info) { for (const auto& listener : toInvoke) { ATRACE_NAME("invoking onHdrLayerInfoChanged"); - listener->onHdrLayerInfoChanged(info.numberOfHdrLayers, info.maxW, info.maxH, info.flags); + listener->onHdrLayerInfoChanged(info.numberOfHdrLayers, info.maxW, info.maxH, info.flags, + info.maxDesiredHdrSdrRatio); } } @@ -51,7 +52,7 @@ void HdrLayerInfoReporter::binderDied(const wp& who) { void HdrLayerInfoReporter::addListener(const sp& listener) { sp asBinder = IInterface::asBinder(listener); - asBinder->linkToDeath(this); + asBinder->linkToDeath(sp::fromExisting(this)); std::lock_guard lock(mMutex); mListeners.emplace(wp(asBinder), TrackedListener{listener, HdrLayerInfo{}}); } diff --git a/services/surfaceflinger/HdrLayerInfoReporter.h b/services/surfaceflinger/HdrLayerInfoReporter.h index 4ada2b637241bab0f5ea031270e584a4e1c5fe6f..bf7c7753d2bc91f6b5478583e949f89c7b8917ed 100644 --- a/services/surfaceflinger/HdrLayerInfoReporter.h +++ b/services/surfaceflinger/HdrLayerInfoReporter.h @@ -33,13 +33,29 @@ public: int32_t maxW = 0; int32_t maxH = 0; int32_t flags = 0; + // Counter-intuitively a value of "1" means "as much as you can give me" due to "1" being + // the default value for all layers, so any HDR layer with a value of 1.f means no + // reduced maximum has been requested + // TODO: Should the max desired ratio have a better meaning for HLG/PQ so this can be + // eliminated? If we assume an SDR white point of even just 100 nits for those content + // then HLG could have a meaningful max ratio of 10.f and PQ of 100.f instead of needing + // to treat 1.f as "uncapped" + // With peak display brightnesses exceeding 1,000 nits currently, HLG's request could + // actually be satisfied in some ambient conditions such that limiting that max for that + // content in theory makes sense + float maxDesiredHdrSdrRatio = 0.f; bool operator==(const HdrLayerInfo& other) const { return numberOfHdrLayers == other.numberOfHdrLayers && maxW == other.maxW && - maxH == other.maxH && flags == other.flags; + maxH == other.maxH && flags == other.flags && + maxDesiredHdrSdrRatio == other.maxDesiredHdrSdrRatio; } bool operator!=(const HdrLayerInfo& other) const { return !(*this == other); } + + void mergeDesiredRatio(float update) { + maxDesiredHdrSdrRatio = std::max(maxDesiredHdrSdrRatio, update); + } }; HdrLayerInfoReporter() = default; diff --git a/services/surfaceflinger/Layer.cpp b/services/surfaceflinger/Layer.cpp index a31cdf0f2389e4f98205bbcaf3cc5a3d1294b30f..f12aab766e923121da4dee084fa6a780fffb47a1 100644 --- a/services/surfaceflinger/Layer.cpp +++ b/services/surfaceflinger/Layer.cpp @@ -29,6 +29,7 @@ #include #include #include +#include #include #include #include @@ -41,6 +42,7 @@ #include #include #include +#include #include #include #include @@ -50,8 +52,11 @@ #include #include #include +#include #include #include +#include +#include #include #include #include @@ -60,75 +65,117 @@ #include #include +#include #include -#include "BufferLayer.h" -#include "Colorizer.h" #include "DisplayDevice.h" #include "DisplayHardware/HWComposer.h" -#include "EffectLayer.h" #include "FrameTimeline.h" #include "FrameTracer/FrameTracer.h" +#include "FrontEnd/LayerCreationArgs.h" +#include "FrontEnd/LayerHandle.h" #include "LayerProtoHelper.h" -#include "LayerRejecter.h" -#include "MonitoredProducer.h" #include "MutexUtils.h" #include "SurfaceFlinger.h" #include "TimeStats/TimeStats.h" #include "TunnelModeEnabledReporter.h" #define DEBUG_RESIZE 0 +#define EARLY_RELEASE_ENABLED false namespace android { namespace { constexpr int kDumpTableRowLength = 159; + const ui::Transform kIdentityTransform; + +bool assignTransform(ui::Transform* dst, ui::Transform& from) { + if (*dst == from) { + return false; + } + *dst = from; + return true; +} + +TimeStats::SetFrameRateVote frameRateToSetFrameRateVotePayload(Layer::FrameRate frameRate) { + using FrameRateCompatibility = TimeStats::SetFrameRateVote::FrameRateCompatibility; + using Seamlessness = TimeStats::SetFrameRateVote::Seamlessness; + const auto frameRateCompatibility = [frameRate] { + switch (frameRate.type) { + case Layer::FrameRateCompatibility::Default: + return FrameRateCompatibility::Default; + case Layer::FrameRateCompatibility::ExactOrMultiple: + return FrameRateCompatibility::ExactOrMultiple; + default: + return FrameRateCompatibility::Undefined; + } + }(); + + const auto seamlessness = [frameRate] { + switch (frameRate.seamlessness) { + case scheduler::Seamlessness::OnlySeamless: + return Seamlessness::ShouldBeSeamless; + case scheduler::Seamlessness::SeamedAndSeamless: + return Seamlessness::NotRequired; + default: + return Seamlessness::Undefined; + } + }(); + + return TimeStats::SetFrameRateVote{.frameRate = frameRate.rate.getValue(), + .frameRateCompatibility = frameRateCompatibility, + .seamlessness = seamlessness}; +} + } // namespace using namespace ftl::flag_operators; using base::StringAppendF; +using frontend::LayerSnapshot; +using frontend::RoundedCornerState; +using gui::GameMode; +using gui::LayerMetadata; using gui::WindowInfo; using PresentState = frametimeline::SurfaceFrame::PresentState; -std::atomic Layer::sSequence{1}; - Layer::Layer(const LayerCreationArgs& args) - : sequence(args.sequence.value_or(sSequence++)), - mFlinger(args.flinger), + : sequence(args.sequence), + mFlinger(sp::fromExisting(args.flinger)), mName(base::StringPrintf("%s#%d", args.name.c_str(), sequence)), mClientRef(args.client), - mWindowType(static_cast(args.metadata.getInt32(METADATA_WINDOW_TYPE, 0))), - mLayerCreationFlags(args.flags) { + mWindowType(static_cast( + args.metadata.getInt32(gui::METADATA_WINDOW_TYPE, 0))), + mLayerCreationFlags(args.flags), + mBorderEnabled(false), + mTextureName(args.textureName), + mLegacyLayerFE(args.flinger->getFactory().createLayerFE(mName)) { + ALOGV("Creating Layer %s", getDebugName()); + uint32_t layerFlags = 0; if (args.flags & ISurfaceComposerClient::eHidden) layerFlags |= layer_state_t::eLayerHidden; if (args.flags & ISurfaceComposerClient::eOpaque) layerFlags |= layer_state_t::eLayerOpaque; if (args.flags & ISurfaceComposerClient::eSecure) layerFlags |= layer_state_t::eLayerSecure; if (args.flags & ISurfaceComposerClient::eSkipScreenshot) layerFlags |= layer_state_t::eLayerSkipScreenshot; - if (args.sequence) { - sSequence = *args.sequence + 1; - } mDrawingState.flags = layerFlags; - mDrawingState.active_legacy.transform.set(0, 0); mDrawingState.crop.makeInvalid(); - mDrawingState.requestedCrop = mDrawingState.crop; mDrawingState.z = 0; mDrawingState.color.a = 1.0f; mDrawingState.layerStack = ui::DEFAULT_LAYER_STACK; mDrawingState.sequence = 0; - mDrawingState.requested_legacy = mDrawingState.active_legacy; - mDrawingState.width = UINT32_MAX; - mDrawingState.height = UINT32_MAX; mDrawingState.transform.set(0, 0); mDrawingState.frameNumber = 0; + mDrawingState.barrierFrameNumber = 0; + mDrawingState.producerId = 0; + mDrawingState.barrierProducerId = 0; mDrawingState.bufferTransform = 0; mDrawingState.transformToDisplayInverse = false; mDrawingState.crop.makeInvalid(); mDrawingState.acquireFence = sp::make(-1); mDrawingState.acquireFenceTime = std::make_shared(mDrawingState.acquireFence); - mDrawingState.dataspace = ui::Dataspace::UNKNOWN; + mDrawingState.dataspace = ui::Dataspace::V0_SRGB; mDrawingState.hdrMetadata.validTypes = 0; mDrawingState.surfaceDamageRegion = Region::INVALID_REGION; mDrawingState.cornerRadius = 0.0f; @@ -146,6 +193,7 @@ Layer::Layer(const LayerCreationArgs& args) mDrawingState.isTrustedOverlay = false; mDrawingState.dropInputMode = gui::DropInputMode::NONE; mDrawingState.dimmingEnabled = true; + mDrawingState.defaultFrameRateCompatibility = FrameRateCompatibility::Default; if (args.flags & ISurfaceComposerClient::eNoColorFill) { // Set an invalid color so there is no color fill. @@ -153,22 +201,22 @@ Layer::Layer(const LayerCreationArgs& args) mDrawingState.color.g = -1.0_hf; mDrawingState.color.b = -1.0_hf; } - CompositorTiming compositorTiming; - args.flinger->getCompositorTiming(&compositorTiming); - mFrameTracker.setDisplayRefreshPeriod(compositorTiming.interval); - mCallingPid = args.callingPid; - mCallingUid = args.callingUid; + mFrameTracker.setDisplayRefreshPeriod( + args.flinger->mScheduler->getPacesetterVsyncPeriod().ns()); - if (mCallingUid == AID_GRAPHICS || mCallingUid == AID_SYSTEM) { - // If the system didn't send an ownerUid, use the callingUid for the ownerUid. - mOwnerUid = args.metadata.getInt32(METADATA_OWNER_UID, mCallingUid); - mOwnerPid = args.metadata.getInt32(METADATA_OWNER_PID, mCallingPid); - } else { - // A create layer request from a non system request cannot specify the owner uid - mOwnerUid = mCallingUid; - mOwnerPid = mCallingPid; - } + mOwnerUid = args.ownerUid; + mOwnerPid = args.ownerPid; + + mPremultipliedAlpha = !(args.flags & ISurfaceComposerClient::eNonPremultiplied); + mPotentialCursor = args.flags & ISurfaceComposerClient::eCursorWindow; + mProtectedByApp = args.flags & ISurfaceComposerClient::eProtectedByApp; + + mSnapshot->sequence = sequence; + mSnapshot->name = getDebugName(); + mSnapshot->textureName = mTextureName; + mSnapshot->premultipliedAlpha = mPremultipliedAlpha; + mSnapshot->parentTransform = {}; } void Layer::onFirstRef() { @@ -176,10 +224,28 @@ void Layer::onFirstRef() { } Layer::~Layer() { - sp c(mClientRef.promote()); - if (c != 0) { - c->detachLayer(this); + LOG_ALWAYS_FATAL_IF(std::this_thread::get_id() != mFlinger->mMainThreadId, + "Layer destructor called off the main thread."); + + // The original layer and the clone layer share the same texture and buffer. Therefore, only + // one of the layers, in this case the original layer, needs to handle the deletion. The + // original layer and the clone should be removed at the same time so there shouldn't be any + // issue with the clone layer trying to use the texture. + if (mBufferInfo.mBuffer != nullptr) { + callReleaseBufferCallback(mDrawingState.releaseBufferListener, + mBufferInfo.mBuffer->getBuffer(), mBufferInfo.mFrameNumber, + mBufferInfo.mFence); + } + if (!isClone()) { + // The original layer and the clone layer share the same texture. Therefore, only one of + // the layers, in this case the original layer, needs to handle the deletion. The original + // layer and the clone should be removed at the same time so there shouldn't be any issue + // with the clone layer trying to use the deleted texture. + mFlinger->deleteTextureAsync(mTextureName); } + const int32_t layerId = getSequence(); + mFlinger->mTimeStats->onDestroy(layerId); + mFlinger->mFrameTracer->onDestroy(layerId); mFrameTracker.logAndResetStats(mName); mFlinger->onLayerDestroyed(this); @@ -188,33 +254,19 @@ Layer::~Layer() { mFlinger->mTunnelModeEnabledReporter->decrementTunnelModeCount(); } if (mHadClonedChild) { - mFlinger->mNumClones--; + auto& roots = mFlinger->mLayerMirrorRoots; + roots.erase(std::remove(roots.begin(), roots.end(), this), roots.end()); + } + if (hasTrustedPresentationListener()) { + mFlinger->mNumTrustedPresentationListeners--; + updateTrustedPresentationState(nullptr, nullptr, -1 /* time_in_ms */, true /* leaveState*/); } -} - -LayerCreationArgs::LayerCreationArgs(SurfaceFlinger* flinger, sp client, std::string name, - uint32_t flags, LayerMetadata metadata) - : flinger(flinger), - client(std::move(client)), - name(std::move(name)), - flags(flags), - metadata(std::move(metadata)) { - IPCThreadState* ipc = IPCThreadState::self(); - callingPid = ipc->getCallingPid(); - callingUid = ipc->getCallingUid(); } // --------------------------------------------------------------------------- // callbacks // --------------------------------------------------------------------------- -/* - * onLayerDisplayed is only meaningful for BufferLayer, but, is called through - * Layer. So, the implementation is done in BufferLayer. When called on a - * EffectLayer object, it's essentially a NOP. - */ -void Layer::onLayerDisplayed(ftl::SharedFuture) {} - void Layer::removeRelativeZ(const std::vector& layersInTree) { if (mDrawingState.zOrderRelativeOf == nullptr) { return; @@ -227,7 +279,7 @@ void Layer::removeRelativeZ(const std::vector& layersInTree) { } if (!std::binary_search(layersInTree.begin(), layersInTree.end(), strongRelative.get())) { - strongRelative->removeZOrderRelative(this); + strongRelative->removeZOrderRelative(wp::fromExisting(this)); mFlinger->setTransactionFlags(eTraversalNeeded); setZOrderRelativeOf(nullptr); } @@ -238,14 +290,15 @@ void Layer::removeFromCurrentState() { mRemovedFromDrawingState = true; mFlinger->mScheduler->deregisterLayer(this); } + updateTrustedPresentationState(nullptr, nullptr, -1 /* time_in_ms */, true /* leaveState*/); - mFlinger->markLayerPendingRemovalLocked(this); + mFlinger->markLayerPendingRemovalLocked(sp::fromExisting(this)); } sp Layer::getRootLayer() { sp parent = getParent(); if (parent == nullptr) { - return this; + return sp::fromExisting(this); } return parent->getRootLayer(); } @@ -290,7 +343,8 @@ sp Layer::getHandle() { return nullptr; } mGetHandleCalled = true; - return new Handle(mFlinger, this); + mHandleAlive = true; + return sp::make(mFlinger, sp::fromExisting(this)); } // --------------------------------------------------------------------------- @@ -337,6 +391,107 @@ FloatRect Layer::getBounds(const Region& activeTransparentRegion) const { return reduce(mBounds, activeTransparentRegion); } +// No early returns. +void Layer::updateTrustedPresentationState(const DisplayDevice* display, + const frontend::LayerSnapshot* snapshot, + int64_t time_in_ms, bool leaveState) { + if (!hasTrustedPresentationListener()) { + return; + } + const bool lastState = mLastComputedTrustedPresentationState; + mLastComputedTrustedPresentationState = false; + + if (!leaveState) { + const auto outputLayer = findOutputLayerForDisplay(display); + if (outputLayer != nullptr) { + if (outputLayer->getState().coveredRegionExcludingDisplayOverlays) { + Region coveredRegion = + *outputLayer->getState().coveredRegionExcludingDisplayOverlays; + mLastComputedTrustedPresentationState = + computeTrustedPresentationState(snapshot->geomLayerBounds, + snapshot->sourceBounds(), coveredRegion, + snapshot->transformedBounds, + snapshot->alpha, + snapshot->geomLayerTransform, + mTrustedPresentationThresholds); + } else { + ALOGE("CoveredRegionExcludingDisplayOverlays was not set for %s. Don't compute " + "TrustedPresentationState", + getDebugName()); + } + } + } + const bool newState = mLastComputedTrustedPresentationState; + if (lastState && !newState) { + // We were in the trusted presentation state, but now we left it, + // emit the callback if needed + if (mLastReportedTrustedPresentationState) { + mLastReportedTrustedPresentationState = false; + mTrustedPresentationListener.invoke(false); + } + // Reset the timer + mEnteredTrustedPresentationStateTime = -1; + } else if (!lastState && newState) { + // We were not in the trusted presentation state, but we entered it, begin the timer + // and make sure this gets called at least once more! + mEnteredTrustedPresentationStateTime = time_in_ms; + mFlinger->forceFutureUpdate(mTrustedPresentationThresholds.stabilityRequirementMs * 1.5); + } + + // Has the timer elapsed, but we are still in the state? Emit a callback if needed + if (!mLastReportedTrustedPresentationState && newState && + (time_in_ms - mEnteredTrustedPresentationStateTime > + mTrustedPresentationThresholds.stabilityRequirementMs)) { + mLastReportedTrustedPresentationState = true; + mTrustedPresentationListener.invoke(true); + } +} + +/** + * See SurfaceComposerClient.h: setTrustedPresentationCallback for discussion + * of how the parameters and thresholds are interpreted. The general spirit is + * to produce an upper bound on the amount of the buffer which was presented. + */ +bool Layer::computeTrustedPresentationState(const FloatRect& bounds, const FloatRect& sourceBounds, + const Region& coveredRegion, + const FloatRect& screenBounds, float alpha, + const ui::Transform& effectiveTransform, + const TrustedPresentationThresholds& thresholds) { + if (alpha < thresholds.minAlpha) { + return false; + } + if (sourceBounds.getWidth() == 0 || sourceBounds.getHeight() == 0) { + return false; + } + if (screenBounds.getWidth() == 0 || screenBounds.getHeight() == 0) { + return false; + } + + const float sx = effectiveTransform.dsdx(); + const float sy = effectiveTransform.dsdy(); + float fractionRendered = std::min(sx * sy, 1.0f); + + float boundsOverSourceW = bounds.getWidth() / (float)sourceBounds.getWidth(); + float boundsOverSourceH = bounds.getHeight() / (float)sourceBounds.getHeight(); + fractionRendered *= boundsOverSourceW * boundsOverSourceH; + + Region tJunctionFreeRegion = Region::createTJunctionFreeRegion(coveredRegion); + // Compute the size of all the rects since they may be disconnected. + float coveredSize = 0; + for (auto rect = tJunctionFreeRegion.begin(); rect < tJunctionFreeRegion.end(); rect++) { + float size = rect->width() * rect->height(); + coveredSize += size; + } + + fractionRendered *= (1 - (coveredSize / (screenBounds.getWidth() * screenBounds.getHeight()))); + + if (fractionRendered < thresholds.minFractionRendered) { + return false; + } + + return true; +} + void Layer::computeBounds(FloatRect parentBounds, ui::Transform parentTransform, float parentShadowRadius) { const State& s(getDrawingState()); @@ -382,6 +537,10 @@ void Layer::computeBounds(FloatRect parentBounds, ui::Transform parentTransform, for (const sp& child : mDrawingChildren) { child->computeBounds(mBounds, mEffectiveTransform, childShadowRadius); } + + if (mPotentialCursor) { + prepareCursorCompositionState(); + } } Rect Layer::getCroppedBufferSize(const State& s) const { @@ -417,40 +576,43 @@ void Layer::prepareBasicGeometryCompositionState() { : Hwc2::IComposerClient::BlendMode::COVERAGE; } - auto* compositionState = editCompositionState(); - compositionState->outputFilter = getOutputFilter(); - compositionState->isVisible = isVisible(); - compositionState->isOpaque = opaque && !usesRoundedCorners && alpha == 1.f; - compositionState->shadowRadius = mEffectiveShadowRadius; + // Please keep in sync with LayerSnapshotBuilder + auto* snapshot = editLayerSnapshot(); + snapshot->outputFilter = getOutputFilter(); + snapshot->isVisible = isVisible(); + snapshot->isOpaque = opaque && !usesRoundedCorners && alpha == 1.f; + snapshot->shadowRadius = mEffectiveShadowRadius; - compositionState->contentDirty = contentDirty; + snapshot->contentDirty = contentDirty; contentDirty = false; - compositionState->geomLayerBounds = mBounds; - compositionState->geomLayerTransform = getTransform(); - compositionState->geomInverseLayerTransform = compositionState->geomLayerTransform.inverse(); - compositionState->transparentRegionHint = getActiveTransparentRegion(drawingState); - - compositionState->blendMode = static_cast(blendMode); - compositionState->alpha = alpha; - compositionState->backgroundBlurRadius = drawingState.backgroundBlurRadius; - compositionState->blurRegions = drawingState.blurRegions; - compositionState->stretchEffect = getStretchEffect(); + snapshot->geomLayerBounds = mBounds; + snapshot->geomLayerTransform = getTransform(); + snapshot->geomInverseLayerTransform = snapshot->geomLayerTransform.inverse(); + snapshot->transparentRegionHint = getActiveTransparentRegion(drawingState); + snapshot->localTransform = getActiveTransform(drawingState); + snapshot->localTransformInverse = snapshot->localTransform.inverse(); + snapshot->blendMode = static_cast(blendMode); + snapshot->alpha = alpha; + snapshot->backgroundBlurRadius = drawingState.backgroundBlurRadius; + snapshot->blurRegions = drawingState.blurRegions; + snapshot->stretchEffect = getStretchEffect(); } void Layer::prepareGeometryCompositionState() { const auto& drawingState{getDrawingState()}; - auto* compositionState = editCompositionState(); - - compositionState->geomBufferSize = getBufferSize(drawingState); - compositionState->geomContentCrop = getBufferCrop(); - compositionState->geomCrop = getCrop(drawingState); - compositionState->geomBufferTransform = getBufferTransform(); - compositionState->geomBufferUsesDisplayInverseTransform = getTransformToDisplayInverse(); - compositionState->geomUsesSourceCrop = usesSourceCrop(); - compositionState->isSecure = isSecure(); - - compositionState->metadata.clear(); + auto* snapshot = editLayerSnapshot(); + + // Please keep in sync with LayerSnapshotBuilder + snapshot->geomBufferSize = getBufferSize(drawingState); + snapshot->geomContentCrop = getBufferCrop(); + snapshot->geomCrop = getCrop(drawingState); + snapshot->geomBufferTransform = getBufferTransform(); + snapshot->geomBufferUsesDisplayInverseTransform = getTransformToDisplayInverse(); + snapshot->geomUsesSourceCrop = usesSourceCrop(); + snapshot->isSecure = isSecure(); + + snapshot->metadata.clear(); const auto& supportedMetadata = mFlinger->getHwComposer().getSupportedLayerGenericMetadata(); for (const auto& [key, mandatory] : supportedMetadata) { const auto& genericLayerMetadataCompatibilityMap = @@ -466,50 +628,97 @@ void Layer::prepareGeometryCompositionState() { continue; } - compositionState->metadata - .emplace(key, compositionengine::GenericLayerMetadataEntry{mandatory, it->second}); + snapshot->metadata.emplace(key, + compositionengine::GenericLayerMetadataEntry{mandatory, + it->second}); } } void Layer::preparePerFrameCompositionState() { const auto& drawingState{getDrawingState()}; - auto* compositionState = editCompositionState(); - - compositionState->forceClientComposition = false; - - compositionState->isColorspaceAgnostic = isColorSpaceAgnostic(); - compositionState->dataspace = getDataSpace(); - compositionState->colorTransform = getColorTransform(); - compositionState->colorTransformIsIdentity = !hasColorTransform(); - compositionState->surfaceDamage = surfaceDamageRegion; - compositionState->hasProtectedContent = isProtected(); - compositionState->dimmingEnabled = isDimmingEnabled(); + // Please keep in sync with LayerSnapshotBuilder + auto* snapshot = editLayerSnapshot(); + + snapshot->forceClientComposition = false; + + snapshot->isColorspaceAgnostic = isColorSpaceAgnostic(); + snapshot->dataspace = getDataSpace(); + snapshot->colorTransform = getColorTransform(); + snapshot->colorTransformIsIdentity = !hasColorTransform(); + snapshot->surfaceDamage = surfaceDamageRegion; + snapshot->hasProtectedContent = isProtected(); + snapshot->dimmingEnabled = isDimmingEnabled(); + snapshot->currentHdrSdrRatio = getCurrentHdrSdrRatio(); + snapshot->desiredHdrSdrRatio = getDesiredHdrSdrRatio(); + snapshot->cachingHint = getCachingHint(); const bool usesRoundedCorners = hasRoundedCorners(); - compositionState->isOpaque = - isOpaque(drawingState) && !usesRoundedCorners && getAlpha() == 1.0_hf; + snapshot->isOpaque = isOpaque(drawingState) && !usesRoundedCorners && getAlpha() == 1.0_hf; // Force client composition for special cases known only to the front-end. // Rounded corners no longer force client composition, since we may use a // hole punch so that the layer will appear to have rounded corners. if (isHdrY410() || drawShadows() || drawingState.blurRegions.size() > 0 || - compositionState->stretchEffect.hasEffect()) { - compositionState->forceClientComposition = true; + snapshot->stretchEffect.hasEffect()) { + snapshot->forceClientComposition = true; } // If there are no visible region changes, we still need to update blur parameters. - compositionState->blurRegions = drawingState.blurRegions; - compositionState->backgroundBlurRadius = drawingState.backgroundBlurRadius; + snapshot->blurRegions = drawingState.blurRegions; + snapshot->backgroundBlurRadius = drawingState.backgroundBlurRadius; // Layer framerate is used in caching decisions. // Retrieve it from the scheduler which maintains an instance of LayerHistory, and store it in // LayerFECompositionState where it would be visible to Flattener. - compositionState->fps = mFlinger->getLayerFramerate(systemTime(), getSequence()); + snapshot->fps = mFlinger->getLayerFramerate(systemTime(), getSequence()); + + if (hasBufferOrSidebandStream()) { + preparePerFrameBufferCompositionState(); + } else { + preparePerFrameEffectsCompositionState(); + } +} + +void Layer::preparePerFrameBufferCompositionState() { + // Please keep in sync with LayerSnapshotBuilder + auto* snapshot = editLayerSnapshot(); + // Sideband layers + if (snapshot->sidebandStream.get() && !snapshot->sidebandStreamHasFrame) { + snapshot->compositionType = + aidl::android::hardware::graphics::composer3::Composition::SIDEBAND; + return; + } else if ((mDrawingState.flags & layer_state_t::eLayerIsDisplayDecoration) != 0) { + snapshot->compositionType = + aidl::android::hardware::graphics::composer3::Composition::DISPLAY_DECORATION; + } else if ((mDrawingState.flags & layer_state_t::eLayerIsRefreshRateIndicator) != 0) { + snapshot->compositionType = + aidl::android::hardware::graphics::composer3::Composition::REFRESH_RATE_INDICATOR; + } else { + // Normal buffer layers + snapshot->hdrMetadata = mBufferInfo.mHdrMetadata; + snapshot->compositionType = mPotentialCursor + ? aidl::android::hardware::graphics::composer3::Composition::CURSOR + : aidl::android::hardware::graphics::composer3::Composition::DEVICE; + } + + snapshot->buffer = getBuffer(); + snapshot->acquireFence = mBufferInfo.mFence; + snapshot->frameNumber = mBufferInfo.mFrameNumber; + snapshot->sidebandStreamHasFrame = false; +} + +void Layer::preparePerFrameEffectsCompositionState() { + // Please keep in sync with LayerSnapshotBuilder + auto* snapshot = editLayerSnapshot(); + snapshot->color = getColor(); + snapshot->compositionType = + aidl::android::hardware::graphics::composer3::Composition::SOLID_COLOR; } void Layer::prepareCursorCompositionState() { const State& drawingState{getDrawingState()}; - auto* compositionState = editCompositionState(); + // Please keep in sync with LayerSnapshotBuilder + auto* snapshot = editLayerSnapshot(); // Apply the layer's transform, followed by the display's global transform // Here we're guaranteed that the layer's transform preserves rects @@ -518,52 +727,7 @@ void Layer::prepareCursorCompositionState() { Rect bounds = reduce(win, getActiveTransparentRegion(drawingState)); Rect frame(getTransform().transform(bounds)); - compositionState->cursorFrame = frame; -} - -sp Layer::asLayerFE() const { - return const_cast( - static_cast(this)); -} - -sp Layer::getCompositionEngineLayerFE() const { - return nullptr; -} - -compositionengine::LayerFECompositionState* Layer::editCompositionState() { - return nullptr; -} - -const compositionengine::LayerFECompositionState* Layer::getCompositionState() const { - return nullptr; -} - -bool Layer::onPreComposition(nsecs_t) { - return false; -} - -void Layer::prepareCompositionState(compositionengine::LayerFE::StateSubset subset) { - using StateSubset = compositionengine::LayerFE::StateSubset; - - switch (subset) { - case StateSubset::BasicGeometry: - prepareBasicGeometryCompositionState(); - break; - - case StateSubset::GeometryAndContent: - prepareBasicGeometryCompositionState(); - prepareGeometryCompositionState(); - preparePerFrameCompositionState(); - break; - - case StateSubset::Content: - preparePerFrameCompositionState(); - break; - - case StateSubset::Cursor: - prepareCursorCompositionState(); - break; - } + snapshot->cursorFrame = frame; } const char* Layer::getDebugName() const { @@ -574,109 +738,6 @@ const char* Layer::getDebugName() const { // drawing... // --------------------------------------------------------------------------- -std::optional Layer::prepareClientComposition( - compositionengine::LayerFE::ClientCompositionTargetSettings& targetSettings) { - if (!getCompositionState()) { - return {}; - } - - FloatRect bounds = getBounds(); - half alpha = getAlpha(); - - compositionengine::LayerFE::LayerSettings layerSettings; - layerSettings.geometry.boundaries = bounds; - layerSettings.geometry.positionTransform = getTransform().asMatrix4(); - - // skip drawing content if the targetSettings indicate the content will be occluded - const bool drawContent = targetSettings.realContentIsVisible || targetSettings.clearContent; - layerSettings.skipContentDraw = !drawContent; - - if (hasColorTransform()) { - layerSettings.colorTransform = getColorTransform(); - } - - const auto roundedCornerState = getRoundedCornerState(); - layerSettings.geometry.roundedCornersRadius = roundedCornerState.radius; - layerSettings.geometry.roundedCornersCrop = roundedCornerState.cropRect; - - layerSettings.alpha = alpha; - layerSettings.sourceDataspace = getDataSpace(); - - // Override the dataspace transfer from 170M to sRGB if the device configuration requests this. - // We do this here instead of in buffer info so that dumpsys can still report layers that are - // using the 170M transfer. - if (mFlinger->mTreat170mAsSrgb && - (layerSettings.sourceDataspace & HAL_DATASPACE_TRANSFER_MASK) == - HAL_DATASPACE_TRANSFER_SMPTE_170M) { - layerSettings.sourceDataspace = static_cast( - (layerSettings.sourceDataspace & HAL_DATASPACE_STANDARD_MASK) | - (layerSettings.sourceDataspace & HAL_DATASPACE_RANGE_MASK) | - HAL_DATASPACE_TRANSFER_SRGB); - } - - layerSettings.whitePointNits = targetSettings.whitePointNits; - switch (targetSettings.blurSetting) { - case LayerFE::ClientCompositionTargetSettings::BlurSetting::Enabled: - layerSettings.backgroundBlurRadius = getBackgroundBlurRadius(); - layerSettings.blurRegions = getBlurRegions(); - layerSettings.blurRegionTransform = - getActiveTransform(getDrawingState()).inverse().asMatrix4(); - break; - case LayerFE::ClientCompositionTargetSettings::BlurSetting::BackgroundBlurOnly: - layerSettings.backgroundBlurRadius = getBackgroundBlurRadius(); - break; - case LayerFE::ClientCompositionTargetSettings::BlurSetting::BlurRegionsOnly: - layerSettings.blurRegions = getBlurRegions(); - layerSettings.blurRegionTransform = - getActiveTransform(getDrawingState()).inverse().asMatrix4(); - break; - case LayerFE::ClientCompositionTargetSettings::BlurSetting::Disabled: - default: - break; - } - layerSettings.stretchEffect = getStretchEffect(); - // Record the name of the layer for debugging further down the stack. - layerSettings.name = getName(); - return layerSettings; -} - -void Layer::prepareClearClientComposition(LayerFE::LayerSettings& layerSettings, - bool blackout) const { - layerSettings.source.buffer.buffer = nullptr; - layerSettings.source.solidColor = half3(0.0, 0.0, 0.0); - layerSettings.disableBlending = true; - layerSettings.bufferId = 0; - layerSettings.frameNumber = 0; - - // If layer is blacked out, force alpha to 1 so that we draw a black color layer. - layerSettings.alpha = blackout ? 1.0f : 0.0f; - layerSettings.name = getName(); -} - -// TODO(b/188891810): This method now only ever returns 0 or 1 layers so we should return -// std::optional instead of a vector. Additionally, we should consider removing -// this method entirely in favor of calling prepareClientComposition directly. -std::vector Layer::prepareClientCompositionList( - compositionengine::LayerFE::ClientCompositionTargetSettings& targetSettings) { - std::optional layerSettings = - prepareClientComposition(targetSettings); - // Nothing to render. - if (!layerSettings) { - return {}; - } - - // HWC requests to clear this layer. - if (targetSettings.clearContent) { - prepareClearClientComposition(*layerSettings, false /* blackout */); - return {*layerSettings}; - } - - // set the shadow for the layer if needed - prepareShadowClientComposition(*layerSettings, targetSettings.viewport); - - return {*layerSettings}; -} - aidl::android::hardware::graphics::composer3::Composition Layer::getCompositionType( const DisplayDevice& display) const { const auto outputLayer = findOutputLayerForDisplay(&display); @@ -704,6 +765,40 @@ bool Layer::isSecure() const { return (p != nullptr) ? p->isSecure() : false; } +void Layer::transferAvailableJankData(const std::deque>& handles, + std::vector& jankData) { + if (mPendingJankClassifications.empty() || + !mPendingJankClassifications.front()->getJankType()) { + return; + } + + bool includeJankData = false; + for (const auto& handle : handles) { + for (const auto& cb : handle->callbackIds) { + if (cb.includeJankData) { + includeJankData = true; + break; + } + } + + if (includeJankData) { + jankData.reserve(mPendingJankClassifications.size()); + break; + } + } + + while (!mPendingJankClassifications.empty() && + mPendingJankClassifications.front()->getJankType()) { + if (includeJankData) { + std::shared_ptr surfaceFrame = + mPendingJankClassifications.front(); + jankData.emplace_back( + JankData(surfaceFrame->getToken(), surfaceFrame->getJankType().value())); + } + mPendingJankClassifications.pop_front(); + } +} + // ---------------------------------------------------------------------------- // transaction // ---------------------------------------------------------------------------- @@ -724,7 +819,7 @@ uint32_t Layer::doTransaction(uint32_t flags) { if (s.sequence != mLastCommittedTxSequence) { // invalidate and recompute the visible regions if needed - mLastCommittedTxSequence = s.sequence; + mLastCommittedTxSequence = s.sequence; flags |= eVisibleRegion; this->contentDirty = true; @@ -732,6 +827,10 @@ uint32_t Layer::doTransaction(uint32_t flags) { mNeedsFiltering = getActiveTransform(s).needsBilinearFiltering(); } + if (!mPotentialCursor && (flags & Layer::eVisibleRegion)) { + mFlinger->mUpdateInputInfo = true; + } + commitTransaction(mDrawingState); return flags; @@ -744,7 +843,7 @@ void Layer::commitTransaction(State&) { if (surfaceFrame->getPresentState() != PresentState::Presented) { // With applyPendingStates, we could end up having presented surfaceframes from previous // states - surfaceFrame->setPresentState(PresentState::Presented); + surfaceFrame->setPresentState(PresentState::Presented, mLastLatchTime); mFlinger->mFrameTimeline->addSurfaceFrame(surfaceFrame); } } @@ -761,16 +860,6 @@ void Layer::setTransactionFlags(uint32_t mask) { mTransactionFlags |= mask; } -bool Layer::setPosition(float x, float y) { - if (mDrawingState.transform.tx() == x && mDrawingState.transform.ty() == y) return false; - mDrawingState.sequence++; - mDrawingState.transform.set(x, y); - - mDrawingState.modified = true; - setTransactionFlags(eTransactionNeeded); - return true; -} - bool Layer::setChildLayer(const sp& childLayer, int32_t z) { ssize_t idx = mCurrentChildren.indexOf(childLayer); if (idx < 0) { @@ -810,7 +899,7 @@ bool Layer::setLayer(int32_t z) { if (mDrawingState.zOrderRelativeOf != nullptr) { sp strongRelative = mDrawingState.zOrderRelativeOf.promote(); if (strongRelative != nullptr) { - strongRelative->removeZOrderRelative(this); + strongRelative->removeZOrderRelative(wp::fromExisting(this)); } setZOrderRelativeOf(nullptr); } @@ -842,7 +931,7 @@ void Layer::setZOrderRelativeOf(const wp& relativeOf) { } bool Layer::setRelativeLayer(const sp& relativeToHandle, int32_t relativeZ) { - sp relative = fromHandle(relativeToHandle).promote(); + sp relative = LayerHandle::getLayer(relativeToHandle); if (relative == nullptr) { return false; } @@ -868,10 +957,10 @@ bool Layer::setRelativeLayer(const sp& relativeToHandle, int32_t relati auto oldZOrderRelativeOf = mDrawingState.zOrderRelativeOf.promote(); if (oldZOrderRelativeOf != nullptr) { - oldZOrderRelativeOf->removeZOrderRelative(this); + oldZOrderRelativeOf->removeZOrderRelative(wp::fromExisting(this)); } setZOrderRelativeOf(relative); - relative->addZOrderRelative(this); + relative->addZOrderRelative(wp::fromExisting(this)); setTransactionFlags(eTransactionNeeded); @@ -882,7 +971,7 @@ bool Layer::setTrustedOverlay(bool isTrustedOverlay) { if (mDrawingState.isTrustedOverlay == isTrustedOverlay) return false; mDrawingState.isTrustedOverlay = isTrustedOverlay; mDrawingState.modified = true; - mFlinger->mInputInfoChanged = true; + mFlinger->mUpdateInputInfo = true; setTransactionFlags(eTransactionNeeded); return true; } @@ -895,20 +984,6 @@ bool Layer::isTrustedOverlay() const { return (p != nullptr) && p->isTrustedOverlay(); } -bool Layer::setSize(uint32_t w, uint32_t h) { - if (mDrawingState.requested_legacy.w == w && mDrawingState.requested_legacy.h == h) - return false; - mDrawingState.requested_legacy.w = w; - mDrawingState.requested_legacy.h = h; - mDrawingState.modified = true; - setTransactionFlags(eTransactionNeeded); - - // record the new size, from this point on, when the client request - // a buffer, it'll get the new size. - setDefaultBufferSize(mDrawingState.requested_legacy.w, mDrawingState.requested_legacy.h); - return true; -} - bool Layer::setAlpha(float alpha) { if (mDrawingState.color.a == alpha) return false; mDrawingState.sequence++; @@ -980,20 +1055,10 @@ bool Layer::setBackgroundBlurRadius(int backgroundBlurRadius) { setTransactionFlags(eTransactionNeeded); return true; } -bool Layer::setMatrix(const layer_state_t::matrix22_t& matrix) { - ui::Transform t; - t.set(matrix.dsdx, matrix.dtdy, matrix.dtdx, matrix.dsdy); - - mDrawingState.sequence++; - mDrawingState.transform.set(matrix.dsdx, matrix.dtdy, matrix.dtdx, matrix.dsdy); - mDrawingState.modified = true; - - setTransactionFlags(eTransactionNeeded); - return true; -} bool Layer::setTransparentRegionHint(const Region& transparent) { - mDrawingState.requestedTransparentRegion_legacy = transparent; + mDrawingState.sequence++; + mDrawingState.transparentRegionHint = transparent; mDrawingState.modified = true; setTransactionFlags(eTransactionNeeded); return true; @@ -1022,9 +1087,8 @@ bool Layer::setFlags(uint32_t flags, uint32_t mask) { } bool Layer::setCrop(const Rect& crop) { - if (mDrawingState.requestedCrop == crop) return false; + if (mDrawingState.crop == crop) return false; mDrawingState.sequence++; - mDrawingState.requestedCrop = crop; mDrawingState.crop = crop; mDrawingState.modified = true; @@ -1092,12 +1156,27 @@ int32_t Layer::getFrameRateSelectionPriority() const { return Layer::PRIORITY_UNSET; } +bool Layer::setDefaultFrameRateCompatibility(FrameRateCompatibility compatibility) { + if (mDrawingState.defaultFrameRateCompatibility == compatibility) return false; + mDrawingState.defaultFrameRateCompatibility = compatibility; + mDrawingState.modified = true; + mFlinger->mScheduler->setDefaultFrameRateCompatibility(this); + setTransactionFlags(eTransactionNeeded); + return true; +} + +scheduler::LayerInfo::FrameRateCompatibility Layer::getDefaultFrameRateCompatibility() const { + return mDrawingState.defaultFrameRateCompatibility; +} + bool Layer::isLayerFocusedBasedOnPriority(int32_t priority) { return priority == PRIORITY_FOCUSED_WITH_MODE || priority == PRIORITY_FOCUSED_WITHOUT_MODE; }; -ui::LayerStack Layer::getLayerStack() const { - if (const auto parent = mDrawingParent.promote()) { +ui::LayerStack Layer::getLayerStack(LayerVector::StateSet state) const { + bool useDrawing = state == LayerVector::StateSet::Drawing; + const auto parent = useDrawing ? mDrawingParent.promote() : mCurrentParent.promote(); + if (parent) { return parent->getLayerStack(); } return getDrawingState().layerStack; @@ -1156,6 +1235,28 @@ 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* transactionNeeded) { // The frame rate for layer tree is this layer's frame rate if present, or the parent frame rate const auto frameRate = [&] { @@ -1167,7 +1268,7 @@ bool Layer::propagateFrameRateForLayerTree(FrameRate parentFrameRate, bool* tran return parentFrameRate; }(); - *transactionNeeded |= setFrameRateForLayerTree(frameRate); + *transactionNeeded |= setFrameRateForLayerTreeLegacy(frameRate); // The frame rate is propagated to the children bool childrenHaveFrameRate = false; @@ -1181,12 +1282,12 @@ bool Layer::propagateFrameRateForLayerTree(FrameRate parentFrameRate, bool* tran if (!frameRate.rate.isValid() && frameRate.type != FrameRateCompatibility::NoVote && childrenHaveFrameRate) { *transactionNeeded |= - setFrameRateForLayerTree(FrameRate(Fps(), FrameRateCompatibility::NoVote)); + setFrameRateForLayerTreeLegacy(FrameRate(Fps(), FrameRateCompatibility::NoVote)); } // We return whether this layer ot its children has a vote. We ignore ExactOrMultiple votes for // the same reason we are allowing touch boost for those layers. See - // RefreshRateConfigs::getBestRefreshRate for more details. + // RefreshRateSelector::rankFrameRates for details. const auto layerVotedWithDefaultCompatibility = frameRate.rate.isValid() && frameRate.type == FrameRateCompatibility::Default; const auto layerVotedWithNoVote = frameRate.type == FrameRateCompatibility::NoVote; @@ -1198,7 +1299,7 @@ bool Layer::propagateFrameRateForLayerTree(FrameRate parentFrameRate, bool* tran void Layer::updateTreeHasFrameRateVote() { const auto root = [&]() -> sp { - sp layer = this; + sp layer = sp::fromExisting(this); while (auto parent = layer->getParent()) { layer = parent; } @@ -1294,7 +1395,7 @@ void Layer::addSurfaceFramePresentedForBuffer( surfaceFrame->setAcquireFenceTime(acquireFenceTime); surfaceFrame->setPresentState(PresentState::Presented, mLastLatchTime); mFlinger->mFrameTimeline->addSurfaceFrame(surfaceFrame); - mLastLatchTime = currentLatchTime; + updateLastLatchTime(currentLatchTime); } std::shared_ptr Layer::createSurfaceFrameForTransaction( @@ -1329,12 +1430,11 @@ std::shared_ptr Layer::createSurfaceFrameForBuffer( if (fps) { surfaceFrame->setRenderRate(*fps); } - // TODO(b/178542907): Implement onSurfaceFrameCreated for BQLayer as well. onSurfaceFrameCreated(surfaceFrame); return surfaceFrame; } -bool Layer::setFrameRateForLayerTree(FrameRate frameRate) { +bool Layer::setFrameRateForLayerTreeLegacy(FrameRate frameRate) { if (mDrawingState.frameRateForLayerTree == frameRate) { return false; } @@ -1347,9 +1447,21 @@ bool Layer::setFrameRateForLayerTree(FrameRate frameRate) { mDrawingState.modified = true; setTransactionFlags(eTransactionNeeded); - using LayerUpdateType = scheduler::LayerHistory::LayerUpdateType; - mFlinger->mScheduler->recordLayerHistory(this, systemTime(), LayerUpdateType::SetFrameRate); + mFlinger->mScheduler + ->recordLayerHistory(sequence, getLayerProps(), systemTime(), + scheduler::LayerHistory::LayerUpdateType::SetFrameRate); + return true; +} + +bool Layer::setFrameRateForLayerTree(FrameRate frameRate, const scheduler::LayerProps& layerProps) { + if (mDrawingState.frameRateForLayerTree == frameRate) { + return false; + } + mDrawingState.frameRateForLayerTree = frameRate; + mFlinger->mScheduler + ->recordLayerHistory(sequence, layerProps, systemTime(), + scheduler::LayerHistory::LayerUpdateType::SetFrameRate); return true; } @@ -1391,12 +1503,16 @@ uint32_t Layer::getEffectiveUsage(uint32_t usage) const { return usage; } +void Layer::skipReportingTransformHint() { + mSkipReportingTransformHint = true; +} + void Layer::updateTransformHint(ui::Transform::RotationFlags transformHint) { if (mFlinger->mDebugDisableTransformHint || transformHint & ui::Transform::ROT_INVALID) { transformHint = ui::Transform::ROT_0; } - setTransformHint(transformHint); + setTransformHintLegacy(transformHint); } // ---------------------------------------------------------------------------- @@ -1404,16 +1520,15 @@ void Layer::updateTransformHint(ui::Transform::RotationFlags transformHint) { // ---------------------------------------------------------------------------- // TODO(marissaw): add new layer state info to layer debugging -LayerDebugInfo Layer::getLayerDebugInfo(const DisplayDevice* display) const { +gui::LayerDebugInfo Layer::getLayerDebugInfo(const DisplayDevice* display) const { using namespace std::string_literals; - LayerDebugInfo info; + gui::LayerDebugInfo info; const State& ds = getDrawingState(); info.mName = getName(); sp parent = mDrawingParent.promote(); info.mParentName = parent ? parent->getName() : "none"s; info.mType = getType(); - info.mTransparentRegion = ds.activeTransparentRegion_legacy; info.mVisibleRegion = getVisibleRegion(display); info.mSurfaceDamageRegion = surfaceDamageRegion; @@ -1421,8 +1536,6 @@ LayerDebugInfo Layer::getLayerDebugInfo(const DisplayDevice* display) const { info.mX = ds.transform.tx(); info.mY = ds.transform.ty(); info.mZ = ds.z; - info.mWidth = ds.width; - info.mHeight = ds.height; info.mCrop = ds.crop; info.mColor = ds.color; info.mFlags = ds.flags; @@ -1535,9 +1648,10 @@ void Layer::getFrameStats(FrameStats* outStats) const { mFrameTracker.getStats(outStats); } -void Layer::dumpCallingUidPid(std::string& result) const { - StringAppendF(&result, "Layer %s (%s) callingPid:%d callingUid:%d ownerUid:%d\n", - getName().c_str(), getType(), mCallingPid, mCallingUid, mOwnerUid); +void Layer::dumpOffscreenDebugInfo(std::string& result) const { + std::string hasBuffer = hasBufferOrSidebandStream() ? " (contains buffer)" : ""; + StringAppendF(&result, "Layer %s%s pid:%d uid:%d%s\n", getName().c_str(), hasBuffer.c_str(), + mOwnerPid, mOwnerUid, isHandleAlive() ? " handleAlive" : ""); } void Layer::onDisconnect() { @@ -1546,9 +1660,9 @@ void Layer::onDisconnect() { mFlinger->mFrameTracer->onDestroy(layerId); } -size_t Layer::getChildrenCount() const { +size_t Layer::getDescendantCount() const { size_t count = 0; - for (const sp& child : mCurrentChildren) { + for (const sp& child : mDrawingChildren) { count += 1 + child->getChildrenCount(); } return count; @@ -1556,8 +1670,9 @@ size_t Layer::getChildrenCount() const { void Layer::setGameModeForTree(GameMode gameMode) { const auto& currentState = getDrawingState(); - if (currentState.metadata.has(METADATA_GAME_MODE)) { - gameMode = static_cast(currentState.metadata.getInt32(METADATA_GAME_MODE, 0)); + if (currentState.metadata.has(gui::METADATA_GAME_MODE)) { + gameMode = + static_cast(currentState.metadata.getInt32(gui::METADATA_GAME_MODE, 0)); } setGameMode(gameMode); for (const sp& child : mCurrentChildren) { @@ -1570,7 +1685,7 @@ void Layer::addChild(const sp& layer) { setTransactionFlags(eTransactionNeeded); mCurrentChildren.add(layer); - layer->setParent(this); + layer->setParent(sp::fromExisting(this)); layer->setGameModeForTree(mGameMode); updateTreeHasFrameRateVote(); } @@ -1602,7 +1717,7 @@ void Layer::setChildrenDrawingParent(const sp& newParent) { bool Layer::reparent(const sp& newParentHandle) { sp newParent; if (newParentHandle != nullptr) { - newParent = fromHandle(newParentHandle).promote(); + newParent = LayerHandle::getLayer(newParentHandle); if (newParent == nullptr) { ALOGE("Unable to promote Layer handle"); return false; @@ -1615,11 +1730,11 @@ bool Layer::reparent(const sp& newParentHandle) { sp parent = getParent(); if (parent != nullptr) { - parent->removeChild(this); + parent->removeChild(sp::fromExisting(this)); } if (newParentHandle != nullptr) { - newParent->addChild(this); + newParent->addChild(sp::fromExisting(this)); if (!newParent->isRemovedFromCurrentState()) { addToCurrentState(); } else { @@ -1794,6 +1909,12 @@ void Layer::traverse(LayerVector::StateSet state, const LayerVector::Visitor& vi } } +void Layer::traverseChildren(const LayerVector::Visitor& visitor) { + for (const sp& child : mDrawingChildren) { + visitor(child.get()); + } +} + LayerVector Layer::makeChildrenTraversalList(LayerVector::StateSet stateSet, const std::vector& layersInTree) { LOG_ALWAYS_FATAL_IF(stateSet == LayerVector::StateSet::Invalid, @@ -1901,8 +2022,11 @@ half4 Layer::getColor() const { } int32_t Layer::getBackgroundBlurRadius() const { - const auto& p = mDrawingParent.promote(); + if (getDrawingState().backgroundBlurRadius == 0) { + return 0; + } + const auto& p = mDrawingParent.promote(); half parentAlpha = (p != nullptr) ? p->getAlpha() : 1.0_hf; return parentAlpha * getDrawingState().backgroundBlurRadius; } @@ -1916,7 +2040,7 @@ const std::vector Layer::getBlurRegions() const { return regionsCopy; } -Layer::RoundedCornerState Layer::getRoundedCornerState() const { +RoundedCornerState Layer::getRoundedCornerState() const { // Get parent settings RoundedCornerState parentSettings; const auto& parent = mDrawingParent.promote(); @@ -1957,39 +2081,6 @@ Layer::RoundedCornerState Layer::getRoundedCornerState() const { return {}; } -void Layer::prepareShadowClientComposition(LayerFE::LayerSettings& caster, - const Rect& layerStackRect) { - renderengine::ShadowSettings state = mFlinger->mDrawingState.globalShadowSettings; - - // Note: this preserves existing behavior of shadowing the entire layer and not cropping it if - // transparent regions are present. This may not be necessary since shadows are only cast by - // SurfaceFlinger's EffectLayers, which do not typically use transparent regions. - state.boundaries = mBounds; - - // Shift the spot light x-position to the middle of the display and then - // offset it by casting layer's screen pos. - state.lightPos.x = (layerStackRect.width() / 2.f) - mScreenBounds.left; - state.lightPos.y -= mScreenBounds.top; - - state.length = mEffectiveShadowRadius; - - if (state.length > 0.f) { - const float casterAlpha = caster.alpha; - const bool casterIsOpaque = - ((caster.source.buffer.buffer != nullptr) && caster.source.buffer.isOpaque); - - // If the casting layer is translucent, we need to fill in the shadow underneath the layer. - // Otherwise the generated shadow will only be shown around the casting layer. - state.casterIsTranslucent = !casterIsOpaque || (casterAlpha < 1.0f); - state.ambientColor *= casterAlpha; - state.spotColor *= casterAlpha; - - if (state.ambientColor.a > 0.f && state.spotColor.a > 0.f) { - caster.shadow = state; - } - } -} - bool Layer::findInHierarchy(const sp& l) { if (l == this) { return true; @@ -2017,7 +2108,7 @@ void Layer::commitChildList() { zOrderRelativeOf->mName.c_str()); ALOGE("Severing rel Z loop, potentially dangerous"); mDrawingState.isRelativeOf = false; - zOrderRelativeOf->removeZOrderRelative(this); + zOrderRelativeOf->removeZOrderRelative(wp::fromExisting(this)); } } } @@ -2025,9 +2116,10 @@ void Layer::commitChildList() { void Layer::setInputInfo(const WindowInfo& info) { mDrawingState.inputInfo = info; - mDrawingState.touchableRegionCrop = fromHandle(info.touchableRegionCropHandle.promote()); + mDrawingState.touchableRegionCrop = + LayerHandle::getLayer(info.touchableRegionCropHandle.promote()); mDrawingState.modified = true; - mFlinger->mInputInfoChanged = true; + mFlinger->mUpdateInputInfo = true; setTransactionFlags(eTransactionNeeded); } @@ -2037,15 +2129,9 @@ LayerProto* Layer::writeToProto(LayersProto& layersProto, uint32_t traceFlags) { writeToProtoCommonState(layerProto, LayerVector::StateSet::Drawing, traceFlags); if (traceFlags & LayerTracing::TRACE_COMPOSITION) { - ftl::FakeGuard guard(mFlinger->mStateLock); // Called from the main thread. - - // Only populate for the primary display. - if (const auto display = mFlinger->getDefaultDisplayDeviceLocked()) { - const auto compositionType = getCompositionType(*display); - layerProto->set_hwc_composition_type(static_cast(compositionType)); - LayerProtoHelper::writeToProto(getVisibleRegion(display.get()), - [&]() { return layerProto->mutable_visible_region(); }); - } + ui::LayerStack layerStack = + (mSnapshot) ? mSnapshot->outputFilter.layerStack : ui::INVALID_LAYER_STACK; + writeCompositionStateToProto(layerProto, layerStack); } for (const sp& layer : mDrawingChildren) { @@ -2055,6 +2141,19 @@ LayerProto* Layer::writeToProto(LayersProto& layersProto, uint32_t traceFlags) { return layerProto; } +void Layer::writeCompositionStateToProto(LayerProto* layerProto, ui::LayerStack layerStack) { + ftl::FakeGuard guard(mFlinger->mStateLock); // Called from the main thread. + ftl::FakeGuard mainThreadGuard(kMainThreadContext); + + // Only populate for the primary display. + if (const auto display = mFlinger->getDisplayFromLayerStack(layerStack)) { + const auto compositionType = getCompositionType(*display); + layerProto->set_hwc_composition_type(static_cast(compositionType)); + LayerProtoHelper::writeToProto(getVisibleRegion(display), + [&]() { return layerProto->mutable_visible_region(); }); + } +} + void Layer::writeToProtoDrawingState(LayerProto* layerInfo) { const ui::Transform transform = getTransform(); auto buffer = getExternalTexture(); @@ -2069,8 +2168,6 @@ void Layer::writeToProtoDrawingState(LayerProto* layerInfo) { layerInfo->set_dataspace(dataspaceDetails(static_cast(getDataSpace()))); layerInfo->set_queued_frames(getQueuedFrameCount()); layerInfo->set_curr_frame(mCurrentFrameNumber); - layerInfo->set_effective_scaling_mode(getEffectiveScalingMode()); - layerInfo->set_requested_corner_radius(getDrawingState().cornerRadius); layerInfo->set_corner_radius( (getRoundedCornerState().radius.x + getRoundedCornerState().radius.y) / 2.0); @@ -2119,7 +2216,7 @@ void Layer::writeToProtoCommonState(LayerProto* layerInfo, LayerVector::StateSet } } - LayerProtoHelper::writeToProto(state.activeTransparentRegion_legacy, + LayerProtoHelper::writeToProto(state.transparentRegionHint, [&]() { return layerInfo->mutable_transparent_region(); }); layerInfo->set_layer_stack(getLayerStack().id); @@ -2129,9 +2226,6 @@ void Layer::writeToProtoCommonState(LayerProto* layerInfo, LayerVector::StateSet return layerInfo->mutable_requested_position(); }); - LayerProtoHelper::writeSizeToProto(state.width, state.height, - [&]() { return layerInfo->mutable_size(); }); - LayerProtoHelper::writeToProto(state.crop, [&]() { return layerInfo->mutable_crop(); }); layerInfo->set_is_opaque(isOpaque(state)); @@ -2191,14 +2285,6 @@ bool Layer::isRemovedFromCurrentState() const { return mRemovedFromDrawingState; } -ui::Transform Layer::getInputTransform() const { - return getTransform(); -} - -Rect Layer::getInputBounds() const { - return getCroppedBufferSize(getDrawingState()); -} - // Applies the given transform to the region, while protecting against overflows caused by any // offsets. If applying the offset in the transform to any of the Rects in the region would result // in an overflow, they are not added to the output Region. @@ -2231,62 +2317,21 @@ static Region transformTouchableRegionSafely(const ui::Transform& t, const Regio } void Layer::fillInputFrameInfo(WindowInfo& info, const ui::Transform& screenToDisplay) { - Rect tmpBounds = getInputBounds(); - if (!tmpBounds.isValid()) { + auto [inputBounds, inputBoundsValid] = getInputBounds(/*fillParentBounds=*/false); + if (!inputBoundsValid) { info.touchableRegion.clear(); - // A layer could have invalid input bounds and still expect to receive touch input if it has - // replaceTouchableRegionWithCrop. For that case, the input transform needs to be calculated - // correctly to determine the coordinate space for input events. Use an empty rect so that - // the layer will receive input in its own layer space. - tmpBounds = Rect::EMPTY_RECT; } - // InputDispatcher works in the display device's coordinate space. Here, we calculate the - // frame and transform used for the layer, which determines the bounds and the coordinate space - // within which the layer will receive input. - // - // The coordinate space within which each of the bounds are specified is explicitly documented - // in the variable name. For example "inputBoundsInLayer" is specified in layer space. A - // Transform converts one coordinate space to another, which is apparent in its naming. For - // example, "layerToDisplay" transforms layer space to display space. - // - // Coordinate space definitions: - // - display: The display device's coordinate space. Correlates to pixels on the display. - // - screen: The post-rotation coordinate space for the display, a.k.a. logical display space. - // - layer: The coordinate space of this layer. - // - input: The coordinate space in which this layer will receive input events. This could be - // different than layer space if a surfaceInset is used, which changes the origin - // of the input space. - const FloatRect inputBoundsInLayer = tmpBounds.toFloatRect(); - - // Clamp surface inset to the input bounds. - const auto surfaceInset = static_cast(info.surfaceInset); - const float xSurfaceInset = - std::max(0.f, std::min(surfaceInset, inputBoundsInLayer.getWidth() / 2.f)); - const float ySurfaceInset = - std::max(0.f, std::min(surfaceInset, inputBoundsInLayer.getHeight() / 2.f)); - - // Apply the insets to the input bounds. - const FloatRect insetBoundsInLayer(inputBoundsInLayer.left + xSurfaceInset, - inputBoundsInLayer.top + ySurfaceInset, - inputBoundsInLayer.right - xSurfaceInset, - inputBoundsInLayer.bottom - ySurfaceInset); - - // Crop the input bounds to ensure it is within the parent's bounds. - const FloatRect croppedInsetBoundsInLayer = mBounds.intersect(insetBoundsInLayer); - - const ui::Transform layerToScreen = getInputTransform(); - const ui::Transform layerToDisplay = screenToDisplay * layerToScreen; - - const Rect roundedFrameInDisplay{layerToDisplay.transform(croppedInsetBoundsInLayer)}; + const Rect roundedFrameInDisplay = getInputBoundsInDisplaySpace(inputBounds, screenToDisplay); info.frameLeft = roundedFrameInDisplay.left; info.frameTop = roundedFrameInDisplay.top; info.frameRight = roundedFrameInDisplay.right; info.frameBottom = roundedFrameInDisplay.bottom; ui::Transform inputToLayer; - inputToLayer.set(insetBoundsInLayer.left, insetBoundsInLayer.top); - const ui::Transform inputToDisplay = layerToDisplay * inputToLayer; + inputToLayer.set(inputBounds.left, inputBounds.top); + const ui::Transform layerToScreen = getInputTransform(); + const ui::Transform inputToDisplay = screenToDisplay * layerToScreen * inputToLayer; // InputDispatcher expects a display-to-input transform. info.transform = inputToDisplay.inverse(); @@ -2297,7 +2342,7 @@ void Layer::fillInputFrameInfo(WindowInfo& info, const ui::Transform& screenToDi } void Layer::fillTouchOcclusionMode(WindowInfo& info) { - sp p = this; + sp p = sp::fromExisting(this); while (p != nullptr && !p->hasInputInfo()) { p = p->mDrawingParent.promote(); } @@ -2410,13 +2455,23 @@ WindowInfo Layer::fillInputInfo(const InputDisplayArgs& displayArgs) { info.inputConfig |= WindowInfo::InputConfig::DROP_INPUT; } - auto cropLayer = mDrawingState.touchableRegionCrop.promote(); + sp cropLayer = mDrawingState.touchableRegionCrop.promote(); if (info.replaceTouchableRegionWithCrop) { - const Rect bounds(cropLayer ? cropLayer->mScreenBounds : mScreenBounds); - info.touchableRegion = Region(displayTransform.transform(bounds)); - } else if (cropLayer != nullptr) { - info.touchableRegion = info.touchableRegion.intersect( - displayTransform.transform(Rect{cropLayer->mScreenBounds})); + Rect inputBoundsInDisplaySpace; + if (!cropLayer) { + FloatRect inputBounds = getInputBounds(/*fillParentBounds=*/true).first; + inputBoundsInDisplaySpace = getInputBoundsInDisplaySpace(inputBounds, displayTransform); + } else { + FloatRect inputBounds = cropLayer->getInputBounds(/*fillParentBounds=*/true).first; + inputBoundsInDisplaySpace = + cropLayer->getInputBoundsInDisplaySpace(inputBounds, displayTransform); + } + info.touchableRegion = Region(inputBoundsInDisplaySpace); + } else if (cropLayer != nullptr) { + FloatRect inputBounds = cropLayer->getInputBounds(/*fillParentBounds=*/true).first; + Rect inputBoundsInDisplaySpace = + cropLayer->getInputBoundsInDisplaySpace(inputBounds, displayTransform); + info.touchableRegion = info.touchableRegion.intersect(inputBoundsInDisplaySpace); } // Inherit the trusted state from the parent hierarchy, but don't clobber the trusted state @@ -2428,7 +2483,7 @@ WindowInfo Layer::fillInputInfo(const InputDisplayArgs& displayArgs) { // If the layer is a clone, we need to crop the input region to cloned root to prevent // touches from going outside the cloned area. if (isClone()) { - info.isClone = true; + info.inputConfig |= WindowInfo::InputConfig::CLONE; if (const sp clonedRoot = getClonedRoot()) { const Rect rect = displayTransform.transform(Rect{clonedRoot->mScreenBounds}); info.touchableRegion = info.touchableRegion.intersect(rect); @@ -2438,9 +2493,30 @@ WindowInfo Layer::fillInputInfo(const InputDisplayArgs& displayArgs) { return info; } +Rect Layer::getInputBoundsInDisplaySpace(const FloatRect& inputBounds, + const ui::Transform& screenToDisplay) { + // InputDispatcher works in the display device's coordinate space. Here, we calculate the + // frame and transform used for the layer, which determines the bounds and the coordinate space + // within which the layer will receive input. + + // Coordinate space definitions: + // - display: The display device's coordinate space. Correlates to pixels on the display. + // - screen: The post-rotation coordinate space for the display, a.k.a. logical display space. + // - layer: The coordinate space of this layer. + // - input: The coordinate space in which this layer will receive input events. This could be + // different than layer space if a surfaceInset is used, which changes the origin + // of the input space. + + // Crop the input bounds to ensure it is within the parent's bounds. + const FloatRect croppedInputBounds = mBounds.intersect(inputBounds); + const ui::Transform layerToScreen = getInputTransform(); + const ui::Transform layerToDisplay = screenToDisplay * layerToScreen; + return Rect{layerToDisplay.transform(croppedInputBounds)}; +} + sp Layer::getClonedRoot() { if (mClonedChild != nullptr) { - return this; + return sp::fromExisting(this); } if (mDrawingParent == nullptr || mDrawingParent.promote() == nullptr) { return nullptr; @@ -2453,14 +2529,23 @@ bool Layer::hasInputInfo() const { mDrawingState.inputInfo.inputConfig.test(WindowInfo::InputConfig::NO_INPUT_CHANNEL); } -bool Layer::canReceiveInput() const { - return !isHiddenByPolicy(); -} - compositionengine::OutputLayer* Layer::findOutputLayerForDisplay( const DisplayDevice* display) const { if (!display) return nullptr; - return display->getCompositionDisplay()->getOutputLayerForLayer(getCompositionEngineLayerFE()); + if (!mFlinger->mLayerLifecycleManagerEnabled) { + return display->getCompositionDisplay()->getOutputLayerForLayer( + getCompositionEngineLayerFE()); + } + sp layerFE; + frontend::LayerHierarchy::TraversalPath path{.id = static_cast(sequence)}; + for (auto& [p, layer] : mLayerFEs) { + if (p == path) { + layerFE = layer; + } + } + + if (!layerFE) return nullptr; + return display->getCompositionDisplay()->getOutputLayerForLayer(layerFE); } Region Layer::getVisibleRegion(const DisplayDevice* display) const { @@ -2468,12 +2553,49 @@ Region Layer::getVisibleRegion(const DisplayDevice* display) const { return outputLayer ? outputLayer->getState().visibleRegion : Region(); } -void Layer::setInitialValuesForClone(const sp& clonedFrom) { +void Layer::setInitialValuesForClone(const sp& clonedFrom, uint32_t mirrorRootId) { + mSnapshot->path.id = clonedFrom->getSequence(); + mSnapshot->path.mirrorRootId = mirrorRootId; + cloneDrawingState(clonedFrom.get()); mClonedFrom = clonedFrom; + mPremultipliedAlpha = clonedFrom->mPremultipliedAlpha; + mPotentialCursor = clonedFrom->mPotentialCursor; + mProtectedByApp = clonedFrom->mProtectedByApp; + updateCloneBufferInfo(); +} + +void Layer::updateCloneBufferInfo() { + if (!isClone() || !isClonedFromAlive()) { + return; + } + + sp clonedFrom = getClonedFrom(); + mBufferInfo = clonedFrom->mBufferInfo; + mSidebandStream = clonedFrom->mSidebandStream; + surfaceDamageRegion = clonedFrom->surfaceDamageRegion; + mCurrentFrameNumber = clonedFrom->mCurrentFrameNumber.load(); + mPreviousFrameNumber = clonedFrom->mPreviousFrameNumber; + + // After buffer info is updated, the drawingState from the real layer needs to be copied into + // the cloned. This is because some properties of drawingState can change when latchBuffer is + // called. However, copying the drawingState would also overwrite the cloned layer's relatives + // and touchableRegionCrop. Therefore, temporarily store the relatives so they can be set in + // the cloned drawingState again. + wp tmpZOrderRelativeOf = mDrawingState.zOrderRelativeOf; + SortedVector> tmpZOrderRelatives = mDrawingState.zOrderRelatives; + wp tmpTouchableRegionCrop = mDrawingState.touchableRegionCrop; + WindowInfo tmpInputInfo = mDrawingState.inputInfo; + + cloneDrawingState(clonedFrom.get()); + + mDrawingState.touchableRegionCrop = tmpTouchableRegionCrop; + mDrawingState.zOrderRelativeOf = tmpZOrderRelativeOf; + mDrawingState.zOrderRelatives = tmpZOrderRelatives; + mDrawingState.inputInfo = tmpInputInfo; } -void Layer::updateMirrorInfo() { +bool Layer::updateMirrorInfo(const std::deque& cloneRootsPendingUpdates) { if (mClonedChild == nullptr || !mClonedChild->isClonedFromAlive()) { // If mClonedChild is null, there is nothing to mirror. If isClonedFromAlive returns false, // it means that there is a clone, but the layer it was cloned from has been destroyed. In @@ -2481,7 +2603,7 @@ void Layer::updateMirrorInfo() { // destroyed. The root, this layer, will still be around since the client can continue // to hold a reference, but no cloned layers will be displayed. mClonedChild = nullptr; - return; + return true; } std::map, sp> clonedLayersMap; @@ -2494,8 +2616,15 @@ void Layer::updateMirrorInfo() { } mClonedChild->updateClonedDrawingState(clonedLayersMap); - mClonedChild->updateClonedChildren(this, clonedLayersMap); + mClonedChild->updateClonedChildren(sp::fromExisting(this), clonedLayersMap); mClonedChild->updateClonedRelatives(clonedLayersMap); + + for (Layer* root : cloneRootsPendingUpdates) { + if (clonedLayersMap.find(sp::fromExisting(root)) != clonedLayersMap.end()) { + return false; + } + } + return true; } void Layer::updateClonedDrawingState(std::map, sp>& clonedLayersMap) { @@ -2505,7 +2634,7 @@ void Layer::updateClonedDrawingState(std::map, sp>& clonedLayer if (isClonedFromAlive()) { sp clonedFrom = getClonedFrom(); cloneDrawingState(clonedFrom.get()); - clonedLayersMap.emplace(clonedFrom, this); + clonedLayersMap.emplace(clonedFrom, sp::fromExisting(this)); } // The clone layer may have children in drawingState since they may have been created and @@ -2535,7 +2664,7 @@ void Layer::updateClonedChildren(const sp& mirrorRoot, } sp clonedChild = clonedLayersMap[child]; if (clonedChild == nullptr) { - clonedChild = child->createClone(); + clonedChild = child->createClone(mirrorRoot->getSequence()); clonedLayersMap[child] = clonedChild; } addChildToDrawing(clonedChild); @@ -2549,7 +2678,7 @@ void Layer::updateClonedInputInfo(const std::map, sp>& clonedLa if (clonedLayersMap.count(cropLayer) == 0) { // Real layer had a crop layer but it's not in the cloned hierarchy. Just set to // self as crop layer to avoid going outside bounds. - mDrawingState.touchableRegionCrop = this; + mDrawingState.touchableRegionCrop = wp::fromExisting(this); } else { const sp& clonedCropLayer = clonedLayersMap.at(cropLayer); mDrawingState.touchableRegionCrop = clonedCropLayer; @@ -2561,7 +2690,7 @@ void Layer::updateClonedInputInfo(const std::map, sp>& clonedLa } void Layer::updateClonedRelatives(const std::map, sp>& clonedLayersMap) { - mDrawingState.zOrderRelativeOf = nullptr; + mDrawingState.zOrderRelativeOf = wp(); mDrawingState.zOrderRelatives.clear(); if (!isClonedFromAlive()) { @@ -2597,7 +2726,7 @@ void Layer::updateClonedRelatives(const std::map, sp>& clonedLa void Layer::addChildToDrawing(const sp& layer) { mDrawingChildren.add(layer); - layer->mDrawingParent = this; + layer->mDrawingParent = sp::fromExisting(this); } Layer::FrameRateCompatibility Layer::FrameRate::convertCompatibility(int8_t compatibility) { @@ -2608,6 +2737,8 @@ Layer::FrameRateCompatibility Layer::FrameRate::convertCompatibility(int8_t comp return FrameRateCompatibility::ExactOrMultiple; case ANATIVEWINDOW_FRAME_RATE_EXACT: return FrameRateCompatibility::Exact; + case ANATIVEWINDOW_FRAME_RATE_MIN: + return FrameRateCompatibility::Min; case ANATIVEWINDOW_FRAME_RATE_NO_VOTE: return FrameRateCompatibility::NoVote; default: @@ -2641,24 +2772,7 @@ bool Layer::isInternalDisplayOverlay() const { void Layer::setClonedChild(const sp& clonedChild) { mClonedChild = clonedChild; mHadClonedChild = true; - mFlinger->mNumClones++; -} - -const String16 Layer::Handle::kDescriptor = String16("android.Layer.Handle"); - -wp Layer::fromHandle(const sp& handleBinder) { - if (handleBinder == nullptr) { - return nullptr; - } - - BBinder* b = handleBinder->localBinder(); - if (b == nullptr || b->getInterfaceDescriptor() != Handle::kDescriptor) { - return nullptr; - } - - // We can safely cast this binder since its local and we verified its interface descriptor. - sp handle = static_cast(handleBinder.get()); - return handle->owner; + mFlinger->mLayerMirrorRoots.push_back(this); } bool Layer::setDropInputMode(gui::DropInputMode mode) { @@ -2673,19 +2787,1497 @@ void Layer::cloneDrawingState(const Layer* from) { mDrawingState = from->mDrawingState; // Skip callback info since they are not applicable for cloned layers. mDrawingState.releaseBufferListener = nullptr; + // TODO (b/238781169) currently broken for mirror layers because we do not + // track release fences for mirror layers composed on other displays + mDrawingState.callbackHandles = {}; +} + +void Layer::callReleaseBufferCallback(const sp& listener, + const sp& buffer, uint64_t framenumber, + const sp& releaseFence) { + if (!listener) { + return; + } + ATRACE_FORMAT_INSTANT("callReleaseBufferCallback %s - %" PRIu64, getDebugName(), framenumber); + uint32_t currentMaxAcquiredBufferCount = + mFlinger->getMaxAcquiredBufferCountForCurrentRefreshRate(mOwnerUid); + listener->onReleaseBuffer({buffer->getId(), framenumber}, + releaseFence ? releaseFence : Fence::NO_FENCE, + currentMaxAcquiredBufferCount); +} + +void Layer::onLayerDisplayed(ftl::SharedFuture futureFenceResult, + ui::LayerStack layerStack) { + // 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 + // to merge all the client comp fences. We could do this, but for now we just + // disable the optimization when a layer is composed on multiple displays. + if (mClearClientCompositionFenceOnLayerDisplayed) { + mLastClientCompositionFence = nullptr; + } else { + mClearClientCompositionFenceOnLayerDisplayed = true; + } + + // The previous release fence notifies the client that SurfaceFlinger is done with the previous + // buffer that was presented on this layer. The first transaction that came in this frame that + // replaced the previous buffer on this layer needs this release fence, because the fence will + // let the client know when that previous buffer is removed from the screen. + // + // Every other transaction on this layer does not need a release fence because no other + // Transactions that were set on this layer this frame are going to have their preceding buffer + // removed from the display this frame. + // + // For example, if we have 3 transactions this frame. The first transaction doesn't contain a + // buffer so it doesn't need a previous release fence because the layer still needs the previous + // buffer. The second transaction contains a buffer so it needs a previous release fence because + // the previous buffer will be released this frame. The third transaction also contains a + // buffer. It replaces the buffer in the second transaction. The buffer in the second + // transaction will now no longer be presented so it is released immediately and the third + // transaction doesn't need a previous release fence. + sp ch; + for (auto& handle : mDrawingState.callbackHandles) { + if (handle->releasePreviousBuffer && mPreviousReleaseBufferEndpoint == handle->listener) { + ch = handle; + break; + } + } + + // Prevent tracing the same release multiple times. + if (mPreviousFrameNumber != mPreviousReleasedFrameNumber) { + mPreviousReleasedFrameNumber = mPreviousFrameNumber; + } + + if (ch != nullptr) { + ch->previousReleaseCallbackId = mPreviousReleaseCallbackId; + ch->previousReleaseFences.emplace_back(std::move(futureFenceResult)); + ch->name = mName; + } + mPreviouslyPresentedLayerStacks.push_back(layerStack); +} + +void Layer::onSurfaceFrameCreated( + const std::shared_ptr& surfaceFrame) { + while (mPendingJankClassifications.size() >= kPendingClassificationMaxSurfaceFrames) { + // Too many SurfaceFrames pending classification. The front of the deque is probably not + // tracked by FrameTimeline and will never be presented. This will only result in a memory + // leak. + if (hasBufferOrSidebandStreamInDrawing()) { + // Only log for layers with a buffer, since we expect the jank data to be drained for + // these, while there may be no jank listeners for bufferless layers. + ALOGW("Removing the front of pending jank deque from layer - %s to prevent memory leak", + mName.c_str()); + std::string miniDump = mPendingJankClassifications.front()->miniDump(); + ALOGD("Head SurfaceFrame mini dump\n%s", miniDump.c_str()); + } + mPendingJankClassifications.pop_front(); + } + mPendingJankClassifications.emplace_back(surfaceFrame); +} + +void Layer::releasePendingBuffer(nsecs_t dequeueReadyTime) { + for (const auto& handle : mDrawingState.callbackHandles) { + if (mFlinger->mLayerLifecycleManagerEnabled) { + handle->transformHint = mTransformHint; + } else { + handle->transformHint = mSkipReportingTransformHint + ? std::nullopt + : std::make_optional(mTransformHintLegacy); + } + handle->dequeueReadyTime = dequeueReadyTime; + handle->currentMaxAcquiredBufferCount = + mFlinger->getMaxAcquiredBufferCountForCurrentRefreshRate(mOwnerUid); + ATRACE_FORMAT_INSTANT("releasePendingBuffer %s - %" PRIu64, getDebugName(), + handle->previousReleaseCallbackId.framenumber); + } + + for (auto& handle : mDrawingState.callbackHandles) { + if (handle->releasePreviousBuffer && mPreviousReleaseBufferEndpoint == handle->listener) { + handle->previousReleaseCallbackId = mPreviousReleaseCallbackId; + break; + } + } + + std::vector jankData; + transferAvailableJankData(mDrawingState.callbackHandles, jankData); + mFlinger->getTransactionCallbackInvoker().addCallbackHandles(mDrawingState.callbackHandles, + jankData); mDrawingState.callbackHandles = {}; } -bool Layer::setTransactionCompletedListeners(const std::vector>& handles) { +bool Layer::willPresentCurrentTransaction() const { + // Returns true if the most recent Transaction applied to CurrentState will be presented. + return (getSidebandStreamChanged() || getAutoRefresh() || + (mDrawingState.modified && + (mDrawingState.buffer != nullptr || mDrawingState.bgColorLayer != nullptr))); +} + +bool Layer::setTransform(uint32_t transform) { + if (mDrawingState.bufferTransform == transform) return false; + mDrawingState.bufferTransform = transform; + mDrawingState.modified = true; + setTransactionFlags(eTransactionNeeded); + return true; +} + +bool Layer::setTransformToDisplayInverse(bool transformToDisplayInverse) { + if (mDrawingState.transformToDisplayInverse == transformToDisplayInverse) return false; + mDrawingState.sequence++; + mDrawingState.transformToDisplayInverse = transformToDisplayInverse; + mDrawingState.modified = true; + setTransactionFlags(eTransactionNeeded); + return true; +} + +bool Layer::setBufferCrop(const Rect& bufferCrop) { + if (mDrawingState.bufferCrop == bufferCrop) return false; + + mDrawingState.sequence++; + mDrawingState.bufferCrop = bufferCrop; + + mDrawingState.modified = true; + setTransactionFlags(eTransactionNeeded); + return true; +} + +bool Layer::setDestinationFrame(const Rect& destinationFrame) { + if (mDrawingState.destinationFrame == destinationFrame) return false; + + mDrawingState.sequence++; + mDrawingState.destinationFrame = destinationFrame; + + mDrawingState.modified = true; + setTransactionFlags(eTransactionNeeded); + return true; +} + +// Translate destination frame into scale and position. If a destination frame is not set, use the +// provided scale and position +bool Layer::updateGeometry() { + if ((mDrawingState.flags & layer_state_t::eIgnoreDestinationFrame) || + mDrawingState.destinationFrame.isEmpty()) { + // If destination frame is not set, use the requested transform set via + // Layer::setPosition and Layer::setMatrix. + return assignTransform(&mDrawingState.transform, mRequestedTransform); + } + + Rect destRect = mDrawingState.destinationFrame; + int32_t destW = destRect.width(); + int32_t destH = destRect.height(); + if (destRect.left < 0) { + destRect.left = 0; + destRect.right = destW; + } + if (destRect.top < 0) { + destRect.top = 0; + destRect.bottom = destH; + } + + if (!mDrawingState.buffer) { + ui::Transform t; + t.set(destRect.left, destRect.top); + return assignTransform(&mDrawingState.transform, t); + } + + uint32_t bufferWidth = mDrawingState.buffer->getWidth(); + uint32_t bufferHeight = mDrawingState.buffer->getHeight(); + // Undo any transformations on the buffer. + if (mDrawingState.bufferTransform & ui::Transform::ROT_90) { + std::swap(bufferWidth, bufferHeight); + } + uint32_t invTransform = SurfaceFlinger::getActiveDisplayRotationFlags(); + if (mDrawingState.transformToDisplayInverse) { + if (invTransform & ui::Transform::ROT_90) { + std::swap(bufferWidth, bufferHeight); + } + } + + float sx = destW / static_cast(bufferWidth); + float sy = destH / static_cast(bufferHeight); + ui::Transform t; + t.set(sx, 0, 0, sy); + t.set(destRect.left, destRect.top); + return assignTransform(&mDrawingState.transform, t); +} + +bool Layer::setMatrix(const layer_state_t::matrix22_t& matrix) { + if (mRequestedTransform.dsdx() == matrix.dsdx && mRequestedTransform.dtdy() == matrix.dtdy && + mRequestedTransform.dtdx() == matrix.dtdx && mRequestedTransform.dsdy() == matrix.dsdy) { + return false; + } + + mRequestedTransform.set(matrix.dsdx, matrix.dtdy, matrix.dtdx, matrix.dsdy); + + mDrawingState.sequence++; + mDrawingState.modified = true; + setTransactionFlags(eTransactionNeeded); + + return true; +} + +bool Layer::setPosition(float x, float y) { + if (mRequestedTransform.tx() == x && mRequestedTransform.ty() == y) { + return false; + } + + mRequestedTransform.set(x, y); + + mDrawingState.sequence++; + mDrawingState.modified = true; + setTransactionFlags(eTransactionNeeded); + + return true; +} + +void Layer::resetDrawingStateBufferInfo() { + mDrawingState.producerId = 0; + mDrawingState.frameNumber = 0; + mDrawingState.releaseBufferListener = nullptr; + mDrawingState.buffer = nullptr; + mDrawingState.acquireFence = sp::make(-1); + mDrawingState.acquireFenceTime = std::make_unique(mDrawingState.acquireFence); + mCallbackHandleAcquireTimeOrFence = mDrawingState.acquireFenceTime->getSignalTime(); + mDrawingState.releaseBufferEndpoint = nullptr; +} + +bool Layer::setBuffer(std::shared_ptr& buffer, + const BufferData& bufferData, nsecs_t postTime, nsecs_t desiredPresentTime, + bool isAutoTimestamp, std::optional dequeueTime, + const FrameTimelineInfo& info) { + ATRACE_FORMAT("setBuffer %s - hasBuffer=%s", getDebugName(), (buffer ? "true" : "false")); + + const bool frameNumberChanged = + bufferData.flags.test(BufferData::BufferDataChange::frameNumberChanged); + const uint64_t frameNumber = + frameNumberChanged ? bufferData.frameNumber : mDrawingState.frameNumber + 1; + ATRACE_FORMAT_INSTANT("setBuffer %s - %" PRIu64, getDebugName(), frameNumber); + + if (mDrawingState.buffer) { + mReleasePreviousBuffer = true; + if (!mBufferInfo.mBuffer || + (!mDrawingState.buffer->hasSameBuffer(*mBufferInfo.mBuffer) || + mDrawingState.frameNumber != mBufferInfo.mFrameNumber)) { + // If mDrawingState has a buffer, and we are about to update again + // before swapping to drawing state, then the first buffer will be + // dropped and we should decrement the pending buffer count and + // call any release buffer callbacks if set. + callReleaseBufferCallback(mDrawingState.releaseBufferListener, + mDrawingState.buffer->getBuffer(), mDrawingState.frameNumber, + mDrawingState.acquireFence); + decrementPendingBufferCount(); + if (mDrawingState.bufferSurfaceFrameTX != nullptr && + mDrawingState.bufferSurfaceFrameTX->getPresentState() != PresentState::Presented) { + addSurfaceFrameDroppedForBuffer(mDrawingState.bufferSurfaceFrameTX); + mDrawingState.bufferSurfaceFrameTX.reset(); + } + } else if (EARLY_RELEASE_ENABLED && mLastClientCompositionFence != nullptr) { + callReleaseBufferCallback(mDrawingState.releaseBufferListener, + mDrawingState.buffer->getBuffer(), mDrawingState.frameNumber, + mLastClientCompositionFence); + mLastClientCompositionFence = nullptr; + } + } else if (buffer) { + // if we are latching a buffer for the first time then clear the mLastLatchTime since + // we don't want to incorrectly classify a frame if we miss the desired present time. + updateLastLatchTime(0); + } + + mDrawingState.desiredPresentTime = desiredPresentTime; + mDrawingState.isAutoTimestamp = isAutoTimestamp; + mDrawingState.latchedVsyncId = info.vsyncId; + mDrawingState.useVsyncIdForRefreshRateSelection = info.useForRefreshRateSelection; + mDrawingState.modified = true; + if (!buffer) { + resetDrawingStateBufferInfo(); + setTransactionFlags(eTransactionNeeded); + mDrawingState.bufferSurfaceFrameTX = nullptr; + setFrameTimelineVsyncForBufferlessTransaction(info, postTime); + return true; + } + + mDrawingState.producerId = bufferData.producerId; + mDrawingState.barrierProducerId = + std::max(mDrawingState.producerId, mDrawingState.barrierProducerId); + mDrawingState.frameNumber = frameNumber; + mDrawingState.barrierFrameNumber = + std::max(mDrawingState.frameNumber, mDrawingState.barrierFrameNumber); + + // TODO(b/277265947) log and flush transaction trace when we detect out of order updates + mDrawingState.releaseBufferListener = bufferData.releaseBufferListener; + mDrawingState.buffer = std::move(buffer); + mDrawingState.acquireFence = bufferData.flags.test(BufferData::BufferDataChange::fenceChanged) + ? bufferData.acquireFence + : Fence::NO_FENCE; + mDrawingState.acquireFenceTime = std::make_unique(mDrawingState.acquireFence); + if (mDrawingState.acquireFenceTime->getSignalTime() == Fence::SIGNAL_TIME_PENDING) { + // We latched this buffer unsiganled, so we need to pass the acquire fence + // on the callback instead of just the acquire time, since it's unknown at + // this point. + mCallbackHandleAcquireTimeOrFence = mDrawingState.acquireFence; + } else { + mCallbackHandleAcquireTimeOrFence = mDrawingState.acquireFenceTime->getSignalTime(); + } + setTransactionFlags(eTransactionNeeded); + + const int32_t layerId = getSequence(); + mFlinger->mTimeStats->setPostTime(layerId, mDrawingState.frameNumber, getName().c_str(), + mOwnerUid, postTime, getGameMode()); + + if (mFlinger->mLegacyFrontEndEnabled) { + recordLayerHistoryBufferUpdate(getLayerProps()); + } + + setFrameTimelineVsyncForBufferTransaction(info, postTime); + + if (dequeueTime && *dequeueTime != 0) { + const uint64_t bufferId = mDrawingState.buffer->getId(); + mFlinger->mFrameTracer->traceNewLayer(layerId, getName().c_str()); + mFlinger->mFrameTracer->traceTimestamp(layerId, bufferId, frameNumber, *dequeueTime, + FrameTracer::FrameEvent::DEQUEUE); + mFlinger->mFrameTracer->traceTimestamp(layerId, bufferId, frameNumber, postTime, + FrameTracer::FrameEvent::QUEUE); + } + + mDrawingState.releaseBufferEndpoint = bufferData.releaseBufferEndpoint; + return true; +} + +void Layer::setDesiredPresentTime(nsecs_t desiredPresentTime, bool isAutoTimestamp) { + mDrawingState.desiredPresentTime = desiredPresentTime; + mDrawingState.isAutoTimestamp = isAutoTimestamp; +} + +void Layer::recordLayerHistoryBufferUpdate(const scheduler::LayerProps& layerProps) { + ATRACE_CALL(); + const nsecs_t presentTime = [&] { + if (!mDrawingState.isAutoTimestamp) { + ATRACE_FORMAT_INSTANT("desiredPresentTime"); + return mDrawingState.desiredPresentTime; + } + + if (mDrawingState.useVsyncIdForRefreshRateSelection) { + const auto prediction = + mFlinger->mFrameTimeline->getTokenManager()->getPredictionsForToken( + mDrawingState.latchedVsyncId); + if (prediction.has_value()) { + ATRACE_FORMAT_INSTANT("predictedPresentTime"); + return prediction->presentTime; + } + } + + return static_cast(0); + }(); + + if (ATRACE_ENABLED() && presentTime > 0) { + const auto presentIn = TimePoint::fromNs(presentTime) - TimePoint::now(); + ATRACE_FORMAT_INSTANT("presentIn %s", to_string(presentIn).c_str()); + } + + mFlinger->mScheduler->recordLayerHistory(sequence, layerProps, presentTime, + scheduler::LayerHistory::LayerUpdateType::Buffer); +} + +void Layer::recordLayerHistoryAnimationTx(const scheduler::LayerProps& layerProps) { + const nsecs_t presentTime = + mDrawingState.isAutoTimestamp ? 0 : mDrawingState.desiredPresentTime; + mFlinger->mScheduler->recordLayerHistory(sequence, layerProps, presentTime, + scheduler::LayerHistory::LayerUpdateType::AnimationTX); +} + +bool Layer::setDataspace(ui::Dataspace dataspace) { + if (mDrawingState.dataspace == dataspace) return false; + mDrawingState.dataspace = dataspace; + mDrawingState.modified = true; + setTransactionFlags(eTransactionNeeded); + return true; +} + +bool Layer::setExtendedRangeBrightness(float currentBufferRatio, float desiredRatio) { + if (mDrawingState.currentHdrSdrRatio == currentBufferRatio && + mDrawingState.desiredHdrSdrRatio == desiredRatio) + return false; + mDrawingState.currentHdrSdrRatio = currentBufferRatio; + mDrawingState.desiredHdrSdrRatio = desiredRatio; + mDrawingState.modified = true; + setTransactionFlags(eTransactionNeeded); + return true; +} + +bool Layer::setCachingHint(gui::CachingHint cachingHint) { + if (mDrawingState.cachingHint == cachingHint) return false; + mDrawingState.cachingHint = cachingHint; + mDrawingState.modified = true; + setTransactionFlags(eTransactionNeeded); + return true; +} + +bool Layer::setHdrMetadata(const HdrMetadata& hdrMetadata) { + if (mDrawingState.hdrMetadata == hdrMetadata) return false; + mDrawingState.hdrMetadata = hdrMetadata; + mDrawingState.modified = true; + setTransactionFlags(eTransactionNeeded); + return true; +} + +bool Layer::setSurfaceDamageRegion(const Region& surfaceDamage) { + if (mDrawingState.surfaceDamageRegion.hasSameRects(surfaceDamage)) return false; + mDrawingState.surfaceDamageRegion = surfaceDamage; + mDrawingState.modified = true; + setTransactionFlags(eTransactionNeeded); + return true; +} + +bool Layer::setApi(int32_t api) { + if (mDrawingState.api == api) return false; + mDrawingState.api = api; + mDrawingState.modified = true; + setTransactionFlags(eTransactionNeeded); + return true; +} + +bool Layer::setSidebandStream(const sp& sidebandStream) { + if (mDrawingState.sidebandStream == sidebandStream) return false; + + if (mDrawingState.sidebandStream != nullptr && sidebandStream == nullptr) { + mFlinger->mTunnelModeEnabledReporter->decrementTunnelModeCount(); + } else if (sidebandStream != nullptr) { + mFlinger->mTunnelModeEnabledReporter->incrementTunnelModeCount(); + } + + mDrawingState.sidebandStream = sidebandStream; + mDrawingState.modified = true; + setTransactionFlags(eTransactionNeeded); + if (!mSidebandStreamChanged.exchange(true)) { + // mSidebandStreamChanged was false + mFlinger->onLayerUpdate(); + } + return true; +} + +bool Layer::setTransactionCompletedListeners(const std::vector>& handles, + bool willPresent) { + // If there is no handle, we will not send a callback so reset mReleasePreviousBuffer and return if (handles.empty()) { + mReleasePreviousBuffer = false; return false; } + std::deque> remainingHandles; for (const auto& handle : handles) { - mFlinger->getTransactionCallbackInvoker().registerUnpresentedCallbackHandle(handle); + // If this transaction set a buffer on this layer, release its previous buffer + handle->releasePreviousBuffer = mReleasePreviousBuffer; + + // If this layer will be presented in this frame + if (willPresent) { + // If this transaction set an acquire fence on this layer, set its acquire time + handle->acquireTimeOrFence = mCallbackHandleAcquireTimeOrFence; + handle->frameNumber = mDrawingState.frameNumber; + + // Store so latched time and release fence can be set + mDrawingState.callbackHandles.push_back(handle); + + } else { // If this layer will NOT need to be relatched and presented this frame + // Queue this handle to be notified below. + remainingHandles.push_back(handle); + } } - return true; + if (!remainingHandles.empty()) { + // Notify the transaction completed threads these handles are done. These are only the + // handles that were not added to the mDrawingState, which will be notified later. + std::vector jankData; + transferAvailableJankData(remainingHandles, jankData); + mFlinger->getTransactionCallbackInvoker().addCallbackHandles(remainingHandles, jankData); + } + + mReleasePreviousBuffer = false; + mCallbackHandleAcquireTimeOrFence = -1; + + return willPresent; +} + +Rect Layer::getBufferSize(const State& /*s*/) const { + // for buffer state layers we use the display frame size as the buffer size. + + if (mBufferInfo.mBuffer == nullptr) { + return Rect::INVALID_RECT; + } + + uint32_t bufWidth = mBufferInfo.mBuffer->getWidth(); + uint32_t bufHeight = mBufferInfo.mBuffer->getHeight(); + + // Undo any transformations on the buffer and return the result. + if (mBufferInfo.mTransform & ui::Transform::ROT_90) { + std::swap(bufWidth, bufHeight); + } + + if (getTransformToDisplayInverse()) { + uint32_t invTransform = SurfaceFlinger::getActiveDisplayRotationFlags(); + if (invTransform & ui::Transform::ROT_90) { + std::swap(bufWidth, bufHeight); + } + } + + return Rect(0, 0, static_cast(bufWidth), static_cast(bufHeight)); +} + +FloatRect Layer::computeSourceBounds(const FloatRect& parentBounds) const { + if (mBufferInfo.mBuffer == nullptr) { + return parentBounds; + } + + return getBufferSize(getDrawingState()).toFloatRect(); +} + +bool Layer::fenceHasSignaled() const { + if (SurfaceFlinger::enableLatchUnsignaledConfig != LatchUnsignaledConfig::Disabled) { + return true; + } + + const bool fenceSignaled = + getDrawingState().acquireFence->getStatus() == Fence::Status::Signaled; + if (!fenceSignaled) { + mFlinger->mTimeStats->incrementLatchSkipped(getSequence(), + TimeStats::LatchSkipReason::LateAcquire); + } + + return fenceSignaled; +} + +void Layer::onPreComposition(nsecs_t refreshStartTime) { + for (const auto& handle : mDrawingState.callbackHandles) { + handle->refreshStartTime = refreshStartTime; + } +} + +void Layer::setAutoRefresh(bool autoRefresh) { + mDrawingState.autoRefresh = autoRefresh; +} + +bool Layer::latchSidebandStream(bool& recomputeVisibleRegions) { + // We need to update the sideband stream if the layer has both a buffer and a sideband stream. + auto* snapshot = editLayerSnapshot(); + snapshot->sidebandStreamHasFrame = hasFrameUpdate() && mSidebandStream.get(); + + if (mSidebandStreamChanged.exchange(false)) { + const State& s(getDrawingState()); + // mSidebandStreamChanged was true + mSidebandStream = s.sidebandStream; + snapshot->sidebandStream = mSidebandStream; + if (mSidebandStream != nullptr) { + setTransactionFlags(eTransactionNeeded); + mFlinger->setTransactionFlags(eTraversalNeeded); + } + recomputeVisibleRegions = true; + + return true; + } + return false; +} + +bool Layer::hasFrameUpdate() const { + const State& c(getDrawingState()); + return (mDrawingStateModified || mDrawingState.modified) && + (c.buffer != nullptr || c.bgColorLayer != nullptr); +} + +void Layer::updateTexImage(nsecs_t latchTime, bool bgColorOnly) { + const State& s(getDrawingState()); + + if (!s.buffer) { + if (bgColorOnly || mBufferInfo.mBuffer) { + for (auto& handle : mDrawingState.callbackHandles) { + handle->latchTime = latchTime; + } + } + return; + } + + for (auto& handle : mDrawingState.callbackHandles) { + if (handle->frameNumber == mDrawingState.frameNumber) { + handle->latchTime = latchTime; + } + } + + const int32_t layerId = getSequence(); + const uint64_t bufferId = mDrawingState.buffer->getId(); + const uint64_t frameNumber = mDrawingState.frameNumber; + const auto acquireFence = std::make_shared(mDrawingState.acquireFence); + mFlinger->mTimeStats->setAcquireFence(layerId, frameNumber, acquireFence); + mFlinger->mTimeStats->setLatchTime(layerId, frameNumber, latchTime); + + mFlinger->mFrameTracer->traceFence(layerId, bufferId, frameNumber, acquireFence, + FrameTracer::FrameEvent::ACQUIRE_FENCE); + mFlinger->mFrameTracer->traceTimestamp(layerId, bufferId, frameNumber, latchTime, + FrameTracer::FrameEvent::LATCH); + + auto& bufferSurfaceFrame = mDrawingState.bufferSurfaceFrameTX; + if (bufferSurfaceFrame != nullptr && + bufferSurfaceFrame->getPresentState() != PresentState::Presented) { + // Update only if the bufferSurfaceFrame wasn't already presented. A Presented + // bufferSurfaceFrame could be seen here if a pending state was applied successfully and we + // are processing the next state. + addSurfaceFramePresentedForBuffer(bufferSurfaceFrame, + mDrawingState.acquireFenceTime->getSignalTime(), + latchTime); + mDrawingState.bufferSurfaceFrameTX.reset(); + } + + std::deque> remainingHandles; + mFlinger->getTransactionCallbackInvoker() + .addOnCommitCallbackHandles(mDrawingState.callbackHandles, remainingHandles); + mDrawingState.callbackHandles = remainingHandles; + + mDrawingStateModified = false; +} + +void Layer::gatherBufferInfo() { + mPreviousReleaseCallbackId = {getCurrentBufferId(), mBufferInfo.mFrameNumber}; + mPreviousReleaseBufferEndpoint = mBufferInfo.mReleaseBufferEndpoint; + if (!mDrawingState.buffer) { + mBufferInfo = {}; + return; + } + + if ((!mBufferInfo.mBuffer || !mDrawingState.buffer->hasSameBuffer(*mBufferInfo.mBuffer))) { + decrementPendingBufferCount(); + } + + mBufferInfo.mBuffer = mDrawingState.buffer; + mBufferInfo.mReleaseBufferEndpoint = mDrawingState.releaseBufferEndpoint; + mBufferInfo.mFence = mDrawingState.acquireFence; + mBufferInfo.mFrameNumber = mDrawingState.frameNumber; + mBufferInfo.mPixelFormat = + !mBufferInfo.mBuffer ? PIXEL_FORMAT_NONE : mBufferInfo.mBuffer->getPixelFormat(); + mBufferInfo.mFrameLatencyNeeded = true; + mBufferInfo.mDesiredPresentTime = mDrawingState.desiredPresentTime; + mBufferInfo.mFenceTime = std::make_shared(mDrawingState.acquireFence); + mBufferInfo.mFence = mDrawingState.acquireFence; + mBufferInfo.mTransform = mDrawingState.bufferTransform; + auto lastDataspace = mBufferInfo.mDataspace; + mBufferInfo.mDataspace = translateDataspace(mDrawingState.dataspace); + if (mBufferInfo.mBuffer != nullptr) { + auto& mapper = GraphicBufferMapper::get(); + // TODO: We should measure if it's faster to do a blind write if we're on newer api levels + // and don't need to possibly remaps buffers. + ui::Dataspace dataspace = ui::Dataspace::UNKNOWN; + status_t err = OK; + { + ATRACE_NAME("getDataspace"); + err = mapper.getDataspace(mBufferInfo.mBuffer->getBuffer()->handle, &dataspace); + } + if (err != OK || dataspace != mBufferInfo.mDataspace) { + { + ATRACE_NAME("setDataspace"); + err = mapper.setDataspace(mBufferInfo.mBuffer->getBuffer()->handle, + static_cast(mBufferInfo.mDataspace)); + } + + // Some GPU drivers may cache gralloc metadata which means before we composite we need + // to upsert RenderEngine's caches. Put in a special workaround to be backwards + // compatible with old vendors, with a ticking clock. + static const int32_t kVendorVersion = + base::GetIntProperty("ro.vndk.version", __ANDROID_API_FUTURE__); + if (const auto format = + static_cast( + mBufferInfo.mBuffer->getPixelFormat()); + err == OK && kVendorVersion < __ANDROID_API_U__ && + (format == + aidl::android::hardware::graphics::common::PixelFormat:: + IMPLEMENTATION_DEFINED || + format == aidl::android::hardware::graphics::common::PixelFormat::YCBCR_420_888 || + format == aidl::android::hardware::graphics::common::PixelFormat::YV12 || + format == aidl::android::hardware::graphics::common::PixelFormat::YCBCR_P010)) { + mBufferInfo.mBuffer->remapBuffer(); + } + } + } + if (lastDataspace != mBufferInfo.mDataspace) { + mFlinger->mHdrLayerInfoChanged = true; + } + if (mBufferInfo.mDesiredHdrSdrRatio != mDrawingState.desiredHdrSdrRatio) { + mBufferInfo.mDesiredHdrSdrRatio = mDrawingState.desiredHdrSdrRatio; + mFlinger->mHdrLayerInfoChanged = true; + } + mBufferInfo.mCrop = computeBufferCrop(mDrawingState); + mBufferInfo.mScaleMode = NATIVE_WINDOW_SCALING_MODE_SCALE_TO_WINDOW; + mBufferInfo.mSurfaceDamage = mDrawingState.surfaceDamageRegion; + mBufferInfo.mHdrMetadata = mDrawingState.hdrMetadata; + mBufferInfo.mApi = mDrawingState.api; + mBufferInfo.mTransformToDisplayInverse = mDrawingState.transformToDisplayInverse; +} + +Rect Layer::computeBufferCrop(const State& s) { + if (s.buffer && !s.bufferCrop.isEmpty()) { + Rect bufferCrop; + s.buffer->getBounds().intersect(s.bufferCrop, &bufferCrop); + return bufferCrop; + } else if (s.buffer) { + return s.buffer->getBounds(); + } else { + return s.bufferCrop; + } +} + +sp Layer::createClone(uint32_t mirrorRootId) { + LayerCreationArgs args(mFlinger.get(), nullptr, mName + " (Mirror)", 0, LayerMetadata()); + args.textureName = mTextureName; + sp layer = mFlinger->getFactory().createBufferStateLayer(args); + layer->setInitialValuesForClone(sp::fromExisting(this), mirrorRootId); + return layer; +} + +void Layer::decrementPendingBufferCount() { + int32_t pendingBuffers = --mPendingBufferTransactions; + tracePendingBufferCount(pendingBuffers); +} + +void Layer::tracePendingBufferCount(int32_t pendingBuffers) { + ATRACE_INT(mBlastTransactionName.c_str(), pendingBuffers); +} + +/* + * We don't want to send the layer's transform to input, but rather the + * parent's transform. This is because Layer's transform is + * information about how the buffer is placed on screen. The parent's + * transform makes more sense to send since it's information about how the + * layer is placed on screen. This transform is used by input to determine + * how to go from screen space back to window space. + */ +ui::Transform Layer::getInputTransform() const { + if (!hasBufferOrSidebandStream()) { + return getTransform(); + } + sp parent = mDrawingParent.promote(); + if (parent == nullptr) { + return ui::Transform(); + } + + return parent->getTransform(); +} + +/** + * Returns the bounds used to fill the input frame and the touchable region. + * + * Similar to getInputTransform, we need to update the bounds to include the transform. + * This is because bounds don't include the buffer transform, where the input assumes + * that's already included. + */ +std::pair Layer::getInputBounds(bool fillParentBounds) const { + Rect croppedBufferSize = getCroppedBufferSize(getDrawingState()); + FloatRect inputBounds = croppedBufferSize.toFloatRect(); + if (hasBufferOrSidebandStream() && croppedBufferSize.isValid() && + mDrawingState.transform.getType() != ui::Transform::IDENTITY) { + inputBounds = mDrawingState.transform.transform(inputBounds); + } + + bool inputBoundsValid = croppedBufferSize.isValid(); + if (!inputBoundsValid) { + /** + * Input bounds are based on the layer crop or buffer size. But if we are using + * the layer bounds as the input bounds (replaceTouchableRegionWithCrop flag) then + * we can use the parent bounds as the input bounds if the layer does not have buffer + * or a crop. We want to unify this logic but because of compat reasons we cannot always + * use the parent bounds. A layer without a buffer can get input. So when a window is + * initially added, its touchable region can fill its parent layer bounds and that can + * have negative consequences. + */ + inputBounds = fillParentBounds ? mBounds : FloatRect{}; + } + + // Clamp surface inset to the input bounds. + const float inset = static_cast(mDrawingState.inputInfo.surfaceInset); + const float xSurfaceInset = std::clamp(inset, 0.f, inputBounds.getWidth() / 2.f); + const float ySurfaceInset = std::clamp(inset, 0.f, inputBounds.getHeight() / 2.f); + + // Apply the insets to the input bounds. + inputBounds.left += xSurfaceInset; + inputBounds.top += ySurfaceInset; + inputBounds.right -= xSurfaceInset; + inputBounds.bottom -= ySurfaceInset; + + return {inputBounds, inputBoundsValid}; +} + +bool Layer::simpleBufferUpdate(const layer_state_t& s) const { + const uint64_t requiredFlags = layer_state_t::eBufferChanged; + + 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::eAutoRefreshChanged | + layer_state_t::eReparent; + + const uint64_t allowedFlags = layer_state_t::eHasListenerCallbacksChanged | + layer_state_t::eFrameRateSelectionPriority | layer_state_t::eFrameRateChanged | + layer_state_t::eSurfaceDamageRegionChanged | layer_state_t::eApiChanged | + layer_state_t::eMetadataChanged | layer_state_t::eDropInputModeChanged | + layer_state_t::eInputInfoChanged; + + if ((s.what & requiredFlags) != requiredFlags) { + ALOGV("%s: false [missing required flags 0x%" PRIx64 "]", __func__, + (s.what | requiredFlags) & ~s.what); + return false; + } + + if (s.what & deniedFlags) { + ALOGV("%s: false [has denied flags 0x%" PRIx64 "]", __func__, s.what & deniedFlags); + return false; + } + + if (s.what & allowedFlags) { + ALOGV("%s: [has allowed flags 0x%" PRIx64 "]", __func__, s.what & allowedFlags); + } + + if (s.what & layer_state_t::ePositionChanged) { + if (mRequestedTransform.tx() != s.x || mRequestedTransform.ty() != s.y) { + ALOGV("%s: false [ePositionChanged changed]", __func__); + return false; + } + } + + if (s.what & layer_state_t::eAlphaChanged) { + if (mDrawingState.color.a != s.color.a) { + ALOGV("%s: false [eAlphaChanged changed]", __func__); + return false; + } + } + + if (s.what & layer_state_t::eColorTransformChanged) { + if (mDrawingState.colorTransform != s.colorTransform) { + ALOGV("%s: false [eColorTransformChanged changed]", __func__); + return false; + } + } + + if (s.what & layer_state_t::eBackgroundColorChanged) { + if (mDrawingState.bgColorLayer || s.bgColor.a != 0) { + ALOGV("%s: false [eBackgroundColorChanged changed]", __func__); + return false; + } + } + + if (s.what & layer_state_t::eMatrixChanged) { + if (mRequestedTransform.dsdx() != s.matrix.dsdx || + mRequestedTransform.dtdy() != s.matrix.dtdy || + mRequestedTransform.dtdx() != s.matrix.dtdx || + mRequestedTransform.dsdy() != s.matrix.dsdy) { + ALOGV("%s: false [eMatrixChanged changed]", __func__); + return false; + } + } + + if (s.what & layer_state_t::eCornerRadiusChanged) { + if (mDrawingState.cornerRadius != s.cornerRadius) { + ALOGV("%s: false [eCornerRadiusChanged changed]", __func__); + return false; + } + } + + if (s.what & layer_state_t::eBackgroundBlurRadiusChanged) { + if (mDrawingState.backgroundBlurRadius != static_cast(s.backgroundBlurRadius)) { + ALOGV("%s: false [eBackgroundBlurRadiusChanged changed]", __func__); + return false; + } + } + + if (s.what & layer_state_t::eBufferTransformChanged) { + if (mDrawingState.bufferTransform != s.bufferTransform) { + ALOGV("%s: false [eBufferTransformChanged changed]", __func__); + return false; + } + } + + if (s.what & layer_state_t::eTransformToDisplayInverseChanged) { + if (mDrawingState.transformToDisplayInverse != s.transformToDisplayInverse) { + ALOGV("%s: false [eTransformToDisplayInverseChanged changed]", __func__); + return false; + } + } + + if (s.what & layer_state_t::eCropChanged) { + if (mDrawingState.crop != s.crop) { + ALOGV("%s: false [eCropChanged changed]", __func__); + return false; + } + } + + if (s.what & layer_state_t::eDataspaceChanged) { + if (mDrawingState.dataspace != s.dataspace) { + ALOGV("%s: false [eDataspaceChanged changed]", __func__); + return false; + } + } + + if (s.what & layer_state_t::eHdrMetadataChanged) { + if (mDrawingState.hdrMetadata != s.hdrMetadata) { + ALOGV("%s: false [eHdrMetadataChanged changed]", __func__); + return false; + } + } + + if (s.what & layer_state_t::eSidebandStreamChanged) { + if (mDrawingState.sidebandStream != s.sidebandStream) { + ALOGV("%s: false [eSidebandStreamChanged changed]", __func__); + return false; + } + } + + if (s.what & layer_state_t::eColorSpaceAgnosticChanged) { + if (mDrawingState.colorSpaceAgnostic != s.colorSpaceAgnostic) { + ALOGV("%s: false [eColorSpaceAgnosticChanged changed]", __func__); + return false; + } + } + + if (s.what & layer_state_t::eShadowRadiusChanged) { + if (mDrawingState.shadowRadius != s.shadowRadius) { + ALOGV("%s: false [eShadowRadiusChanged changed]", __func__); + return false; + } + } + + if (s.what & layer_state_t::eFixedTransformHintChanged) { + if (mDrawingState.fixedTransformHint != s.fixedTransformHint) { + ALOGV("%s: false [eFixedTransformHintChanged changed]", __func__); + return false; + } + } + + if (s.what & layer_state_t::eTrustedOverlayChanged) { + if (mDrawingState.isTrustedOverlay != s.isTrustedOverlay) { + ALOGV("%s: false [eTrustedOverlayChanged changed]", __func__); + return false; + } + } + + if (s.what & layer_state_t::eStretchChanged) { + StretchEffect temp = s.stretchEffect; + temp.sanitize(); + if (mDrawingState.stretchEffect != temp) { + ALOGV("%s: false [eStretchChanged changed]", __func__); + return false; + } + } + + if (s.what & layer_state_t::eBufferCropChanged) { + if (mDrawingState.bufferCrop != s.bufferCrop) { + ALOGV("%s: false [eBufferCropChanged changed]", __func__); + return false; + } + } + + if (s.what & layer_state_t::eDestinationFrameChanged) { + if (mDrawingState.destinationFrame != s.destinationFrame) { + ALOGV("%s: false [eDestinationFrameChanged changed]", __func__); + return false; + } + } + + if (s.what & layer_state_t::eDimmingEnabledChanged) { + if (mDrawingState.dimmingEnabled != s.dimmingEnabled) { + ALOGV("%s: false [eDimmingEnabledChanged changed]", __func__); + return false; + } + } + + if (s.what & layer_state_t::eExtendedRangeBrightnessChanged) { + if (mDrawingState.currentHdrSdrRatio != s.currentHdrSdrRatio || + mDrawingState.desiredHdrSdrRatio != s.desiredHdrSdrRatio) { + ALOGV("%s: false [eExtendedRangeBrightnessChanged changed]", __func__); + return false; + } + } + + ALOGV("%s: true", __func__); + return true; +} + +bool Layer::isHdrY410() const { + // pixel format is HDR Y410 masquerading as RGBA_1010102 + return (mBufferInfo.mDataspace == ui::Dataspace::BT2020_ITU_PQ && + mBufferInfo.mApi == NATIVE_WINDOW_API_MEDIA && + mBufferInfo.mPixelFormat == HAL_PIXEL_FORMAT_RGBA_1010102); +} + +sp Layer::getCompositionEngineLayerFE() const { + // There's no need to get a CE Layer if the layer isn't going to draw anything. + return hasSomethingToDraw() ? mLegacyLayerFE : nullptr; +} + +const LayerSnapshot* Layer::getLayerSnapshot() const { + return mSnapshot.get(); +} + +LayerSnapshot* Layer::editLayerSnapshot() { + return mSnapshot.get(); +} + +std::unique_ptr Layer::stealLayerSnapshot() { + return std::move(mSnapshot); +} + +void Layer::updateLayerSnapshot(std::unique_ptr snapshot) { + mSnapshot = std::move(snapshot); +} + +const compositionengine::LayerFECompositionState* Layer::getCompositionState() const { + return mSnapshot.get(); +} + +sp Layer::copyCompositionEngineLayerFE() const { + auto result = mFlinger->getFactory().createLayerFE(mName); + result->mSnapshot = std::make_unique(*mSnapshot); + return result; +} + +sp Layer::getCompositionEngineLayerFE( + const frontend::LayerHierarchy::TraversalPath& path) { + for (auto& [p, layerFE] : mLayerFEs) { + if (p == path) { + return layerFE; + } + } + auto layerFE = mFlinger->getFactory().createLayerFE(mName); + mLayerFEs.emplace_back(path, layerFE); + return layerFE; +} + +void Layer::useSurfaceDamage() { + if (mFlinger->mForceFullDamage) { + surfaceDamageRegion = Region::INVALID_REGION; + } else { + surfaceDamageRegion = mBufferInfo.mSurfaceDamage; + } +} + +void Layer::useEmptyDamage() { + surfaceDamageRegion.clear(); +} + +bool Layer::isOpaque(const Layer::State& s) const { + // if we don't have a buffer or sidebandStream yet, we're translucent regardless of the + // layer's opaque flag. + if (!hasSomethingToDraw()) { + return false; + } + + // if the layer has the opaque flag, then we're always opaque + if ((s.flags & layer_state_t::eLayerOpaque) == layer_state_t::eLayerOpaque) { + return true; + } + + // If the buffer has no alpha channel, then we are opaque + if (hasBufferOrSidebandStream() && LayerSnapshot::isOpaqueFormat(getPixelFormat())) { + return true; + } + + // Lastly consider the layer opaque if drawing a color with alpha == 1.0 + return fillsColor() && getAlpha() == 1.0_hf; +} + +bool Layer::canReceiveInput() const { + return !isHiddenByPolicy() && (mBufferInfo.mBuffer == nullptr || getAlpha() > 0.0f); +} + +bool Layer::isVisible() const { + if (!hasSomethingToDraw()) { + return false; + } + + if (isHiddenByPolicy()) { + return false; + } + + return getAlpha() > 0.0f || hasBlur(); +} + +void Layer::onPostComposition(const DisplayDevice* display, + const std::shared_ptr& glDoneFence, + const std::shared_ptr& presentFence, + const CompositorTiming& compositorTiming) { + // mFrameLatencyNeeded is true when a new frame was latched for the + // composition. + if (!mBufferInfo.mFrameLatencyNeeded) return; + + for (const auto& handle : mDrawingState.callbackHandles) { + handle->gpuCompositionDoneFence = glDoneFence; + handle->compositorTiming = compositorTiming; + } + + // Update mFrameTracker. + nsecs_t desiredPresentTime = mBufferInfo.mDesiredPresentTime; + mFrameTracker.setDesiredPresentTime(desiredPresentTime); + + const int32_t layerId = getSequence(); + mFlinger->mTimeStats->setDesiredTime(layerId, mCurrentFrameNumber, desiredPresentTime); + + const auto outputLayer = findOutputLayerForDisplay(display); + if (outputLayer && outputLayer->requiresClientComposition()) { + nsecs_t clientCompositionTimestamp = outputLayer->getState().clientCompositionTimestamp; + mFlinger->mFrameTracer->traceTimestamp(layerId, getCurrentBufferId(), mCurrentFrameNumber, + clientCompositionTimestamp, + FrameTracer::FrameEvent::FALLBACK_COMPOSITION); + // Update the SurfaceFrames in the drawing state + if (mDrawingState.bufferSurfaceFrameTX) { + mDrawingState.bufferSurfaceFrameTX->setGpuComposition(); + } + for (auto& [token, surfaceFrame] : mDrawingState.bufferlessSurfaceFramesTX) { + surfaceFrame->setGpuComposition(); + } + } + + std::shared_ptr frameReadyFence = mBufferInfo.mFenceTime; + if (frameReadyFence->isValid()) { + mFrameTracker.setFrameReadyFence(std::move(frameReadyFence)); + } else { + // There was no fence for this frame, so assume that it was ready + // to be presented at the desired present time. + mFrameTracker.setFrameReadyTime(desiredPresentTime); + } + + if (display) { + const Fps refreshRate = display->refreshRateSelector().getActiveMode().fps; + const std::optional renderRate = + mFlinger->mScheduler->getFrameRateOverride(getOwnerUid()); + + const auto vote = frameRateToSetFrameRateVotePayload(mDrawingState.frameRate); + const auto gameMode = getGameMode(); + + if (presentFence->isValid()) { + mFlinger->mTimeStats->setPresentFence(layerId, mCurrentFrameNumber, presentFence, + refreshRate, renderRate, vote, gameMode); + mFlinger->mFrameTracer->traceFence(layerId, getCurrentBufferId(), mCurrentFrameNumber, + presentFence, + FrameTracer::FrameEvent::PRESENT_FENCE); + mFrameTracker.setActualPresentFence(std::shared_ptr(presentFence)); + } else if (const auto displayId = PhysicalDisplayId::tryCast(display->getId()); + displayId && mFlinger->getHwComposer().isConnected(*displayId)) { + // The HWC doesn't support present fences, so use the present timestamp instead. + const nsecs_t presentTimestamp = + mFlinger->getHwComposer().getPresentTimestamp(*displayId); + + const nsecs_t now = systemTime(CLOCK_MONOTONIC); + const nsecs_t vsyncPeriod = display->getVsyncPeriodFromHWC(); + const nsecs_t actualPresentTime = now - ((now - presentTimestamp) % vsyncPeriod); + + mFlinger->mTimeStats->setPresentTime(layerId, mCurrentFrameNumber, actualPresentTime, + refreshRate, renderRate, vote, gameMode); + mFlinger->mFrameTracer->traceTimestamp(layerId, getCurrentBufferId(), + mCurrentFrameNumber, actualPresentTime, + FrameTracer::FrameEvent::PRESENT_FENCE); + mFrameTracker.setActualPresentTime(actualPresentTime); + } + } + + mFrameTracker.advanceFrame(); + mBufferInfo.mFrameLatencyNeeded = false; +} + +bool Layer::willReleaseBufferOnLatch() const { + return !mDrawingState.buffer && mBufferInfo.mBuffer; +} + +bool Layer::latchBuffer(bool& recomputeVisibleRegions, nsecs_t latchTime) { + const bool bgColorOnly = mDrawingState.bgColorLayer != nullptr; + return latchBufferImpl(recomputeVisibleRegions, latchTime, bgColorOnly); +} + +bool Layer::latchBufferImpl(bool& recomputeVisibleRegions, nsecs_t latchTime, bool bgColorOnly) { + ATRACE_FORMAT_INSTANT("latchBuffer %s - %" PRIu64, getDebugName(), + getDrawingState().frameNumber); + + bool refreshRequired = latchSidebandStream(recomputeVisibleRegions); + + if (refreshRequired) { + return refreshRequired; + } + + // If the head buffer's acquire fence hasn't signaled yet, return and + // try again later + if (!fenceHasSignaled()) { + ATRACE_NAME("!fenceHasSignaled()"); + mFlinger->onLayerUpdate(); + return false; + } + updateTexImage(latchTime, bgColorOnly); + + // Capture the old state of the layer for comparisons later + BufferInfo oldBufferInfo = mBufferInfo; + const bool oldOpacity = isOpaque(mDrawingState); + mPreviousFrameNumber = mCurrentFrameNumber; + mCurrentFrameNumber = mDrawingState.frameNumber; + gatherBufferInfo(); + + if (mBufferInfo.mBuffer) { + // We latched a buffer that will be presented soon. Clear the previously presented layer + // stack list. + mPreviouslyPresentedLayerStacks.clear(); + } + + if (mDrawingState.buffer == nullptr) { + const bool bufferReleased = oldBufferInfo.mBuffer != nullptr; + recomputeVisibleRegions = bufferReleased; + return bufferReleased; + } + + if (oldBufferInfo.mBuffer == nullptr) { + // the first time we receive a buffer, we need to trigger a + // geometry invalidation. + recomputeVisibleRegions = true; + } + + if ((mBufferInfo.mCrop != oldBufferInfo.mCrop) || + (mBufferInfo.mTransform != oldBufferInfo.mTransform) || + (mBufferInfo.mScaleMode != oldBufferInfo.mScaleMode) || + (mBufferInfo.mTransformToDisplayInverse != oldBufferInfo.mTransformToDisplayInverse)) { + recomputeVisibleRegions = true; + } + + if (oldBufferInfo.mBuffer != nullptr) { + uint32_t bufWidth = mBufferInfo.mBuffer->getWidth(); + uint32_t bufHeight = mBufferInfo.mBuffer->getHeight(); + if (bufWidth != oldBufferInfo.mBuffer->getWidth() || + bufHeight != oldBufferInfo.mBuffer->getHeight()) { + recomputeVisibleRegions = true; + } + } + + if (oldOpacity != isOpaque(mDrawingState)) { + recomputeVisibleRegions = true; + } + + return true; +} + +bool Layer::hasReadyFrame() const { + return hasFrameUpdate() || getSidebandStreamChanged() || getAutoRefresh(); +} + +bool Layer::isProtected() const { + return (mBufferInfo.mBuffer != nullptr) && + (mBufferInfo.mBuffer->getUsage() & GRALLOC_USAGE_PROTECTED); +} + +void Layer::latchAndReleaseBuffer() { + if (hasReadyFrame()) { + bool ignored = false; + latchBuffer(ignored, systemTime()); + } + releasePendingBuffer(systemTime()); +} + +PixelFormat Layer::getPixelFormat() const { + return mBufferInfo.mPixelFormat; +} + +bool Layer::getTransformToDisplayInverse() const { + return mBufferInfo.mTransformToDisplayInverse; +} + +Rect Layer::getBufferCrop() const { + // this is the crop rectangle that applies to the buffer + // itself (as opposed to the window) + if (!mBufferInfo.mCrop.isEmpty()) { + // if the buffer crop is defined, we use that + return mBufferInfo.mCrop; + } else if (mBufferInfo.mBuffer != nullptr) { + // otherwise we use the whole buffer + return mBufferInfo.mBuffer->getBounds(); + } else { + // if we don't have a buffer yet, we use an empty/invalid crop + return Rect(); + } +} + +uint32_t Layer::getBufferTransform() const { + return mBufferInfo.mTransform; +} + +ui::Dataspace Layer::getDataSpace() const { + return hasBufferOrSidebandStream() ? mBufferInfo.mDataspace : mDrawingState.dataspace; +} + +ui::Dataspace Layer::translateDataspace(ui::Dataspace dataspace) { + ui::Dataspace updatedDataspace = dataspace; + // translate legacy dataspaces to modern dataspaces + switch (dataspace) { + // Treat unknown dataspaces as V0_sRGB + case ui::Dataspace::UNKNOWN: + case ui::Dataspace::SRGB: + updatedDataspace = ui::Dataspace::V0_SRGB; + break; + case ui::Dataspace::SRGB_LINEAR: + updatedDataspace = ui::Dataspace::V0_SRGB_LINEAR; + break; + case ui::Dataspace::JFIF: + updatedDataspace = ui::Dataspace::V0_JFIF; + break; + case ui::Dataspace::BT601_625: + updatedDataspace = ui::Dataspace::V0_BT601_625; + break; + case ui::Dataspace::BT601_525: + updatedDataspace = ui::Dataspace::V0_BT601_525; + break; + case ui::Dataspace::BT709: + updatedDataspace = ui::Dataspace::V0_BT709; + break; + default: + break; + } + + return updatedDataspace; +} + +sp Layer::getBuffer() const { + return mBufferInfo.mBuffer ? mBufferInfo.mBuffer->getBuffer() : nullptr; +} + +void Layer::setTransformHintLegacy(ui::Transform::RotationFlags displayTransformHint) { + mTransformHintLegacy = getFixedTransformHint(); + if (mTransformHintLegacy == ui::Transform::ROT_INVALID) { + mTransformHintLegacy = displayTransformHint; + } + mSkipReportingTransformHint = false; +} + +const std::shared_ptr& Layer::getExternalTexture() const { + return mBufferInfo.mBuffer; +} + +bool Layer::setColor(const half3& color) { + if (mDrawingState.color.rgb == color) { + return false; + } + + mDrawingState.sequence++; + mDrawingState.color.rgb = color; + mDrawingState.modified = true; + setTransactionFlags(eTransactionNeeded); + return true; +} + +bool Layer::fillsColor() const { + return !hasBufferOrSidebandStream() && mDrawingState.color.r >= 0.0_hf && + mDrawingState.color.g >= 0.0_hf && mDrawingState.color.b >= 0.0_hf; +} + +bool Layer::hasBlur() const { + return getBackgroundBlurRadius() > 0 || getDrawingState().blurRegions.size() > 0; +} + +void Layer::updateSnapshot(bool updateGeometry) { + if (!getCompositionEngineLayerFE()) { + return; + } + + auto* snapshot = editLayerSnapshot(); + if (updateGeometry) { + prepareBasicGeometryCompositionState(); + prepareGeometryCompositionState(); + snapshot->roundedCorner = getRoundedCornerState(); + snapshot->stretchEffect = getStretchEffect(); + snapshot->transformedBounds = mScreenBounds; + if (mEffectiveShadowRadius > 0.f) { + snapshot->shadowSettings = mFlinger->mDrawingState.globalShadowSettings; + + // Note: this preserves existing behavior of shadowing the entire layer and not cropping + // it if transparent regions are present. This may not be necessary since shadows are + // typically cast by layers without transparent regions. + snapshot->shadowSettings.boundaries = mBounds; + + const float casterAlpha = snapshot->alpha; + const bool casterIsOpaque = + ((mBufferInfo.mBuffer != nullptr) && isOpaque(mDrawingState)); + + // If the casting layer is translucent, we need to fill in the shadow underneath the + // layer. Otherwise the generated shadow will only be shown around the casting layer. + snapshot->shadowSettings.casterIsTranslucent = !casterIsOpaque || (casterAlpha < 1.0f); + snapshot->shadowSettings.ambientColor *= casterAlpha; + snapshot->shadowSettings.spotColor *= casterAlpha; + } + snapshot->shadowSettings.length = mEffectiveShadowRadius; + } + snapshot->contentOpaque = isOpaque(mDrawingState); + snapshot->layerOpaqueFlagSet = + (mDrawingState.flags & layer_state_t::eLayerOpaque) == layer_state_t::eLayerOpaque; + snapshot->isHdrY410 = isHdrY410(); + sp p = mDrawingParent.promote(); + if (p != nullptr) { + snapshot->parentTransform = p->getTransform(); + } else { + snapshot->parentTransform.reset(); + } + snapshot->bufferSize = getBufferSize(mDrawingState); + snapshot->externalTexture = mBufferInfo.mBuffer; + snapshot->hasReadyFrame = hasReadyFrame(); + preparePerFrameCompositionState(); +} + +void Layer::updateChildrenSnapshots(bool updateGeometry) { + for (const sp& child : mDrawingChildren) { + child->updateSnapshot(updateGeometry); + child->updateChildrenSnapshots(updateGeometry); + } +} + +void Layer::updateMetadataSnapshot(const LayerMetadata& parentMetadata) { + mSnapshot->layerMetadata = parentMetadata; + mSnapshot->layerMetadata.merge(mDrawingState.metadata); + for (const sp& child : mDrawingChildren) { + child->updateMetadataSnapshot(mSnapshot->layerMetadata); + } +} + +void Layer::updateRelativeMetadataSnapshot(const LayerMetadata& relativeLayerMetadata, + std::unordered_set& visited) { + if (visited.find(this) != visited.end()) { + ALOGW("Cycle containing layer %s detected in z-order relatives", getDebugName()); + return; + } + visited.insert(this); + + mSnapshot->relativeLayerMetadata = relativeLayerMetadata; + + if (mDrawingState.zOrderRelatives.empty()) { + return; + } + LayerMetadata childRelativeLayerMetadata = mSnapshot->relativeLayerMetadata; + childRelativeLayerMetadata.merge(mSnapshot->layerMetadata); + for (wp weakRelative : mDrawingState.zOrderRelatives) { + sp relative = weakRelative.promote(); + if (!relative) { + continue; + } + relative->updateRelativeMetadataSnapshot(childRelativeLayerMetadata, visited); + } +} + +bool Layer::setTrustedPresentationInfo(TrustedPresentationThresholds const& thresholds, + TrustedPresentationListener const& listener) { + bool hadTrustedPresentationListener = hasTrustedPresentationListener(); + mTrustedPresentationListener = listener; + mTrustedPresentationThresholds = thresholds; + bool haveTrustedPresentationListener = hasTrustedPresentationListener(); + if (!hadTrustedPresentationListener && haveTrustedPresentationListener) { + mFlinger->mNumTrustedPresentationListeners++; + } else if (hadTrustedPresentationListener && !haveTrustedPresentationListener) { + mFlinger->mNumTrustedPresentationListeners--; + } + + // Reset trusted presentation states to ensure we start the time again. + mEnteredTrustedPresentationStateTime = -1; + mLastReportedTrustedPresentationState = false; + mLastComputedTrustedPresentationState = false; + + // If there's a new trusted presentation listener, the code needs to go through the composite + // path to ensure it recomutes the current state and invokes the TrustedPresentationListener if + // we're already in the requested state. + return haveTrustedPresentationListener; +} + +void Layer::updateLastLatchTime(nsecs_t latchTime) { + mLastLatchTime = latchTime; } // --------------------------------------------------------------------------- diff --git a/services/surfaceflinger/Layer.h b/services/surfaceflinger/Layer.h index 5ffcabf86b0930f96101e90339b81a5b201759d6..f7596e20e541b6dfce391493b84d4aacaf7937fe 100644 --- a/services/surfaceflinger/Layer.h +++ b/services/surfaceflinger/Layer.h @@ -17,8 +17,8 @@ #pragma once #include +#include #include -#include #include #include #include @@ -38,6 +38,7 @@ #include #include +#include #include #include @@ -48,13 +49,10 @@ #include #include "Client.h" -#include "ClientCache.h" -#include "DisplayHardware/ComposerHal.h" #include "DisplayHardware/HWComposer.h" #include "FrameTracker.h" +#include "LayerFE.h" #include "LayerVector.h" -#include "MonitoredProducer.h" -#include "RenderArea.h" #include "Scheduler/LayerInfo.h" #include "SurfaceFlinger.h" #include "Tracing/LayerTracing.h" @@ -69,39 +67,22 @@ class Colorizer; class DisplayDevice; class GraphicBuffer; class SurfaceFlinger; -class LayerDebugInfo; namespace compositionengine { class OutputLayer; struct LayerFECompositionState; } -namespace impl { -class SurfaceInterceptor; +namespace gui { +class LayerDebugInfo; } namespace frametimeline { class SurfaceFrame; } // namespace frametimeline -struct LayerCreationArgs { - LayerCreationArgs(SurfaceFlinger*, sp, std::string name, uint32_t flags, LayerMetadata); - - SurfaceFlinger* flinger; - const sp client; - std::string name; - uint32_t flags; - LayerMetadata metadata; - - pid_t callingPid; - uid_t callingUid; - uint32_t textureName; - std::optional sequence = std::nullopt; - bool addToRoot = true; -}; - -class Layer : public virtual RefBase, compositionengine::LayerFE { - static std::atomic sSequence; +class Layer : public virtual RefBase { +public: // The following constants represent priority of the window. SF uses this information when // deciding which window has a priority when deciding about the refresh rate of the screen. // Priority 0 is considered the highest priority. -1 means that the priority is unset. @@ -113,7 +94,6 @@ class Layer : public virtual RefBase, compositionengine::LayerFE { // Windows that are not in focus, but voted for a specific mode ID. static constexpr int32_t PRIORITY_NOT_FOCUSED_WITH_MODE = 2; -public: enum { // flags for doTransaction() eDontUpdateGeometryState = 0x00000001, eVisibleRegion = 0x00000002, @@ -132,85 +112,55 @@ public: inline bool operator!=(const Geometry& rhs) const { return !operator==(rhs); } }; - struct RoundedCornerState { - RoundedCornerState() = default; - RoundedCornerState(const FloatRect& cropRect, const vec2& radius) - : cropRect(cropRect), radius(radius) {} - - // Rounded rectangle in local layer coordinate space. - FloatRect cropRect = FloatRect(); - // Radius of the rounded rectangle. - vec2 radius; - bool hasRoundedCorners() const { return radius.x > 0.0f && radius.y > 0.0f; } - }; - using FrameRate = scheduler::LayerInfo::FrameRate; using FrameRateCompatibility = scheduler::LayerInfo::FrameRateCompatibility; struct State { - Geometry active_legacy; - Geometry requested_legacy; int32_t z; - ui::LayerStack layerStack; - uint32_t flags; - uint8_t reserved[2]; int32_t sequence; // changes when visible regions can change bool modified; - // Crop is expressed in layer space coordinate. Rect crop; - Rect requestedCrop; - - // the transparentRegion hint is a bit special, it's latched only - // when we receive a buffer -- this is because it's "content" - // dependent. - Region activeTransparentRegion_legacy; - Region requestedTransparentRegion_legacy; - LayerMetadata metadata; - // If non-null, a Surface this Surface's Z-order is interpreted relative to. wp zOrderRelativeOf; bool isRelativeOf{false}; // A list of surfaces whose Z-order is interpreted relative to ours. SortedVector> zOrderRelatives; - half4 color; float cornerRadius; int backgroundBlurRadius; - gui::WindowInfo inputInfo; wp touchableRegionCrop; - // dataspace is only used by BufferStateLayer and EffectLayer ui::Dataspace dataspace; - // The fields below this point are only used by BufferStateLayer uint64_t frameNumber; - uint32_t width; - uint32_t height; + // high watermark framenumber to use to check for barriers to protect ourselves + // from out of order transactions + uint64_t barrierFrameNumber; ui::Transform transform; + uint32_t producerId = 0; + // high watermark producerId to use to check for barriers to protect ourselves + // from out of order transactions + uint32_t barrierProducerId = 0; + uint32_t bufferTransform; bool transformToDisplayInverse; - Region transparentRegionHint; - std::shared_ptr buffer; - client_cache_t clientCacheId; sp acquireFence; std::shared_ptr acquireFenceTime; HdrMetadata hdrMetadata; Region surfaceDamageRegion; int32_t api; - sp sidebandStream; mat4 colorTransform; bool hasColorTransform; - // pointer to background color layer that, if set, appears below the buffer state layer // and the buffer state layer's children. Z order will be set to // INT_MIN @@ -233,6 +183,8 @@ public: // Priority of the layer assigned by Window Manager. int32_t frameRateSelectionPriority; + // Default frame rate compatibility used to set the layer refresh rate votetype. + FrameRateCompatibility defaultFrameRateCompatibility; FrameRate frameRate; // The combined frame rate of parents / children of this layer @@ -252,7 +204,6 @@ public: // When the transaction was posted nsecs_t postTime; - sp releaseBufferListener; // SurfaceFrame that tracks the timeline of Transactions that contain a Buffer. Only one // such SurfaceFrame exists because only one buffer can be presented on the layer per vsync. @@ -273,59 +224,19 @@ public: // Whether or not this layer is a trusted overlay for input bool isTrustedOverlay; - Rect bufferCrop; Rect destinationFrame; - sp releaseBufferEndpoint; - gui::DropInputMode dropInputMode; - bool autoRefresh = false; - bool dimmingEnabled = true; + float currentHdrSdrRatio = 1.f; + float desiredHdrSdrRatio = 1.f; + gui::CachingHint cachingHint = gui::CachingHint::Enabled; + int64_t latchedVsyncId = 0; + bool useVsyncIdForRefreshRateSelection = false; }; - /* - * Trivial class, used to ensure that mFlinger->onLayerDestroyed(mLayer) - * is called. - */ - class LayerCleaner { - sp mFlinger; - sp mLayer; - BBinder* mHandle; - - protected: - ~LayerCleaner() { - // destroy client resources - mFlinger->onHandleDestroyed(mHandle, mLayer); - } - - public: - LayerCleaner(const sp& flinger, const sp& layer, BBinder* handle) - : mFlinger(flinger), mLayer(layer), mHandle(handle) {} - }; - - /* - * The layer handle is just a BBinder object passed to the client - * (remote process) -- we don't keep any reference on our side such that - * the dtor is called when the remote side let go of its reference. - * - * LayerCleaner ensures that mFlinger->onLayerDestroyed() is called for - * this layer when the handle is destroyed. - */ - class Handle : public BBinder, public LayerCleaner { - public: - Handle(const sp& flinger, const sp& layer) - : LayerCleaner(flinger, layer, this), owner(layer) {} - const String16& getInterfaceDescriptor() const override { return kDescriptor; } - - static const String16 kDescriptor; - wp owner; - }; - - static wp fromHandle(const sp& handle); - explicit Layer(const LayerCreationArgs& args); virtual ~Layer(); @@ -333,43 +244,17 @@ public: static void miniDumpHeader(std::string& result); // Provide unique string for each class type in the Layer hierarchy - virtual const char* getType() const = 0; + virtual const char* getType() const { return "Layer"; } // true if this layer is visible, false otherwise - virtual bool isVisible() const = 0; + virtual bool isVisible() const; - virtual sp createClone() = 0; + virtual sp createClone(uint32_t mirrorRoot); - // Geometry setting functions. - // - // The following group of functions are used to specify the layers - // bounds, and the mapping of the texture on to those bounds. According - // to various settings changes to them may apply immediately, or be delayed until - // a pending resize is completed by the producer submitting a buffer. For example - // if we were to change the buffer size, and update the matrix ahead of the - // new buffer arriving, then we would be stretching the buffer to a different - // aspect before and after the buffer arriving, which probably isn't what we wanted. - // - // The first set of geometry functions are controlled by the scaling mode, described - // in window.h. The scaling mode may be set by the client, as it submits buffers. - // - // Put simply, if our scaling mode is SCALING_MODE_FREEZE, then - // matrix updates will not be applied while a resize is pending - // and the size and transform will remain in their previous state - // until a new buffer is submitted. If the scaling mode is another value - // then the old-buffer will immediately be scaled to the pending size - // and the new matrix will be immediately applied following this scaling - // transformation. - - // Set the default buffer size for the assosciated Producer, in pixels. This is - // also the rendered size of the layer prior to any transformations. Parent - // or local matrix transformations will not affect the size of the buffer, - // but may affect it's on-screen size or clipping. - virtual bool setSize(uint32_t w, uint32_t h); // Set a 2x2 transformation matrix on the layer. This transform // will be applied after parent transforms, but before any final // producer specified transform. - virtual bool setMatrix(const layer_state_t::matrix22_t& matrix); + bool setMatrix(const layer_state_t::matrix22_t& matrix); // This second set of geometry attributes are controlled by // setGeometryAppliesWithResize, and their default mode is to be @@ -379,9 +264,9 @@ public: // setPosition operates in parent buffer space (pre parent-transform) or display // space for top-level layers. - virtual bool setPosition(float x, float y); + bool setPosition(float x, float y); // Buffer space - virtual bool setCrop(const Rect& crop); + bool setCrop(const Rect& crop); // TODO(b/38182121): Could we eliminate the various latching modes by // using the layer hierarchy? @@ -390,7 +275,7 @@ public: virtual bool setRelativeLayer(const sp& relativeToHandle, int32_t relativeZ); virtual bool setAlpha(float alpha); - virtual bool setColor(const half3& /*color*/) { return false; }; + bool setColor(const half3& /*color*/); // Set rounded corner radius for this layer and its children. // @@ -402,11 +287,13 @@ public: // is specified in pixels. virtual bool setBackgroundBlurRadius(int backgroundBlurRadius); virtual bool setBlurRegions(const std::vector& effectRegions); - virtual bool setTransparentRegionHint(const Region& transparent); + bool setTransparentRegionHint(const Region& transparent); virtual bool setTrustedOverlay(bool); virtual bool setFlags(uint32_t flags, uint32_t mask); virtual bool setLayerStack(ui::LayerStack); - virtual ui::LayerStack getLayerStack() const; + virtual ui::LayerStack getLayerStack( + LayerVector::StateSet state = LayerVector::StateSet::Drawing) const; + virtual bool setMetadata(const LayerMetadata& data); virtual void setChildrenDrawingParent(const sp&); virtual bool reparent(const sp& newParentHandle) REQUIRES(mFlinger->mStateLock); @@ -414,48 +301,61 @@ public: virtual mat4 getColorTransform() const; virtual bool hasColorTransform() const; virtual bool isColorSpaceAgnostic() const { return mDrawingState.colorSpaceAgnostic; } - virtual bool isDimmingEnabled() const { return getDrawingState().dimmingEnabled; }; - - // Used only to set BufferStateLayer state - virtual bool setTransform(uint32_t /*transform*/) { return false; }; - virtual bool setTransformToDisplayInverse(bool /*transformToDisplayInverse*/) { return false; }; - virtual bool setBuffer(std::shared_ptr& /* buffer */, - const BufferData& /* bufferData */, nsecs_t /* postTime */, - nsecs_t /*desiredPresentTime*/, bool /*isAutoTimestamp*/, - std::optional /* dequeueTime */, - const FrameTimelineInfo& /*info*/) { - return false; - }; - virtual bool setDataspace(ui::Dataspace /*dataspace*/) { return false; }; - virtual bool setHdrMetadata(const HdrMetadata& /*hdrMetadata*/) { return false; }; - virtual bool setSurfaceDamageRegion(const Region& /*surfaceDamage*/) { return false; }; - virtual bool setApi(int32_t /*api*/) { return false; }; - virtual bool setSidebandStream(const sp& /*sidebandStream*/) { return false; }; - virtual bool setTransactionCompletedListeners( - const std::vector>& /*handles*/); + virtual bool isDimmingEnabled() const { return getDrawingState().dimmingEnabled; } + float getDesiredHdrSdrRatio() const { return getDrawingState().desiredHdrSdrRatio; } + float getCurrentHdrSdrRatio() const { return getDrawingState().currentHdrSdrRatio; } + gui::CachingHint getCachingHint() const { return getDrawingState().cachingHint; } + + bool setTransform(uint32_t /*transform*/); + bool setTransformToDisplayInverse(bool /*transformToDisplayInverse*/); + bool setBuffer(std::shared_ptr& /* buffer */, + const BufferData& /* bufferData */, nsecs_t /* postTime */, + nsecs_t /*desiredPresentTime*/, bool /*isAutoTimestamp*/, + std::optional /* dequeueTime */, const FrameTimelineInfo& /*info*/); + void setDesiredPresentTime(nsecs_t /*desiredPresentTime*/, bool /*isAutoTimestamp*/); + bool setDataspace(ui::Dataspace /*dataspace*/); + bool setExtendedRangeBrightness(float currentBufferRatio, float desiredRatio); + bool setCachingHint(gui::CachingHint cachingHint); + bool setHdrMetadata(const HdrMetadata& /*hdrMetadata*/); + bool setSurfaceDamageRegion(const Region& /*surfaceDamage*/); + bool setApi(int32_t /*api*/); + bool setSidebandStream(const sp& /*sidebandStream*/); + bool setTransactionCompletedListeners(const std::vector>& /*handles*/, + bool willPresent); virtual bool setBackgroundColor(const half3& color, float alpha, ui::Dataspace dataspace) REQUIRES(mFlinger->mStateLock); virtual bool setColorSpaceAgnostic(const bool agnostic); virtual bool setDimmingEnabled(const bool dimmingEnabled); + virtual bool setDefaultFrameRateCompatibility(FrameRateCompatibility compatibility); virtual bool setFrameRateSelectionPriority(int32_t priority); virtual bool setFixedTransformHint(ui::Transform::RotationFlags fixedTransformHint); - virtual void setAutoRefresh(bool /* autoRefresh */) {} + void setAutoRefresh(bool /* autoRefresh */); bool setDropInputMode(gui::DropInputMode); // If the variable is not set on the layer, it traverses up the tree to inherit the frame // rate priority from its parent. virtual int32_t getFrameRateSelectionPriority() const; - virtual ui::Dataspace getDataSpace() const { return ui::Dataspace::UNKNOWN; } + // + virtual FrameRateCompatibility getDefaultFrameRateCompatibility() const; + // + ui::Dataspace getDataSpace() const; + + virtual sp getCompositionEngineLayerFE() const; + virtual sp copyCompositionEngineLayerFE() const; + sp getCompositionEngineLayerFE(const frontend::LayerHierarchy::TraversalPath&); - virtual sp getCompositionEngineLayerFE() const; - virtual compositionengine::LayerFECompositionState* editCompositionState(); + const frontend::LayerSnapshot* getLayerSnapshot() const; + frontend::LayerSnapshot* editLayerSnapshot(); + std::unique_ptr stealLayerSnapshot(); + void updateLayerSnapshot(std::unique_ptr snapshot); // If we have received a new buffer this frame, we will pass its surface // damage down to hardware composer. Otherwise, we must send a region with // one empty rect. - virtual void useSurfaceDamage() {} - virtual void useEmptyDamage() {} + void useSurfaceDamage(); + void useEmptyDamage(); Region getVisibleRegion(const DisplayDevice*) const; + void updateLastLatchTime(nsecs_t latchtime); /* * isOpaque - true if this surface is opaque @@ -464,12 +364,12 @@ public: * pixel format includes an alpha channel) and the "opaque" flag set * on the layer. It does not examine the current plane alpha value. */ - virtual bool isOpaque(const Layer::State&) const { return false; } + bool isOpaque(const Layer::State&) const; /* * Returns whether this layer can receive input. */ - virtual bool canReceiveInput() const; + bool canReceiveInput() const; /* * Whether or not the layer should be considered visible for input calculations. @@ -490,7 +390,7 @@ public: * isProtected - true if the layer may contain protected contents in the * GRALLOC_USAGE_PROTECTED sense. */ - virtual bool isProtected() const { return false; } + bool isProtected() const; /* * isFixedSize - true if content has a fixed size @@ -500,21 +400,19 @@ public: /* * usesSourceCrop - true if content should use a source crop */ - virtual bool usesSourceCrop() const { return false; } + bool usesSourceCrop() const { return hasBufferOrSidebandStream(); } // Most layers aren't created from the main thread, and therefore need to // grab the SF state lock to access HWC, but ContainerLayer does, so we need // to avoid grabbing the lock again to avoid deadlock virtual bool isCreatedFromMainThread() const { return false; } - uint32_t getActiveWidth(const Layer::State& s) const { return s.width; } - uint32_t getActiveHeight(const Layer::State& s) const { return s.height; } ui::Transform getActiveTransform(const Layer::State& s) const { return s.transform; } - virtual Region getActiveTransparentRegion(const Layer::State& s) const { - return s.activeTransparentRegion_legacy; + Region getActiveTransparentRegion(const Layer::State& s) const { + return s.transparentRegionHint; } - virtual Rect getCrop(const Layer::State& s) const { return s.crop; } - virtual bool needsFiltering(const DisplayDevice*) const { return false; } + Rect getCrop(const Layer::State& s) const { return s.crop; } + bool needsFiltering(const DisplayDevice*) const; // True if this layer requires filtering // This method is distinct from needsFiltering() in how the filter @@ -525,32 +423,25 @@ public: // different. // If the parent transform needs to be undone when capturing the layer, then // the inverse parent transform is also required. - virtual bool needsFilteringForScreenshots(const DisplayDevice*, const ui::Transform&) const { - return false; - } + bool needsFilteringForScreenshots(const DisplayDevice*, const ui::Transform&) const; - virtual void updateCloneBufferInfo(){}; + // from graphics API + ui::Dataspace translateDataspace(ui::Dataspace dataspace); + void updateCloneBufferInfo(); + uint64_t mPreviousFrameNumber = 0; - virtual void setDefaultBufferSize(uint32_t /*w*/, uint32_t /*h*/) {} - - virtual bool isHdrY410() const { return false; } - - virtual bool shouldPresentNow(nsecs_t /*expectedPresentTime*/) const { return false; } + bool isHdrY410() const; /* * called after composition. * returns true if the layer latched a new buffer this frame. */ - virtual void onPostComposition(const DisplayDevice*, - const std::shared_ptr& /*glDoneFence*/, - const std::shared_ptr& /*presentFence*/, - const CompositorTiming&) {} + void onPostComposition(const DisplayDevice*, const std::shared_ptr& /*glDoneFence*/, + const std::shared_ptr& /*presentFence*/, + const CompositorTiming&); // If a buffer was replaced this frame, release the former buffer - virtual void releasePendingBuffer(nsecs_t /*dequeueReadyTime*/) { } - - virtual void finalizeFrameEventHistory(const std::shared_ptr& /*glDoneFence*/, - const CompositorTiming& /*compositorTiming*/) {} + void releasePendingBuffer(nsecs_t /*dequeueReadyTime*/); /* * latchBuffer - called each time the screen is redrawn and returns whether @@ -558,89 +449,119 @@ public: * operation, so this should be set only if needed). Typically this is used * to figure out if the content or size of a surface has changed. */ - virtual bool latchBuffer(bool& /*recomputeVisibleRegions*/, nsecs_t /*latchTime*/, - nsecs_t /*expectedPresentTime*/) { - return false; - } + bool latchBuffer(bool& /*recomputeVisibleRegions*/, nsecs_t /*latchTime*/); + + bool latchBufferImpl(bool& /*recomputeVisibleRegions*/, nsecs_t /*latchTime*/, + bool bgColorOnly); + + /* + * Returns true if the currently presented buffer will be released when this layer state + * is latched. This will return false if there is no buffer currently presented. + */ + bool willReleaseBufferOnLatch() const; - virtual void latchAndReleaseBuffer() {} + /* + * Calls latchBuffer if the buffer has a frame queued and then releases the buffer. + * This is used if the buffer is just latched and releases to free up the buffer + * and will not be shown on screen. + * Should only be called on the main thread. + */ + void latchAndReleaseBuffer(); /* * returns the rectangle that crops the content of the layer and scales it * to the layer's size. */ - virtual Rect getBufferCrop() const { return Rect(); } + Rect getBufferCrop() const; /* * Returns the transform applied to the buffer. */ - virtual uint32_t getBufferTransform() const { return 0; } - - virtual sp getBuffer() const { return nullptr; } - virtual const std::shared_ptr& getExternalTexture() const { - return mDrawingState.buffer; - }; + uint32_t getBufferTransform() const; - virtual ui::Transform::RotationFlags getTransformHint() const { return ui::Transform::ROT_0; } + sp getBuffer() const; + const std::shared_ptr& getExternalTexture() const; /* * Returns if a frame is ready */ - virtual bool hasReadyFrame() const { return false; } + bool hasReadyFrame() const; virtual int32_t getQueuedFrameCount() const { return 0; } /** * Returns active buffer size in the correct orientation. Buffer size is determined by undoing - * any buffer transformations. If the layer has no buffer then return INVALID_RECT. + * any buffer transformations. Returns Rect::INVALID_RECT if the layer has no buffer or the + * layer does not have a display frame and its parent is not bounded. */ - virtual Rect getBufferSize(const Layer::State&) const { return Rect::INVALID_RECT; } + Rect getBufferSize(const Layer::State&) const; /** * Returns the source bounds. If the bounds are not defined, it is inferred from the * buffer size. Failing that, the bounds are determined from the passed in parent bounds. * For the root layer, this is the display viewport size. */ - virtual FloatRect computeSourceBounds(const FloatRect& parentBounds) const { - return parentBounds; - } + FloatRect computeSourceBounds(const FloatRect& parentBounds) const; virtual FrameRate getFrameRateForLayerTree() const; - virtual bool getTransformToDisplayInverse() const { return false; } + bool getTransformToDisplayInverse() const; // Returns how rounded corners should be drawn for this layer. // A layer can override its parent's rounded corner settings if the parent's rounded // corner crop does not intersect with its own rounded corner crop. - virtual RoundedCornerState getRoundedCornerState() const; + virtual frontend::RoundedCornerState getRoundedCornerState() const; - bool hasRoundedCorners() const override { return getRoundedCornerState().hasRoundedCorners(); } + bool hasRoundedCorners() const { return getRoundedCornerState().hasRoundedCorners(); } - virtual PixelFormat getPixelFormat() const { return PIXEL_FORMAT_NONE; } + PixelFormat getPixelFormat() const; /** - * Return whether this layer needs an input info. For most layer types - * this is only true if they explicitly set an input-info but BufferLayer - * overrides this so we can generate input-info for Buffered layers that don't - * have them (for input occlusion detection checks). + * Return whether this layer needs an input info. We generate InputWindowHandles for all + * non-cursor buffered layers regardless of whether they have an InputChannel. This is to enable + * the InputDispatcher to do PID based occlusion detection. */ - virtual bool needsInputInfo() const { return hasInputInfo(); } + bool needsInputInfo() const { + return (hasInputInfo() || hasBufferOrSidebandStream()) && !mPotentialCursor; + } // Implements RefBase. void onFirstRef() override; + struct BufferInfo { + nsecs_t mDesiredPresentTime; + std::shared_ptr mFenceTime; + sp mFence; + uint32_t mTransform{0}; + ui::Dataspace mDataspace{ui::Dataspace::UNKNOWN}; + Rect mCrop; + uint32_t mScaleMode{NATIVE_WINDOW_SCALING_MODE_FREEZE}; + Region mSurfaceDamage; + HdrMetadata mHdrMetadata; + int mApi; + PixelFormat mPixelFormat{PIXEL_FORMAT_NONE}; + bool mTransformToDisplayInverse{false}; + + std::shared_ptr mBuffer; + uint64_t mFrameNumber; + sp mReleaseBufferEndpoint; + + bool mFrameLatencyNeeded{false}; + float mDesiredHdrSdrRatio = 1.f; + }; + + BufferInfo mBufferInfo; + // implements compositionengine::LayerFE - const compositionengine::LayerFECompositionState* getCompositionState() const override; - bool onPreComposition(nsecs_t) override; - void prepareCompositionState(compositionengine::LayerFE::StateSubset subset) override; - std::vector prepareClientCompositionList( - compositionengine::LayerFE::ClientCompositionTargetSettings&) override; - void onLayerDisplayed(ftl::SharedFuture) override; - - void setWasClientComposed(const sp& fence) override { + const compositionengine::LayerFECompositionState* getCompositionState() const; + bool fenceHasSignaled() const; + void onPreComposition(nsecs_t refreshStartTime); + void onLayerDisplayed(ftl::SharedFuture, ui::LayerStack layerStack); + + void setWasClientComposed(const sp& fence) { mLastClientCompositionFence = fence; mClearClientCompositionFenceOnLayerDisplayed = false; } - const char* getDebugName() const override; + const char* getDebugName() const; bool setShadowRadius(float shadowRadius); @@ -653,6 +574,20 @@ public: uint32_t getTransactionFlags() const { return mTransactionFlags; } + static bool computeTrustedPresentationState(const FloatRect& bounds, + const FloatRect& sourceBounds, + const Region& coveredRegion, + const FloatRect& screenBounds, float, + const ui::Transform&, + const TrustedPresentationThresholds&); + void updateTrustedPresentationState(const DisplayDevice* display, + const frontend::LayerSnapshot* snapshot, int64_t time_in_ms, + bool leaveState); + + inline bool hasTrustedPresentationListener() { + return mTrustedPresentationListener.callbackInterface != nullptr; + } + // Sets the masked bits. void setTransactionFlags(uint32_t mask); @@ -661,11 +596,13 @@ public: FloatRect getBounds(const Region& activeTransparentRegion) const; FloatRect getBounds() const; + Rect getInputBoundsInDisplaySpace(const FloatRect& insetBounds, + const ui::Transform& displayTransform); // Compute bounds for the layer and cache the results. void computeBounds(FloatRect parentBounds, ui::Transform parentTransform, float shadowRadius); - int32_t getSequence() const override { return sequence; } + int32_t getSequence() const { return sequence; } // For tracing. // TODO: Replace with raw buffer id from buffer metadata when that becomes available. @@ -701,6 +638,7 @@ public: bool isRemovedFromCurrentState() const; LayerProto* writeToProto(LayersProto& layersProto, uint32_t traceFlags); + void writeCompositionStateToProto(LayerProto* layerProto, ui::LayerStack layerStack); // Write states that are modified by the main thread. This includes drawing // state as well as buffer data. This should be called in the main or tracing @@ -714,7 +652,7 @@ public: gui::WindowInfo::Type getWindowType() const { return mWindowType; } - void updateMirrorInfo(); + bool updateMirrorInfo(const std::deque& cloneRootsPendingUpdates); /* * doTransaction - process the transaction. This is a good place to figure @@ -748,15 +686,15 @@ public: * Sets display transform hint on BufferLayerConsumer. */ void updateTransformHint(ui::Transform::RotationFlags); - + void skipReportingTransformHint(); inline const State& getDrawingState() const { return mDrawingState; } inline State& getDrawingState() { return mDrawingState; } - LayerDebugInfo getLayerDebugInfo(const DisplayDevice*) const; + gui::LayerDebugInfo getLayerDebugInfo(const DisplayDevice*) const; void miniDump(std::string& result, const DisplayDevice&) const; void dumpFrameStats(std::string& result) const; - void dumpCallingUidPid(std::string& result) const; + void dumpOffscreenDebugInfo(std::string& result) const; void clearFrameStats(); void logFrameStats(); void getFrameStats(FrameStats* outStats) const; @@ -790,6 +728,7 @@ public: void traverse(LayerVector::StateSet, const LayerVector::Visitor&); void traverseInReverseZOrder(LayerVector::StateSet, const LayerVector::Visitor&); void traverseInZOrder(LayerVector::StateSet, const LayerVector::Visitor&); + void traverseChildren(const LayerVector::Visitor&); /** * Traverse only children in z order, ignoring relative layers that are not children of the @@ -797,7 +736,10 @@ public: */ void traverseChildrenInZOrder(LayerVector::StateSet, const LayerVector::Visitor&); - size_t getChildrenCount() const; + size_t getDescendantCount() const; + size_t getChildrenCount() const { return mDrawingChildren.size(); } + bool isHandleAlive() const { return mHandleAlive; } + bool onHandleDestroyed() { return mHandleAlive = false; } // ONLY CALL THIS FROM THE LAYER DTOR! // See b/141111965. We need to add current children to offscreen layers in @@ -854,6 +796,9 @@ public: std::shared_ptr createSurfaceFrameForBuffer( const FrameTimelineInfo& info, nsecs_t queueTime, std::string debugName); + bool setTrustedPresentationInfo(TrustedPresentationThresholds const& thresholds, + TrustedPresentationListener const& listener); + // Creates a new handle each time, so we only expect // this to be called once. sp getHandle(); @@ -872,12 +817,12 @@ public: */ bool hasInputInfo() const; - // Sets the GameMode for the tree rooted at this layer. A layer in the tree inherits this - // GameMode unless it (or an ancestor) has GAME_MODE_METADATA. - void setGameModeForTree(GameMode); + // Sets the gui::GameMode for the tree rooted at this layer. A layer in the tree inherits this + // gui::GameMode unless it (or an ancestor) has GAME_MODE_METADATA. + void setGameModeForTree(gui::GameMode); - void setGameMode(GameMode gameMode) { mGameMode = gameMode; } - GameMode getGameMode() const { return mGameMode; } + void setGameMode(gui::GameMode gameMode) { mGameMode = gameMode; } + gui::GameMode getGameMode() const { return mGameMode; } virtual uid_t getOwnerUid() const { return mOwnerUid; } @@ -902,25 +847,75 @@ public: bool mPendingHWCDestroy{false}; - bool backpressureEnabled() { return mDrawingState.flags & layer_state_t::eEnableBackpressure; } + bool backpressureEnabled() const { + return mDrawingState.flags & layer_state_t::eEnableBackpressure; + } 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 */); + // See mPendingBufferTransactions + void decrementPendingBufferCount(); + std::atomic* getPendingBufferCounter() { return &mPendingBufferTransactions; } + std::string getPendingBufferCounterName() { return mBlastTransactionName; } + bool updateGeometry(); + + bool simpleBufferUpdate(const layer_state_t&) const; + + static bool isOpaqueFormat(PixelFormat format); + + // Updates the LayerSnapshot. This must be called prior to sending layer data to + // CompositionEngine or RenderEngine (i.e. before calling CompositionEngine::present or + // LayerFE::prepareClientComposition). + // + // TODO(b/238781169) Remove direct calls to RenderEngine::drawLayers that don't go through + // CompositionEngine to create a single path for composing layers. + void updateSnapshot(bool updateGeometry); + void updateChildrenSnapshots(bool updateGeometry); + void updateMetadataSnapshot(const LayerMetadata& parentMetadata); + void updateRelativeMetadataSnapshot(const LayerMetadata& relativeLayerMetadata, + std::unordered_set& visited); + sp getClonedFrom() const { + return mClonedFrom != nullptr ? mClonedFrom.promote() : nullptr; + } + bool isClone() { return mClonedFrom != nullptr; } - virtual bool setBufferCrop(const Rect& /* bufferCrop */) { return false; } - virtual bool setDestinationFrame(const Rect& /* destinationFrame */) { return false; } - virtual std::atomic* getPendingBufferCounter() { return nullptr; } - virtual std::string getPendingBufferCounterName() { return ""; } - virtual bool updateGeometry() { return false; } - - virtual bool simpleBufferUpdate(const layer_state_t&) const { return false; } - + bool willPresentCurrentTransaction() const; + + void callReleaseBufferCallback(const sp& listener, + const sp& buffer, uint64_t framenumber, + const sp& releaseFence); + bool setFrameRateForLayerTreeLegacy(FrameRate); + bool setFrameRateForLayerTree(FrameRate, const scheduler::LayerProps&); + void recordLayerHistoryBufferUpdate(const scheduler::LayerProps&); + void recordLayerHistoryAnimationTx(const scheduler::LayerProps&); + auto getLayerProps() const { + return scheduler::LayerProps{ + .visible = isVisible(), + .bounds = getBounds(), + .transform = getTransform(), + .setFrameRateVote = getFrameRateForLayerTree(), + .frameRateSelectionPriority = getFrameRateSelectionPriority(), + }; + }; + bool hasBuffer() const { return mBufferInfo.mBuffer != nullptr; } + void setTransformHint(std::optional transformHint) { + mTransformHint = transformHint; + } + // Keeps track of the previously presented layer stacks. This is used to get + // the release fences from the correct displays when we release the last buffer + // from the layer. + std::vector mPreviouslyPresentedLayerStacks; // Exposed so SurfaceFlinger can assert that it's held const sp mFlinger; protected: - friend class impl::SurfaceInterceptor; - // For unit tests friend class TestableSurfaceFlinger; friend class FpsReporterTest; @@ -929,21 +924,14 @@ protected: friend class TransactionFrameTracerTest; friend class TransactionSurfaceFrameTest; - virtual void setInitialValuesForClone(const sp& clonedFrom); - virtual std::optional prepareClientComposition( - compositionengine::LayerFE::ClientCompositionTargetSettings&); - virtual void preparePerFrameCompositionState(); + virtual void setInitialValuesForClone(const sp& clonedFrom, uint32_t mirrorRootId); + void preparePerFrameCompositionState(); + void preparePerFrameBufferCompositionState(); + void preparePerFrameEffectsCompositionState(); virtual void commitTransaction(State& stateToCommit); - virtual void onSurfaceFrameCreated(const std::shared_ptr&) {} + void gatherBufferInfo(); + void onSurfaceFrameCreated(const std::shared_ptr&); - // Returns mCurrentScaling mode (originating from the - // Client) or mOverrideScalingMode mode (originating from - // the Surface Controller) if set. - virtual uint32_t getEffectiveScalingMode() const { return 0; } - - sp asLayerFE() const; - sp getClonedFrom() { return mClonedFrom != nullptr ? mClonedFrom.promote() : nullptr; } - bool isClone() { return mClonedFrom != nullptr; } bool isClonedFromAlive() { return getClonedFrom() != nullptr; } void cloneDrawingState(const Layer* from); @@ -954,11 +942,6 @@ protected: void addChildToDrawing(const sp&); void updateClonedInputInfo(const std::map, sp>& clonedLayersMap); - // Modifies the passed in layer settings to clear the contents. If the blackout flag is set, - // the settings clears the content with a solid black fill. - void prepareClearClientComposition(LayerFE::LayerSettings&, bool blackout) const; - void prepareShadowClientComposition(LayerFE::LayerSettings& caster, const Rect& layerStackRect); - void prepareBasicGeometryCompositionState(); void prepareGeometryCompositionState(); void prepareCursorCompositionState(); @@ -991,7 +974,7 @@ protected: * "replaceTouchableRegionWithCrop" is specified. In this case, the layer will receive input * in this layer's space, regardless of the specified crop layer. */ - virtual Rect getInputBounds() const; + std::pair getInputBounds(bool fillParentBounds) const; bool mPremultipliedAlpha{true}; const std::string mName; @@ -1000,6 +983,12 @@ protected: // These are only accessed by the main thread or the tracing thread. State mDrawingState; + TrustedPresentationThresholds mTrustedPresentationThresholds; + TrustedPresentationListener mTrustedPresentationListener; + bool mLastComputedTrustedPresentationState = false; + bool mLastReportedTrustedPresentationState = false; + int64_t mEnteredTrustedPresentationStateTime = -1; + uint32_t mTransactionFlags{0}; // Updated in doTransaction, used to track the last sequence number we // committed. Currently this is really only used for updating visible @@ -1059,7 +1048,14 @@ protected: sp mLastClientCompositionFence; bool mClearClientCompositionFenceOnLayerDisplayed = false; private: - virtual void setTransformHint(ui::Transform::RotationFlags) {} + friend class SlotGenerationTest; + friend class TransactionFrameTracerTest; + friend class TransactionSurfaceFrameTest; + + bool getAutoRefresh() const { return mDrawingState.autoRefresh; } + bool getSidebandStreamChanged() const { return mSidebandStreamChanged; } + + std::atomic mSidebandStreamChanged{false}; // Returns true if the layer can draw shadows on its border. virtual bool canDrawShadows() const { return true; } @@ -1083,7 +1079,6 @@ private: void updateTreeHasFrameRateVote(); bool propagateFrameRateForLayerTree(FrameRate parentFrameRate, bool* transactionNeeded); - bool setFrameRateForLayerTree(FrameRate); void setZOrderRelativeOf(const wp& relativeOf); bool isTrustedOverlay() const; gui::DropInputMode getDropInputMode() const; @@ -1104,6 +1099,47 @@ private: // Fills in the frame and transform info for the gui::WindowInfo. void fillInputFrameInfo(gui::WindowInfo&, const ui::Transform& screenToDisplay); + inline void tracePendingBufferCount(int32_t pendingBuffers); + + // Latch sideband stream and returns true if the dirty region should be updated. + bool latchSidebandStream(bool& recomputeVisibleRegions); + + bool hasFrameUpdate() const; + + void updateTexImage(nsecs_t latchTime, bool bgColorOnly = false); + + // Crop that applies to the buffer + Rect computeBufferCrop(const State& s); + + void callReleaseBufferCallback(const sp& listener, + const sp& buffer, uint64_t framenumber, + const sp& releaseFence, + uint32_t currentMaxAcquiredBufferCount); + + // Returns true if the transformed buffer size does not match the layer size and we need + // to apply filtering. + bool bufferNeedsFiltering() const; + + // Returns true if there is a valid color to fill. + bool fillsColor() const; + // Returns true if this layer has a blur value. + bool hasBlur() const; + bool hasEffect() const { return fillsColor() || drawShadows() || hasBlur(); } + bool hasBufferOrSidebandStream() const { + return ((mSidebandStream != nullptr) || (mBufferInfo.mBuffer != nullptr)); + } + + bool hasBufferOrSidebandStreamInDrawing() const { + return ((mDrawingState.sidebandStream != nullptr) || (mDrawingState.buffer != nullptr)); + } + + bool hasSomethingToDraw() const { return hasEffect() || hasBufferOrSidebandStream(); } + + // Fills the provided vector with the currently available JankData and removes the processed + // JankData from the pending list. + void transferAvailableJankData(const std::deque>& handles, + std::vector& jankData); + // Cached properties computed from drawing state // Effective transform taking into account parent transforms and any parent scaling, which is // a transform from the current layer coordinate space to display(screen) coordinate space. @@ -1122,11 +1158,6 @@ private: bool mGetHandleCalled = false; - // Tracks the process and user id of the caller when creating this layer - // to help debugging. - pid_t mCallingPid; - uid_t mCallingUid; - // The current layer is a clone of mClonedFrom. This means that this layer will update it's // properties based on mClonedFrom. When mClonedFrom latches a new buffer for BufferLayers, // this layer will update it's buffer. When mClonedFrom updates it's drawing state, children, @@ -1139,7 +1170,7 @@ private: float mEffectiveShadowRadius = 0.f; // Game mode for the layer. Set by WindowManagerShell and recorded by SurfaceFlingerStats. - GameMode mGameMode = GameMode::Unsupported; + gui::GameMode mGameMode = gui::GameMode::Unsupported; // A list of regions on this layer that should have blurs. const std::vector getBlurRegions() const; @@ -1147,7 +1178,61 @@ private: bool mIsAtRoot = false; uint32_t mLayerCreationFlags; + bool findInHierarchy(const sp&); + + bool mBorderEnabled = false; + float mBorderWidth; + half4 mBorderColor; + + void setTransformHintLegacy(ui::Transform::RotationFlags); + void resetDrawingStateBufferInfo(); + + const uint32_t mTextureName; + + // Transform hint provided to the producer. This must be accessed holding + // the mStateLock. + ui::Transform::RotationFlags mTransformHintLegacy = ui::Transform::ROT_0; + bool mSkipReportingTransformHint = true; + std::optional mTransformHint = std::nullopt; + + ReleaseCallbackId mPreviousReleaseCallbackId = ReleaseCallbackId::INVALID_ID; + sp mPreviousReleaseBufferEndpoint; + uint64_t mPreviousReleasedFrameNumber = 0; + + uint64_t mPreviousBarrierFrameNumber = 0; + + bool mReleasePreviousBuffer = false; + + // Stores the last set acquire fence signal time used to populate the callback handle's acquire + // time. + std::variant> mCallbackHandleAcquireTimeOrFence = -1; + + std::deque> mPendingJankClassifications; + // An upper bound on the number of SurfaceFrames in the pending classifications deque. + static constexpr int kPendingClassificationMaxSurfaceFrames = 50; + + const std::string mBlastTransactionName{"BufferTX - " + mName}; + // This integer is incremented everytime a buffer arrives at the server for this layer, + // and decremented when a buffer is dropped or latched. When changed the integer is exported + // to systrace with ATRACE_INT and mBlastTransactionName. This way when debugging perf it is + // possible to see when a buffer arrived at the server, and in which frame it latched. + // + // You can understand the trace this way: + // - If the integer increases, a buffer arrived at the server. + // - If the integer decreases in latchBuffer, that buffer was latched + // - If the integer decreases in setBuffer or doTransaction, a buffer was dropped + std::atomic mPendingBufferTransactions{0}; + + // Contains requested position and matrix updates. This will be applied if the client does + // not specify a destination frame. + ui::Transform mRequestedTransform; + + sp mLegacyLayerFE; + std::vector>> mLayerFEs; + std::unique_ptr mSnapshot = + std::make_unique(); + bool mHandleAlive = false; }; std::ostream& operator<<(std::ostream& stream, const Layer::FrameRate& rate); diff --git a/services/surfaceflinger/LayerFE.cpp b/services/surfaceflinger/LayerFE.cpp new file mode 100644 index 0000000000000000000000000000000000000000..f855f278c3f6ba98289a6d277dc33bb223b8714c --- /dev/null +++ b/services/surfaceflinger/LayerFE.cpp @@ -0,0 +1,384 @@ +/* + * 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. + */ + +// #define LOG_NDEBUG 0 +#undef LOG_TAG +#define LOG_TAG "LayerFE" +#define ATRACE_TAG ATRACE_TAG_GRAPHICS + +#include +#include +#include +#include +#include + +#include "LayerFE.h" +#include "SurfaceFlinger.h" + +namespace android { + +namespace { +constexpr float defaultMaxLuminance = 1000.0; + +constexpr mat4 inverseOrientation(uint32_t transform) { + const mat4 flipH(-1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 1, 0, 0, 1); + const mat4 flipV(1, 0, 0, 0, 0, -1, 0, 0, 0, 0, 1, 0, 0, 1, 0, 1); + const mat4 rot90(0, 1, 0, 0, -1, 0, 0, 0, 0, 0, 1, 0, 1, 0, 0, 1); + mat4 tr; + + if (transform & NATIVE_WINDOW_TRANSFORM_ROT_90) { + tr = tr * rot90; + } + if (transform & NATIVE_WINDOW_TRANSFORM_FLIP_H) { + tr = tr * flipH; + } + if (transform & NATIVE_WINDOW_TRANSFORM_FLIP_V) { + tr = tr * flipV; + } + return inverse(tr); +} + +FloatRect reduce(const FloatRect& win, const Region& exclude) { + if (CC_LIKELY(exclude.isEmpty())) { + return win; + } + // Convert through Rect (by rounding) for lack of FloatRegion + return Region(Rect{win}).subtract(exclude).getBounds().toFloatRect(); +} + +// Computes the transform matrix using the setFilteringEnabled to determine whether the +// transform matrix should be computed for use with bilinear filtering. +void getDrawingTransformMatrix(const std::shared_ptr& buffer, + Rect bufferCrop, uint32_t bufferTransform, bool filteringEnabled, + float outMatrix[16]) { + if (!buffer) { + ALOGE("Buffer should not be null!"); + return; + } + GLConsumer::computeTransformMatrix(outMatrix, static_cast(buffer->getWidth()), + static_cast(buffer->getHeight()), + buffer->getPixelFormat(), bufferCrop, bufferTransform, + filteringEnabled); +} + +} // namespace + +LayerFE::LayerFE(const std::string& name) : mName(name) {} + +const compositionengine::LayerFECompositionState* LayerFE::getCompositionState() const { + return mSnapshot.get(); +} + +bool LayerFE::onPreComposition(nsecs_t refreshStartTime, bool) { + mCompositionResult.refreshStartTime = refreshStartTime; + return mSnapshot->hasReadyFrame; +} + +std::optional LayerFE::prepareClientComposition( + compositionengine::LayerFE::ClientCompositionTargetSettings& targetSettings) const { + std::optional layerSettings = + prepareClientCompositionInternal(targetSettings); + // Nothing to render. + if (!layerSettings) { + return {}; + } + + // HWC requests to clear this layer. + if (targetSettings.clearContent) { + prepareClearClientComposition(*layerSettings, false /* blackout */); + return layerSettings; + } + + // set the shadow for the layer if needed + prepareShadowClientComposition(*layerSettings, targetSettings.viewport); + + return layerSettings; +} + +std::optional LayerFE::prepareClientCompositionInternal( + compositionengine::LayerFE::ClientCompositionTargetSettings& targetSettings) const { + ATRACE_CALL(); + compositionengine::LayerFE::LayerSettings layerSettings; + layerSettings.geometry.boundaries = + reduce(mSnapshot->geomLayerBounds, mSnapshot->transparentRegionHint); + layerSettings.geometry.positionTransform = mSnapshot->geomLayerTransform.asMatrix4(); + + // skip drawing content if the targetSettings indicate the content will be occluded + const bool drawContent = targetSettings.realContentIsVisible || targetSettings.clearContent; + layerSettings.skipContentDraw = !drawContent; + + if (!mSnapshot->colorTransformIsIdentity) { + layerSettings.colorTransform = mSnapshot->colorTransform; + } + + const auto& roundedCornerState = mSnapshot->roundedCorner; + layerSettings.geometry.roundedCornersRadius = roundedCornerState.radius; + layerSettings.geometry.roundedCornersCrop = roundedCornerState.cropRect; + + layerSettings.alpha = mSnapshot->alpha; + layerSettings.sourceDataspace = mSnapshot->dataspace; + + // Override the dataspace transfer from 170M to sRGB if the device configuration requests this. + // We do this here instead of in buffer info so that dumpsys can still report layers that are + // using the 170M transfer. + if (targetSettings.treat170mAsSrgb && + (layerSettings.sourceDataspace & HAL_DATASPACE_TRANSFER_MASK) == + HAL_DATASPACE_TRANSFER_SMPTE_170M) { + layerSettings.sourceDataspace = static_cast( + (layerSettings.sourceDataspace & HAL_DATASPACE_STANDARD_MASK) | + (layerSettings.sourceDataspace & HAL_DATASPACE_RANGE_MASK) | + HAL_DATASPACE_TRANSFER_SRGB); + } + + layerSettings.whitePointNits = targetSettings.whitePointNits; + switch (targetSettings.blurSetting) { + case LayerFE::ClientCompositionTargetSettings::BlurSetting::Enabled: + layerSettings.backgroundBlurRadius = mSnapshot->backgroundBlurRadius; + layerSettings.blurRegions = mSnapshot->blurRegions; + layerSettings.blurRegionTransform = mSnapshot->localTransformInverse.asMatrix4(); + break; + case LayerFE::ClientCompositionTargetSettings::BlurSetting::BackgroundBlurOnly: + layerSettings.backgroundBlurRadius = mSnapshot->backgroundBlurRadius; + break; + case LayerFE::ClientCompositionTargetSettings::BlurSetting::BlurRegionsOnly: + layerSettings.blurRegions = mSnapshot->blurRegions; + layerSettings.blurRegionTransform = mSnapshot->localTransformInverse.asMatrix4(); + break; + case LayerFE::ClientCompositionTargetSettings::BlurSetting::Disabled: + default: + break; + } + layerSettings.stretchEffect = mSnapshot->stretchEffect; + // Record the name of the layer for debugging further down the stack. + layerSettings.name = mSnapshot->name; + + if (hasEffect() && !hasBufferOrSidebandStream()) { + prepareEffectsClientComposition(layerSettings, targetSettings); + return layerSettings; + } + + prepareBufferStateClientComposition(layerSettings, targetSettings); + return layerSettings; +} + +void LayerFE::prepareClearClientComposition(LayerFE::LayerSettings& layerSettings, + bool blackout) const { + layerSettings.source.buffer.buffer = nullptr; + layerSettings.source.solidColor = half3(0.0f, 0.0f, 0.0f); + layerSettings.disableBlending = true; + layerSettings.bufferId = 0; + layerSettings.frameNumber = 0; + + // If layer is blacked out, force alpha to 1 so that we draw a black color layer. + layerSettings.alpha = blackout ? 1.0f : 0.0f; + layerSettings.name = mSnapshot->name; +} + +void LayerFE::prepareEffectsClientComposition( + compositionengine::LayerFE::LayerSettings& layerSettings, + compositionengine::LayerFE::ClientCompositionTargetSettings& targetSettings) const { + // If fill bounds are occluded or the fill color is invalid skip the fill settings. + if (targetSettings.realContentIsVisible && fillsColor()) { + // Set color for color fill settings. + layerSettings.source.solidColor = mSnapshot->color.rgb; + } else if (hasBlur() || drawShadows()) { + layerSettings.skipContentDraw = true; + } +} + +void LayerFE::prepareBufferStateClientComposition( + compositionengine::LayerFE::LayerSettings& layerSettings, + compositionengine::LayerFE::ClientCompositionTargetSettings& targetSettings) const { + ATRACE_CALL(); + if (CC_UNLIKELY(!mSnapshot->externalTexture)) { + // If there is no buffer for the layer or we have sidebandstream where there is no + // activeBuffer, then we need to return LayerSettings. + return; + } + const bool blackOutLayer = + (mSnapshot->hasProtectedContent && !targetSettings.supportsProtectedContent) || + ((mSnapshot->isSecure || mSnapshot->hasProtectedContent) && !targetSettings.isSecure); + const bool bufferCanBeUsedAsHwTexture = + mSnapshot->externalTexture->getUsage() & GraphicBuffer::USAGE_HW_TEXTURE; + if (blackOutLayer || !bufferCanBeUsedAsHwTexture) { + ALOGE_IF(!bufferCanBeUsedAsHwTexture, "%s is blacked out as buffer is not gpu readable", + mSnapshot->name.c_str()); + prepareClearClientComposition(layerSettings, true /* blackout */); + return; + } + + layerSettings.source.buffer.buffer = mSnapshot->externalTexture; + layerSettings.source.buffer.isOpaque = mSnapshot->contentOpaque; + layerSettings.source.buffer.fence = mSnapshot->acquireFence; + layerSettings.source.buffer.textureName = mSnapshot->textureName; + layerSettings.source.buffer.usePremultipliedAlpha = mSnapshot->premultipliedAlpha; + layerSettings.source.buffer.isY410BT2020 = mSnapshot->isHdrY410; + bool hasSmpte2086 = mSnapshot->hdrMetadata.validTypes & HdrMetadata::SMPTE2086; + bool hasCta861_3 = mSnapshot->hdrMetadata.validTypes & HdrMetadata::CTA861_3; + float maxLuminance = 0.f; + if (hasSmpte2086 && hasCta861_3) { + maxLuminance = std::min(mSnapshot->hdrMetadata.smpte2086.maxLuminance, + mSnapshot->hdrMetadata.cta8613.maxContentLightLevel); + } else if (hasSmpte2086) { + maxLuminance = mSnapshot->hdrMetadata.smpte2086.maxLuminance; + } else if (hasCta861_3) { + maxLuminance = mSnapshot->hdrMetadata.cta8613.maxContentLightLevel; + } else { + switch (layerSettings.sourceDataspace & HAL_DATASPACE_TRANSFER_MASK) { + case HAL_DATASPACE_TRANSFER_ST2084: + case HAL_DATASPACE_TRANSFER_HLG: + // Behavior-match previous releases for HDR content + maxLuminance = defaultMaxLuminance; + break; + } + } + layerSettings.source.buffer.maxLuminanceNits = maxLuminance; + layerSettings.frameNumber = mSnapshot->frameNumber; + layerSettings.bufferId = mSnapshot->externalTexture->getId(); + + // Query the texture matrix given our current filtering mode. + float textureMatrix[16]; + getDrawingTransformMatrix(layerSettings.source.buffer.buffer, mSnapshot->geomContentCrop, + mSnapshot->geomBufferTransform, targetSettings.needsFiltering, + textureMatrix); + + if (mSnapshot->geomBufferUsesDisplayInverseTransform) { + /* + * the code below applies the primary display's inverse transform to + * the texture transform + */ + uint32_t transform = SurfaceFlinger::getActiveDisplayRotationFlags(); + mat4 tr = inverseOrientation(transform); + + /** + * TODO(b/36727915): This is basically a hack. + * + * Ensure that regardless of the parent transformation, + * this buffer is always transformed from native display + * orientation to display orientation. For example, in the case + * of a camera where the buffer remains in native orientation, + * we want the pixels to always be upright. + */ + const auto parentTransform = mSnapshot->parentTransform; + tr = tr * inverseOrientation(parentTransform.getOrientation()); + + // and finally apply it to the original texture matrix + const mat4 texTransform(mat4(static_cast(textureMatrix)) * tr); + memcpy(textureMatrix, texTransform.asArray(), sizeof(textureMatrix)); + } + + const Rect win{layerSettings.geometry.boundaries}; + float bufferWidth = static_cast(mSnapshot->bufferSize.getWidth()); + float bufferHeight = static_cast(mSnapshot->bufferSize.getHeight()); + + // Layers can have a "buffer size" of [0, 0, -1, -1] when no display frame has + // been set and there is no parent layer bounds. In that case, the scale is meaningless so + // ignore them. + if (!mSnapshot->bufferSize.isValid()) { + bufferWidth = float(win.right) - float(win.left); + bufferHeight = float(win.bottom) - float(win.top); + } + + const float scaleHeight = (float(win.bottom) - float(win.top)) / bufferHeight; + const float scaleWidth = (float(win.right) - float(win.left)) / bufferWidth; + const float translateY = float(win.top) / bufferHeight; + const float translateX = float(win.left) / bufferWidth; + + // Flip y-coordinates because GLConsumer expects OpenGL convention. + mat4 tr = mat4::translate(vec4(.5f, .5f, 0.f, 1.f)) * mat4::scale(vec4(1.f, -1.f, 1.f, 1.f)) * + mat4::translate(vec4(-.5f, -.5f, 0.f, 1.f)) * + mat4::translate(vec4(translateX, translateY, 0.f, 1.f)) * + mat4::scale(vec4(scaleWidth, scaleHeight, 1.0f, 1.0f)); + + layerSettings.source.buffer.useTextureFiltering = targetSettings.needsFiltering; + layerSettings.source.buffer.textureTransform = + mat4(static_cast(textureMatrix)) * tr; + + return; +} + +void LayerFE::prepareShadowClientComposition(LayerFE::LayerSettings& caster, + const Rect& layerStackRect) const { + renderengine::ShadowSettings state = mSnapshot->shadowSettings; + if (state.length <= 0.f || (state.ambientColor.a <= 0.f && state.spotColor.a <= 0.f)) { + return; + } + + // Shift the spot light x-position to the middle of the display and then + // offset it by casting layer's screen pos. + state.lightPos.x = + (static_cast(layerStackRect.width()) / 2.f) - mSnapshot->transformedBounds.left; + state.lightPos.y -= mSnapshot->transformedBounds.top; + caster.shadow = state; +} + +void LayerFE::onLayerDisplayed(ftl::SharedFuture futureFenceResult, + ui::LayerStack layerStack) { + mCompositionResult.releaseFences.emplace_back(std::move(futureFenceResult), layerStack); +} + +CompositionResult&& LayerFE::stealCompositionResult() { + return std::move(mCompositionResult); +} + +const char* LayerFE::getDebugName() const { + return mName.c_str(); +} + +const LayerMetadata* LayerFE::getMetadata() const { + return &mSnapshot->layerMetadata; +} + +const LayerMetadata* LayerFE::getRelativeMetadata() const { + return &mSnapshot->relativeLayerMetadata; +} + +int32_t LayerFE::getSequence() const { + return mSnapshot->sequence; +} + +bool LayerFE::hasRoundedCorners() const { + return mSnapshot->roundedCorner.hasRoundedCorners(); +} + +void LayerFE::setWasClientComposed(const sp& fence) { + mCompositionResult.lastClientCompositionFence = fence; +} + +bool LayerFE::hasBufferOrSidebandStream() const { + return mSnapshot->externalTexture || mSnapshot->sidebandStream; +} + +bool LayerFE::fillsColor() const { + return mSnapshot->color.r >= 0.0_hf && mSnapshot->color.g >= 0.0_hf && + mSnapshot->color.b >= 0.0_hf; +} + +bool LayerFE::hasBlur() const { + return mSnapshot->backgroundBlurRadius > 0 || mSnapshot->blurRegions.size() > 0; +} + +bool LayerFE::drawShadows() const { + return mSnapshot->shadowSettings.length > 0.f && + (mSnapshot->shadowSettings.ambientColor.a > 0 || + mSnapshot->shadowSettings.spotColor.a > 0); +}; + +const sp LayerFE::getBuffer() const { + return mSnapshot->externalTexture ? mSnapshot->externalTexture->getBuffer() : nullptr; +} + +} // namespace android diff --git a/services/surfaceflinger/LayerFE.h b/services/surfaceflinger/LayerFE.h new file mode 100644 index 0000000000000000000000000000000000000000..d584fb7eabdfb416367722742ff14274d7a7a1da --- /dev/null +++ b/services/surfaceflinger/LayerFE.h @@ -0,0 +1,84 @@ +/* + * 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 +#include "FrontEnd/LayerSnapshot.h" +#include "compositionengine/LayerFE.h" +#include "compositionengine/LayerFECompositionState.h" +#include "renderengine/LayerSettings.h" + +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; +}; + +class LayerFE : public virtual RefBase, public virtual compositionengine::LayerFE { +public: + LayerFE(const std::string& name); + + // compositionengine::LayerFE overrides + const compositionengine::LayerFECompositionState* getCompositionState() const override; + bool onPreComposition(nsecs_t refreshStartTime, bool updatingOutputGeometryThisFrame) override; + void onLayerDisplayed(ftl::SharedFuture, ui::LayerStack) override; + const char* getDebugName() const override; + int32_t getSequence() const override; + bool hasRoundedCorners() const override; + void setWasClientComposed(const sp&) override; + const gui::LayerMetadata* getMetadata() const override; + const gui::LayerMetadata* getRelativeMetadata() const override; + std::optional prepareClientComposition( + compositionengine::LayerFE::ClientCompositionTargetSettings&) const; + CompositionResult&& stealCompositionResult(); + + std::unique_ptr mSnapshot; + +private: + std::optional prepareClientCompositionInternal( + compositionengine::LayerFE::ClientCompositionTargetSettings&) const; + // Modifies the passed in layer settings to clear the contents. If the blackout flag is set, + // the settings clears the content with a solid black fill. + void prepareClearClientComposition(LayerFE::LayerSettings&, bool blackout) const; + void prepareShadowClientComposition(LayerFE::LayerSettings& caster, + const Rect& layerStackRect) const; + void prepareBufferStateClientComposition( + compositionengine::LayerFE::LayerSettings&, + compositionengine::LayerFE::ClientCompositionTargetSettings&) const; + void prepareEffectsClientComposition( + compositionengine::LayerFE::LayerSettings&, + compositionengine::LayerFE::ClientCompositionTargetSettings&) const; + + bool hasEffect() const { return fillsColor() || drawShadows() || hasBlur(); } + bool hasBufferOrSidebandStream() const; + + bool fillsColor() const; + bool hasBlur() const; + bool drawShadows() const; + + const sp getBuffer() const; + + CompositionResult mCompositionResult; + std::string mName; +}; + +} // namespace android diff --git a/services/surfaceflinger/LayerProtoHelper.cpp b/services/surfaceflinger/LayerProtoHelper.cpp index 0506c475556ad4471e4bc712b2dcc74ab316e3a4..3472d201f3f123c6e0a8d1969d87cc4d8d701242 100644 --- a/services/surfaceflinger/LayerProtoHelper.cpp +++ b/services/surfaceflinger/LayerProtoHelper.cpp @@ -15,6 +15,8 @@ */ // TODO(b/129481165): remove the #pragma below and fix conversion issues +#include "FrontEnd/LayerCreationArgs.h" +#include "FrontEnd/LayerSnapshot.h" #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wconversion" #pragma clang diagnostic ignored "-Wextra" @@ -247,6 +249,218 @@ void LayerProtoHelper::readFromProto(const BlurRegion& proto, android::BlurRegio outRegion.right = proto.right(); outRegion.bottom = proto.bottom(); } + +LayersProto LayerProtoFromSnapshotGenerator::generate(const frontend::LayerHierarchy& root) { + mLayersProto.clear_layers(); + std::unordered_set stackIdsToSkip; + if ((mTraceFlags & LayerTracing::TRACE_VIRTUAL_DISPLAYS) == 0) { + for (const auto& [layerStack, displayInfo] : mDisplayInfos) { + if (displayInfo.isVirtual) { + stackIdsToSkip.insert(layerStack.id); + } + } + } + + frontend::LayerHierarchy::TraversalPath path = frontend::LayerHierarchy::TraversalPath::ROOT; + for (auto& [child, variant] : root.mChildren) { + if (variant != frontend::LayerHierarchy::Variant::Attached || + stackIdsToSkip.find(child->getLayer()->layerStack.id) != stackIdsToSkip.end()) { + continue; + } + frontend::LayerHierarchy::ScopedAddToTraversalPath addChildToPath(path, + child->getLayer()->id, + variant); + LayerProtoFromSnapshotGenerator::writeHierarchyToProto(*child, path); + } + + // fill in relative and parent info + for (int i = 0; i < mLayersProto.layers_size(); i++) { + auto layerProto = mLayersProto.mutable_layers()->Mutable(i); + auto it = mChildToRelativeParent.find(layerProto->id()); + if (it == mChildToRelativeParent.end()) { + layerProto->set_z_order_relative_of(-1); + } else { + layerProto->set_z_order_relative_of(it->second); + } + it = mChildToParent.find(layerProto->id()); + if (it == mChildToParent.end()) { + layerProto->set_parent(-1); + } else { + layerProto->set_parent(it->second); + } + } + + mDefaultSnapshots.clear(); + mChildToRelativeParent.clear(); + return std::move(mLayersProto); +} + +frontend::LayerSnapshot* LayerProtoFromSnapshotGenerator::getSnapshot( + frontend::LayerHierarchy::TraversalPath& path, const frontend::RequestedLayerState& layer) { + frontend::LayerSnapshot* snapshot = mSnapshotBuilder.getSnapshot(path); + if (snapshot) { + return snapshot; + } else { + mDefaultSnapshots[path] = frontend::LayerSnapshot(layer, path); + return &mDefaultSnapshots[path]; + } +} + +void LayerProtoFromSnapshotGenerator::writeHierarchyToProto( + const frontend::LayerHierarchy& root, frontend::LayerHierarchy::TraversalPath& path) { + using Variant = frontend::LayerHierarchy::Variant; + LayerProto* layerProto = mLayersProto.add_layers(); + const frontend::RequestedLayerState& layer = *root.getLayer(); + frontend::LayerSnapshot* snapshot = getSnapshot(path, layer); + LayerProtoHelper::writeSnapshotToProto(layerProto, layer, *snapshot, mTraceFlags); + + for (const auto& [child, variant] : root.mChildren) { + frontend::LayerHierarchy::ScopedAddToTraversalPath addChildToPath(path, + child->getLayer()->id, + variant); + frontend::LayerSnapshot* childSnapshot = getSnapshot(path, layer); + if (variant == Variant::Attached || variant == Variant::Detached || + variant == Variant::Mirror) { + mChildToParent[childSnapshot->uniqueSequence] = snapshot->uniqueSequence; + layerProto->add_children(childSnapshot->uniqueSequence); + } else if (variant == Variant::Relative) { + mChildToRelativeParent[childSnapshot->uniqueSequence] = snapshot->uniqueSequence; + layerProto->add_relatives(childSnapshot->uniqueSequence); + } + } + + if (mTraceFlags & LayerTracing::TRACE_COMPOSITION) { + auto it = mLegacyLayers.find(layer.id); + if (it != mLegacyLayers.end()) { + it->second->writeCompositionStateToProto(layerProto, snapshot->outputFilter.layerStack); + } + } + + for (const auto& [child, variant] : root.mChildren) { + // avoid visiting relative layers twice + if (variant == Variant::Detached) { + continue; + } + frontend::LayerHierarchy::ScopedAddToTraversalPath addChildToPath(path, + child->getLayer()->id, + variant); + writeHierarchyToProto(*child, path); + } +} + +void LayerProtoHelper::writeSnapshotToProto(LayerProto* layerInfo, + const frontend::RequestedLayerState& requestedState, + const frontend::LayerSnapshot& snapshot, + uint32_t traceFlags) { + const ui::Transform transform = snapshot.geomLayerTransform; + auto buffer = requestedState.externalTexture; + if (buffer != nullptr) { + LayerProtoHelper::writeToProto(*buffer, + [&]() { return layerInfo->mutable_active_buffer(); }); + LayerProtoHelper::writeToProtoDeprecated(ui::Transform(requestedState.bufferTransform), + layerInfo->mutable_buffer_transform()); + } + layerInfo->set_invalidate(snapshot.contentDirty); + layerInfo->set_is_protected(snapshot.hasProtectedContent); + layerInfo->set_dataspace(dataspaceDetails(static_cast(snapshot.dataspace))); + layerInfo->set_curr_frame(requestedState.bufferData->frameNumber); + layerInfo->set_requested_corner_radius(requestedState.cornerRadius); + 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); + LayerProtoHelper::writeToProtoDeprecated(transform, layerInfo->mutable_transform()); + LayerProtoHelper::writePositionToProto(transform.tx(), transform.ty(), + [&]() { return layerInfo->mutable_position(); }); + LayerProtoHelper::writeToProto(snapshot.geomLayerBounds, + [&]() { return layerInfo->mutable_bounds(); }); + LayerProtoHelper::writeToProto(snapshot.surfaceDamage, + [&]() { return layerInfo->mutable_damage_region(); }); + + if (requestedState.hasColorTransform) { + LayerProtoHelper::writeToProto(snapshot.colorTransform, + layerInfo->mutable_color_transform()); + } + + LayerProtoHelper::writeToProto(snapshot.croppedBufferSize.toFloatRect(), + [&]() { return layerInfo->mutable_source_bounds(); }); + LayerProtoHelper::writeToProto(snapshot.transformedBounds, + [&]() { return layerInfo->mutable_screen_bounds(); }); + LayerProtoHelper::writeToProto(snapshot.roundedCorner.cropRect, + [&]() { return layerInfo->mutable_corner_radius_crop(); }); + layerInfo->set_shadow_radius(snapshot.shadowRadius); + + layerInfo->set_id(snapshot.uniqueSequence); + layerInfo->set_original_id(snapshot.sequence); + if (!snapshot.path.isClone()) { + layerInfo->set_name(requestedState.name); + } else { + layerInfo->set_name(requestedState.name + "(Mirror)"); + } + layerInfo->set_type("Layer"); + + LayerProtoHelper::writeToProto(requestedState.transparentRegion, + [&]() { return layerInfo->mutable_transparent_region(); }); + + layerInfo->set_layer_stack(snapshot.outputFilter.layerStack.id); + layerInfo->set_z(requestedState.z); + + ui::Transform requestedTransform = requestedState.getTransform(0); + LayerProtoHelper::writePositionToProto(requestedTransform.tx(), requestedTransform.ty(), [&]() { + return layerInfo->mutable_requested_position(); + }); + + LayerProtoHelper::writeToProto(requestedState.crop, + [&]() { return layerInfo->mutable_crop(); }); + + layerInfo->set_is_opaque(snapshot.contentOpaque); + if (requestedState.externalTexture) + layerInfo->set_pixel_format( + decodePixelFormat(requestedState.externalTexture->getPixelFormat())); + LayerProtoHelper::writeToProto(snapshot.color, [&]() { return layerInfo->mutable_color(); }); + LayerProtoHelper::writeToProto(requestedState.color, + [&]() { return layerInfo->mutable_requested_color(); }); + layerInfo->set_flags(requestedState.flags); + + LayerProtoHelper::writeToProtoDeprecated(requestedTransform, + layerInfo->mutable_requested_transform()); + + layerInfo->set_is_relative_of(requestedState.isRelativeOf); + + layerInfo->set_owner_uid(requestedState.ownerUid); + + if ((traceFlags & LayerTracing::TRACE_INPUT) && snapshot.hasInputInfo()) { + LayerProtoHelper::writeToProto(snapshot.inputInfo, {}, + [&]() { return layerInfo->mutable_input_window_info(); }); + } + + if (traceFlags & LayerTracing::TRACE_EXTRA) { + auto protoMap = layerInfo->mutable_metadata(); + for (const auto& entry : requestedState.metadata.mMap) { + (*protoMap)[entry.first] = std::string(entry.second.cbegin(), entry.second.cend()); + } + } + + LayerProtoHelper::writeToProto(requestedState.destinationFrame, + [&]() { return layerInfo->mutable_destination_frame(); }); +} + +google::protobuf::RepeatedPtrField LayerProtoHelper::writeDisplayInfoToProto( + const display::DisplayMap& displayInfos) { + google::protobuf::RepeatedPtrField displays; + displays.Reserve(displayInfos.size()); + for (const auto& [layerStack, displayInfo] : displayInfos) { + auto displayProto = displays.Add(); + displayProto->set_id(displayInfo.info.displayId); + displayProto->set_layer_stack(layerStack.id); + displayProto->mutable_size()->set_w(displayInfo.info.logicalWidth); + displayProto->mutable_size()->set_h(displayInfo.info.logicalHeight); + writeTransformToProto(displayInfo.transform, displayProto->mutable_transform()); + displayProto->set_is_virtual(displayInfo.isVirtual); + } + return displays; +} + } // namespace surfaceflinger } // namespace android diff --git a/services/surfaceflinger/LayerProtoHelper.h b/services/surfaceflinger/LayerProtoHelper.h index 6ade1435e570fc3d398002d2d4480fb7821b618e..b84a49b27c9a8b0bc42e2e93ea8558d55a12000c 100644 --- a/services/surfaceflinger/LayerProtoHelper.h +++ b/services/surfaceflinger/LayerProtoHelper.h @@ -25,6 +25,9 @@ #include #include #include +#include +#include "FrontEnd/LayerHierarchy.h" +#include "FrontEnd/LayerSnapshot.h" namespace android { namespace surfaceflinger { @@ -58,6 +61,44 @@ public: static void readFromProto(const ColorTransformProto& colorTransformProto, mat4& matrix); static void writeToProto(const android::BlurRegion region, BlurRegion*); static void readFromProto(const BlurRegion& proto, android::BlurRegion& outRegion); + static void writeSnapshotToProto(LayerProto* outProto, + const frontend::RequestedLayerState& requestedState, + const frontend::LayerSnapshot& snapshot, uint32_t traceFlags); + static google::protobuf::RepeatedPtrField writeDisplayInfoToProto( + const display::DisplayMap& displayInfos); +}; + +class LayerProtoFromSnapshotGenerator { +public: + LayerProtoFromSnapshotGenerator( + const frontend::LayerSnapshotBuilder& snapshotBuilder, + const display::DisplayMap& displayInfos, + const std::unordered_map>& legacyLayers, uint32_t traceFlags) + : mSnapshotBuilder(snapshotBuilder), + mLegacyLayers(legacyLayers), + mDisplayInfos(displayInfos), + mTraceFlags(traceFlags) {} + LayersProto generate(const frontend::LayerHierarchy& root); + +private: + void writeHierarchyToProto(const frontend::LayerHierarchy& root, + frontend::LayerHierarchy::TraversalPath& path); + frontend::LayerSnapshot* getSnapshot(frontend::LayerHierarchy::TraversalPath& path, + const frontend::RequestedLayerState& layer); + + const frontend::LayerSnapshotBuilder& mSnapshotBuilder; + const std::unordered_map>& mLegacyLayers; + const display::DisplayMap& mDisplayInfos; + uint32_t mTraceFlags; + LayersProto mLayersProto; + // winscope expects all the layers, so provide a snapshot even if it not currently drawing + std::unordered_map + mDefaultSnapshots; + std::unordered_map + mChildToRelativeParent; + std::unordered_map + mChildToParent; }; } // namespace surfaceflinger diff --git a/services/surfaceflinger/LayerRejecter.cpp b/services/surfaceflinger/LayerRejecter.cpp deleted file mode 100644 index 1c0263ba0bb8451b3b45f42d266ac8e13041331b..0000000000000000000000000000000000000000 --- a/services/surfaceflinger/LayerRejecter.cpp +++ /dev/null @@ -1,143 +0,0 @@ -/* - * Copyright (C) 2011 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 -#pragma clang diagnostic push -#pragma clang diagnostic ignored "-Wconversion" - -#include "LayerRejecter.h" - -#include -#include - -#define DEBUG_RESIZE 0 - -namespace android { - -LayerRejecter::LayerRejecter(Layer::State& front, Layer::State& current, - bool& recomputeVisibleRegions, bool stickySet, const std::string& name, - bool transformToDisplayInverse) - : mFront(front), - mCurrent(current), - mRecomputeVisibleRegions(recomputeVisibleRegions), - mStickyTransformSet(stickySet), - mName(name), - mTransformToDisplayInverse(transformToDisplayInverse) {} - -bool LayerRejecter::reject(const sp& buf, const BufferItem& item) { - if (buf == nullptr) { - return false; - } - - uint32_t bufWidth = buf->getWidth(); - uint32_t bufHeight = buf->getHeight(); - - // check that we received a buffer of the right size - // (Take the buffer's orientation into account) - if (item.mTransform & ui::Transform::ROT_90) { - std::swap(bufWidth, bufHeight); - } - - if (mTransformToDisplayInverse) { - uint32_t invTransform = DisplayDevice::getPrimaryDisplayRotationFlags(); - if (invTransform & ui::Transform::ROT_90) { - std::swap(bufWidth, bufHeight); - } - } - - int actualScalingMode = item.mScalingMode; - bool isFixedSize = actualScalingMode != NATIVE_WINDOW_SCALING_MODE_FREEZE; - if (mFront.active_legacy != mFront.requested_legacy) { - if (isFixedSize || - (bufWidth == mFront.requested_legacy.w && bufHeight == mFront.requested_legacy.h)) { - // Here we pretend the transaction happened by updating the - // current and drawing states. Drawing state is only accessed - // in this thread, no need to have it locked - mFront.active_legacy = mFront.requested_legacy; - - // We also need to update the current state so that - // we don't end-up overwriting the drawing state with - // this stale current state during the next transaction - // - // NOTE: We don't need to hold the transaction lock here - // because State::active_legacy is only accessed from this thread. - mCurrent.active_legacy = mFront.active_legacy; - mCurrent.modified = true; - - // recompute visible region - mRecomputeVisibleRegions = true; - - if (mFront.crop != mFront.requestedCrop) { - mFront.crop = mFront.requestedCrop; - mCurrent.crop = mFront.requestedCrop; - mRecomputeVisibleRegions = true; - } - } - - ALOGD_IF(DEBUG_RESIZE, - "[%s] latchBuffer/reject: buffer (%ux%u, tr=%02x), scalingMode=%d\n" - " drawing={ active_legacy ={ wh={%4u,%4u} crop={%4d,%4d,%4d,%4d} " - "(%4d,%4d) " - "}\n" - " requested_legacy={ wh={%4u,%4u} }}\n", - mName.c_str(), bufWidth, bufHeight, item.mTransform, item.mScalingMode, - mFront.active_legacy.w, mFront.active_legacy.h, mFront.crop.left, mFront.crop.top, - mFront.crop.right, mFront.crop.bottom, mFront.crop.getWidth(), - mFront.crop.getHeight(), mFront.requested_legacy.w, mFront.requested_legacy.h); - } - - if (!isFixedSize && !mStickyTransformSet) { - if (mFront.active_legacy.w != bufWidth || mFront.active_legacy.h != bufHeight) { - // reject this buffer - ALOGE("[%s] rejecting buffer: " - "bufWidth=%d, bufHeight=%d, front.active_legacy.{w=%d, h=%d}", - mName.c_str(), bufWidth, bufHeight, mFront.active_legacy.w, - mFront.active_legacy.h); - return true; - } - } - - // if the transparent region has changed (this test is - // conservative, but that's fine, worst case we're doing - // a bit of extra work), we latch the new one and we - // trigger a visible-region recompute. - // - // We latch the transparent region here, instead of above where we latch - // the rest of the geometry because it is only content but not necessarily - // resize dependent. - if (!mFront.activeTransparentRegion_legacy.hasSameRects( - mFront.requestedTransparentRegion_legacy)) { - mFront.activeTransparentRegion_legacy = mFront.requestedTransparentRegion_legacy; - - // We also need to update the current state so that - // we don't end-up overwriting the drawing state with - // this stale current state during the next transaction - // - // NOTE: We don't need to hold the transaction lock here - // because State::active_legacy is only accessed from this thread. - mCurrent.activeTransparentRegion_legacy = mFront.activeTransparentRegion_legacy; - - // recompute visible region - mRecomputeVisibleRegions = true; - } - - return false; -} - -} // namespace android - -// TODO(b/129481165): remove the #pragma below and fix conversion issues -#pragma clang diagnostic pop // ignored "-Wconversion" diff --git a/services/surfaceflinger/LayerRejecter.h b/services/surfaceflinger/LayerRejecter.h deleted file mode 100644 index 4981f451d9d725da8cb7fc763b51fbdb80da974d..0000000000000000000000000000000000000000 --- a/services/surfaceflinger/LayerRejecter.h +++ /dev/null @@ -1,41 +0,0 @@ -/* - * Copyright (C) 2007 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 "Layer.h" -#include "BufferLayerConsumer.h" - -namespace android { - -class LayerRejecter : public BufferLayerConsumer::BufferRejecter { -public: - LayerRejecter(Layer::State& front, Layer::State& current, bool& recomputeVisibleRegions, - bool stickySet, const std::string& name, - bool transformToDisplayInverse); - - virtual bool reject(const sp&, const BufferItem&); - -private: - Layer::State& mFront; - Layer::State& mCurrent; - bool& mRecomputeVisibleRegions; - const bool mStickyTransformSet; - const std::string& mName; - const bool mTransformToDisplayInverse; -}; - -} // namespace android diff --git a/services/surfaceflinger/LayerRenderArea.cpp b/services/surfaceflinger/LayerRenderArea.cpp index 896f25404ddce8dea6d9a2e018bc18aa404e2a88..51d4ff854f0dcbb773bc14915b828ad0a384e079 100644 --- a/services/surfaceflinger/LayerRenderArea.cpp +++ b/services/surfaceflinger/LayerRenderArea.cpp @@ -17,8 +17,8 @@ #include #include -#include "ContainerLayer.h" #include "DisplayDevice.h" +#include "FrontEnd/LayerCreationArgs.h" #include "Layer.h" #include "LayerRenderArea.h" #include "SurfaceFlinger.h" @@ -31,6 +31,7 @@ void reparentForDrawing(const sp& oldParent, const sp& newParent, // Compute and cache the bounds for the new parent layer. newParent->computeBounds(drawingBounds.toFloatRect(), ui::Transform(), 0.f /* shadowRadius */); + newParent->updateSnapshot(true /* updateGeometry */); oldParent->setChildrenDrawingParent(newParent); }; @@ -38,9 +39,13 @@ void reparentForDrawing(const sp& oldParent, const sp& newParent, LayerRenderArea::LayerRenderArea(SurfaceFlinger& flinger, sp layer, const Rect& crop, ui::Size reqSize, ui::Dataspace reqDataSpace, bool childrenOnly, - const Rect& layerStackRect, bool allowSecureLayers) - : RenderArea(reqSize, CaptureFill::CLEAR, reqDataSpace, layerStackRect, allowSecureLayers), + bool allowSecureLayers, const ui::Transform& layerTransform, + const Rect& layerBufferSize, bool hintForSeamlessTransition) + : RenderArea(reqSize, CaptureFill::CLEAR, reqDataSpace, hintForSeamlessTransition, + allowSecureLayers), mLayer(std::move(layer)), + mLayerTransform(layerTransform), + mLayerBufferSize(layerBufferSize), mCrop(crop), mFlinger(flinger), mChildrenOnly(childrenOnly) {} @@ -49,33 +54,18 @@ const ui::Transform& LayerRenderArea::getTransform() const { return mTransform; } -Rect LayerRenderArea::getBounds() const { - return mLayer->getBufferSize(mLayer->getDrawingState()); -} - -int LayerRenderArea::getHeight() const { - return mLayer->getBufferSize(mLayer->getDrawingState()).getHeight(); -} - -int LayerRenderArea::getWidth() const { - return mLayer->getBufferSize(mLayer->getDrawingState()).getWidth(); -} - bool LayerRenderArea::isSecure() const { return mAllowSecureLayers; } -bool LayerRenderArea::needsFiltering() const { - return mNeedsFiltering; -} - sp LayerRenderArea::getDisplayDevice() const { return nullptr; } Rect LayerRenderArea::getSourceCrop() const { if (mCrop.isEmpty()) { - return getBounds(); + // TODO this should probably be mBounds instead of just buffer bounds + return mLayerBufferSize; } else { return mCrop; } @@ -84,20 +74,23 @@ Rect LayerRenderArea::getSourceCrop() const { void LayerRenderArea::render(std::function drawLayers) { using namespace std::string_literals; - const Rect sourceCrop = getSourceCrop(); - // no need to check rotation because there is none - mNeedsFiltering = sourceCrop.width() != getReqWidth() || sourceCrop.height() != getReqHeight(); + if (!mChildrenOnly) { + mTransform = mLayerTransform.inverse(); + } + if (mFlinger.mLayerLifecycleManagerEnabled) { + drawLayers(); + return; + } // If layer is offscreen, update mirroring info if it exists if (mLayer->isRemovedFromCurrentState()) { mLayer->traverse(LayerVector::StateSet::Drawing, - [&](Layer* layer) { layer->updateMirrorInfo(); }); + [&](Layer* layer) { layer->updateMirrorInfo({}); }); mLayer->traverse(LayerVector::StateSet::Drawing, [&](Layer* layer) { layer->updateCloneBufferInfo(); }); } if (!mChildrenOnly) { - mTransform = mLayer->getTransform().inverse(); // If the layer is offscreen, compute bounds since we don't compute bounds for offscreen // layers in a regular cycles. if (mLayer->isRemovedFromCurrentState()) { @@ -110,11 +103,12 @@ void LayerRenderArea::render(std::function drawLayers) { // layer which has no properties set and which does not draw. // We hold the statelock as the reparent-for-drawing operation modifies the // hierarchy and there could be readers on Binder threads, like dump. - sp screenshotParentLayer = mFlinger.getFactory().createContainerLayer( - {&mFlinger, nullptr, "Screenshot Parent"s, 0, LayerMetadata()}); + auto screenshotParentLayer = mFlinger.getFactory().createEffectLayer( + {&mFlinger, nullptr, "Screenshot Parent"s, ISurfaceComposerClient::eNoColorFill, + LayerMetadata()}); { Mutex::Autolock _l(mFlinger.mStateLock); - reparentForDrawing(mLayer, screenshotParentLayer, sourceCrop); + reparentForDrawing(mLayer, screenshotParentLayer, getSourceCrop()); } drawLayers(); { @@ -122,6 +116,8 @@ void LayerRenderArea::render(std::function drawLayers) { mLayer->setChildrenDrawingParent(mLayer); } } + mLayer->updateSnapshot(/*updateGeometry=*/true); + mLayer->updateChildrenSnapshots(/*updateGeometry=*/true); } } // namespace android diff --git a/services/surfaceflinger/LayerRenderArea.h b/services/surfaceflinger/LayerRenderArea.h index 41273e01c1f0e4ce6adcae8b1083648168cc1622..aa609eea38875de4c1a6f5595646aa20455fcf79 100644 --- a/services/surfaceflinger/LayerRenderArea.h +++ b/services/surfaceflinger/LayerRenderArea.h @@ -33,15 +33,12 @@ class SurfaceFlinger; class LayerRenderArea : public RenderArea { public: LayerRenderArea(SurfaceFlinger& flinger, sp layer, const Rect& crop, ui::Size reqSize, - ui::Dataspace reqDataSpace, bool childrenOnly, const Rect& layerStackRect, - bool allowSecureLayers); + ui::Dataspace reqDataSpace, bool childrenOnly, bool allowSecureLayers, + const ui::Transform& layerTransform, const Rect& layerBufferSize, + bool hintForSeamlessTransition); const ui::Transform& getTransform() const override; - Rect getBounds() const override; - int getHeight() const override; - int getWidth() const override; bool isSecure() const override; - bool needsFiltering() const override; sp getDisplayDevice() const override; Rect getSourceCrop() const override; @@ -50,10 +47,11 @@ public: private: const sp mLayer; + const ui::Transform mLayerTransform; + const Rect mLayerBufferSize; const Rect mCrop; ui::Transform mTransform; - bool mNeedsFiltering = false; SurfaceFlinger& mFlinger; const bool mChildrenOnly; diff --git a/services/surfaceflinger/LocklessQueue.h b/services/surfaceflinger/LocklessQueue.h new file mode 100644 index 0000000000000000000000000000000000000000..6b633607ae645157eaea4af28053cf1f21f204bb --- /dev/null +++ b/services/surfaceflinger/LocklessQueue.h @@ -0,0 +1,82 @@ +/* + * 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 + +template +// Single consumer multi producer stack. We can understand the two operations independently to see +// why they are without race condition. +// +// push is responsible for maintaining a linked list stored in mPush, and called from multiple +// threads without lock. We can see that if two threads never observe the same value from +// mPush.load, it just functions as a normal linked list. In the case where two threads observe the +// same value, one of them has to execute the compare_exchange first. The one that doesn't execute +// the compare exchange first, will receive false from compare_exchange. previousHead is updated (by +// compare_exchange) to the most recent value of mPush, and we try again. It's relatively clear to +// see that the process can repeat with an arbitrary number of threads. +// +// Pop is much simpler. If mPop is empty (as it begins) it atomically exchanges +// the entire push list with null. This is safe, since the only other reader (push) +// of mPush will retry if it changes in between it's read and atomic compare. We +// then store the list and pop one element. +// +// If we already had something in the pop list we just pop directly. +class LocklessQueue { +public: + class Entry { + public: + T mValue; + std::atomic mNext; + Entry(T value) : mValue(value) {} + }; + std::atomic mPush = nullptr; + std::atomic mPop = nullptr; + bool isEmpty() { return (mPush.load() == nullptr) && (mPop.load() == nullptr); } + + void push(T value) { + Entry* entry = new Entry(value); + Entry* previousHead = mPush.load(/*std::memory_order_relaxed*/); + do { + entry->mNext = previousHead; + } while (!mPush.compare_exchange_weak(previousHead, entry)); /*std::memory_order_release*/ + } + std::optional pop() { + Entry* popped = mPop.load(/*std::memory_order_acquire*/); + if (popped) { + // Single consumer so this is fine + mPop.store(popped->mNext /* , std::memory_order_release */); + auto value = popped->mValue; + delete popped; + return std::move(value); + } else { + Entry* grabbedList = mPush.exchange(nullptr /* , std::memory_order_acquire */); + if (!grabbedList) return std::nullopt; + // Reverse the list + while (grabbedList->mNext) { + Entry* next = grabbedList->mNext; + grabbedList->mNext = popped; + popped = grabbedList; + grabbedList = next; + } + mPop.store(popped /* , std::memory_order_release */); + auto value = grabbedList->mValue; + delete grabbedList; + return std::move(value); + } + } +}; diff --git a/services/surfaceflinger/MonitoredProducer.cpp b/services/surfaceflinger/MonitoredProducer.cpp deleted file mode 100644 index df76f50112adc2ae47b5c932dc10eba0eebd7ceb..0000000000000000000000000000000000000000 --- a/services/surfaceflinger/MonitoredProducer.cpp +++ /dev/null @@ -1,170 +0,0 @@ -/* - * Copyright 2014 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 -#pragma clang diagnostic push -#pragma clang diagnostic ignored "-Wconversion" - -#include "MonitoredProducer.h" -#include "Layer.h" -#include "SurfaceFlinger.h" - -#include "Scheduler/MessageQueue.h" - -namespace android { - -MonitoredProducer::MonitoredProducer(const sp& producer, - const sp& flinger, - const wp& layer) : - mProducer(producer), - mFlinger(flinger), - mLayer(layer) {} - -MonitoredProducer::~MonitoredProducer() {} - -status_t MonitoredProducer::requestBuffer(int slot, sp* buf) { - return mProducer->requestBuffer(slot, buf); -} - -status_t MonitoredProducer::setMaxDequeuedBufferCount( - int maxDequeuedBuffers) { - return mProducer->setMaxDequeuedBufferCount(maxDequeuedBuffers); -} - -status_t MonitoredProducer::setAsyncMode(bool async) { - return mProducer->setAsyncMode(async); -} - -status_t MonitoredProducer::dequeueBuffer(int* slot, sp* fence, uint32_t w, uint32_t h, - PixelFormat format, uint64_t usage, - uint64_t* outBufferAge, - FrameEventHistoryDelta* outTimestamps) { - return mProducer->dequeueBuffer(slot, fence, w, h, format, usage, outBufferAge, outTimestamps); -} - -status_t MonitoredProducer::detachBuffer(int slot) { - return mProducer->detachBuffer(slot); -} - -status_t MonitoredProducer::detachNextBuffer(sp* outBuffer, - sp* outFence) { - return mProducer->detachNextBuffer(outBuffer, outFence); -} - -status_t MonitoredProducer::attachBuffer(int* outSlot, - const sp& buffer) { - return mProducer->attachBuffer(outSlot, buffer); -} - -status_t MonitoredProducer::queueBuffer(int slot, const QueueBufferInput& input, - QueueBufferOutput* output) { - return mProducer->queueBuffer(slot, input, output); -} - -status_t MonitoredProducer::cancelBuffer(int slot, const sp& fence) { - return mProducer->cancelBuffer(slot, fence); -} - -int MonitoredProducer::query(int what, int* value) { - return mProducer->query(what, value); -} - -status_t MonitoredProducer::connect(const sp& listener, - int api, bool producerControlledByApp, QueueBufferOutput* output) { - return mProducer->connect(listener, api, producerControlledByApp, output); -} - -status_t MonitoredProducer::disconnect(int api, DisconnectMode mode) { - return mProducer->disconnect(api, mode); -} - -status_t MonitoredProducer::setSidebandStream(const sp& stream) { - return mProducer->setSidebandStream(stream); -} - -void MonitoredProducer::allocateBuffers(uint32_t width, uint32_t height, - PixelFormat format, uint64_t usage) { - mProducer->allocateBuffers(width, height, format, usage); -} - -status_t MonitoredProducer::allowAllocation(bool allow) { - return mProducer->allowAllocation(allow); -} - -status_t MonitoredProducer::setGenerationNumber(uint32_t generationNumber) { - return mProducer->setGenerationNumber(generationNumber); -} - -String8 MonitoredProducer::getConsumerName() const { - return mProducer->getConsumerName(); -} - -status_t MonitoredProducer::setSharedBufferMode(bool sharedBufferMode) { - return mProducer->setSharedBufferMode(sharedBufferMode); -} - -status_t MonitoredProducer::setAutoRefresh(bool autoRefresh) { - return mProducer->setAutoRefresh(autoRefresh); -} - -status_t MonitoredProducer::setDequeueTimeout(nsecs_t timeout) { - return mProducer->setDequeueTimeout(timeout); -} - -status_t MonitoredProducer::setLegacyBufferDrop(bool drop) { - return mProducer->setLegacyBufferDrop(drop); -} - -status_t MonitoredProducer::getLastQueuedBuffer(sp* outBuffer, - sp* outFence, float outTransformMatrix[16]) { - return mProducer->getLastQueuedBuffer(outBuffer, outFence, - outTransformMatrix); -} - -status_t MonitoredProducer::getLastQueuedBuffer(sp* outBuffer, sp* outFence, - Rect* outRect, uint32_t* outTransform) { - return mProducer->getLastQueuedBuffer(outBuffer, outFence, outRect, outTransform); -} - -void MonitoredProducer::getFrameTimestamps(FrameEventHistoryDelta* outDelta) { - mProducer->getFrameTimestamps(outDelta); -} - -status_t MonitoredProducer::getUniqueId(uint64_t* outId) const { - return mProducer->getUniqueId(outId); -} - -status_t MonitoredProducer::getConsumerUsage(uint64_t* outUsage) const { - return mProducer->getConsumerUsage(outUsage); -} - -status_t MonitoredProducer::setAutoPrerotation(bool autoPrerotation) { - return mProducer->setAutoPrerotation(autoPrerotation); -} - -IBinder* MonitoredProducer::onAsBinder() { - return this; -} - -sp MonitoredProducer::getLayer() const { - return mLayer.promote(); -} - -// --------------------------------------------------------------------------- -}; // namespace android - -// TODO(b/129481165): remove the #pragma below and fix conversion issues -#pragma clang diagnostic pop // ignored "-Wconversion" diff --git a/services/surfaceflinger/MonitoredProducer.h b/services/surfaceflinger/MonitoredProducer.h deleted file mode 100644 index 3778277fd367bf9c10aeca5d50f8f3c247b4e946..0000000000000000000000000000000000000000 --- a/services/surfaceflinger/MonitoredProducer.h +++ /dev/null @@ -1,89 +0,0 @@ -/* - * Copyright 2014 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_MONITORED_PRODUCER_H -#define ANDROID_MONITORED_PRODUCER_H - -#include - -namespace android { - -class IProducerListener; -class NativeHandle; -class SurfaceFlinger; -class Layer; - -// MonitoredProducer wraps an IGraphicBufferProducer so that SurfaceFlinger will -// be notified upon its destruction -class MonitoredProducer : public BnGraphicBufferProducer { -public: - MonitoredProducer(const sp& producer, - const sp& flinger, - const wp& layer); - virtual ~MonitoredProducer(); - - // From IGraphicBufferProducer - virtual status_t requestBuffer(int slot, sp* buf); - virtual status_t setMaxDequeuedBufferCount(int maxDequeuedBuffers); - virtual status_t setAsyncMode(bool async); - virtual status_t dequeueBuffer(int* slot, sp* fence, uint32_t w, uint32_t h, - PixelFormat format, uint64_t usage, uint64_t* outBufferAge, - FrameEventHistoryDelta* outTimestamps); - virtual status_t detachBuffer(int slot); - virtual status_t detachNextBuffer(sp* outBuffer, - sp* outFence); - virtual status_t attachBuffer(int* outSlot, - const sp& buffer); - virtual status_t queueBuffer(int slot, const QueueBufferInput& input, - QueueBufferOutput* output); - virtual status_t cancelBuffer(int slot, const sp& fence); - virtual int query(int what, int* value); - virtual status_t connect(const sp& token, int api, - bool producerControlledByApp, QueueBufferOutput* output); - virtual status_t disconnect(int api, DisconnectMode mode); - virtual status_t setSidebandStream(const sp& stream); - virtual void allocateBuffers(uint32_t width, uint32_t height, - PixelFormat format, uint64_t usage); - virtual status_t allowAllocation(bool allow); - virtual status_t setGenerationNumber(uint32_t generationNumber); - virtual String8 getConsumerName() const override; - virtual status_t setDequeueTimeout(nsecs_t timeout) override; - virtual status_t setLegacyBufferDrop(bool drop) override; - virtual status_t getLastQueuedBuffer(sp* outBuffer, - sp* outFence, float outTransformMatrix[16]) override; - virtual status_t getLastQueuedBuffer(sp* outBuffer, sp* outFence, - Rect* outRect, uint32_t* outTransform) override; - virtual IBinder* onAsBinder(); - virtual status_t setSharedBufferMode(bool sharedBufferMode) override; - virtual status_t setAutoRefresh(bool autoRefresh) override; - virtual void getFrameTimestamps(FrameEventHistoryDelta *outDelta) override; - virtual status_t getUniqueId(uint64_t* outId) const override; - virtual status_t getConsumerUsage(uint64_t* outUsage) const override; - virtual status_t setAutoPrerotation(bool autoPrerotation) override; - - // The Layer which created this producer, and on which queued Buffer's will be displayed. - sp getLayer() const; - -private: - sp mProducer; - sp mFlinger; - // The Layer which created this producer, and on which queued Buffer's will be displayed. - wp mLayer; -}; - -}; // namespace android - -#endif // ANDROID_MONITORED_PRODUCER_H diff --git a/services/surfaceflinger/NativeWindowSurface.cpp b/services/surfaceflinger/NativeWindowSurface.cpp index 3fff9283eeb63557f8c2a2acfb6efafec3643405..a6a3eec8ffc3f1de578ddad3af9cf5d0e68362dd 100644 --- a/services/surfaceflinger/NativeWindowSurface.cpp +++ b/services/surfaceflinger/NativeWindowSurface.cpp @@ -30,7 +30,7 @@ std::unique_ptr createNativeWindowSurface( class NativeWindowSurface final : public surfaceflinger::NativeWindowSurface { public: explicit NativeWindowSurface(const sp& producer) - : mSurface(new Surface(producer, /* controlledByApp */ false)) {} + : mSurface(sp::make(producer, /* controlledByApp */ false)) {} ~NativeWindowSurface() override = default; diff --git a/services/surfaceflinger/RefreshRateOverlay.cpp b/services/surfaceflinger/RefreshRateOverlay.cpp index a9180d4483512f70ae5dbc0f077dc4ee09534ca1..f1fd6db0a03f081ebfbc29549efdf1f46c8242dc 100644 --- a/services/surfaceflinger/RefreshRateOverlay.cpp +++ b/services/surfaceflinger/RefreshRateOverlay.cpp @@ -29,7 +29,6 @@ #include #include #include -#include #include #undef LOG_TAG @@ -42,19 +41,10 @@ constexpr int kDigitWidth = 64; constexpr int kDigitHeight = 100; constexpr int kDigitSpace = 16; -// Layout is digit, space, digit, space, digit, space, spinner. -constexpr int kBufferWidth = 4 * kDigitWidth + 3 * kDigitSpace; +constexpr int kMaxDigits = /*displayFps*/ 3 + /*renderFps*/ 3 + /*spinner*/ 1; +constexpr int kBufferWidth = kMaxDigits * kDigitWidth + (kMaxDigits - 1) * kDigitSpace; constexpr int kBufferHeight = kDigitHeight; -SurfaceComposerClient::Transaction createTransaction(const sp& surface) { - constexpr float kFrameRate = 0.f; - constexpr int8_t kCompatibility = ANATIVEWINDOW_FRAME_RATE_NO_VOTE; - constexpr int8_t kSeamlessness = ANATIVEWINDOW_CHANGE_FRAME_RATE_ONLY_IF_SEAMLESS; - - return SurfaceComposerClient::Transaction().setFrameRate(surface, kFrameRate, kCompatibility, - kSeamlessness); -} - } // namespace SurfaceControlHolder::~SurfaceControlHolder() { @@ -121,16 +111,10 @@ void RefreshRateOverlay::SevenSegmentDrawer::drawDigit(int digit, int left, SkCo drawSegment(Segment::Bottom, left, color, canvas); } -auto RefreshRateOverlay::SevenSegmentDrawer::draw(int number, SkColor color, +auto RefreshRateOverlay::SevenSegmentDrawer::draw(int displayFps, int renderFps, SkColor color, ui::Transform::RotationFlags rotation, - bool showSpinner) -> Buffers { - if (number < 0 || number > 1000) return {}; - - const auto hundreds = number / 100; - const auto tens = (number / 10) % 10; - const auto ones = number % 10; - - const size_t loopCount = showSpinner ? 6 : 1; + ftl::Flags features) -> Buffers { + const size_t loopCount = features.test(Features::Spinner) ? 6 : 1; Buffers buffers; buffers.reserve(loopCount); @@ -152,13 +136,13 @@ auto RefreshRateOverlay::SevenSegmentDrawer::draw(int number, SkColor color, } }(); - sp buffer = - new GraphicBuffer(static_cast(bufferWidth), - static_cast(bufferHeight), HAL_PIXEL_FORMAT_RGBA_8888, - 1, - GRALLOC_USAGE_SW_WRITE_RARELY | GRALLOC_USAGE_HW_COMPOSER | - GRALLOC_USAGE_HW_TEXTURE, - "RefreshRateOverlayBuffer"); + const auto kUsageFlags = + static_cast(GRALLOC_USAGE_SW_WRITE_RARELY | GRALLOC_USAGE_HW_COMPOSER | + GRALLOC_USAGE_HW_TEXTURE); + sp buffer = sp::make(static_cast(bufferWidth), + static_cast(bufferHeight), + HAL_PIXEL_FORMAT_RGBA_8888, 1u, + kUsageFlags, "RefreshRateOverlayBuffer"); const status_t bufferStatus = buffer->initCheck(); LOG_ALWAYS_FATAL_IF(bufferStatus != OK, "RefreshRateOverlay: Buffer failed to allocate: %d", @@ -169,20 +153,9 @@ auto RefreshRateOverlay::SevenSegmentDrawer::draw(int number, SkColor color, canvas->setMatrix(canvasTransform); int left = 0; - if (hundreds != 0) { - drawDigit(hundreds, left, color, *canvas); - } - left += kDigitWidth + kDigitSpace; - - if (tens != 0) { - drawDigit(tens, left, color, *canvas); - } - left += kDigitWidth + kDigitSpace; - - drawDigit(ones, left, color, *canvas); - left += kDigitWidth + kDigitSpace; - - if (showSpinner) { + drawNumber(displayFps, left, color, *canvas); + left += 3 * (kDigitWidth + kDigitSpace); + if (features.test(Features::Spinner)) { switch (i) { case 0: drawSegment(Segment::Upper, left, color, *canvas); @@ -205,6 +178,13 @@ auto RefreshRateOverlay::SevenSegmentDrawer::draw(int number, SkColor color, } } + left += kDigitWidth + kDigitSpace; + + if (features.test(Features::RenderRate)) { + drawNumber(renderFps, left, color, *canvas); + } + left += 3 * (kDigitWidth + kDigitSpace); + void* pixels = nullptr; buffer->lock(GRALLOC_USAGE_SW_WRITE_RARELY, reinterpret_cast(&pixels)); @@ -219,6 +199,23 @@ auto RefreshRateOverlay::SevenSegmentDrawer::draw(int number, SkColor color, return buffers; } +void RefreshRateOverlay::SevenSegmentDrawer::drawNumber(int number, int left, SkColor color, + SkCanvas& canvas) { + if (number < 0 || number >= 1000) return; + + if (number >= 100) { + drawDigit(number / 100, left, color, canvas); + } + left += kDigitWidth + kDigitSpace; + + if (number >= 10) { + drawDigit((number / 10) % 10, left, color, canvas); + } + left += kDigitWidth + kDigitSpace; + + drawDigit(number % 10, left, color, canvas); +} + std::unique_ptr createSurfaceControlHolder() { sp surfaceControl = SurfaceComposerClient::getDefault() @@ -228,25 +225,28 @@ std::unique_ptr createSurfaceControlHolder() { return std::make_unique(std::move(surfaceControl)); } -RefreshRateOverlay::RefreshRateOverlay(FpsRange fpsRange, bool showSpinner) - : mFpsRange(fpsRange), - mShowSpinner(showSpinner), - mSurfaceControl(createSurfaceControlHolder()) { +RefreshRateOverlay::RefreshRateOverlay(FpsRange fpsRange, ftl::Flags features) + : mFpsRange(fpsRange), mFeatures(features), mSurfaceControl(createSurfaceControlHolder()) { if (!mSurfaceControl) { ALOGE("%s: Failed to create buffer state layer", __func__); return; } - createTransaction(mSurfaceControl->get()) + createTransaction() .setLayer(mSurfaceControl->get(), INT32_MAX - 2) .setTrustedOverlay(mSurfaceControl->get(), true) .apply(); } -auto RefreshRateOverlay::getOrCreateBuffers(Fps fps) -> const Buffers& { +auto RefreshRateOverlay::getOrCreateBuffers(Fps displayFps, Fps renderFps) -> const Buffers& { static const Buffers kNoBuffers; if (!mSurfaceControl) return kNoBuffers; + // avoid caching different render rates if RenderRate is anyway not visible + if (!mFeatures.test(Features::RenderRate)) { + renderFps = 0_Hz; + } + const auto transformHint = static_cast(mSurfaceControl->get()->getTransformHint()); @@ -262,21 +262,24 @@ auto RefreshRateOverlay::getOrCreateBuffers(Fps fps) -> const Buffers& { } }(); - createTransaction(mSurfaceControl->get()) - .setTransform(mSurfaceControl->get(), transform) - .apply(); + createTransaction().setTransform(mSurfaceControl->get(), transform).apply(); - BufferCache::const_iterator it = mBufferCache.find({fps.getIntValue(), transformHint}); + BufferCache::const_iterator it = + mBufferCache.find({displayFps.getIntValue(), renderFps.getIntValue(), transformHint}); if (it == mBufferCache.end()) { - const int minFps = mFpsRange.min.getIntValue(); + // 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 fps may be outside of this range if the display has - // changed its set of supported refresh rates. - const int intFps = std::clamp(fps.getIntValue(), minFps, maxFps); + // Clamp to the range. The current displayFps may be outside of this range if the display + // has changed its set of supported refresh rates. + const int displayIntFps = std::clamp(displayFps.getIntValue(), minFps, maxFps); + const int renderIntFps = renderFps.getIntValue(); // Ensure non-zero range to avoid division by zero. - const float fpsScale = static_cast(intFps - minFps) / std::max(1, maxFps - minFps); + const float fpsScale = + static_cast(displayIntFps - minFps) / std::max(1, maxFps - minFps); constexpr SkColor kMinFpsColor = SK_ColorRED; constexpr SkColor kMaxFpsColor = SK_ColorGREEN; @@ -292,8 +295,11 @@ auto RefreshRateOverlay::getOrCreateBuffers(Fps fps) -> const Buffers& { const SkColor color = colorBase.toSkColor(); - auto buffers = SevenSegmentDrawer::draw(intFps, color, transformHint, mShowSpinner); - it = mBufferCache.try_emplace({intFps, transformHint}, std::move(buffers)).first; + auto buffers = SevenSegmentDrawer::draw(displayIntFps, renderIntFps, color, transformHint, + mFeatures); + it = mBufferCache + .try_emplace({displayIntFps, renderIntFps, transformHint}, std::move(buffers)) + .first; } return it->second; @@ -303,10 +309,15 @@ void RefreshRateOverlay::setViewport(ui::Size viewport) { constexpr int32_t kMaxWidth = 1000; const auto width = std::min({kMaxWidth, viewport.width, viewport.height}); const auto height = 2 * width; - Rect frame((3 * width) >> 4, height >> 5); - frame.offsetBy(width >> 5, height >> 4); + Rect frame((5 * width) >> 4, height >> 5); - createTransaction(mSurfaceControl->get()) + if (!mFeatures.test(Features::ShowInMiddle)) { + frame.offsetBy(width >> 5, height >> 4); + } else { + frame.offsetBy(width >> 1, height >> 4); + } + + createTransaction() .setMatrix(mSurfaceControl->get(), frame.getWidth() / static_cast(kBufferWidth), 0, 0, frame.getHeight() / static_cast(kBufferHeight)) .setPosition(mSurfaceControl->get(), frame.left, frame.top) @@ -314,22 +325,41 @@ void RefreshRateOverlay::setViewport(ui::Size viewport) { } void RefreshRateOverlay::setLayerStack(ui::LayerStack stack) { - createTransaction(mSurfaceControl->get()).setLayerStack(mSurfaceControl->get(), stack).apply(); + createTransaction().setLayerStack(mSurfaceControl->get(), stack).apply(); } -void RefreshRateOverlay::changeRefreshRate(Fps fps) { - mCurrentFps = fps; - const auto buffer = getOrCreateBuffers(fps)[mFrame]; - createTransaction(mSurfaceControl->get()).setBuffer(mSurfaceControl->get(), buffer).apply(); +void RefreshRateOverlay::changeRefreshRate(Fps displayFps, Fps renderFps) { + mDisplayFps = displayFps; + mRenderFps = renderFps; + const auto buffer = getOrCreateBuffers(displayFps, renderFps)[mFrame]; + createTransaction().setBuffer(mSurfaceControl->get(), buffer).apply(); } void RefreshRateOverlay::animate() { - if (!mShowSpinner || !mCurrentFps) return; + if (!mFeatures.test(Features::Spinner) || !mDisplayFps) return; - const auto& buffers = getOrCreateBuffers(*mCurrentFps); + const auto& buffers = getOrCreateBuffers(*mDisplayFps, *mRenderFps); mFrame = (mFrame + 1) % buffers.size(); const auto buffer = buffers[mFrame]; - createTransaction(mSurfaceControl->get()).setBuffer(mSurfaceControl->get(), buffer).apply(); + createTransaction().setBuffer(mSurfaceControl->get(), buffer).apply(); +} + +SurfaceComposerClient::Transaction RefreshRateOverlay::createTransaction() const { + constexpr float kFrameRate = 0.f; + constexpr int8_t kCompatibility = ANATIVEWINDOW_FRAME_RATE_NO_VOTE; + constexpr int8_t kSeamlessness = ANATIVEWINDOW_CHANGE_FRAME_RATE_ONLY_IF_SEAMLESS; + + const sp& surface = mSurfaceControl->get(); + + SurfaceComposerClient::Transaction transaction; + if (isSetByHwc()) { + transaction.setFlags(surface, layer_state_t::eLayerIsRefreshRateIndicator, + layer_state_t::eLayerIsRefreshRateIndicator); + // Disable overlay layer caching when refresh rate is updated by the HWC. + transaction.setCachingHint(surface, gui::CachingHint::Disabled); + } + transaction.setFrameRate(surface, kFrameRate, kCompatibility, kSeamlessness); + return transaction; } } // namespace android diff --git a/services/surfaceflinger/RefreshRateOverlay.h b/services/surfaceflinger/RefreshRateOverlay.h index a2966e654eaf14a85c2d9ae1fccc13a44720dfdc..0b89b8e3a1270cbaa6a6980abb7a2b2699fa157b 100644 --- a/services/surfaceflinger/RefreshRateOverlay.h +++ b/services/surfaceflinger/RefreshRateOverlay.h @@ -19,7 +19,9 @@ #include #include +#include #include +#include #include #include #include @@ -50,44 +52,61 @@ private: class RefreshRateOverlay { public: - RefreshRateOverlay(FpsRange, bool showSpinner); + enum class Features { + Spinner = 1 << 0, + RenderRate = 1 << 1, + ShowInMiddle = 1 << 2, + SetByHwc = 1 << 3, + }; + + RefreshRateOverlay(FpsRange, ftl::Flags); void setLayerStack(ui::LayerStack); void setViewport(ui::Size); - void changeRefreshRate(Fps); + void changeRefreshRate(Fps, Fps); void animate(); + bool isSetByHwc() const { return mFeatures.test(RefreshRateOverlay::Features::SetByHwc); } private: using Buffers = std::vector>; class SevenSegmentDrawer { public: - static Buffers draw(int number, SkColor, ui::Transform::RotationFlags, bool showSpinner); + static Buffers draw(int displayFps, int renderFps, SkColor, ui::Transform::RotationFlags, + ftl::Flags); private: enum class Segment { Upper, UpperLeft, UpperRight, Middle, LowerLeft, LowerRight, Bottom }; static void drawSegment(Segment, int left, SkColor, SkCanvas&); static void drawDigit(int digit, int left, SkColor, SkCanvas&); + static void drawNumber(int number, int left, SkColor, SkCanvas&); }; - const Buffers& getOrCreateBuffers(Fps); + const Buffers& getOrCreateBuffers(Fps, Fps); + + SurfaceComposerClient::Transaction createTransaction() const; struct Key { - int fps; + int displayFps; + int renderFps; ui::Transform::RotationFlags flags; - bool operator==(Key other) const { return fps == other.fps && flags == other.flags; } + bool operator==(Key other) const { + return displayFps == other.displayFps && renderFps == other.renderFps && + flags == other.flags; + } }; using BufferCache = ftl::SmallMap; BufferCache mBufferCache; - std::optional mCurrentFps; + std::optional mDisplayFps; + std::optional mRenderFps; size_t mFrame = 0; const FpsRange mFpsRange; // For color interpolation. - const bool mShowSpinner; + const ftl::Flags mFeatures; const std::unique_ptr mSurfaceControl; }; diff --git a/services/surfaceflinger/RegionSamplingThread.cpp b/services/surfaceflinger/RegionSamplingThread.cpp index e126931e6c5a6db62f4113a996f29427f9e07d3c..8f658d5a09fca3ead1600a358721060d2afd5e47 100644 --- a/services/surfaceflinger/RegionSamplingThread.cpp +++ b/services/surfaceflinger/RegionSamplingThread.cpp @@ -40,6 +40,7 @@ #include "DisplayDevice.h" #include "DisplayRenderArea.h" +#include "FrontEnd/LayerCreationArgs.h" #include "Layer.h" #include "Scheduler/VsyncController.h" #include "SurfaceFlinger.h" @@ -129,12 +130,12 @@ RegionSamplingThread::~RegionSamplingThread() { } } -void RegionSamplingThread::addListener(const Rect& samplingArea, const wp& stopLayer, +void RegionSamplingThread::addListener(const Rect& samplingArea, uint32_t stopLayerId, const sp& listener) { sp asBinder = IInterface::asBinder(listener); - asBinder->linkToDeath(this); + asBinder->linkToDeath(sp::fromExisting(this)); std::lock_guard lock(mSamplingMutex); - mDescriptors.emplace(wp(asBinder), Descriptor{samplingArea, stopLayer, listener}); + mDescriptors.emplace(wp(asBinder), Descriptor{samplingArea, stopLayerId, listener}); } void RegionSamplingThread::removeListener(const sp& listener) { @@ -276,55 +277,84 @@ void RegionSamplingThread::captureSample() { const Rect sampledBounds = sampleRegion.bounds(); constexpr bool kUseIdentityTransform = false; + constexpr bool kHintForSeamlessTransition = false; SurfaceFlinger::RenderAreaFuture renderAreaFuture = ftl::defer([=] { return DisplayRenderArea::create(displayWeak, sampledBounds, sampledBounds.getSize(), - ui::Dataspace::V0_SRGB, kUseIdentityTransform); + ui::Dataspace::V0_SRGB, kUseIdentityTransform, + kHintForSeamlessTransition); }); std::unordered_set, SpHash> listeners; - 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; - - // Likewise if we just found a stop layer, set the flag and abort - for (const auto& [area, stopLayer, listener] : descriptors) { - if (layer == stopLayer.promote().get()) { - stopLayerFound = true; - return; - } + auto layerFilterFn = [&](const char* layerName, uint32_t layerId, const Rect& bounds, + const ui::Transform transform, bool& outStopTraversal) -> bool { + // Likewise if we just found a stop layer, set the flag and abort + for (const auto& [area, stopLayerId, listener] : descriptors) { + if (stopLayerId != UNASSIGNED_LAYER_ID && layerId == stopLayerId) { + outStopTraversal = true; + return false; } + } - // Compute the layer's position on the screen - const Rect bounds = Rect(layer->getBounds()); - const ui::Transform transform = layer->getTransform(); - constexpr bool roundOutwards = true; - Rect transformed = transform.transform(bounds, roundOutwards); - - // If this layer doesn't intersect with the larger sampledBounds, skip capturing it - Rect ignore; - if (!transformed.intersect(sampledBounds, &ignore)) return; - - // If the layer doesn't intersect a sampling area, skip capturing it - bool intersectsAnyArea = false; - for (const auto& [area, stopLayer, listener] : descriptors) { - if (transformed.intersect(area, &ignore)) { - intersectsAnyArea = true; - listeners.insert(listener); - } + // Compute the layer's position on the screen + constexpr bool roundOutwards = true; + Rect transformed = transform.transform(bounds, roundOutwards); + + // If this layer doesn't intersect with the larger sampledBounds, skip capturing it + Rect ignore; + if (!transformed.intersect(sampledBounds, &ignore)) return false; + + // If the layer doesn't intersect a sampling area, skip capturing it + bool intersectsAnyArea = false; + for (const auto& [area, stopLayer, listener] : descriptors) { + if (transformed.intersect(area, &ignore)) { + intersectsAnyArea = true; + listeners.insert(listener); } - if (!intersectsAnyArea) return; + } + if (!intersectsAnyArea) return false; - ALOGV("Traversing [%s] [%d, %d, %d, %d]", layer->getDebugName(), bounds.left, - bounds.top, bounds.right, bounds.bottom); - visitor(layer); - }; - mFlinger.traverseLayersInLayerStack(layerStack, CaptureArgs::UNSET_UID, filterVisitor); + ALOGV("Traversing [%s] [%d, %d, %d, %d]", layerName, bounds.left, bounds.top, bounds.right, + bounds.bottom); + + 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); + } + std::shared_ptr buffer = nullptr; if (mCachedBuffer && mCachedBuffer->getBuffer()->getWidth() == sampledBounds.getWidth() && mCachedBuffer->getBuffer()->getHeight() == sampledBounds.getHeight()) { @@ -333,8 +363,8 @@ void RegionSamplingThread::captureSample() { const uint32_t usage = GRALLOC_USAGE_SW_READ_OFTEN | GRALLOC_USAGE_HW_RENDER | GRALLOC_USAGE_HW_TEXTURE; sp graphicBuffer = - new GraphicBuffer(sampledBounds.getWidth(), sampledBounds.getHeight(), - PIXEL_FORMAT_RGBA_8888, 1, usage, "RegionSamplingThread"); + sp::make(sampledBounds.getWidth(), sampledBounds.getHeight(), + PIXEL_FORMAT_RGBA_8888, 1, usage, "RegionSamplingThread"); const status_t bufferStatus = graphicBuffer->initCheck(); LOG_ALWAYS_FATAL_IF(bufferStatus != OK, "captureSample: Buffer failed to allocate: %d", bufferStatus); @@ -348,7 +378,7 @@ void RegionSamplingThread::captureSample() { constexpr bool kGrayscale = false; if (const auto fenceResult = - mFlinger.captureScreenCommon(std::move(renderAreaFuture), traverseLayers, buffer, + mFlinger.captureScreenCommon(std::move(renderAreaFuture), getLayerSnapshots, buffer, kRegionSampling, kGrayscale, nullptr) .get(); fenceResult.ok()) { diff --git a/services/surfaceflinger/RegionSamplingThread.h b/services/surfaceflinger/RegionSamplingThread.h index 686b4b1e1fb4bf84a95e0a77f0fd0c891e7ace5a..e8c891e4bf0f747a4f2f02b313b12bc64a541892 100644 --- a/services/surfaceflinger/RegionSamplingThread.h +++ b/services/surfaceflinger/RegionSamplingThread.h @@ -26,6 +26,7 @@ #include #include +#include #include #include #include @@ -36,7 +37,6 @@ namespace android { class Layer; -class Scheduler; class SurfaceFlinger; struct SamplingOffsetCallback; @@ -73,7 +73,7 @@ public: // Add a listener to receive luma notifications. The luma reported via listener will // report the median luma for the layers under the stopLayerHandle, in the samplingArea region. - void addListener(const Rect& samplingArea, const wp& stopLayer, + void addListener(const Rect& samplingArea, uint32_t stopLayerId, const sp& listener); // Remove the listener to stop receiving median luma notifications. void removeListener(const sp& listener); @@ -87,7 +87,7 @@ public: private: struct Descriptor { Rect area = Rect::EMPTY_RECT; - wp stopLayer; + uint32_t stopLayerId; sp listener; }; diff --git a/services/surfaceflinger/RenderArea.h b/services/surfaceflinger/RenderArea.h index 387364c03aedadac7de1dfa3917a88fd57fc3c02..71b85bd3b2e24c0ffbca4ca425ac44d87e2718f7 100644 --- a/services/surfaceflinger/RenderArea.h +++ b/services/surfaceflinger/RenderArea.h @@ -25,14 +25,29 @@ public: static float getCaptureFillValue(CaptureFill captureFill); RenderArea(ui::Size reqSize, CaptureFill captureFill, ui::Dataspace reqDataSpace, - const Rect& layerStackRect, bool allowSecureLayers = false, + bool hintForSeamlessTransition, bool allowSecureLayers = false, RotationFlags rotation = ui::Transform::ROT_0) : mAllowSecureLayers(allowSecureLayers), mReqSize(reqSize), mReqDataSpace(reqDataSpace), mCaptureFill(captureFill), mRotationFlags(rotation), - mLayerStackSpaceRect(layerStackRect) {} + mHintForSeamlessTransition(hintForSeamlessTransition) {} + + static std::function>>()> fromTraverseLayersLambda( + std::function traverseLayers) { + return [traverseLayers = std::move(traverseLayers)]() { + std::vector>> layers; + traverseLayers([&](Layer* layer) { + // Layer::prepareClientComposition uses the layer's snapshot to populate the + // resulting LayerSettings. Calling Layer::updateSnapshot ensures that LayerSettings + // are generated with the layer's current buffer and geometry. + layer->updateSnapshot(true /* updateGeometry */); + layers.emplace_back(layer, layer->copyCompositionEngineLayerFE()); + }); + return layers; + }; + } virtual ~RenderArea() = default; @@ -43,20 +58,10 @@ public: // blacked out / skipped when rendered to an insecure render area. virtual bool isSecure() const = 0; - // Returns true if the otherwise disabled layer filtering should be - // enabled when rendering to this render area. - virtual bool needsFiltering() const = 0; - // Returns the transform to be applied on layers to transform them into // the logical render area. virtual const ui::Transform& getTransform() const = 0; - // Returns the size of the logical render area. Layers are clipped to the - // logical render area. - virtual int getWidth() const = 0; - virtual int getHeight() const = 0; - virtual Rect getBounds() const = 0; - // Returns the source crop of the render area. The source crop defines // how layers are projected from the logical render area onto the physical // render area. It can be larger than the logical render area. It can @@ -83,13 +88,14 @@ public: virtual sp getDisplayDevice() const = 0; - // Returns the source display viewport. - const Rect& getLayerStackSpaceRect() const { return mLayerStackSpaceRect; } - // If this is a LayerRenderArea, return the root layer of the // capture operation. virtual sp getParentLayer() 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; } + protected: const bool mAllowSecureLayers; @@ -98,7 +104,7 @@ private: const ui::Dataspace mReqDataSpace; const CaptureFill mCaptureFill; const RotationFlags mRotationFlags; - const Rect mLayerStackSpaceRect; + const bool mHintForSeamlessTransition; }; } // namespace android diff --git a/services/surfaceflinger/Scheduler/Android.bp b/services/surfaceflinger/Scheduler/Android.bp index 8df9ba50739e4523e01dbf90e0236ee19028ad45..d5d868839f9400b271750afccad67abb7de23dc4 100644 --- a/services/surfaceflinger/Scheduler/Android.bp +++ b/services/surfaceflinger/Scheduler/Android.bp @@ -18,6 +18,7 @@ cc_defaults { "libbase", "libcutils", "liblog", + "libui", "libutils", ], } @@ -39,6 +40,7 @@ cc_library_static { name: "libscheduler", defaults: ["libscheduler_defaults"], srcs: [ + "src/PresentLatencyTracker.cpp", "src/Timer.cpp", ], local_include_dirs: ["include"], @@ -50,6 +52,7 @@ cc_test { test_suites: ["device-tests"], defaults: ["libscheduler_defaults"], srcs: [ + "tests/PresentLatencyTrackerTest.cpp", "tests/TimerTest.cpp", ], static_libs: [ diff --git a/services/surfaceflinger/Scheduler/DispSyncSource.cpp b/services/surfaceflinger/Scheduler/DispSyncSource.cpp deleted file mode 100644 index 4af1f5c65fe7d5d9ce885eff3f106153b6bdd12a..0000000000000000000000000000000000000000 --- a/services/surfaceflinger/Scheduler/DispSyncSource.cpp +++ /dev/null @@ -1,202 +0,0 @@ -/* - * 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. - */ - -#define ATRACE_TAG ATRACE_TAG_GRAPHICS - -#include "DispSyncSource.h" - -#include -#include -#include - -#include "EventThread.h" -#include "VSyncTracker.h" -#include "VsyncController.h" - -namespace android::scheduler { -using base::StringAppendF; -using namespace std::chrono_literals; - -class CallbackRepeater { -public: - CallbackRepeater(VSyncDispatch& dispatch, VSyncDispatch::Callback cb, const char* name, - std::chrono::nanoseconds workDuration, std::chrono::nanoseconds readyDuration, - std::chrono::nanoseconds notBefore) - : mName(name), - mCallback(cb), - mRegistration(dispatch, - std::bind(&CallbackRepeater::callback, this, std::placeholders::_1, - std::placeholders::_2, std::placeholders::_3), - mName), - mStarted(false), - mWorkDuration(workDuration), - mReadyDuration(readyDuration), - mLastCallTime(notBefore) {} - - ~CallbackRepeater() { - std::lock_guard lock(mMutex); - mRegistration.cancel(); - } - - void start(std::chrono::nanoseconds workDuration, std::chrono::nanoseconds readyDuration) { - std::lock_guard lock(mMutex); - mStarted = true; - mWorkDuration = workDuration; - mReadyDuration = readyDuration; - - auto const scheduleResult = - mRegistration.schedule({.workDuration = mWorkDuration.count(), - .readyDuration = mReadyDuration.count(), - .earliestVsync = mLastCallTime.count()}); - LOG_ALWAYS_FATAL_IF((!scheduleResult.has_value()), "Error scheduling callback"); - } - - void stop() { - std::lock_guard lock(mMutex); - LOG_ALWAYS_FATAL_IF(!mStarted, "DispSyncInterface misuse: callback already stopped"); - mStarted = false; - mRegistration.cancel(); - } - - void dump(std::string& result) const { - std::lock_guard lock(mMutex); - const auto relativeLastCallTime = - mLastCallTime - std::chrono::steady_clock::now().time_since_epoch(); - StringAppendF(&result, "\t%s: ", mName.c_str()); - StringAppendF(&result, "mWorkDuration=%.2f mReadyDuration=%.2f last vsync time ", - mWorkDuration.count() / 1e6f, mReadyDuration.count() / 1e6f); - StringAppendF(&result, "%.2fms relative to now (%s)\n", relativeLastCallTime.count() / 1e6f, - mStarted ? "running" : "stopped"); - } - -private: - void callback(nsecs_t vsyncTime, nsecs_t wakeupTime, nsecs_t readyTime) { - { - std::lock_guard lock(mMutex); - mLastCallTime = std::chrono::nanoseconds(vsyncTime); - } - - mCallback(vsyncTime, wakeupTime, readyTime); - - { - std::lock_guard lock(mMutex); - if (!mStarted) { - return; - } - auto const scheduleResult = - mRegistration.schedule({.workDuration = mWorkDuration.count(), - .readyDuration = mReadyDuration.count(), - .earliestVsync = vsyncTime}); - LOG_ALWAYS_FATAL_IF(!scheduleResult.has_value(), "Error rescheduling callback"); - } - } - - const std::string mName; - scheduler::VSyncDispatch::Callback mCallback; - - mutable std::mutex mMutex; - VSyncCallbackRegistration mRegistration GUARDED_BY(mMutex); - bool mStarted GUARDED_BY(mMutex) = false; - std::chrono::nanoseconds mWorkDuration GUARDED_BY(mMutex) = 0ns; - std::chrono::nanoseconds mReadyDuration GUARDED_BY(mMutex) = 0ns; - std::chrono::nanoseconds mLastCallTime GUARDED_BY(mMutex) = 0ns; -}; - -DispSyncSource::DispSyncSource(VSyncDispatch& vSyncDispatch, VSyncTracker& vSyncTracker, - std::chrono::nanoseconds workDuration, - std::chrono::nanoseconds readyDuration, bool traceVsync, - const char* name) - : mName(name), - mValue(base::StringPrintf("VSYNC-%s", name), 0), - mTraceVsync(traceVsync), - mVsyncOnLabel(base::StringPrintf("VsyncOn-%s", name)), - mVSyncTracker(vSyncTracker), - mWorkDuration(base::StringPrintf("VsyncWorkDuration-%s", name), workDuration), - mReadyDuration(readyDuration) { - mCallbackRepeater = - std::make_unique(vSyncDispatch, - std::bind(&DispSyncSource::onVsyncCallback, this, - std::placeholders::_1, - std::placeholders::_2, - std::placeholders::_3), - name, workDuration, readyDuration, - std::chrono::steady_clock::now().time_since_epoch()); -} - -DispSyncSource::~DispSyncSource() = default; - -void DispSyncSource::setVSyncEnabled(bool enable) { - std::lock_guard lock(mVsyncMutex); - if (enable) { - mCallbackRepeater->start(mWorkDuration, mReadyDuration); - // ATRACE_INT(mVsyncOnLabel.c_str(), 1); - } else { - mCallbackRepeater->stop(); - // ATRACE_INT(mVsyncOnLabel.c_str(), 0); - } - mEnabled = enable; -} - -void DispSyncSource::setCallback(VSyncSource::Callback* callback) { - std::lock_guard lock(mCallbackMutex); - mCallback = callback; -} - -void DispSyncSource::setDuration(std::chrono::nanoseconds workDuration, - std::chrono::nanoseconds readyDuration) { - std::lock_guard lock(mVsyncMutex); - mWorkDuration = workDuration; - mReadyDuration = readyDuration; - - // If we're not enabled, we don't need to mess with the listeners - if (!mEnabled) { - return; - } - - mCallbackRepeater->start(mWorkDuration, mReadyDuration); -} - -void DispSyncSource::onVsyncCallback(nsecs_t vsyncTime, nsecs_t targetWakeupTime, - nsecs_t readyTime) { - VSyncSource::Callback* callback; - { - std::lock_guard lock(mCallbackMutex); - callback = mCallback; - } - - if (mTraceVsync) { - mValue = (mValue + 1) % 2; - } - - if (callback != nullptr) { - callback->onVSyncEvent(targetWakeupTime, {vsyncTime, readyTime}); - } -} - -VSyncSource::VSyncData DispSyncSource::getLatestVSyncData() const { - std::lock_guard lock(mVsyncMutex); - nsecs_t expectedPresentationTime = mVSyncTracker.nextAnticipatedVSyncTimeFrom( - systemTime() + mWorkDuration.get().count() + mReadyDuration.count()); - nsecs_t deadline = expectedPresentationTime - mReadyDuration.count(); - return {expectedPresentationTime, deadline}; -} - -void DispSyncSource::dump(std::string& result) const { - std::lock_guard lock(mVsyncMutex); - StringAppendF(&result, "DispSyncSource: %s(%s)\n", mName, mEnabled ? "enabled" : "disabled"); -} - -} // namespace android::scheduler diff --git a/services/surfaceflinger/Scheduler/DispSyncSource.h b/services/surfaceflinger/Scheduler/DispSyncSource.h deleted file mode 100644 index edcd3ac709f31dfb37c0d26813787686fb847975..0000000000000000000000000000000000000000 --- a/services/surfaceflinger/Scheduler/DispSyncSource.h +++ /dev/null @@ -1,69 +0,0 @@ -/* - * 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. - */ -#pragma once - -#include -#include - -#include "EventThread.h" -#include "TracedOrdinal.h" -#include "VSyncDispatch.h" - -namespace android::scheduler { -class CallbackRepeater; -class VSyncTracker; - -class DispSyncSource final : public VSyncSource { -public: - DispSyncSource(VSyncDispatch& vSyncDispatch, VSyncTracker& vSyncTracker, - std::chrono::nanoseconds workDuration, std::chrono::nanoseconds readyDuration, - bool traceVsync, const char* name); - - ~DispSyncSource() override; - - // The following methods are implementation of VSyncSource. - const char* getName() const override { return mName; } - void setVSyncEnabled(bool enable) override; - void setCallback(VSyncSource::Callback* callback) override; - void setDuration(std::chrono::nanoseconds workDuration, - std::chrono::nanoseconds readyDuration) override; - VSyncData getLatestVSyncData() const override; - - void dump(std::string&) const override; - -private: - void onVsyncCallback(nsecs_t vsyncTime, nsecs_t targetWakeupTime, nsecs_t readyTime); - - const char* const mName; - TracedOrdinal mValue; - - const bool mTraceVsync; - const std::string mVsyncOnLabel; - - const VSyncTracker& mVSyncTracker; - - std::unique_ptr mCallbackRepeater; - - std::mutex mCallbackMutex; - VSyncSource::Callback* mCallback GUARDED_BY(mCallbackMutex) = nullptr; - - mutable std::mutex mVsyncMutex; - TracedOrdinal mWorkDuration GUARDED_BY(mVsyncMutex); - std::chrono::nanoseconds mReadyDuration GUARDED_BY(mVsyncMutex); - bool mEnabled GUARDED_BY(mVsyncMutex) = false; -}; - -} // namespace android::scheduler diff --git a/services/surfaceflinger/Scheduler/EventThread.cpp b/services/surfaceflinger/Scheduler/EventThread.cpp index 639ba5a3f11a0db625edb6a98834e82bb752b8ef..281b0ae55913d9777bdff95da05fc227b638e83f 100644 --- a/services/surfaceflinger/Scheduler/EventThread.cpp +++ b/services/surfaceflinger/Scheduler/EventThread.cpp @@ -28,6 +28,7 @@ #include #include #include +#include #include @@ -41,8 +42,11 @@ #include #include +#include #include "DisplayHardware/DisplayMode.h" #include "FrameTimeline.h" +#include "VSyncDispatch.h" +#include "VSyncTracker.h" #include "EventThread.h" @@ -124,12 +128,12 @@ DisplayEventReceiver::Event makeVSync(PhysicalDisplayId displayId, nsecs_t times return event; } -DisplayEventReceiver::Event makeModeChanged(DisplayModePtr mode) { +DisplayEventReceiver::Event makeModeChanged(const scheduler::FrameRateMode& mode) { DisplayEventReceiver::Event event; - event.header = {DisplayEventReceiver::DISPLAY_EVENT_MODE_CHANGE, mode->getPhysicalDisplayId(), - systemTime()}; - event.modeChange.modeId = mode->getId().value(); - event.modeChange.vsyncPeriod = mode->getVsyncPeriod(); + event.header = {DisplayEventReceiver::DISPLAY_EVENT_MODE_CHANGE, + mode.modePtr->getPhysicalDisplayId(), systemTime()}; + event.modeChange.modeId = mode.modePtr->getId().value(); + event.modeChange.vsyncPeriod = mode.fps.getPeriodNsecs(); return event; } @@ -157,9 +161,9 @@ DisplayEventReceiver::Event makeFrameRateOverrideFlushEvent(PhysicalDisplayId di } // namespace -EventThreadConnection::EventThreadConnection( - EventThread* eventThread, uid_t callingUid, ResyncCallback resyncCallback, - ISurfaceComposer::EventRegistrationFlags eventRegistration) +EventThreadConnection::EventThreadConnection(EventThread* eventThread, uid_t callingUid, + ResyncCallback resyncCallback, + EventRegistrationFlags eventRegistration) : resyncCallback(std::move(resyncCallback)), mOwnerUid(callingUid), mEventRegistration(eventRegistration), @@ -173,7 +177,7 @@ EventThreadConnection::~EventThreadConnection() { void EventThreadConnection::onFirstRef() { // NOTE: mEventThread doesn't hold a strong reference on us - mEventThread->registerDisplayEventConnection(this); + mEventThread->registerDisplayEventConnection(sp::fromExisting(this)); } binder::Status EventThreadConnection::stealReceiveChannel(gui::BitTube* outChannel) { @@ -188,20 +192,22 @@ binder::Status EventThreadConnection::stealReceiveChannel(gui::BitTube* outChann } binder::Status EventThreadConnection::setVsyncRate(int rate) { - mEventThread->setVsyncRate(static_cast(rate), this); + mEventThread->setVsyncRate(static_cast(rate), + sp::fromExisting(this)); return binder::Status::ok(); } binder::Status EventThreadConnection::requestNextVsync() { ATRACE_CALL(); - mEventThread->requestNextVsync(this); + mEventThread->requestNextVsync(sp::fromExisting(this)); return binder::Status::ok(); } binder::Status EventThreadConnection::getLatestVsyncEventData( ParcelableVsyncEventData* outVsyncEventData) { ATRACE_CALL(); - outVsyncEventData->vsync = mEventThread->getLatestVsyncEventData(this); + outVsyncEventData->vsync = + mEventThread->getLatestVsyncEventData(sp::fromExisting(this)); return binder::Status::ok(); } @@ -233,23 +239,24 @@ EventThread::~EventThread() = default; namespace impl { -EventThread::EventThread(std::unique_ptr vsyncSource, +EventThread::EventThread(const char* name, std::shared_ptr vsyncSchedule, android::frametimeline::TokenManager* tokenManager, - InterceptVSyncsCallback interceptVSyncsCallback, ThrottleVsyncCallback throttleVsyncCallback, - GetVsyncPeriodFunction getVsyncPeriodFunction) - : mVSyncSource(std::move(vsyncSource)), + GetVsyncPeriodFunction getVsyncPeriodFunction, + std::chrono::nanoseconds workDuration, + std::chrono::nanoseconds readyDuration) + : mThreadName(name), + mVsyncTracer(base::StringPrintf("VSYNC-%s", name), 0), + mWorkDuration(base::StringPrintf("VsyncWorkDuration-%s", name), workDuration), + mReadyDuration(readyDuration), + mVsyncSchedule(std::move(vsyncSchedule)), + mVsyncRegistration(mVsyncSchedule->getDispatch(), createDispatchCallback(), name), mTokenManager(tokenManager), - mInterceptVSyncsCallback(std::move(interceptVSyncsCallback)), mThrottleVsyncCallback(std::move(throttleVsyncCallback)), - mGetVsyncPeriodFunction(std::move(getVsyncPeriodFunction)), - mThreadName(mVSyncSource->getName()) { - + mGetVsyncPeriodFunction(std::move(getVsyncPeriodFunction)) { LOG_ALWAYS_FATAL_IF(getVsyncPeriodFunction == nullptr, "getVsyncPeriodFunction must not be null"); - mVSyncSource->setCallback(this); - mThread = std::thread([this]() NO_THREAD_SAFETY_ANALYSIS { std::unique_lock lock(mMutex); threadMain(lock); @@ -271,8 +278,6 @@ EventThread::EventThread(std::unique_ptr vsyncSource, } EventThread::~EventThread() { - mVSyncSource->setCallback(nullptr); - { std::lock_guard lock(mMutex); mState = State::Quit; @@ -284,15 +289,19 @@ EventThread::~EventThread() { void EventThread::setDuration(std::chrono::nanoseconds workDuration, std::chrono::nanoseconds readyDuration) { std::lock_guard lock(mMutex); - mVSyncSource->setDuration(workDuration, readyDuration); + mWorkDuration = workDuration; + mReadyDuration = readyDuration; + + mVsyncRegistration.update({.workDuration = mWorkDuration.get().count(), + .readyDuration = mReadyDuration.count(), + .earliestVsync = mLastVsyncCallbackTime.ns()}); } sp EventThread::createEventConnection( - ResyncCallback resyncCallback, - ISurfaceComposer::EventRegistrationFlags eventRegistration) const { - return new EventThreadConnection(const_cast(this), - IPCThreadState::self()->getCallingUid(), - std::move(resyncCallback), eventRegistration); + ResyncCallback resyncCallback, EventRegistrationFlags eventRegistration) const { + return sp::make(const_cast(this), + IPCThreadState::self()->getCallingUid(), + std::move(resyncCallback), eventRegistration); } status_t EventThread::registerDisplayEventConnection(const sp& connection) { @@ -360,43 +369,35 @@ VsyncEventData EventThread::getLatestVsyncEventData( VsyncEventData vsyncEventData; nsecs_t frameInterval = mGetVsyncPeriodFunction(connection->mOwnerUid); vsyncEventData.frameInterval = frameInterval; - VSyncSource::VSyncData vsyncData; - { + const auto [presentTime, deadline] = [&]() -> std::pair { std::lock_guard lock(mMutex); - vsyncData = mVSyncSource->getLatestVSyncData(); - } + const auto vsyncTime = mVsyncSchedule->getTracker().nextAnticipatedVSyncTimeFrom( + systemTime() + mWorkDuration.get().count() + mReadyDuration.count()); + return {vsyncTime, vsyncTime - mReadyDuration.count()}; + }(); generateFrameTimeline(vsyncEventData, frameInterval, systemTime(SYSTEM_TIME_MONOTONIC), - vsyncData.expectedPresentationTime, vsyncData.deadlineTimestamp); + presentTime, deadline); return vsyncEventData; } -void EventThread::onScreenReleased() { - std::lock_guard lock(mMutex); - if (!mVSyncState || mVSyncState->synthetic) { - return; - } - - mVSyncState->synthetic = true; - mCondition.notify_all(); -} - -void EventThread::onScreenAcquired() { +void EventThread::enableSyntheticVsync(bool enable) { std::lock_guard lock(mMutex); - if (!mVSyncState || !mVSyncState->synthetic) { + if (!mVSyncState || mVSyncState->synthetic == enable) { return; } - mVSyncState->synthetic = false; + mVSyncState->synthetic = enable; mCondition.notify_all(); } -void EventThread::onVSyncEvent(nsecs_t timestamp, VSyncSource::VSyncData vsyncData) { +void EventThread::onVsync(nsecs_t vsyncTime, nsecs_t wakeupTime, nsecs_t readyTime) { std::lock_guard lock(mMutex); + mLastVsyncCallbackTime = TimePoint::fromNs(vsyncTime); LOG_FATAL_IF(!mVSyncState); - mPendingEvents.push_back(makeVSync(mVSyncState->displayId, timestamp, ++mVSyncState->count, - vsyncData.expectedPresentationTime, - vsyncData.deadlineTimestamp)); + mVsyncTracer = (mVsyncTracer + 1) % 2; + mPendingEvents.push_back(makeVSync(mVSyncState->displayId, wakeupTime, ++mVSyncState->count, + vsyncTime, readyTime)); mCondition.notify_all(); } @@ -407,7 +408,7 @@ void EventThread::onHotplugReceived(PhysicalDisplayId displayId, bool connected) mCondition.notify_all(); } -void EventThread::onModeChanged(DisplayModePtr mode) { +void EventThread::onModeChanged(const scheduler::FrameRateMode& mode) { std::lock_guard lock(mMutex); mPendingEvents.push_back(makeModeChanged(mode)); @@ -442,21 +443,13 @@ void EventThread::threadMain(std::unique_lock& lock) { event = mPendingEvents.front(); mPendingEvents.pop_front(); - switch (event->header.type) { - case DisplayEventReceiver::DISPLAY_EVENT_HOTPLUG: - if (event->hotplug.connected && !mVSyncState) { - mVSyncState.emplace(event->header.displayId); - } else if (!event->hotplug.connected && mVSyncState && - mVSyncState->displayId == event->header.displayId) { - mVSyncState.reset(); - } - break; - - case DisplayEventReceiver::DISPLAY_EVENT_VSYNC: - if (mInterceptVSyncsCallback) { - mInterceptVSyncsCallback(event->header.timestamp); - } - break; + if (event->header.type == DisplayEventReceiver::DISPLAY_EVENT_HOTPLUG) { + if (event->hotplug.connected && !mVSyncState) { + mVSyncState.emplace(event->header.displayId); + } else if (!event->hotplug.connected && mVSyncState && + mVSyncState->displayId == event->header.displayId) { + mVSyncState.reset(); + } } } @@ -466,12 +459,12 @@ void EventThread::threadMain(std::unique_lock& lock) { auto it = mDisplayEventConnections.begin(); while (it != mDisplayEventConnections.end()) { if (const auto connection = it->promote()) { - vsyncRequested |= connection->vsyncRequest != VSyncRequest::None; - if (event && shouldConsumeEvent(*event, connection)) { consumers.push_back(connection); } + vsyncRequested |= connection->vsyncRequest != VSyncRequest::None; + ++it; } else { it = mDisplayEventConnections.erase(it); @@ -483,25 +476,24 @@ void EventThread::threadMain(std::unique_lock& lock) { consumers.clear(); } - State nextState; if (mVSyncState && vsyncRequested) { - nextState = mVSyncState->synthetic ? State::SyntheticVSync : State::VSync; + mState = mVSyncState->synthetic ? State::SyntheticVSync : State::VSync; } else { ALOGW_IF(!mVSyncState, "Ignoring VSYNC request while display is disconnected"); - nextState = State::Idle; + mState = State::Idle; } - if (mState != nextState) { - if (mState == State::VSync) { - mVSyncSource->setVSyncEnabled(false); - } else if (nextState == State::VSync) { - mVSyncSource->setVSyncEnabled(true); - } - - mState = nextState; + if (mState == State::VSync) { + const auto scheduleResult = + mVsyncRegistration.schedule({.workDuration = mWorkDuration.get().count(), + .readyDuration = mReadyDuration.count(), + .earliestVsync = mLastVsyncCallbackTime.ns()}); + LOG_ALWAYS_FATAL_IF(!scheduleResult, "Error scheduling callback"); + } else { + mVsyncRegistration.cancel(); } - if (event) { + if (!mPendingEvents.empty()) { continue; } @@ -516,15 +508,6 @@ void EventThread::threadMain(std::unique_lock& lock) { if (mCondition.wait_for(lock, timeout) == std::cv_status::timeout) { if (mState == State::VSync) { ALOGW("Faking VSYNC due to driver stall for thread %s", mThreadName); - std::string debugInfo = "VsyncSource debug info:\n"; - mVSyncSource->dump(debugInfo); - // Log the debug info line-by-line to avoid logcat overflow - auto pos = debugInfo.find('\n'); - while (pos != std::string::npos) { - ALOGW("%s", debugInfo.substr(0, pos).c_str()); - debugInfo = debugInfo.substr(pos + 1); - pos = debugInfo.find('\n'); - } } LOG_FATAL_IF(!mVSyncState); @@ -537,11 +520,20 @@ void EventThread::threadMain(std::unique_lock& lock) { } } } + // cancel any pending vsync event before exiting + mVsyncRegistration.cancel(); } bool EventThread::shouldConsumeEvent(const DisplayEventReceiver::Event& event, const sp& connection) const { - const auto throttleVsync = [&] { + const auto throttleVsync = [&]() REQUIRES(mMutex) { + const auto& vsyncData = event.vsync.vsyncData; + if (connection->frameRate.isValid()) { + return !mVsyncSchedule->getTracker() + .isVSyncInPhase(vsyncData.preferredExpectedPresentationTime(), + connection->frameRate); + } + return mThrottleVsyncCallback && mThrottleVsyncCallback(event.vsync.vsyncData.preferredExpectedPresentationTime(), connection->mOwnerUid); @@ -553,7 +545,7 @@ bool EventThread::shouldConsumeEvent(const DisplayEventReceiver::Event& event, case DisplayEventReceiver::DISPLAY_EVENT_MODE_CHANGE: { return connection->mEventRegistration.test( - ISurfaceComposer::EventRegistration::modeChanged); + gui::ISurfaceComposer::EventRegistration::modeChanged); } case DisplayEventReceiver::DISPLAY_EVENT_VSYNC: @@ -586,7 +578,7 @@ bool EventThread::shouldConsumeEvent(const DisplayEventReceiver::Event& event, [[fallthrough]]; case DisplayEventReceiver::DISPLAY_EVENT_FRAME_RATE_OVERRIDE_FLUSH: return connection->mEventRegistration.test( - ISurfaceComposer::EventRegistration::frameRateOverride); + gui::ISurfaceComposer::EventRegistration::frameRateOverride); default: return false; @@ -606,25 +598,57 @@ void EventThread::generateFrameTimeline(VsyncEventData& outVsyncEventData, nsecs nsecs_t timestamp, nsecs_t preferredExpectedPresentationTime, nsecs_t preferredDeadlineTimestamp) const { + uint32_t currentIndex = 0; // Add 1 to ensure the preferredFrameTimelineIndex entry (when multiplier == 0) is included. - for (int64_t multiplier = -VsyncEventData::kFrameTimelinesLength + 1, currentIndex = 0; - currentIndex < VsyncEventData::kFrameTimelinesLength; multiplier++) { + for (int64_t multiplier = -VsyncEventData::kFrameTimelinesCapacity + 1; + currentIndex < VsyncEventData::kFrameTimelinesCapacity; multiplier++) { nsecs_t deadlineTimestamp = preferredDeadlineTimestamp + multiplier * frameInterval; - // Valid possible frame timelines must have future values. - if (deadlineTimestamp > timestamp) { - if (multiplier == 0) { - outVsyncEventData.preferredFrameTimelineIndex = currentIndex; + // Valid possible frame timelines must have future values, so find a later frame timeline. + if (deadlineTimestamp <= timestamp) { + continue; + } + + nsecs_t expectedPresentationTime = + preferredExpectedPresentationTime + multiplier * frameInterval; + if (expectedPresentationTime >= preferredExpectedPresentationTime + + scheduler::VsyncConfig::kEarlyLatchMaxThreshold.count()) { + if (currentIndex == 0) { + ALOGW("%s: Expected present time is too far in the future but no timelines are " + "valid. preferred EPT=%" PRId64 ", Calculated EPT=%" PRId64 + ", multiplier=%" PRId64 ", frameInterval=%" PRId64 ", threshold=%" PRId64, + __func__, preferredExpectedPresentationTime, expectedPresentationTime, + multiplier, frameInterval, + static_cast( + scheduler::VsyncConfig::kEarlyLatchMaxThreshold.count())); } - nsecs_t expectedPresentationTime = - preferredExpectedPresentationTime + multiplier * frameInterval; - outVsyncEventData.frameTimelines[currentIndex] = - {.vsyncId = - generateToken(timestamp, deadlineTimestamp, expectedPresentationTime), - .deadlineTimestamp = deadlineTimestamp, - .expectedPresentationTime = expectedPresentationTime}; - currentIndex++; + break; + } + + if (multiplier == 0) { + outVsyncEventData.preferredFrameTimelineIndex = currentIndex; } + + outVsyncEventData.frameTimelines[currentIndex] = + {.vsyncId = generateToken(timestamp, deadlineTimestamp, expectedPresentationTime), + .deadlineTimestamp = deadlineTimestamp, + .expectedPresentationTime = expectedPresentationTime}; + currentIndex++; + } + + if (currentIndex == 0) { + ALOGW("%s: No timelines are valid. preferred EPT=%" PRId64 ", frameInterval=%" PRId64 + ", threshold=%" PRId64, + __func__, preferredExpectedPresentationTime, frameInterval, + static_cast(scheduler::VsyncConfig::kEarlyLatchMaxThreshold.count())); + outVsyncEventData.frameTimelines[currentIndex] = + {.vsyncId = generateToken(timestamp, preferredDeadlineTimestamp, + preferredExpectedPresentationTime), + .deadlineTimestamp = preferredDeadlineTimestamp, + .expectedPresentationTime = preferredExpectedPresentationTime}; + currentIndex++; } + + outVsyncEventData.frameTimelinesLength = currentIndex; } void EventThread::dispatchEvent(const DisplayEventReceiver::Event& event, @@ -667,6 +691,12 @@ void EventThread::dump(std::string& result) const { StringAppendF(&result, "none\n"); } + const auto relativeLastCallTime = + ticks(mLastVsyncCallbackTime - TimePoint::now()); + StringAppendF(&result, "mWorkDuration=%.2f mReadyDuration=%.2f last vsync time ", + mWorkDuration.get().count() / 1e6f, mReadyDuration.count() / 1e6f); + StringAppendF(&result, "%.2fms relative to now\n", relativeLastCallTime); + StringAppendF(&result, " pending events (count=%zu):\n", mPendingEvents.size()); for (const auto& event : mPendingEvents) { StringAppendF(&result, " %s\n", toString(event).c_str()); @@ -678,6 +708,7 @@ void EventThread::dump(std::string& result) const { StringAppendF(&result, " %s\n", toString(*connection).c_str()); } } + result += '\n'; } const char* EventThread::toCString(State state) { @@ -693,6 +724,36 @@ const char* EventThread::toCString(State state) { } } +void EventThread::onNewVsyncSchedule(std::shared_ptr schedule) { + // Hold onto the old registration until after releasing the mutex to avoid deadlock. + scheduler::VSyncCallbackRegistration oldRegistration = + onNewVsyncScheduleInternal(std::move(schedule)); +} + +scheduler::VSyncCallbackRegistration EventThread::onNewVsyncScheduleInternal( + std::shared_ptr schedule) { + std::lock_guard lock(mMutex); + const bool reschedule = mVsyncRegistration.cancel() == scheduler::CancelResult::Cancelled; + mVsyncSchedule = std::move(schedule); + auto oldRegistration = + std::exchange(mVsyncRegistration, + scheduler::VSyncCallbackRegistration(mVsyncSchedule->getDispatch(), + createDispatchCallback(), + mThreadName)); + if (reschedule) { + mVsyncRegistration.schedule({.workDuration = mWorkDuration.get().count(), + .readyDuration = mReadyDuration.count(), + .earliestVsync = mLastVsyncCallbackTime.ns()}); + } + return oldRegistration; +} + +scheduler::VSyncDispatch::Callback EventThread::createDispatchCallback() { + return [this](nsecs_t vsyncTime, nsecs_t wakeupTime, nsecs_t readyTime) { + onVsync(vsyncTime, wakeupTime, readyTime); + }; +} + } // namespace impl } // namespace android diff --git a/services/surfaceflinger/Scheduler/EventThread.h b/services/surfaceflinger/Scheduler/EventThread.h index adb96fd46293d3b823c042f59cc902a89f648238..684745b71ba6dd868efee0472d172394e2cca2cf 100644 --- a/services/surfaceflinger/Scheduler/EventThread.h +++ b/services/surfaceflinger/Scheduler/EventThread.h @@ -23,6 +23,7 @@ #include #include +#include #include #include #include @@ -32,6 +33,9 @@ #include #include "DisplayHardware/DisplayMode.h" +#include "TracedOrdinal.h" +#include "VSyncDispatch.h" +#include "VsyncSchedule.h" // --------------------------------------------------------------------------- namespace android { @@ -63,36 +67,10 @@ enum class VSyncRequest { // Subsequent values are periods. }; -class VSyncSource { -public: - class VSyncData { - public: - nsecs_t expectedPresentationTime; - nsecs_t deadlineTimestamp; - }; - - class Callback { - public: - virtual ~Callback() {} - virtual void onVSyncEvent(nsecs_t when, VSyncData vsyncData) = 0; - }; - - virtual ~VSyncSource() {} - - virtual const char* getName() const = 0; - virtual void setVSyncEnabled(bool enable) = 0; - virtual void setCallback(Callback* callback) = 0; - virtual void setDuration(std::chrono::nanoseconds workDuration, - std::chrono::nanoseconds readyDuration) = 0; - virtual VSyncData getLatestVSyncData() const = 0; - - virtual void dump(std::string& result) const = 0; -}; - class EventThreadConnection : public gui::BnDisplayEventConnection { public: EventThreadConnection(EventThread*, uid_t callingUid, ResyncCallback, - ISurfaceComposer::EventRegistrationFlags eventRegistration = {}); + EventRegistrationFlags eventRegistration = {}); virtual ~EventThreadConnection(); virtual status_t postEvent(const DisplayEventReceiver::Event& event); @@ -107,7 +85,10 @@ public: VSyncRequest vsyncRequest = VSyncRequest::None; const uid_t mOwnerUid; - const ISurfaceComposer::EventRegistrationFlags mEventRegistration; + const EventRegistrationFlags mEventRegistration; + + /** The frame rate set to the attached choreographer. */ + Fps frameRate; private: virtual void onFirstRef(); @@ -123,19 +104,15 @@ public: virtual ~EventThread(); virtual sp createEventConnection( - ResyncCallback, - ISurfaceComposer::EventRegistrationFlags eventRegistration = {}) const = 0; - - // called before the screen is turned off from main thread - virtual void onScreenReleased() = 0; + ResyncCallback, EventRegistrationFlags eventRegistration = {}) const = 0; - // called after the screen is turned on from main thread - virtual void onScreenAcquired() = 0; + // Feed clients with fake VSYNC, e.g. while the display is off. + virtual void enableSyntheticVsync(bool) = 0; virtual void onHotplugReceived(PhysicalDisplayId displayId, bool connected) = 0; // called when SF changes the active mode and apps needs to be notified about the change - virtual void onModeChanged(DisplayModePtr) = 0; + virtual void onModeChanged(const scheduler::FrameRateMode&) = 0; // called when SF updates the Frame Rate Override list virtual void onFrameRateOverridesChanged(PhysicalDisplayId displayId, @@ -156,23 +133,24 @@ public: // Retrieves the number of event connections tracked by this EventThread. virtual size_t getEventThreadConnectionCount() = 0; + + virtual void onNewVsyncSchedule(std::shared_ptr) = 0; }; namespace impl { -class EventThread : public android::EventThread, private VSyncSource::Callback { +class EventThread : public android::EventThread { public: - using InterceptVSyncsCallback = std::function; using ThrottleVsyncCallback = std::function; using GetVsyncPeriodFunction = std::function; - EventThread(std::unique_ptr, frametimeline::TokenManager*, InterceptVSyncsCallback, - ThrottleVsyncCallback, GetVsyncPeriodFunction); + EventThread(const char* name, std::shared_ptr, + frametimeline::TokenManager*, ThrottleVsyncCallback, GetVsyncPeriodFunction, + std::chrono::nanoseconds workDuration, std::chrono::nanoseconds readyDuration); ~EventThread(); sp createEventConnection( - ResyncCallback, - ISurfaceComposer::EventRegistrationFlags eventRegistration = {}) const override; + ResyncCallback, EventRegistrationFlags eventRegistration = {}) const override; status_t registerDisplayEventConnection(const sp& connection) override; void setVsyncRate(uint32_t rate, const sp& connection) override; @@ -180,15 +158,11 @@ public: VsyncEventData getLatestVsyncEventData( const sp& connection) const override; - // called before the screen is turned off from main thread - void onScreenReleased() override; - - // called after the screen is turned on from main thread - void onScreenAcquired() override; + void enableSyntheticVsync(bool) override; void onHotplugReceived(PhysicalDisplayId displayId, bool connected) override; - void onModeChanged(DisplayModePtr) override; + void onModeChanged(const scheduler::FrameRateMode&) override; void onFrameRateOverridesChanged(PhysicalDisplayId displayId, std::vector overrides) override; @@ -200,6 +174,8 @@ public: size_t getEventThreadConnectionCount() override; + void onNewVsyncSchedule(std::shared_ptr) override EXCLUDES(mMutex); + private: friend EventThreadTest; @@ -215,8 +191,7 @@ private: void removeDisplayEventConnectionLocked(const wp& connection) REQUIRES(mMutex); - // Implements VSyncSource::Callback - void onVSyncEvent(nsecs_t timestamp, VSyncSource::VSyncData vsyncData) override; + void onVsync(nsecs_t vsyncTime, nsecs_t wakeupTime, nsecs_t readyTime); int64_t generateToken(nsecs_t timestamp, nsecs_t deadlineTimestamp, nsecs_t expectedPresentationTime) const; @@ -224,13 +199,24 @@ private: nsecs_t timestamp, nsecs_t preferredExpectedPresentationTime, nsecs_t preferredDeadlineTimestamp) const; - const std::unique_ptr mVSyncSource GUARDED_BY(mMutex); + scheduler::VSyncDispatch::Callback createDispatchCallback(); + + // Returns the old registration so it can be destructed outside the lock to + // avoid deadlock. + scheduler::VSyncCallbackRegistration onNewVsyncScheduleInternal( + std::shared_ptr) EXCLUDES(mMutex); + + const char* const mThreadName; + TracedOrdinal mVsyncTracer; + TracedOrdinal mWorkDuration GUARDED_BY(mMutex); + std::chrono::nanoseconds mReadyDuration GUARDED_BY(mMutex); + std::shared_ptr mVsyncSchedule GUARDED_BY(mMutex); + TimePoint mLastVsyncCallbackTime GUARDED_BY(mMutex) = TimePoint::now(); + scheduler::VSyncCallbackRegistration mVsyncRegistration GUARDED_BY(mMutex); frametimeline::TokenManager* const mTokenManager; - const InterceptVSyncsCallback mInterceptVSyncsCallback; const ThrottleVsyncCallback mThrottleVsyncCallback; const GetVsyncPeriodFunction mGetVsyncPeriodFunction; - const char* const mThreadName; std::thread mThread; mutable std::mutex mMutex; diff --git a/services/surfaceflinger/Scheduler/FrameRateOverrideMappings.cpp b/services/surfaceflinger/Scheduler/FrameRateOverrideMappings.cpp index c233455994f1465fbec4e8681508aabd294b9443..cb9bfe93db74d04593d367f9ccf29413849a0ee3 100644 --- a/services/surfaceflinger/Scheduler/FrameRateOverrideMappings.cpp +++ b/services/surfaceflinger/Scheduler/FrameRateOverrideMappings.cpp @@ -54,10 +54,9 @@ std::optional FrameRateOverrideMappings::getFrameRateOverrideForUid( std::vector FrameRateOverrideMappings::getAllFrameRateOverrides( bool supportsFrameRateOverrideByContent) { std::lock_guard lock(mFrameRateOverridesLock); + std::vector overrides; - overrides.reserve(std::max({mFrameRateOverridesFromGameManager.size(), - mFrameRateOverridesFromBackdoor.size(), - mFrameRateOverridesByContent.size()})); + overrides.reserve(maxOverridesCount()); for (const auto& [uid, frameRate] : mFrameRateOverridesFromBackdoor) { overrides.emplace_back(FrameRateOverride{uid, frameRate.getValue()}); @@ -83,28 +82,34 @@ std::vector FrameRateOverrideMappings::getAllFrameRateOverrid return overrides; } -void FrameRateOverrideMappings::dump(std::string& result) const { - using base::StringAppendF; +void FrameRateOverrideMappings::dump(utils::Dumper& dumper) const { + using namespace std::string_view_literals; std::lock_guard lock(mFrameRateOverridesLock); - StringAppendF(&result, "Frame Rate Overrides (backdoor): {"); - for (const auto& [uid, frameRate] : mFrameRateOverridesFromBackdoor) { - StringAppendF(&result, "[uid: %d frameRate: %s], ", uid, to_string(frameRate).c_str()); - } - StringAppendF(&result, "}\n"); + const bool hasOverrides = maxOverridesCount() > 0; + dumper.dump("FrameRateOverrides"sv, hasOverrides ? ""sv : "none"sv); - StringAppendF(&result, "Frame Rate Overrides (GameManager): {"); - for (const auto& [uid, frameRate] : mFrameRateOverridesFromGameManager) { - StringAppendF(&result, "[uid: %d frameRate: %s], ", uid, to_string(frameRate).c_str()); - } - StringAppendF(&result, "}\n"); + if (!hasOverrides) return; - StringAppendF(&result, "Frame Rate Overrides (setFrameRate): {"); - for (const auto& [uid, frameRate] : mFrameRateOverridesByContent) { - StringAppendF(&result, "[uid: %d frameRate: %s], ", uid, to_string(frameRate).c_str()); + dump(dumper, "setFrameRate"sv, mFrameRateOverridesByContent); + dump(dumper, "GameManager"sv, mFrameRateOverridesFromGameManager); + dump(dumper, "Backdoor"sv, mFrameRateOverridesFromBackdoor); +} + +void FrameRateOverrideMappings::dump(utils::Dumper& dumper, std::string_view name, + const UidToFrameRateOverride& overrides) const { + if (overrides.empty()) return; + + utils::Dumper::Indent indent(dumper); + dumper.dump(name); + { + utils::Dumper::Indent indent(dumper); + for (const auto& [uid, frameRate] : overrides) { + using namespace std::string_view_literals; + dumper.dump("(uid, frameRate)"sv, uid, frameRate); + } } - StringAppendF(&result, "}\n"); } bool FrameRateOverrideMappings::updateFrameRateOverridesByContent( diff --git a/services/surfaceflinger/Scheduler/FrameRateOverrideMappings.h b/services/surfaceflinger/Scheduler/FrameRateOverrideMappings.h index 4185a4c0ab494242d430c4dcb87554d030ba2ec8..da0f276a3b87f5e7ab11ce804238c3ebf5010344 100644 --- a/services/surfaceflinger/Scheduler/FrameRateOverrideMappings.h +++ b/services/surfaceflinger/Scheduler/FrameRateOverrideMappings.h @@ -23,7 +23,10 @@ #include #include +#include "Utils/Dumper.h" + namespace android::scheduler { + class FrameRateOverrideMappings { using FrameRateOverride = DisplayEventReceiver::Event::FrameRateOverride; using UidToFrameRateOverride = std::map; @@ -34,7 +37,6 @@ public: EXCLUDES(mFrameRateOverridesLock); std::vector getAllFrameRateOverrides(bool supportsFrameRateOverrideByContent) EXCLUDES(mFrameRateOverridesLock); - void dump(std::string& result) const; bool updateFrameRateOverridesByContent(const UidToFrameRateOverride& frameRateOverrides) EXCLUDES(mFrameRateOverridesLock); void setGameModeRefreshRateForUid(FrameRateOverride frameRateOverride) @@ -42,7 +44,17 @@ public: void setPreferredRefreshRateForUid(FrameRateOverride frameRateOverride) EXCLUDES(mFrameRateOverridesLock); + void dump(utils::Dumper&) const; + private: + size_t maxOverridesCount() const REQUIRES(mFrameRateOverridesLock) { + return std::max({mFrameRateOverridesByContent.size(), + mFrameRateOverridesFromGameManager.size(), + mFrameRateOverridesFromBackdoor.size()}); + } + + void dump(utils::Dumper&, std::string_view name, const UidToFrameRateOverride&) const; + // The frame rate override lists need their own mutex as they are being read // by SurfaceFlinger, Scheduler and EventThread (as a callback) to prevent deadlocks mutable std::mutex mFrameRateOverridesLock; @@ -53,4 +65,5 @@ private: UidToFrameRateOverride mFrameRateOverridesFromBackdoor GUARDED_BY(mFrameRateOverridesLock); UidToFrameRateOverride mFrameRateOverridesFromGameManager GUARDED_BY(mFrameRateOverridesLock); }; + } // namespace android::scheduler diff --git a/services/surfaceflinger/Scheduler/ISchedulerCallback.h b/services/surfaceflinger/Scheduler/ISchedulerCallback.h new file mode 100644 index 0000000000000000000000000000000000000000..92c21892444ada990772c4c18e8fd209a3525c6a --- /dev/null +++ b/services/surfaceflinger/Scheduler/ISchedulerCallback.h @@ -0,0 +1,37 @@ +/* + * 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. + */ + +#pragma once + +#include + +#include + +#include "Display/DisplayModeRequest.h" + +namespace android::scheduler { + +struct ISchedulerCallback { + virtual void setVsyncEnabled(PhysicalDisplayId, bool) = 0; + virtual void requestDisplayModes(std::vector) = 0; + virtual void kernelTimerChanged(bool expired) = 0; + virtual void triggerOnFrameRateOverridesChanged() = 0; + +protected: + ~ISchedulerCallback() = default; +}; + +} // namespace android::scheduler diff --git a/services/surfaceflinger/Scheduler/InjectVSyncSource.h b/services/surfaceflinger/Scheduler/InjectVSyncSource.h deleted file mode 100644 index 760a4ee886c8a34708c87b0c29558ba859276408..0000000000000000000000000000000000000000 --- a/services/surfaceflinger/Scheduler/InjectVSyncSource.h +++ /dev/null @@ -1,57 +0,0 @@ -/* - * 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. - */ - -#pragma once - -#include - -#include "EventThread.h" - -namespace android { - -/** - * VSync signals used during SurfaceFlinger trace playback (traces we captured - * with SurfaceInterceptor). - */ -class InjectVSyncSource final : public VSyncSource { -public: - ~InjectVSyncSource() override = default; - - void setCallback(VSyncSource::Callback* callback) override { - std::lock_guard lock(mCallbackMutex); - mCallback = callback; - } - - void onInjectSyncEvent(nsecs_t when, nsecs_t expectedVSyncTimestamp, - nsecs_t deadlineTimestamp) { - std::lock_guard lock(mCallbackMutex); - if (mCallback) { - mCallback->onVSyncEvent(when, {expectedVSyncTimestamp, deadlineTimestamp}); - } - } - - const char* getName() const override { return "inject"; } - void setVSyncEnabled(bool) override {} - void setDuration(std::chrono::nanoseconds, std::chrono::nanoseconds) override {} - VSyncData getLatestVSyncData() const override { return {}; } - void dump(std::string&) const override {} - -private: - std::mutex mCallbackMutex; - VSyncSource::Callback* mCallback GUARDED_BY(mCallbackMutex) = nullptr; -}; - -} // namespace android diff --git a/services/surfaceflinger/Scheduler/LayerHistory.cpp b/services/surfaceflinger/Scheduler/LayerHistory.cpp index 5f64efa3d13bbbba00c7f1fb4fcfa21a4da178f4..beaf9724a3c2ffb0b773f0c77b3bcfe568194b88 100644 --- a/services/surfaceflinger/Scheduler/LayerHistory.cpp +++ b/services/surfaceflinger/Scheduler/LayerHistory.cpp @@ -22,9 +22,9 @@ #include #include +#include #include #include -#include #include #include @@ -32,6 +32,7 @@ #include #include "../Layer.h" +#include "EventThread.h" #include "LayerInfo.h" namespace android::scheduler { @@ -72,6 +73,20 @@ void trace(const LayerInfo& info, LayerHistory::LayerVoteType type, int fps) { ALOGD("%s: %s @ %d Hz", __FUNCTION__, info.getName().c_str(), fps); } + +LayerHistory::LayerVoteType getVoteType(LayerInfo::FrameRateCompatibility compatibility, + bool contentDetectionEnabled) { + LayerHistory::LayerVoteType voteType; + if (!contentDetectionEnabled || compatibility == LayerInfo::FrameRateCompatibility::NoVote) { + voteType = LayerHistory::LayerVoteType::NoVote; + } else if (compatibility == LayerInfo::FrameRateCompatibility::Min) { + voteType = LayerHistory::LayerVoteType::Min; + } else { + voteType = LayerHistory::LayerVoteType::Heuristic; + } + return voteType; +} + } // namespace LayerHistory::LayerHistory() @@ -81,10 +96,12 @@ LayerHistory::LayerHistory() LayerHistory::~LayerHistory() = default; -void LayerHistory::registerLayer(Layer* layer, LayerVoteType type) { +void LayerHistory::registerLayer(Layer* layer, bool contentDetectionEnabled) { std::lock_guard lock(mLock); LOG_ALWAYS_FATAL_IF(findLayer(layer->getSequence()).first != LayerStatus::NotFound, "%s already registered", layer->getName().c_str()); + LayerVoteType type = + getVoteType(layer->getDefaultFrameRateCompatibility(), contentDetectionEnabled); auto info = std::make_unique(layer->getName(), layer->getOwnerUid(), type); // The layer can be placed on either map, it is assumed that partitionLayers() will be called @@ -101,29 +118,35 @@ void LayerHistory::deregisterLayer(Layer* layer) { } } -void LayerHistory::record(Layer* layer, nsecs_t presentTime, nsecs_t now, - LayerUpdateType updateType) { +void LayerHistory::record(int32_t id, const LayerProps& layerProps, nsecs_t presentTime, + nsecs_t now, LayerUpdateType updateType) { std::lock_guard lock(mLock); - auto id = layer->getSequence(); - auto [found, layerPair] = findLayer(id); if (found == LayerStatus::NotFound) { // Offscreen layer - ALOGV("%s: %s not registered", __func__, layer->getName().c_str()); + ALOGV("%s: %d not registered", __func__, id); return; } const auto& info = layerPair->second; - const auto layerProps = LayerInfo::LayerProps{ - .visible = layer->isVisible(), - .bounds = layer->getBounds(), - .transform = layer->getTransform(), - .setFrameRateVote = layer->getFrameRateForLayerTree(), - .frameRateSelectionPriority = layer->getFrameRateSelectionPriority(), - }; - info->setLastPresentTime(presentTime, now, updateType, mModeChangePending, layerProps); + // Set frame rate to attached choreographer. + // TODO(b/260898223): Change to use layer hierarchy and handle frame rate vote. + if (updateType == LayerUpdateType::SetFrameRate) { + auto range = mAttachedChoreographers.equal_range(id); + auto it = range.first; + while (it != range.second) { + sp choreographerConnection = it->second.promote(); + if (choreographerConnection) { + choreographerConnection->frameRate = layerProps.setFrameRateVote.rate; + it++; + } else { + it = mAttachedChoreographers.erase(it); + } + } + } + // Activate layer if inactive. if (found == LayerStatus::LayerInInactiveMap) { mActiveLayerInfos.insert( @@ -132,7 +155,24 @@ void LayerHistory::record(Layer* layer, nsecs_t presentTime, nsecs_t now, } } -auto LayerHistory::summarize(const RefreshRateConfigs& configs, nsecs_t now) -> Summary { +void LayerHistory::setDefaultFrameRateCompatibility(Layer* layer, bool contentDetectionEnabled) { + std::lock_guard lock(mLock); + auto id = layer->getSequence(); + + auto [found, layerPair] = findLayer(id); + if (found == LayerStatus::NotFound) { + // Offscreen layer + ALOGV("%s: %s not registered", __func__, layer->getName().c_str()); + return; + } + + const auto& info = layerPair->second; + info->setDefaultLayerVote( + getVoteType(layer->getDefaultFrameRateCompatibility(), contentDetectionEnabled)); +} + +auto LayerHistory::summarize(const RefreshRateSelector& selector, nsecs_t now) -> Summary { + ATRACE_CALL(); Summary summary; std::lock_guard lock(mLock); @@ -146,7 +186,8 @@ auto LayerHistory::summarize(const RefreshRateConfigs& configs, nsecs_t now) -> ALOGV("%s has priority: %d %s focused", info->getName().c_str(), frameRateSelectionPriority, layerFocused ? "" : "not"); - const auto vote = info->getRefreshRateVote(configs, now); + ATRACE_FORMAT("%s", info->getName().c_str()); + const auto vote = info->getRefreshRateVote(selector, now); // Skip NoVote layer as those don't have any requirements if (vote.type == LayerVoteType::NoVote) { continue; @@ -160,6 +201,8 @@ auto LayerHistory::summarize(const RefreshRateConfigs& configs, nsecs_t now) -> const float layerArea = transformed.getWidth() * transformed.getHeight(); float weight = mDisplayArea ? layerArea / mDisplayArea : 0.0f; + ATRACE_FORMAT_INSTANT("%s %s (%d%)", ftl::enum_string(vote.type).c_str(), + to_string(vote.fps).c_str(), weight * 100); summary.push_back({info->getName(), info->getOwnerUid(), vote.type, vote.fps, vote.seamlessness, weight, layerFocused}); @@ -172,6 +215,7 @@ auto LayerHistory::summarize(const RefreshRateConfigs& configs, nsecs_t now) -> } void LayerHistory::partitionLayers(nsecs_t now) { + ATRACE_CALL(); const nsecs_t threshold = getActiveLayerThreshold(now); // iterate over inactive map @@ -203,6 +247,8 @@ void LayerHistory::partitionLayers(nsecs_t now) { switch (frameRate.type) { case Layer::FrameRateCompatibility::Default: return LayerVoteType::ExplicitDefault; + case Layer::FrameRateCompatibility::Min: + return LayerVoteType::Min; case Layer::FrameRateCompatibility::ExactOrMultiple: return LayerVoteType::ExplicitExactOrMultiple; case Layer::FrameRateCompatibility::NoVote: @@ -241,7 +287,7 @@ void LayerHistory::clear() { std::string LayerHistory::dump() const { std::lock_guard lock(mLock); - return base::StringPrintf("LayerHistory{size=%zu, active=%zu}", + return base::StringPrintf("{size=%zu, active=%zu}", mActiveLayerInfos.size() + mInactiveLayerInfos.size(), mActiveLayerInfos.size()); } @@ -255,6 +301,12 @@ float LayerHistory::getLayerFramerate(nsecs_t now, int32_t id) const { return 0.f; } +void LayerHistory::attachChoreographer(int32_t layerId, + const sp& choreographerConnection) { + std::lock_guard lock(mLock); + mAttachedChoreographers.insert({layerId, wp(choreographerConnection)}); +} + auto LayerHistory::findLayer(int32_t id) -> std::pair { // the layer could be in either the active or inactive map, try both auto it = mActiveLayerInfos.find(id); diff --git a/services/surfaceflinger/Scheduler/LayerHistory.h b/services/surfaceflinger/Scheduler/LayerHistory.h index 7b6096f7f39c7f51a4cf8713d106d292d13a24f7..69caf9ffd2b0c072778023c5b935c7d684339439 100644 --- a/services/surfaceflinger/Scheduler/LayerHistory.h +++ b/services/surfaceflinger/Scheduler/LayerHistory.h @@ -27,7 +27,9 @@ #include #include -#include "RefreshRateConfigs.h" +#include "EventThread.h" + +#include "RefreshRateSelector.h" namespace android { @@ -36,16 +38,17 @@ class Layer; namespace scheduler { class LayerInfo; +struct LayerProps; class LayerHistory { public: - using LayerVoteType = RefreshRateConfigs::LayerVoteType; + using LayerVoteType = RefreshRateSelector::LayerVoteType; LayerHistory(); ~LayerHistory(); // Layers are unregistered when the weak reference expires. - void registerLayer(Layer*, LayerVoteType type); + void registerLayer(Layer*, bool contentDetectionEnabled); // Sets the display size. Client is responsible for synchronization. void setDisplayArea(uint32_t displayArea) { mDisplayArea = displayArea; } @@ -61,12 +64,17 @@ public: }; // Marks the layer as active, and records the given state to its history. - void record(Layer*, nsecs_t presentTime, nsecs_t now, LayerUpdateType updateType); + void record(int32_t id, const LayerProps& props, nsecs_t presentTime, nsecs_t now, + LayerUpdateType updateType); + + // Updates the default frame rate compatibility which takes effect when the app + // does not set a preference for refresh rate. + void setDefaultFrameRateCompatibility(Layer*, bool contentDetectionEnabled); - using Summary = std::vector; + using Summary = std::vector; // Rebuilds sets of active/inactive layers, and accumulates stats for active layers. - Summary summarize(const RefreshRateConfigs&, nsecs_t now); + Summary summarize(const RefreshRateSelector&, nsecs_t now); void clear(); @@ -76,6 +84,9 @@ public: // return the frames per second of the layer with the given sequence id. float getLayerFramerate(nsecs_t now, int32_t id) const; + void attachChoreographer(int32_t layerId, + const sp& choreographerConnection); + private: friend class LayerHistoryTest; friend class TestableScheduler; @@ -113,6 +124,10 @@ private: LayerInfos mActiveLayerInfos GUARDED_BY(mLock); LayerInfos mInactiveLayerInfos GUARDED_BY(mLock); + // Map keyed by layer ID (sequence) to choreographer connections. + std::unordered_multimap> mAttachedChoreographers + GUARDED_BY(mLock); + uint32_t mDisplayArea = 0; // Whether to emit systrace output and debug logs. diff --git a/services/surfaceflinger/Scheduler/LayerInfo.cpp b/services/surfaceflinger/Scheduler/LayerInfo.cpp index 943615c45ca5c511e6e6b914cd1ad6deb963a956..bae37395011d6c861a54e6b288605ece6b6e7067 100644 --- a/services/surfaceflinger/Scheduler/LayerInfo.cpp +++ b/services/surfaceflinger/Scheduler/LayerInfo.cpp @@ -29,6 +29,7 @@ #include #include #include +#include #undef LOG_TAG #define LOG_TAG "LayerInfo" @@ -43,14 +44,17 @@ LayerInfo::LayerInfo(const std::string& name, uid_t ownerUid, mOwnerUid(ownerUid), mDefaultVote(defaultVote), mLayerVote({defaultVote, Fps()}), - mRefreshRateHistory(name) {} + mLayerProps(std::make_unique()), + mRefreshRateHistory(name) { + ; +} void LayerInfo::setLastPresentTime(nsecs_t lastPresentTime, nsecs_t now, LayerUpdateType updateType, - bool pendingModeChange, LayerProps props) { + bool pendingModeChange, const LayerProps& props) { lastPresentTime = std::max(lastPresentTime, static_cast(0)); mLastUpdatedTime = std::max(lastPresentTime, now); - mLayerProps = props; + *mLayerProps = props; switch (updateType) { case LayerUpdateType::AnimationTX: mLastAnimationTime = std::max(lastPresentTime, now); @@ -74,14 +78,52 @@ bool LayerInfo::isFrameTimeValid(const FrameTimeData& frameTime) const { .count(); } -bool LayerInfo::isFrequent(nsecs_t now) const { - using fps_approx_ops::operator>=; - // If we know nothing about this layer we consider it as frequent as it might be the start - // of an animation. +LayerInfo::Frequent LayerInfo::isFrequent(nsecs_t now) const { + // If we know nothing about this layer (e.g. after touch event), + // we consider it as frequent as it might be the start of an animation. if (mFrameTimes.size() < kFrequentLayerWindowSize) { - return true; + return {/* isFrequent */ true, /* clearHistory */ false, /* isConclusive */ true}; + } + + // Non-active layers are also infrequent + if (mLastUpdatedTime < getActiveLayerThreshold(now)) { + return {/* isFrequent */ false, /* clearHistory */ false, /* isConclusive */ true}; } - return getFps(now) >= kMinFpsForFrequentLayer; + + // We check whether we can classify this layer as frequent or infrequent: + // - frequent: a layer posted kFrequentLayerWindowSize within + // kMaxPeriodForFrequentLayerNs of each other. + // - infrequent: a layer posted kFrequentLayerWindowSize with longer + // gaps than kFrequentLayerWindowSize. + // If we can't determine the layer classification yet, we return the last + // classification. + bool isFrequent = true; + bool isInfrequent = true; + const auto n = mFrameTimes.size() - 1; + for (size_t i = 0; i < kFrequentLayerWindowSize - 1; i++) { + if (mFrameTimes[n - i].queueTime - mFrameTimes[n - i - 1].queueTime < + kMaxPeriodForFrequentLayerNs.count()) { + isInfrequent = false; + } else { + isFrequent = false; + } + } + + if (isFrequent || isInfrequent) { + // If the layer was previously inconclusive, we clear + // the history as indeterminate layers changed to frequent, + // and we should not look at the stale data. + return {isFrequent, isFrequent && !mIsFrequencyConclusive, /* isConclusive */ true}; + } + + // If we can't determine whether the layer is frequent or not, we return + // the last known classification and mark the layer frequency as inconclusive. + isFrequent = !mLastRefreshRate.infrequent; + + // If the layer was previously tagged as animating, we clear + // the history as it is likely the layer just changed its behavior, + // and we should not look at stale data. + return {isFrequent, isFrequent && mLastRefreshRate.animating, /* isConclusive */ false}; } Fps LayerInfo::getFps(nsecs_t now) const { @@ -187,8 +229,9 @@ std::optional LayerInfo::calculateAverageFrameTime() const { return static_cast(averageFrameTime); } -std::optional LayerInfo::calculateRefreshRateIfPossible( - const RefreshRateConfigs& refreshRateConfigs, nsecs_t now) { +std::optional LayerInfo::calculateRefreshRateIfPossible(const RefreshRateSelector& selector, + nsecs_t now) { + ATRACE_CALL(); static constexpr float MARGIN = 1.0f; // 1Hz if (!hasEnoughDataForHeuristic()) { ALOGV("Not enough data"); @@ -199,7 +242,7 @@ std::optional LayerInfo::calculateRefreshRateIfPossible( const auto refreshRate = Fps::fromPeriodNsecs(*averageFrameTime); const bool refreshRateConsistent = mRefreshRateHistory.add(refreshRate, now); if (refreshRateConsistent) { - const auto knownRefreshRate = refreshRateConfigs.findClosestKnownFrameRate(refreshRate); + const auto knownRefreshRate = selector.findClosestKnownFrameRate(refreshRate); using fps_approx_ops::operator!=; // To avoid oscillation, use the last calculated refresh rate if it is close enough. @@ -222,35 +265,37 @@ std::optional LayerInfo::calculateRefreshRateIfPossible( : std::nullopt; } -LayerInfo::LayerVote LayerInfo::getRefreshRateVote(const RefreshRateConfigs& refreshRateConfigs, +LayerInfo::LayerVote LayerInfo::getRefreshRateVote(const RefreshRateSelector& selector, nsecs_t now) { + ATRACE_CALL(); if (mLayerVote.type != LayerHistory::LayerVoteType::Heuristic) { ALOGV("%s voted %d ", mName.c_str(), static_cast(mLayerVote.type)); return mLayerVote; } if (isAnimating(now)) { + ATRACE_FORMAT_INSTANT("animating"); ALOGV("%s is animating", mName.c_str()); - mLastRefreshRate.animatingOrInfrequent = true; + mLastRefreshRate.animating = true; return {LayerHistory::LayerVoteType::Max, Fps()}; } - if (!isFrequent(now)) { + const LayerInfo::Frequent frequent = isFrequent(now); + mIsFrequencyConclusive = frequent.isConclusive; + if (!frequent.isFrequent) { + ATRACE_FORMAT_INSTANT("infrequent"); ALOGV("%s is infrequent", mName.c_str()); - mLastRefreshRate.animatingOrInfrequent = true; - // Infrequent layers vote for mininal refresh rate for + mLastRefreshRate.infrequent = true; + // Infrequent layers vote for minimal refresh rate for // battery saving purposes and also to prevent b/135718869. return {LayerHistory::LayerVoteType::Min, Fps()}; } - // If the layer was previously tagged as animating or infrequent, we clear - // the history as it is likely the layer just changed its behavior - // and we should not look at stale data - if (mLastRefreshRate.animatingOrInfrequent) { + if (frequent.clearHistory) { clearHistory(now); } - auto refreshRate = calculateRefreshRateIfPossible(refreshRateConfigs, now); + auto refreshRate = calculateRefreshRateIfPossible(selector, now); if (refreshRate.has_value()) { ALOGV("%s calculated refresh rate: %s", mName.c_str(), to_string(*refreshRate).c_str()); return {LayerHistory::LayerVoteType::Heuristic, refreshRate.value()}; @@ -269,6 +314,26 @@ const char* LayerInfo::getTraceTag(LayerHistory::LayerVoteType type) const { return mTraceTags.at(type).c_str(); } +LayerInfo::FrameRate LayerInfo::getSetFrameRateVote() const { + return mLayerProps->setFrameRateVote; +} + +bool LayerInfo::isVisible() const { + return mLayerProps->visible; +} + +int32_t LayerInfo::getFrameRateSelectionPriority() const { + return mLayerProps->frameRateSelectionPriority; +} + +FloatRect LayerInfo::getBounds() const { + return mLayerProps->bounds; +} + +ui::Transform LayerInfo::getTransform() const { + return mLayerProps->transform; +} + LayerInfo::RefreshRateHistory::HeuristicTraceTagData LayerInfo::RefreshRateHistory::makeHeuristicTraceTagData() const { const std::string prefix = "LFPS "; diff --git a/services/surfaceflinger/Scheduler/LayerInfo.h b/services/surfaceflinger/Scheduler/LayerInfo.h index 8a3b0b97e45705223eb8c6a2ed1fe15f40842da2..c5a60573f53e328c00e30e1709dc93dc3e495206 100644 --- a/services/surfaceflinger/Scheduler/LayerInfo.h +++ b/services/surfaceflinger/Scheduler/LayerInfo.h @@ -28,7 +28,7 @@ #include #include "LayerHistory.h" -#include "RefreshRateConfigs.h" +#include "RefreshRateSelector.h" namespace android { @@ -37,7 +37,7 @@ class Layer; namespace scheduler { using namespace std::chrono_literals; - +struct LayerProps; // Maximum period between presents for a layer to be considered active. constexpr std::chrono::nanoseconds MAX_ACTIVE_LAYER_PERIOD_NS = 1200ms; @@ -53,7 +53,7 @@ class LayerInfo { // Layer is considered frequent if the earliest value in the window of most recent present times // is within a threshold. If a layer is infrequent, its average refresh rate is disregarded in // favor of a low refresh rate. - static constexpr size_t kFrequentLayerWindowSize = 3; + static constexpr size_t kFrequentLayerWindowSize = 4; static constexpr Fps kMinFpsForFrequentLayer = 10_Hz; static constexpr auto kMaxPeriodForFrequentLayerNs = std::chrono::nanoseconds(kMinFpsForFrequentLayer.getPeriodNsecs()) + 1ms; @@ -74,6 +74,8 @@ public: enum class FrameRateCompatibility { Default, // Layer didn't specify any specific handling strategy + Min, // Layer needs the minimum frame rate. + Exact, // Layer needs the exact frame rate. ExactOrMultiple, // Layer needs the exact frame rate (or a multiple of it) to present the @@ -130,19 +132,11 @@ public: LayerInfo(const LayerInfo&) = delete; LayerInfo& operator=(const LayerInfo&) = delete; - struct LayerProps { - bool visible = false; - FloatRect bounds; - ui::Transform transform; - FrameRate setFrameRateVote; - int32_t frameRateSelectionPriority = -1; - }; - // Records the last requested present time. It also stores information about when // the layer was last updated. If the present time is farther in the future than the // updated time, the updated time is the present time. void setLastPresentTime(nsecs_t lastPresentTime, nsecs_t now, LayerUpdateType updateType, - bool pendingModeChange, LayerProps props); + bool pendingModeChange, const LayerProps& props); // Sets an explicit layer vote. This usually comes directly from the application via // ANativeWindow_setFrameRate API @@ -160,19 +154,17 @@ public: uid_t getOwnerUid() const { return mOwnerUid; } - LayerVote getRefreshRateVote(const RefreshRateConfigs&, nsecs_t now); + LayerVote getRefreshRateVote(const RefreshRateSelector&, nsecs_t now); // Return the last updated time. If the present time is farther in the future than the // updated time, the updated time is the present time. nsecs_t getLastUpdatedTime() const { return mLastUpdatedTime; } - FrameRate getSetFrameRateVote() const { return mLayerProps.setFrameRateVote; } - bool isVisible() const { return mLayerProps.visible; } - int32_t getFrameRateSelectionPriority() const { return mLayerProps.frameRateSelectionPriority; } - - FloatRect getBounds() const { return mLayerProps.bounds; } - - ui::Transform getTransform() const { return mLayerProps.transform; } + FrameRate getSetFrameRateVote() const; + bool isVisible() const; + int32_t getFrameRateSelectionPriority() const; + FloatRect getBounds() const; + ui::Transform getTransform() const; // Returns a C string for tracing a vote const char* getTraceTag(LayerHistory::LayerVoteType type) const; @@ -189,6 +181,7 @@ public: mFrameTimeValidSince = std::chrono::time_point(timePoint); mLastRefreshRate = {}; mRefreshRateHistory.clear(); + mIsFrequencyConclusive = true; } void clearHistory(nsecs_t now) { @@ -212,7 +205,10 @@ private: Fps reported; // Whether the last reported rate for LayerInfo::getRefreshRate() // was due to animation or infrequent updates - bool animatingOrInfrequent = false; + bool animating = false; + // Whether the last reported rate for LayerInfo::getRefreshRate() + // was due to infrequent updates + bool infrequent = false; }; // Class to store past calculated refresh rate and determine whether @@ -256,10 +252,18 @@ private: static constexpr float MARGIN_CONSISTENT_FPS = 1.0; }; - bool isFrequent(nsecs_t now) const; + // Represents whether we were able to determine either layer is frequent or infrequent + bool mIsFrequencyConclusive = true; + struct Frequent { + bool isFrequent; + bool clearHistory; + // Represents whether we were able to determine isFrequent conclusively + bool isConclusive; + }; + Frequent isFrequent(nsecs_t now) const; bool isAnimating(nsecs_t now) const; bool hasEnoughDataForHeuristic() const; - std::optional calculateRefreshRateIfPossible(const RefreshRateConfigs&, nsecs_t now); + std::optional calculateRefreshRateIfPossible(const RefreshRateSelector&, nsecs_t now); std::optional calculateAverageFrameTime() const; bool isFrameTimeValid(const FrameTimeData&) const; @@ -289,7 +293,7 @@ private: static constexpr size_t HISTORY_SIZE = RefreshRateHistory::HISTORY_SIZE; static constexpr std::chrono::nanoseconds HISTORY_DURATION = 1s; - LayerProps mLayerProps; + std::unique_ptr mLayerProps; RefreshRateHistory mRefreshRateHistory; @@ -299,5 +303,13 @@ private: static bool sTraceEnabled; }; +struct LayerProps { + bool visible = false; + FloatRect bounds; + ui::Transform transform; + LayerInfo::FrameRate setFrameRateVote; + int32_t frameRateSelectionPriority = -1; +}; + } // namespace scheduler } // namespace android diff --git a/services/surfaceflinger/Scheduler/MessageQueue.cpp b/services/surfaceflinger/Scheduler/MessageQueue.cpp index f2af85e94ae9a3e336e1f87068c135d2e85ecdce..18c0a696d5add9481825d8b9f8f6bf413f80dcd0 100644 --- a/services/surfaceflinger/Scheduler/MessageQueue.cpp +++ b/services/surfaceflinger/Scheduler/MessageQueue.cpp @@ -17,12 +17,12 @@ #define ATRACE_TAG ATRACE_TAG_GRAPHICS #include - +#include #include #include #include -#include +#include #include "EventThread.h" #include "FrameTimeline.h" @@ -30,11 +30,11 @@ namespace android::impl { -void MessageQueue::Handler::dispatchFrame(int64_t vsyncId, nsecs_t expectedVsyncTime) { +void MessageQueue::Handler::dispatchFrame(VsyncId vsyncId, TimePoint expectedVsyncTime) { if (!mFramePending.exchange(true)) { mVsyncId = vsyncId; mExpectedVsyncTime = expectedVsyncTime; - mQueue.mLooper->sendMessage(this, Message()); + mQueue.mLooper->sendMessage(sp::fromExisting(this), Message()); } } @@ -44,16 +44,7 @@ bool MessageQueue::Handler::isFramePending() const { void MessageQueue::Handler::handleMessage(const Message&) { mFramePending.store(false); - - const nsecs_t frameTime = systemTime(); - auto& compositor = mQueue.mCompositor; - - if (!compositor.commit(frameTime, mVsyncId, mExpectedVsyncTime)) { - return; - } - - compositor.composite(frameTime, mVsyncId); - compositor.sample(); + mQueue.onFrameSignal(mQueue.mCompositor, mVsyncId, mExpectedVsyncTime); } MessageQueue::MessageQueue(ICompositor& compositor) @@ -66,77 +57,93 @@ MessageQueue::MessageQueue(ICompositor& compositor, sp handler) mLooper(sp::make(kAllowNonCallbacks)), mHandler(std::move(handler)) {} -// TODO(b/169865816): refactor VSyncInjections to use MessageQueue directly -// and remove the EventThread from MessageQueue -void MessageQueue::setInjector(sp connection) { - auto& tube = mInjector.tube; - - if (const int fd = tube.getFd(); fd >= 0) { - mLooper->removeFd(fd); - } - - if (connection) { - // The EventThreadConnection is retained when disabling injection, so avoid subsequently - // stealing invalid FDs. Note that the stolen FDs are kept open. - if (tube.getFd() < 0) { - connection->stealReceiveChannel(&tube); - } else { - ALOGW("Recycling channel for VSYNC injection."); - } - - mLooper->addFd( - tube.getFd(), 0, Looper::EVENT_INPUT, - [](int, int, void* data) { - reinterpret_cast(data)->injectorCallback(); - return 1; // Keep registration. - }, - this); - } - - std::lock_guard lock(mInjector.mutex); - mInjector.connection = std::move(connection); -} - void MessageQueue::vsyncCallback(nsecs_t vsyncTime, nsecs_t targetWakeupTime, nsecs_t readyTime) { ATRACE_CALL(); // Trace VSYNC-sf mVsync.value = (mVsync.value + 1) % 2; + const auto expectedVsyncTime = TimePoint::fromNs(vsyncTime); { std::lock_guard lock(mVsync.mutex); - mVsync.lastCallbackTime = std::chrono::nanoseconds(vsyncTime); + mVsync.lastCallbackTime = expectedVsyncTime; mVsync.scheduledFrameTime.reset(); } - const auto vsyncId = mVsync.tokenManager->generateTokenForPredictions( - {targetWakeupTime, readyTime, vsyncTime}); + const auto vsyncId = VsyncId{mVsync.tokenManager->generateTokenForPredictions( + {targetWakeupTime, readyTime, vsyncTime})}; - mHandler->dispatchFrame(vsyncId, vsyncTime); + mHandler->dispatchFrame(vsyncId, expectedVsyncTime); } -void MessageQueue::initVsync(scheduler::VSyncDispatch& dispatch, +void MessageQueue::initVsync(std::shared_ptr dispatch, frametimeline::TokenManager& tokenManager, std::chrono::nanoseconds workDuration) { - setDuration(workDuration); - mVsync.tokenManager = &tokenManager; + std::unique_ptr oldRegistration; + { + std::lock_guard lock(mVsync.mutex); + mVsync.workDuration = workDuration; + mVsync.tokenManager = &tokenManager; + oldRegistration = onNewVsyncScheduleLocked(std::move(dispatch)); + } + + // See comments in onNewVsyncSchedule. Today, oldRegistration should be + // empty, but nothing prevents us from calling initVsync multiple times, so + // go ahead and destruct it outside the lock for safety. + oldRegistration.reset(); +} + +void MessageQueue::onNewVsyncSchedule(std::shared_ptr dispatch) { + std::unique_ptr oldRegistration; + { + std::lock_guard lock(mVsync.mutex); + oldRegistration = onNewVsyncScheduleLocked(std::move(dispatch)); + } + + // The old registration needs to be deleted after releasing mVsync.mutex to + // avoid deadlock. This is because the callback may be running on the timer + // thread. In that case, timerCallback sets + // VSyncDispatchTimerQueueEntry::mRunning to true, then attempts to lock + // mVsync.mutex. But if it's already locked, the VSyncCallbackRegistration's + // destructor has to wait until VSyncDispatchTimerQueueEntry::mRunning is + // set back to false, but it won't be until mVsync.mutex is released. + oldRegistration.reset(); +} + +std::unique_ptr MessageQueue::onNewVsyncScheduleLocked( + std::shared_ptr dispatch) { + const bool reschedule = mVsync.registration && + mVsync.registration->cancel() == scheduler::CancelResult::Cancelled; + auto oldRegistration = std::move(mVsync.registration); mVsync.registration = std::make_unique< - scheduler::VSyncCallbackRegistration>(dispatch, + scheduler::VSyncCallbackRegistration>(std::move(dispatch), std::bind(&MessageQueue::vsyncCallback, this, std::placeholders::_1, std::placeholders::_2, std::placeholders::_3), "sf"); + if (reschedule) { + mVsync.scheduledFrameTime = + mVsync.registration->schedule({.workDuration = mVsync.workDuration.get().count(), + .readyDuration = 0, + .earliestVsync = mVsync.lastCallbackTime.ns()}); + } + return oldRegistration; +} + +void MessageQueue::destroyVsync() { + std::lock_guard lock(mVsync.mutex); + mVsync.tokenManager = nullptr; + mVsync.registration.reset(); } void MessageQueue::setDuration(std::chrono::nanoseconds workDuration) { ATRACE_CALL(); std::lock_guard lock(mVsync.mutex); mVsync.workDuration = workDuration; - if (mVsync.scheduledFrameTime) { - mVsync.scheduledFrameTime = mVsync.registration->schedule( - {mVsync.workDuration.get().count(), - /*readyDuration=*/0, mVsync.lastCallbackTime.count()}); - } + mVsync.scheduledFrameTime = + mVsync.registration->update({.workDuration = mVsync.workDuration.get().count(), + .readyDuration = 0, + .earliestVsync = mVsync.lastCallbackTime.ns()}); } void MessageQueue::waitMessage() { @@ -165,38 +172,31 @@ void MessageQueue::postMessage(sp&& handler) { mLooper->sendMessage(handler, Message()); } +void MessageQueue::postMessageDelayed(sp&& handler, nsecs_t uptimeDelay) { + mLooper->sendMessageDelayed(uptimeDelay, handler, Message()); +} + +void MessageQueue::scheduleConfigure() { + struct ConfigureHandler : MessageHandler { + explicit ConfigureHandler(ICompositor& compositor) : compositor(compositor) {} + + void handleMessage(const Message&) override { compositor.configure(); } + + ICompositor& compositor; + }; + + // TODO(b/241285876): Batch configure tasks that happen within some duration. + postMessage(sp::make(mCompositor)); +} + void MessageQueue::scheduleFrame() { ATRACE_CALL(); - { - std::lock_guard lock(mInjector.mutex); - if (CC_UNLIKELY(mInjector.connection)) { - ALOGD("%s while injecting VSYNC", __FUNCTION__); - mInjector.connection->requestNextVsync(); - return; - } - } - std::lock_guard lock(mVsync.mutex); mVsync.scheduledFrameTime = mVsync.registration->schedule({.workDuration = mVsync.workDuration.get().count(), .readyDuration = 0, - .earliestVsync = mVsync.lastCallbackTime.count()}); -} - -void MessageQueue::injectorCallback() { - ssize_t n; - DisplayEventReceiver::Event buffer[8]; - while ((n = DisplayEventReceiver::getEvents(&mInjector.tube, buffer, 8)) > 0) { - for (int i = 0; i < n; i++) { - if (buffer[i].header.type == DisplayEventReceiver::DISPLAY_EVENT_VSYNC) { - auto& vsync = buffer[i].vsync; - mHandler->dispatchFrame(vsync.vsyncData.preferredVsyncId(), - vsync.vsyncData.preferredExpectedPresentationTime()); - break; - } - } - } + .earliestVsync = mVsync.lastCallbackTime.ns()}); } auto MessageQueue::getScheduledFrameTime() const -> std::optional { diff --git a/services/surfaceflinger/Scheduler/MessageQueue.h b/services/surfaceflinger/Scheduler/MessageQueue.h index 4082e2687418f90a111863b7daf8f2465b615d68..a523147733439c24368b02b05f2aa642b890ade5 100644 --- a/services/surfaceflinger/Scheduler/MessageQueue.h +++ b/services/surfaceflinger/Scheduler/MessageQueue.h @@ -25,28 +25,28 @@ #include #include #include +#include #include +#include +#include + #include "EventThread.h" #include "TracedOrdinal.h" #include "VSyncDispatch.h" namespace android { -struct ICompositor { - virtual bool commit(nsecs_t frameTime, int64_t vsyncId, nsecs_t expectedVsyncTime) = 0; - virtual void composite(nsecs_t frameTime, int64_t vsyncId) = 0; - virtual void sample() = 0; - -protected: - ~ICompositor() = default; -}; +struct ICompositor; template class Task : public MessageHandler { template friend auto makeTask(G&&); + template + friend sp> sp>::make(Args&&... args); + explicit Task(F&& f) : mTask(std::move(f)) {} void handleMessage(const Message&) override { mTask(); } @@ -57,7 +57,7 @@ class Task : public MessageHandler { template inline auto makeTask(F&& f) { - sp> task = new Task(std::move(f)); + sp> task = sp>::make(std::forward(f)); return std::make_pair(task, task->mTask.get_future()); } @@ -65,12 +65,14 @@ class MessageQueue { public: virtual ~MessageQueue() = default; - virtual void initVsync(scheduler::VSyncDispatch&, frametimeline::TokenManager&, + virtual void initVsync(std::shared_ptr, frametimeline::TokenManager&, std::chrono::nanoseconds workDuration) = 0; + virtual void destroyVsync() = 0; virtual void setDuration(std::chrono::nanoseconds workDuration) = 0; - virtual void setInjector(sp) = 0; virtual void waitMessage() = 0; virtual void postMessage(sp&&) = 0; + virtual void postMessageDelayed(sp&&, nsecs_t uptimeDelay) = 0; + virtual void scheduleConfigure() = 0; virtual void scheduleFrame() = 0; using Clock = std::chrono::steady_clock; @@ -84,8 +86,9 @@ protected: class Handler : public MessageHandler { MessageQueue& mQueue; std::atomic_bool mFramePending = false; - std::atomic mVsyncId = 0; - std::atomic mExpectedVsyncTime = 0; + + std::atomic mVsyncId; + std::atomic mExpectedVsyncTime; public: explicit Handler(MessageQueue& queue) : mQueue(queue) {} @@ -93,7 +96,7 @@ protected: bool isFramePending() const; - virtual void dispatchFrame(int64_t vsyncId, nsecs_t expectedVsyncTime); + virtual void dispatchFrame(VsyncId, TimePoint expectedVsyncTime); }; friend class Handler; @@ -103,45 +106,47 @@ protected: void vsyncCallback(nsecs_t vsyncTime, nsecs_t targetWakeupTime, nsecs_t readyTime); + void onNewVsyncSchedule(std::shared_ptr) EXCLUDES(mVsync.mutex); + private: + virtual void onFrameSignal(ICompositor&, VsyncId, TimePoint expectedVsyncTime) = 0; + ICompositor& mCompositor; const sp mLooper; const sp mHandler; struct Vsync { frametimeline::TokenManager* tokenManager = nullptr; - std::unique_ptr registration; mutable std::mutex mutex; + std::unique_ptr registration GUARDED_BY(mutex); TracedOrdinal workDuration GUARDED_BY(mutex) = {"VsyncWorkDuration-sf", std::chrono::nanoseconds(0)}; - std::chrono::nanoseconds lastCallbackTime GUARDED_BY(mutex) = std::chrono::nanoseconds{0}; + TimePoint lastCallbackTime GUARDED_BY(mutex); std::optional scheduledFrameTime GUARDED_BY(mutex); TracedOrdinal value = {"VSYNC-sf", 0}; }; - struct Injector { - gui::BitTube tube; - std::mutex mutex; - sp connection GUARDED_BY(mutex); - }; - Vsync mVsync; - Injector mInjector; - void injectorCallback(); + // Returns the old registration so it can be destructed outside the lock to + // avoid deadlock. + std::unique_ptr onNewVsyncScheduleLocked( + std::shared_ptr) REQUIRES(mVsync.mutex); public: explicit MessageQueue(ICompositor&); - void initVsync(scheduler::VSyncDispatch&, frametimeline::TokenManager&, + void initVsync(std::shared_ptr, frametimeline::TokenManager&, std::chrono::nanoseconds workDuration) override; + void destroyVsync() override; void setDuration(std::chrono::nanoseconds workDuration) override; - void setInjector(sp) override; void waitMessage() override; void postMessage(sp&&) override; + void postMessageDelayed(sp&&, nsecs_t uptimeDelay) override; + void scheduleConfigure() override; void scheduleFrame() override; std::optional getScheduledFrameTime() const override; diff --git a/services/surfaceflinger/Scheduler/OneShotTimer.cpp b/services/surfaceflinger/Scheduler/OneShotTimer.cpp index 3c8dc64f10f822900b58ebc73ed5d6a1e1a71864..cd45bfdab3de6ab6a7dcc4d93c3a5530d5a9d6e0 100644 --- a/services/surfaceflinger/Scheduler/OneShotTimer.cpp +++ b/services/surfaceflinger/Scheduler/OneShotTimer.cpp @@ -179,11 +179,5 @@ void OneShotTimer::reset() { } } -std::string OneShotTimer::dump() const { - std::ostringstream stream; - stream << mInterval.count() << " ms"; - return stream.str(); -} - } // namespace scheduler } // namespace android diff --git a/services/surfaceflinger/Scheduler/OneShotTimer.h b/services/surfaceflinger/Scheduler/OneShotTimer.h index 2017c315139beb07b149fc05dc69d066c24d3d52..02e8719c0802dc1046df419f6210f21fde8268cc 100644 --- a/services/surfaceflinger/Scheduler/OneShotTimer.h +++ b/services/surfaceflinger/Scheduler/OneShotTimer.h @@ -23,6 +23,7 @@ #include "../Clock.h" #include +#include namespace android { namespace scheduler { @@ -39,9 +40,11 @@ public: OneShotTimer(std::string name, const Interval& interval, const ResetCallback& resetCallback, const TimeoutCallback& timeoutCallback, - std::unique_ptr clock = std::make_unique()); + std::unique_ptr clock = std::make_unique()); ~OneShotTimer(); + Duration interval() const { return mInterval; } + // Initializes and turns on the idle timer. void start(); // Stops the idle timer and any held resources. @@ -49,8 +52,6 @@ public: // Resets the wakeup time and fires the reset callback. void reset(); - std::string dump() const; - private: // Enum to track in what state is the timer. enum class TimerState { @@ -81,7 +82,7 @@ private: std::thread mThread; // Clock object for the timer. Mocked in unit tests. - std::unique_ptr mClock; + std::unique_ptr mClock; // Semaphore to keep mThread synchronized. sem_t mSemaphore; diff --git a/services/surfaceflinger/Scheduler/RefreshRateConfigs.cpp b/services/surfaceflinger/Scheduler/RefreshRateConfigs.cpp deleted file mode 100644 index a48c92137885249bc30a69e110431cd621f8d5d9..0000000000000000000000000000000000000000 --- a/services/surfaceflinger/Scheduler/RefreshRateConfigs.cpp +++ /dev/null @@ -1,1024 +0,0 @@ -/* - * Copyright 2019 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -// #define LOG_NDEBUG 0 -#define ATRACE_TAG ATRACE_TAG_GRAPHICS - -// TODO(b/129481165): remove the #pragma below and fix conversion issues -#pragma clang diagnostic push -#pragma clang diagnostic ignored "-Wextra" - -#include -#include - -#include -#include -#include -#include - -#include "../SurfaceFlingerProperties.h" -#include "RefreshRateConfigs.h" - -#undef LOG_TAG -#define LOG_TAG "RefreshRateConfigs" - -namespace android::scheduler { -namespace { - -struct RefreshRateScore { - DisplayModeIterator modeIt; - float overallScore; - struct { - float modeBelowThreshold; - float modeAboveThreshold; - } fixedRateBelowThresholdLayersScore; -}; - -template -const DisplayModePtr& getMaxScoreRefreshRate(Iterator begin, Iterator end) { - const auto it = - std::max_element(begin, end, [](RefreshRateScore max, RefreshRateScore current) { - const auto& [modeIt, overallScore, _] = current; - - std::string name = to_string(modeIt->second->getFps()); - ALOGV("%s scores %.2f", name.c_str(), overallScore); - - ATRACE_INT(name.c_str(), static_cast(std::round(overallScore * 100))); - - constexpr float kEpsilon = 0.0001f; - return overallScore > max.overallScore * (1 + kEpsilon); - }); - - return it->modeIt->second; -} - -constexpr RefreshRateConfigs::GlobalSignals kNoSignals; - -std::string formatLayerInfo(const RefreshRateConfigs::LayerRequirement& layer, float weight) { - return base::StringPrintf("%s (type=%s, weight=%.2f, seamlessness=%s) %s", layer.name.c_str(), - ftl::enum_string(layer.vote).c_str(), weight, - ftl::enum_string(layer.seamlessness).c_str(), - to_string(layer.desiredRefreshRate).c_str()); -} - -std::vector constructKnownFrameRates(const DisplayModes& modes) { - std::vector knownFrameRates = {24_Hz, 30_Hz, 45_Hz, 60_Hz, 72_Hz}; - knownFrameRates.reserve(knownFrameRates.size() + modes.size()); - - // Add all supported refresh rates. - for (const auto& [id, mode] : modes) { - knownFrameRates.push_back(mode->getFps()); - } - - // Sort and remove duplicates. - std::sort(knownFrameRates.begin(), knownFrameRates.end(), isStrictlyLess); - knownFrameRates.erase(std::unique(knownFrameRates.begin(), knownFrameRates.end(), - isApproxEqual), - knownFrameRates.end()); - return knownFrameRates; -} - -// The Filter is a `bool(const DisplayMode&)` predicate. -template -std::vector sortByRefreshRate(const DisplayModes& modes, Filter&& filter) { - std::vector sortedModes; - sortedModes.reserve(modes.size()); - - for (auto it = modes.begin(); it != modes.end(); ++it) { - const auto& [id, mode] = *it; - - if (filter(*mode)) { - ALOGV("%s: including mode %d", __func__, id.value()); - sortedModes.push_back(it); - } - } - - std::sort(sortedModes.begin(), sortedModes.end(), [](auto it1, auto it2) { - const auto& mode1 = it1->second; - const auto& mode2 = it2->second; - - if (mode1->getVsyncPeriod() == mode2->getVsyncPeriod()) { - return mode1->getGroup() > mode2->getGroup(); - } - - return mode1->getVsyncPeriod() > mode2->getVsyncPeriod(); - }); - - return sortedModes; -} - -bool canModesSupportFrameRateOverride(const std::vector& sortedModes) { - for (const auto it1 : sortedModes) { - const auto& mode1 = it1->second; - for (const auto it2 : sortedModes) { - const auto& mode2 = it2->second; - - if (RefreshRateConfigs::getFrameRateDivisor(mode1->getFps(), mode2->getFps()) >= 2) { - return true; - } - } - } - return false; -} - -} // namespace - -std::string RefreshRateConfigs::Policy::toString() const { - return base::StringPrintf("{defaultModeId=%d, allowGroupSwitching=%s" - ", primaryRange=%s, appRequestRange=%s}", - defaultMode.value(), allowGroupSwitching ? "true" : "false", - to_string(primaryRange).c_str(), to_string(appRequestRange).c_str()); -} - -std::pair RefreshRateConfigs::getDisplayFrames(nsecs_t layerPeriod, - nsecs_t displayPeriod) const { - auto [quotient, remainder] = std::div(layerPeriod, displayPeriod); - if (remainder <= MARGIN_FOR_PERIOD_CALCULATION || - std::abs(remainder - displayPeriod) <= MARGIN_FOR_PERIOD_CALCULATION) { - quotient++; - remainder = 0; - } - - return {quotient, remainder}; -} - -float RefreshRateConfigs::calculateNonExactMatchingLayerScoreLocked(const LayerRequirement& layer, - Fps refreshRate) const { - constexpr float kScoreForFractionalPairs = .8f; - - const auto displayPeriod = refreshRate.getPeriodNsecs(); - const auto layerPeriod = layer.desiredRefreshRate.getPeriodNsecs(); - if (layer.vote == LayerVoteType::ExplicitDefault) { - // Find the actual rate the layer will render, assuming - // that layerPeriod is the minimal period to render a frame. - // For example if layerPeriod is 20ms and displayPeriod is 16ms, - // then the actualLayerPeriod will be 32ms, because it is the - // smallest multiple of the display period which is >= layerPeriod. - auto actualLayerPeriod = displayPeriod; - int multiplier = 1; - while (layerPeriod > actualLayerPeriod + MARGIN_FOR_PERIOD_CALCULATION) { - multiplier++; - actualLayerPeriod = displayPeriod * multiplier; - } - - // Because of the threshold we used above it's possible that score is slightly - // above 1. - return std::min(1.0f, - static_cast(layerPeriod) / static_cast(actualLayerPeriod)); - } - - if (layer.vote == LayerVoteType::ExplicitExactOrMultiple || - layer.vote == LayerVoteType::Heuristic) { - if (isFractionalPairOrMultiple(refreshRate, layer.desiredRefreshRate)) { - return kScoreForFractionalPairs; - } - - // Calculate how many display vsyncs we need to present a single frame for this - // layer - const auto [displayFramesQuotient, displayFramesRemainder] = - getDisplayFrames(layerPeriod, displayPeriod); - static constexpr size_t MAX_FRAMES_TO_FIT = 10; // Stop calculating when score < 0.1 - if (displayFramesRemainder == 0) { - // Layer desired refresh rate matches the display rate. - return 1.0f; - } - - if (displayFramesQuotient == 0) { - // Layer desired refresh rate is higher than the display rate. - return (static_cast(layerPeriod) / static_cast(displayPeriod)) * - (1.0f / (MAX_FRAMES_TO_FIT + 1)); - } - - // Layer desired refresh rate is lower than the display rate. Check how well it fits - // the cadence. - auto diff = std::abs(displayFramesRemainder - (displayPeriod - displayFramesRemainder)); - int iter = 2; - while (diff > MARGIN_FOR_PERIOD_CALCULATION && iter < MAX_FRAMES_TO_FIT) { - diff = diff - (displayPeriod - diff); - iter++; - } - - return (1.0f / iter); - } - - return 0; -} - -float RefreshRateConfigs::calculateLayerScoreLocked(const LayerRequirement& layer, Fps refreshRate, - bool isSeamlessSwitch) const { - // Slightly prefer seamless switches. - constexpr float kSeamedSwitchPenalty = 0.95f; - const float seamlessness = isSeamlessSwitch ? 1.0f : kSeamedSwitchPenalty; - - // If the layer wants Max, give higher score to the higher refresh rate - if (layer.vote == LayerVoteType::Max) { - const auto& maxRefreshRate = mAppRequestRefreshRates.back()->second; - const auto ratio = refreshRate.getValue() / maxRefreshRate->getFps().getValue(); - // use ratio^2 to get a lower score the more we get further from peak - return ratio * ratio; - } - - if (layer.vote == LayerVoteType::ExplicitExact) { - const int divisor = getFrameRateDivisor(refreshRate, layer.desiredRefreshRate); - if (mSupportsFrameRateOverrideByContent) { - // Since we support frame rate override, allow refresh rates which are - // multiples of the layer's request, as those apps would be throttled - // down to run at the desired refresh rate. - return divisor > 0; - } - - return divisor == 1; - } - - // If the layer frame rate is a divisor of the refresh rate it should score - // the highest score. - if (getFrameRateDivisor(refreshRate, layer.desiredRefreshRate) > 0) { - return 1.0f * seamlessness; - } - - // The layer frame rate is not a divisor of the refresh rate, - // there is a small penalty attached to the score to favor the frame rates - // the exactly matches the display refresh rate or a multiple. - constexpr float kNonExactMatchingPenalty = 0.95f; - return calculateNonExactMatchingLayerScoreLocked(layer, refreshRate) * seamlessness * - kNonExactMatchingPenalty; -} - -auto RefreshRateConfigs::getBestRefreshRate(const std::vector& layers, - GlobalSignals signals) const - -> std::pair { - std::lock_guard lock(mLock); - - if (mGetBestRefreshRateCache && - mGetBestRefreshRateCache->arguments == std::make_pair(layers, signals)) { - return mGetBestRefreshRateCache->result; - } - - const auto result = getBestRefreshRateLocked(layers, signals); - mGetBestRefreshRateCache = GetBestRefreshRateCache{{layers, signals}, result}; - return result; -} - -auto RefreshRateConfigs::getBestRefreshRateLocked(const std::vector& layers, - GlobalSignals signals) const - -> std::pair { - using namespace fps_approx_ops; - ATRACE_CALL(); - ALOGV("%s: %zu layers", __func__, layers.size()); - - int noVoteLayers = 0; - int minVoteLayers = 0; - int maxVoteLayers = 0; - int explicitDefaultVoteLayers = 0; - int explicitExactOrMultipleVoteLayers = 0; - int explicitExact = 0; - float maxExplicitWeight = 0; - int seamedFocusedLayers = 0; - - for (const auto& layer : layers) { - switch (layer.vote) { - case LayerVoteType::NoVote: - noVoteLayers++; - break; - case LayerVoteType::Min: - minVoteLayers++; - break; - case LayerVoteType::Max: - maxVoteLayers++; - break; - case LayerVoteType::ExplicitDefault: - explicitDefaultVoteLayers++; - maxExplicitWeight = std::max(maxExplicitWeight, layer.weight); - break; - case LayerVoteType::ExplicitExactOrMultiple: - explicitExactOrMultipleVoteLayers++; - maxExplicitWeight = std::max(maxExplicitWeight, layer.weight); - break; - case LayerVoteType::ExplicitExact: - explicitExact++; - maxExplicitWeight = std::max(maxExplicitWeight, layer.weight); - break; - case LayerVoteType::Heuristic: - break; - } - - if (layer.seamlessness == Seamlessness::SeamedAndSeamless && layer.focused) { - seamedFocusedLayers++; - } - } - - const bool hasExplicitVoteLayers = explicitDefaultVoteLayers > 0 || - explicitExactOrMultipleVoteLayers > 0 || explicitExact > 0; - - const Policy* policy = getCurrentPolicyLocked(); - const auto& defaultMode = mDisplayModes.get(policy->defaultMode)->get(); - // If the default mode group is different from the group of current mode, - // this means a layer requesting a seamed mode switch just disappeared and - // we should switch back to the default group. - // However if a seamed layer is still present we anchor around the group - // of the current mode, in order to prevent unnecessary seamed mode switches - // (e.g. when pausing a video playback). - const auto anchorGroup = - seamedFocusedLayers > 0 ? mActiveModeIt->second->getGroup() : defaultMode->getGroup(); - - // Consider the touch event if there are no Explicit* layers. Otherwise wait until after we've - // selected a refresh rate to see if we should apply touch boost. - if (signals.touch && !hasExplicitVoteLayers) { - const DisplayModePtr& max = getMaxRefreshRateByPolicyLocked(anchorGroup); - ALOGV("TouchBoost - choose %s", to_string(max->getFps()).c_str()); - return {max, GlobalSignals{.touch = true}}; - } - - // If the primary range consists of a single refresh rate then we can only - // move out the of range if layers explicitly request a different refresh - // rate. - const bool primaryRangeIsSingleRate = - isApproxEqual(policy->primaryRange.min, policy->primaryRange.max); - - if (!signals.touch && signals.idle && !(primaryRangeIsSingleRate && hasExplicitVoteLayers)) { - const DisplayModePtr& min = getMinRefreshRateByPolicyLocked(); - ALOGV("Idle - choose %s", to_string(min->getFps()).c_str()); - return {min, GlobalSignals{.idle = true}}; - } - - if (layers.empty() || noVoteLayers == layers.size()) { - const DisplayModePtr& max = getMaxRefreshRateByPolicyLocked(anchorGroup); - ALOGV("no layers with votes - choose %s", to_string(max->getFps()).c_str()); - return {max, kNoSignals}; - } - - // Only if all layers want Min we should return Min - if (noVoteLayers + minVoteLayers == layers.size()) { - const DisplayModePtr& min = getMinRefreshRateByPolicyLocked(); - ALOGV("all layers Min - choose %s", to_string(min->getFps()).c_str()); - return {min, kNoSignals}; - } - - // Find the best refresh rate based on score - std::vector scores; - scores.reserve(mAppRequestRefreshRates.size()); - - for (const DisplayModeIterator modeIt : mAppRequestRefreshRates) { - scores.emplace_back(RefreshRateScore{modeIt, 0.0f}); - } - - for (const auto& layer : layers) { - ALOGV("Calculating score for %s (%s, weight %.2f, desired %.2f) ", layer.name.c_str(), - ftl::enum_string(layer.vote).c_str(), layer.weight, - layer.desiredRefreshRate.getValue()); - if (layer.vote == LayerVoteType::NoVote || layer.vote == LayerVoteType::Min) { - continue; - } - - const auto weight = layer.weight; - - for (auto& [modeIt, overallScore, fixedRateBelowThresholdLayersScore] : scores) { - const auto& [id, mode] = *modeIt; - const bool isSeamlessSwitch = mode->getGroup() == mActiveModeIt->second->getGroup(); - - if (layer.seamlessness == Seamlessness::OnlySeamless && !isSeamlessSwitch) { - ALOGV("%s ignores %s to avoid non-seamless switch. Current mode = %s", - formatLayerInfo(layer, weight).c_str(), to_string(*mode).c_str(), - to_string(*mActiveModeIt->second).c_str()); - continue; - } - - if (layer.seamlessness == Seamlessness::SeamedAndSeamless && !isSeamlessSwitch && - !layer.focused) { - ALOGV("%s ignores %s because it's not focused and the switch is going to be seamed." - " Current mode = %s", - formatLayerInfo(layer, weight).c_str(), to_string(*mode).c_str(), - to_string(*mActiveModeIt->second).c_str()); - continue; - } - - // Layers with default seamlessness vote for the current mode group if - // there are layers with seamlessness=SeamedAndSeamless and for the default - // mode group otherwise. In second case, if the current mode group is different - // from the default, this means a layer with seamlessness=SeamedAndSeamless has just - // disappeared. - const bool isInPolicyForDefault = mode->getGroup() == anchorGroup; - if (layer.seamlessness == Seamlessness::Default && !isInPolicyForDefault) { - ALOGV("%s ignores %s. Current mode = %s", formatLayerInfo(layer, weight).c_str(), - to_string(*mode).c_str(), to_string(*mActiveModeIt->second).c_str()); - continue; - } - - const bool inPrimaryRange = policy->primaryRange.includes(mode->getFps()); - if ((primaryRangeIsSingleRate || !inPrimaryRange) && - !(layer.focused && - (layer.vote == LayerVoteType::ExplicitDefault || - layer.vote == LayerVoteType::ExplicitExact))) { - // Only focused layers with ExplicitDefault frame rate settings are allowed to score - // refresh rates outside the primary range. - continue; - } - - const float layerScore = - calculateLayerScoreLocked(layer, mode->getFps(), isSeamlessSwitch); - const float weightedLayerScore = weight * layerScore; - - // Layer with fixed source has a special consideration which depends on the - // mConfig.frameRateMultipleThreshold. We don't want these layers to score - // refresh rates above the threshold, but we also don't want to favor the lower - // ones by having a greater number of layers scoring them. Instead, we calculate - // the score independently for these layers and later decide which - // refresh rates to add it. For example, desired 24 fps with 120 Hz threshold should not - // score 120 Hz, but desired 60 fps should contribute to the score. - const bool fixedSourceLayer = [](LayerVoteType vote) { - switch (vote) { - case LayerVoteType::ExplicitExactOrMultiple: - case LayerVoteType::Heuristic: - return true; - case LayerVoteType::NoVote: - case LayerVoteType::Min: - case LayerVoteType::Max: - case LayerVoteType::ExplicitDefault: - case LayerVoteType::ExplicitExact: - return false; - } - }(layer.vote); - const bool layerBelowThreshold = mConfig.frameRateMultipleThreshold != 0 && - layer.desiredRefreshRate < - Fps::fromValue(mConfig.frameRateMultipleThreshold / 2); - if (fixedSourceLayer && layerBelowThreshold) { - const bool modeAboveThreshold = - mode->getFps() >= Fps::fromValue(mConfig.frameRateMultipleThreshold); - if (modeAboveThreshold) { - ALOGV("%s gives %s fixed source (above threshold) score of %.4f", - formatLayerInfo(layer, weight).c_str(), to_string(mode->getFps()).c_str(), - layerScore); - fixedRateBelowThresholdLayersScore.modeAboveThreshold += weightedLayerScore; - } else { - ALOGV("%s gives %s fixed source (below threshold) score of %.4f", - formatLayerInfo(layer, weight).c_str(), to_string(mode->getFps()).c_str(), - layerScore); - fixedRateBelowThresholdLayersScore.modeBelowThreshold += weightedLayerScore; - } - } else { - ALOGV("%s gives %s score of %.4f", formatLayerInfo(layer, weight).c_str(), - to_string(mode->getFps()).c_str(), layerScore); - overallScore += weightedLayerScore; - } - } - } - - // We want to find the best refresh rate without the fixed source layers, - // so we could know whether we should add the modeAboveThreshold scores or not. - // If the best refresh rate is already above the threshold, it means that - // some non-fixed source layers already scored it, so we can just add the score - // for all fixed source layers, even the ones that are above the threshold. - const bool maxScoreAboveThreshold = [&] { - if (mConfig.frameRateMultipleThreshold == 0 || scores.empty()) { - return false; - } - - const auto maxScoreIt = - std::max_element(scores.begin(), scores.end(), - [](RefreshRateScore max, RefreshRateScore current) { - const auto& [modeIt, overallScore, _] = current; - return overallScore > max.overallScore; - }); - ALOGV("%s is the best refresh rate without fixed source layers. It is %s the threshold for " - "refresh rate multiples", - to_string(maxScoreIt->modeIt->second->getFps()).c_str(), - maxScoreAboveThreshold ? "above" : "below"); - return maxScoreIt->modeIt->second->getFps() >= - Fps::fromValue(mConfig.frameRateMultipleThreshold); - }(); - - // Now we can add the fixed rate layers score - for (auto& [modeIt, overallScore, fixedRateBelowThresholdLayersScore] : scores) { - overallScore += fixedRateBelowThresholdLayersScore.modeBelowThreshold; - if (maxScoreAboveThreshold) { - overallScore += fixedRateBelowThresholdLayersScore.modeAboveThreshold; - } - ALOGV("%s adjusted overallScore is %.4f", to_string(modeIt->second->getFps()).c_str(), - overallScore); - } - - // Now that we scored all the refresh rates we need to pick the one that got the highest - // overallScore. In case of a tie we will pick the higher refresh rate if any of the layers - // wanted Max, or the lower otherwise. - const DisplayModePtr& bestRefreshRate = maxVoteLayers > 0 - ? getMaxScoreRefreshRate(scores.rbegin(), scores.rend()) - : getMaxScoreRefreshRate(scores.begin(), scores.end()); - - if (primaryRangeIsSingleRate) { - // If we never scored any layers, then choose the rate from the primary - // range instead of picking a random score from the app range. - if (std::all_of(scores.begin(), scores.end(), - [](RefreshRateScore score) { return score.overallScore == 0; })) { - const DisplayModePtr& max = getMaxRefreshRateByPolicyLocked(anchorGroup); - ALOGV("layers not scored - choose %s", to_string(max->getFps()).c_str()); - return {max, kNoSignals}; - } else { - return {bestRefreshRate, kNoSignals}; - } - } - - // Consider the touch event if there are no ExplicitDefault layers. ExplicitDefault are mostly - // interactive (as opposed to ExplicitExactOrMultiple) and therefore if those posted an explicit - // vote we should not change it if we get a touch event. Only apply touch boost if it will - // actually increase the refresh rate over the normal selection. - const DisplayModePtr& touchRefreshRate = getMaxRefreshRateByPolicyLocked(anchorGroup); - - const bool touchBoostForExplicitExact = [&] { - if (mSupportsFrameRateOverrideByContent) { - // Enable touch boost if there are other layers besides exact - return explicitExact + noVoteLayers != layers.size(); - } else { - // Enable touch boost if there are no exact layers - return explicitExact == 0; - } - }(); - - using fps_approx_ops::operator<; - - if (signals.touch && explicitDefaultVoteLayers == 0 && touchBoostForExplicitExact && - bestRefreshRate->getFps() < touchRefreshRate->getFps()) { - ALOGV("TouchBoost - choose %s", to_string(touchRefreshRate->getFps()).c_str()); - return {touchRefreshRate, GlobalSignals{.touch = true}}; - } - - return {bestRefreshRate, kNoSignals}; -} - -std::unordered_map> -groupLayersByUid(const std::vector& layers) { - std::unordered_map> layersByUid; - for (const auto& layer : layers) { - auto iter = layersByUid.emplace(layer.ownerUid, - std::vector()); - auto& layersWithSameUid = iter.first->second; - layersWithSameUid.push_back(&layer); - } - - // Remove uids that can't have a frame rate override - for (auto iter = layersByUid.begin(); iter != layersByUid.end();) { - const auto& layersWithSameUid = iter->second; - bool skipUid = false; - for (const auto& layer : layersWithSameUid) { - if (layer->vote == RefreshRateConfigs::LayerVoteType::Max || - layer->vote == RefreshRateConfigs::LayerVoteType::Heuristic) { - skipUid = true; - break; - } - } - if (skipUid) { - iter = layersByUid.erase(iter); - } else { - ++iter; - } - } - - return layersByUid; -} - -RefreshRateConfigs::UidToFrameRateOverride RefreshRateConfigs::getFrameRateOverrides( - const std::vector& layers, Fps displayRefreshRate, - GlobalSignals globalSignals) const { - ATRACE_CALL(); - - ALOGV("%s: %zu layers", __func__, layers.size()); - - std::lock_guard lock(mLock); - - std::vector scores; - scores.reserve(mDisplayModes.size()); - - for (auto it = mDisplayModes.begin(); it != mDisplayModes.end(); ++it) { - scores.emplace_back(RefreshRateScore{it, 0.0f}); - } - - std::sort(scores.begin(), scores.end(), [](const auto& lhs, const auto& rhs) { - const auto& mode1 = lhs.modeIt->second; - const auto& mode2 = rhs.modeIt->second; - return isStrictlyLess(mode1->getFps(), mode2->getFps()); - }); - - std::unordered_map> layersByUid = - groupLayersByUid(layers); - UidToFrameRateOverride frameRateOverrides; - for (const auto& [uid, layersWithSameUid] : layersByUid) { - // Layers with ExplicitExactOrMultiple expect touch boost - const bool hasExplicitExactOrMultiple = - std::any_of(layersWithSameUid.cbegin(), layersWithSameUid.cend(), - [](const auto& layer) { - return layer->vote == LayerVoteType::ExplicitExactOrMultiple; - }); - - if (globalSignals.touch && hasExplicitExactOrMultiple) { - continue; - } - - for (auto& [_, score, _1] : scores) { - score = 0; - } - - for (const auto& layer : layersWithSameUid) { - if (layer->vote == LayerVoteType::NoVote || layer->vote == LayerVoteType::Min) { - continue; - } - - LOG_ALWAYS_FATAL_IF(layer->vote != LayerVoteType::ExplicitDefault && - layer->vote != LayerVoteType::ExplicitExactOrMultiple && - layer->vote != LayerVoteType::ExplicitExact); - for (auto& [modeIt, score, _] : scores) { - constexpr bool isSeamlessSwitch = true; - const auto layerScore = calculateLayerScoreLocked(*layer, modeIt->second->getFps(), - isSeamlessSwitch); - score += layer->weight * layerScore; - } - } - - // We just care about the refresh rates which are a divisor of the - // display refresh rate - const auto it = std::remove_if(scores.begin(), scores.end(), [&](RefreshRateScore score) { - const auto& [id, mode] = *score.modeIt; - return getFrameRateDivisor(displayRefreshRate, mode->getFps()) == 0; - }); - scores.erase(it, scores.end()); - - // If we never scored any layers, we don't have a preferred frame rate - if (std::all_of(scores.begin(), scores.end(), - [](RefreshRateScore score) { return score.overallScore == 0; })) { - continue; - } - - // Now that we scored all the refresh rates we need to pick the one that got the highest - // score. - const DisplayModePtr& bestRefreshRate = - getMaxScoreRefreshRate(scores.begin(), scores.end()); - - frameRateOverrides.emplace(uid, bestRefreshRate->getFps()); - } - - return frameRateOverrides; -} - -std::optional RefreshRateConfigs::onKernelTimerChanged( - std::optional desiredActiveModeId, bool timerExpired) const { - std::lock_guard lock(mLock); - - const DisplayModePtr& current = desiredActiveModeId - ? mDisplayModes.get(*desiredActiveModeId)->get() - : mActiveModeIt->second; - - const DisplayModePtr& min = mMinRefreshRateModeIt->second; - if (current == min) { - return {}; - } - - const auto& mode = timerExpired ? min : current; - return mode->getFps(); -} - -const DisplayModePtr& RefreshRateConfigs::getMinRefreshRateByPolicyLocked() const { - for (const DisplayModeIterator modeIt : mPrimaryRefreshRates) { - const auto& mode = modeIt->second; - if (mActiveModeIt->second->getGroup() == mode->getGroup()) { - return mode; - } - } - - ALOGE("Can't find min refresh rate by policy with the same mode group" - " as the current mode %s", - to_string(*mActiveModeIt->second).c_str()); - - // Default to the lowest refresh rate. - return mPrimaryRefreshRates.front()->second; -} - -DisplayModePtr RefreshRateConfigs::getMaxRefreshRateByPolicy() const { - std::lock_guard lock(mLock); - return getMaxRefreshRateByPolicyLocked(); -} - -const DisplayModePtr& RefreshRateConfigs::getMaxRefreshRateByPolicyLocked(int anchorGroup) const { - for (auto it = mPrimaryRefreshRates.rbegin(); it != mPrimaryRefreshRates.rend(); ++it) { - const auto& mode = (*it)->second; - if (anchorGroup == mode->getGroup()) { - return mode; - } - } - - ALOGE("Can't find max refresh rate by policy with the same mode group" - " as the current mode %s", - to_string(*mActiveModeIt->second).c_str()); - - // Default to the highest refresh rate. - return mPrimaryRefreshRates.back()->second; -} - -DisplayModePtr RefreshRateConfigs::getActiveMode() const { - std::lock_guard lock(mLock); - return mActiveModeIt->second; -} - -void RefreshRateConfigs::setActiveModeId(DisplayModeId modeId) { - std::lock_guard lock(mLock); - - // Invalidate the cached invocation to getBestRefreshRate. This forces - // the refresh rate to be recomputed on the next call to getBestRefreshRate. - mGetBestRefreshRateCache.reset(); - - mActiveModeIt = mDisplayModes.find(modeId); - LOG_ALWAYS_FATAL_IF(mActiveModeIt == mDisplayModes.end()); -} - -RefreshRateConfigs::RefreshRateConfigs(DisplayModes modes, DisplayModeId activeModeId, - Config config) - : mKnownFrameRates(constructKnownFrameRates(modes)), mConfig(config) { - initializeIdleTimer(); - updateDisplayModes(std::move(modes), activeModeId); -} - -void RefreshRateConfigs::initializeIdleTimer() { - if (mConfig.idleTimerTimeout > 0ms) { - mIdleTimer.emplace( - "IdleTimer", mConfig.idleTimerTimeout, - [this] { - std::scoped_lock lock(mIdleTimerCallbacksMutex); - if (const auto callbacks = getIdleTimerCallbacks()) { - callbacks->onReset(); - } - }, - [this] { - std::scoped_lock lock(mIdleTimerCallbacksMutex); - if (const auto callbacks = getIdleTimerCallbacks()) { - callbacks->onExpired(); - } - }); - } -} - -void RefreshRateConfigs::updateDisplayModes(DisplayModes modes, DisplayModeId activeModeId) { - std::lock_guard lock(mLock); - - // Invalidate the cached invocation to getBestRefreshRate. This forces - // the refresh rate to be recomputed on the next call to getBestRefreshRate. - mGetBestRefreshRateCache.reset(); - - mDisplayModes = std::move(modes); - mActiveModeIt = mDisplayModes.find(activeModeId); - LOG_ALWAYS_FATAL_IF(mActiveModeIt == mDisplayModes.end()); - - const auto sortedModes = - sortByRefreshRate(mDisplayModes, [](const DisplayMode&) { return true; }); - mMinRefreshRateModeIt = sortedModes.front(); - mMaxRefreshRateModeIt = sortedModes.back(); - - // Reset the policy because the old one may no longer be valid. - mDisplayManagerPolicy = {}; - mDisplayManagerPolicy.defaultMode = activeModeId; - - mSupportsFrameRateOverrideByContent = - mConfig.enableFrameRateOverride && canModesSupportFrameRateOverride(sortedModes); - - constructAvailableRefreshRates(); -} - -bool RefreshRateConfigs::isPolicyValidLocked(const Policy& policy) const { - // defaultMode must be a valid mode, and within the given refresh rate range. - if (const auto mode = mDisplayModes.get(policy.defaultMode)) { - if (!policy.primaryRange.includes(mode->get()->getFps())) { - ALOGE("Default mode is not in the primary range."); - return false; - } - } else { - ALOGE("Default mode is not found."); - return false; - } - - using namespace fps_approx_ops; - return policy.appRequestRange.min <= policy.primaryRange.min && - policy.appRequestRange.max >= policy.primaryRange.max; -} - -status_t RefreshRateConfigs::setDisplayManagerPolicy(const Policy& policy) { - std::lock_guard lock(mLock); - if (!isPolicyValidLocked(policy)) { - ALOGE("Invalid refresh rate policy: %s", policy.toString().c_str()); - return BAD_VALUE; - } - mGetBestRefreshRateCache.reset(); - Policy previousPolicy = *getCurrentPolicyLocked(); - mDisplayManagerPolicy = policy; - if (*getCurrentPolicyLocked() == previousPolicy) { - return CURRENT_POLICY_UNCHANGED; - } - constructAvailableRefreshRates(); - return NO_ERROR; -} - -status_t RefreshRateConfigs::setOverridePolicy(const std::optional& policy) { - std::lock_guard lock(mLock); - if (policy && !isPolicyValidLocked(*policy)) { - return BAD_VALUE; - } - mGetBestRefreshRateCache.reset(); - Policy previousPolicy = *getCurrentPolicyLocked(); - mOverridePolicy = policy; - if (*getCurrentPolicyLocked() == previousPolicy) { - return CURRENT_POLICY_UNCHANGED; - } - constructAvailableRefreshRates(); - return NO_ERROR; -} - -const RefreshRateConfigs::Policy* RefreshRateConfigs::getCurrentPolicyLocked() const { - return mOverridePolicy ? &mOverridePolicy.value() : &mDisplayManagerPolicy; -} - -RefreshRateConfigs::Policy RefreshRateConfigs::getCurrentPolicy() const { - std::lock_guard lock(mLock); - return *getCurrentPolicyLocked(); -} - -RefreshRateConfigs::Policy RefreshRateConfigs::getDisplayManagerPolicy() const { - std::lock_guard lock(mLock); - return mDisplayManagerPolicy; -} - -bool RefreshRateConfigs::isModeAllowed(DisplayModeId modeId) const { - std::lock_guard lock(mLock); - return std::any_of(mAppRequestRefreshRates.begin(), mAppRequestRefreshRates.end(), - [modeId](DisplayModeIterator modeIt) { - return modeIt->second->getId() == modeId; - }); -} - -void RefreshRateConfigs::constructAvailableRefreshRates() { - // Filter modes based on current policy and sort on refresh rate. - const Policy* policy = getCurrentPolicyLocked(); - ALOGV("%s: %s ", __func__, policy->toString().c_str()); - - const auto& defaultMode = mDisplayModes.get(policy->defaultMode)->get(); - - const auto filterRefreshRates = [&](FpsRange range, const char* rangeName) REQUIRES(mLock) { - const auto filter = [&](const DisplayMode& mode) { - return mode.getResolution() == defaultMode->getResolution() && - mode.getDpi() == defaultMode->getDpi() && - (policy->allowGroupSwitching || mode.getGroup() == defaultMode->getGroup()) && - range.includes(mode.getFps()); - }; - - const auto modes = sortByRefreshRate(mDisplayModes, filter); - LOG_ALWAYS_FATAL_IF(modes.empty(), "No matching modes for %s range %s", rangeName, - to_string(range).c_str()); - - const auto stringifyModes = [&] { - std::string str; - for (const auto modeIt : modes) { - str += to_string(modeIt->second->getFps()); - str.push_back(' '); - } - return str; - }; - ALOGV("%s refresh rates: %s", rangeName, stringifyModes().c_str()); - - return modes; - }; - - mPrimaryRefreshRates = filterRefreshRates(policy->primaryRange, "primary"); - mAppRequestRefreshRates = filterRefreshRates(policy->appRequestRange, "app request"); -} - -Fps RefreshRateConfigs::findClosestKnownFrameRate(Fps frameRate) const { - using namespace fps_approx_ops; - - if (frameRate <= mKnownFrameRates.front()) { - return mKnownFrameRates.front(); - } - - if (frameRate >= mKnownFrameRates.back()) { - return mKnownFrameRates.back(); - } - - auto lowerBound = std::lower_bound(mKnownFrameRates.begin(), mKnownFrameRates.end(), frameRate, - isStrictlyLess); - - const auto distance1 = std::abs(frameRate.getValue() - lowerBound->getValue()); - const auto distance2 = std::abs(frameRate.getValue() - std::prev(lowerBound)->getValue()); - return distance1 < distance2 ? *lowerBound : *std::prev(lowerBound); -} - -RefreshRateConfigs::KernelIdleTimerAction RefreshRateConfigs::getIdleTimerAction() const { - std::lock_guard lock(mLock); - - const Fps deviceMinFps = mMinRefreshRateModeIt->second->getFps(); - const DisplayModePtr& minByPolicy = getMinRefreshRateByPolicyLocked(); - - // Kernel idle timer will set the refresh rate to the device min. If DisplayManager says that - // the min allowed refresh rate is higher than the device min, we do not want to enable the - // timer. - if (isStrictlyLess(deviceMinFps, minByPolicy->getFps())) { - return KernelIdleTimerAction::TurnOff; - } - - const DisplayModePtr& maxByPolicy = getMaxRefreshRateByPolicyLocked(); - if (minByPolicy == maxByPolicy) { - // Turn on the timer when the min of the primary range is below the device min. - if (const Policy* currentPolicy = getCurrentPolicyLocked(); - isApproxLess(currentPolicy->primaryRange.min, deviceMinFps)) { - return KernelIdleTimerAction::TurnOn; - } - return KernelIdleTimerAction::TurnOff; - } - - // Turn on the timer in all other cases. - return KernelIdleTimerAction::TurnOn; -} - -int RefreshRateConfigs::getFrameRateDivisor(Fps displayRefreshRate, Fps layerFrameRate) { - // This calculation needs to be in sync with the java code - // in DisplayManagerService.getDisplayInfoForFrameRateOverride - - // The threshold must be smaller than 0.001 in order to differentiate - // between the fractional pairs (e.g. 59.94 and 60). - constexpr float kThreshold = 0.0009f; - const auto numPeriods = displayRefreshRate.getValue() / layerFrameRate.getValue(); - const auto numPeriodsRounded = std::round(numPeriods); - if (std::abs(numPeriods - numPeriodsRounded) > kThreshold) { - return 0; - } - - return static_cast(numPeriodsRounded); -} - -bool RefreshRateConfigs::isFractionalPairOrMultiple(Fps smaller, Fps bigger) { - if (isStrictlyLess(bigger, smaller)) { - return isFractionalPairOrMultiple(bigger, smaller); - } - - const auto multiplier = std::round(bigger.getValue() / smaller.getValue()); - constexpr float kCoef = 1000.f / 1001.f; - return isApproxEqual(bigger, Fps::fromValue(smaller.getValue() * multiplier / kCoef)) || - isApproxEqual(bigger, Fps::fromValue(smaller.getValue() * multiplier * kCoef)); -} - -void RefreshRateConfigs::dump(std::string& result) const { - using namespace std::string_literals; - - std::lock_guard lock(mLock); - - const auto activeModeId = mActiveModeIt->first; - result += " activeModeId="s; - result += std::to_string(activeModeId.value()); - - result += "\n displayModes=\n"s; - for (const auto& [id, mode] : mDisplayModes) { - result += " "s; - result += to_string(*mode); - result += '\n'; - } - - base::StringAppendF(&result, " displayManagerPolicy=%s\n", - mDisplayManagerPolicy.toString().c_str()); - - if (const Policy& currentPolicy = *getCurrentPolicyLocked(); - mOverridePolicy && currentPolicy != mDisplayManagerPolicy) { - base::StringAppendF(&result, " overridePolicy=%s\n", currentPolicy.toString().c_str()); - } - - base::StringAppendF(&result, " supportsFrameRateOverrideByContent=%s\n", - mSupportsFrameRateOverrideByContent ? "true" : "false"); - - result += " idleTimer="s; - if (mIdleTimer) { - result += mIdleTimer->dump(); - } else { - result += "off"s; - } - - if (const auto controller = mConfig.kernelIdleTimerController) { - base::StringAppendF(&result, " (kernel via %s)", ftl::enum_string(*controller).c_str()); - } else { - result += " (platform)"s; - } - - result += '\n'; -} - -std::chrono::milliseconds RefreshRateConfigs::getIdleTimerTimeout() { - return mConfig.idleTimerTimeout; -} - -} // namespace android::scheduler - -// TODO(b/129481165): remove the #pragma below and fix conversion issues -#pragma clang diagnostic pop // ignored "-Wextra" diff --git a/services/surfaceflinger/Scheduler/RefreshRateSelector.cpp b/services/surfaceflinger/Scheduler/RefreshRateSelector.cpp new file mode 100644 index 0000000000000000000000000000000000000000..f136e9f9df0ddee21226dcc93e4114b310d12f00 --- /dev/null +++ b/services/surfaceflinger/Scheduler/RefreshRateSelector.cpp @@ -0,0 +1,1362 @@ +/* + * Copyright 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// #define LOG_NDEBUG 0 +#define ATRACE_TAG ATRACE_TAG_GRAPHICS + +// TODO(b/129481165): remove the #pragma below and fix conversion issues +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wextra" + +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "../SurfaceFlingerProperties.h" +#include "RefreshRateSelector.h" + +#undef LOG_TAG +#define LOG_TAG "RefreshRateSelector" + +namespace android::scheduler { +namespace { + +struct RefreshRateScore { + FrameRateMode frameRateMode; + float overallScore; + struct { + float modeBelowThreshold; + float modeAboveThreshold; + } fixedRateBelowThresholdLayersScore; +}; + +constexpr RefreshRateSelector::GlobalSignals kNoSignals; + +std::string formatLayerInfo(const RefreshRateSelector::LayerRequirement& layer, float weight) { + return base::StringPrintf("%s (type=%s, weight=%.2f, seamlessness=%s) %s", layer.name.c_str(), + ftl::enum_string(layer.vote).c_str(), weight, + ftl::enum_string(layer.seamlessness).c_str(), + to_string(layer.desiredRefreshRate).c_str()); +} + +std::vector constructKnownFrameRates(const DisplayModes& modes) { + std::vector knownFrameRates = {24_Hz, 30_Hz, 45_Hz, 60_Hz, 72_Hz}; + knownFrameRates.reserve(knownFrameRates.size() + modes.size()); + + // Add all supported refresh rates. + for (const auto& [id, mode] : modes) { + knownFrameRates.push_back(mode->getFps()); + } + + // Sort and remove duplicates. + std::sort(knownFrameRates.begin(), knownFrameRates.end(), isStrictlyLess); + knownFrameRates.erase(std::unique(knownFrameRates.begin(), knownFrameRates.end(), + isApproxEqual), + knownFrameRates.end()); + return knownFrameRates; +} + +std::vector sortByRefreshRate(const DisplayModes& modes) { + std::vector sortedModes; + sortedModes.reserve(modes.size()); + for (auto it = modes.begin(); it != modes.end(); ++it) { + sortedModes.push_back(it); + } + + std::sort(sortedModes.begin(), sortedModes.end(), [](auto it1, auto it2) { + const auto& mode1 = it1->second; + const auto& mode2 = it2->second; + + if (mode1->getVsyncPeriod() == mode2->getVsyncPeriod()) { + return mode1->getGroup() > mode2->getGroup(); + } + + return mode1->getVsyncPeriod() > mode2->getVsyncPeriod(); + }); + + return sortedModes; +} + +std::pair divisorRange(Fps fps, FpsRange range, + RefreshRateSelector::Config::FrameRateOverride config) { + if (config != RefreshRateSelector::Config::FrameRateOverride::Enabled) { + return {1, 1}; + } + + using fps_approx_ops::operator/; + // use signed type as `fps / range.max` might be 0 + const auto start = std::max(1, static_cast(fps / range.max) - 1); + const auto end = fps / + std::max(range.min, RefreshRateSelector::kMinSupportedFrameRate, + fps_approx_ops::operator<); + + return {start, end}; +} + +bool shouldEnableFrameRateOverride(const std::vector& sortedModes) { + for (const auto it1 : sortedModes) { + const auto& mode1 = it1->second; + for (const auto it2 : sortedModes) { + const auto& mode2 = it2->second; + + if (RefreshRateSelector::getFrameRateDivisor(mode1->getFps(), mode2->getFps()) >= 2) { + return true; + } + } + } + return false; +} + +std::string toString(const RefreshRateSelector::PolicyVariant& policy) { + using namespace std::string_literals; + + return ftl::match( + policy, + [](const RefreshRateSelector::DisplayManagerPolicy& policy) { + return "DisplayManagerPolicy"s + policy.toString(); + }, + [](const RefreshRateSelector::OverridePolicy& policy) { + return "OverridePolicy"s + policy.toString(); + }, + [](RefreshRateSelector::NoOverridePolicy) { return "NoOverridePolicy"s; }); +} + +} // namespace + +auto RefreshRateSelector::createFrameRateModes( + std::function&& filterModes, const FpsRange& renderRange) const + -> std::vector { + struct Key { + Fps fps; + int32_t group; + }; + + struct KeyLess { + bool operator()(const Key& a, const Key& b) const { + using namespace fps_approx_ops; + if (a.fps != b.fps) { + return a.fps < b.fps; + } + + // For the same fps the order doesn't really matter, but we still + // want the behaviour of a strictly less operator. + // We use the group id as the secondary ordering for that. + return a.group < b.group; + } + }; + + std::map ratesMap; + for (auto it = mDisplayModes.begin(); it != mDisplayModes.end(); ++it) { + const auto& [id, mode] = *it; + + if (!filterModes(*mode)) { + continue; + } + const auto [start, end] = + divisorRange(mode->getFps(), renderRange, mConfig.enableFrameRateOverride); + for (auto divisor = start; divisor <= end; divisor++) { + const auto fps = mode->getFps() / divisor; + using fps_approx_ops::operator<; + if (divisor > 1 && fps < kMinSupportedFrameRate) { + break; + } + + if (mConfig.enableFrameRateOverride == Config::FrameRateOverride::Enabled && + !renderRange.includes(fps)) { + continue; + } + + if (mConfig.enableFrameRateOverride == + Config::FrameRateOverride::AppOverrideNativeRefreshRates && + !isNativeRefreshRate(fps)) { + continue; + } + + const auto [existingIter, emplaceHappened] = + ratesMap.try_emplace(Key{fps, mode->getGroup()}, it); + if (emplaceHappened) { + ALOGV("%s: including %s (%s)", __func__, to_string(fps).c_str(), + to_string(mode->getFps()).c_str()); + } else { + // We might need to update the map as we found a lower refresh rate + if (isStrictlyLess(mode->getFps(), existingIter->second->second->getFps())) { + existingIter->second = it; + ALOGV("%s: changing %s (%s)", __func__, to_string(fps).c_str(), + to_string(mode->getFps()).c_str()); + } + } + } + } + + std::vector frameRateModes; + frameRateModes.reserve(ratesMap.size()); + for (const auto& [key, mode] : ratesMap) { + frameRateModes.emplace_back(FrameRateMode{key.fps, ftl::as_non_null(mode->second)}); + } + + // We always want that the lowest frame rate will be corresponding to the + // lowest mode for power saving. + const auto lowestRefreshRateIt = + std::min_element(frameRateModes.begin(), frameRateModes.end(), + [](const FrameRateMode& lhs, const FrameRateMode& rhs) { + return isStrictlyLess(lhs.modePtr->getFps(), + rhs.modePtr->getFps()); + }); + frameRateModes.erase(frameRateModes.begin(), lowestRefreshRateIt); + + return frameRateModes; +} + +struct RefreshRateSelector::RefreshRateScoreComparator { + bool operator()(const RefreshRateScore& lhs, const RefreshRateScore& rhs) const { + const auto& [frameRateMode, overallScore, _] = lhs; + + std::string name = to_string(frameRateMode); + + ALOGV("%s sorting scores %.2f", name.c_str(), overallScore); + + if (!ScoredFrameRate::scoresEqual(overallScore, rhs.overallScore)) { + return overallScore > rhs.overallScore; + } + + if (refreshRateOrder == RefreshRateOrder::Descending) { + using fps_approx_ops::operator>; + return frameRateMode.fps > rhs.frameRateMode.fps; + } else { + using fps_approx_ops::operator<; + return frameRateMode.fps < rhs.frameRateMode.fps; + } + } + + const RefreshRateOrder refreshRateOrder; +}; + +std::string RefreshRateSelector::Policy::toString() const { + return base::StringPrintf("{defaultModeId=%d, allowGroupSwitching=%s" + ", primaryRanges=%s, appRequestRanges=%s}", + defaultMode.value(), allowGroupSwitching ? "true" : "false", + to_string(primaryRanges).c_str(), + to_string(appRequestRanges).c_str()); +} + +std::pair RefreshRateSelector::getDisplayFrames(nsecs_t layerPeriod, + nsecs_t displayPeriod) const { + auto [quotient, remainder] = std::div(layerPeriod, displayPeriod); + if (remainder <= MARGIN_FOR_PERIOD_CALCULATION || + std::abs(remainder - displayPeriod) <= MARGIN_FOR_PERIOD_CALCULATION) { + quotient++; + remainder = 0; + } + + return {quotient, remainder}; +} + +float RefreshRateSelector::calculateNonExactMatchingLayerScoreLocked(const LayerRequirement& layer, + Fps refreshRate) const { + constexpr float kScoreForFractionalPairs = .8f; + + const auto displayPeriod = refreshRate.getPeriodNsecs(); + const auto layerPeriod = layer.desiredRefreshRate.getPeriodNsecs(); + if (layer.vote == LayerVoteType::ExplicitDefault) { + // Find the actual rate the layer will render, assuming + // that layerPeriod is the minimal period to render a frame. + // For example if layerPeriod is 20ms and displayPeriod is 16ms, + // then the actualLayerPeriod will be 32ms, because it is the + // smallest multiple of the display period which is >= layerPeriod. + auto actualLayerPeriod = displayPeriod; + int multiplier = 1; + while (layerPeriod > actualLayerPeriod + MARGIN_FOR_PERIOD_CALCULATION) { + multiplier++; + actualLayerPeriod = displayPeriod * multiplier; + } + + // Because of the threshold we used above it's possible that score is slightly + // above 1. + return std::min(1.0f, + static_cast(layerPeriod) / static_cast(actualLayerPeriod)); + } + + if (layer.vote == LayerVoteType::ExplicitExactOrMultiple || + layer.vote == LayerVoteType::Heuristic) { + const float multiplier = refreshRate.getValue() / layer.desiredRefreshRate.getValue(); + + // We only want to score this layer as a fractional pair if the content is not + // significantly faster than the display rate, at it would cause a significant frame drop. + // It is more appropriate to choose a higher display rate even if + // a pull-down will be required. + constexpr float kMinMultiplier = 0.75f; + if (multiplier >= kMinMultiplier && + isFractionalPairOrMultiple(refreshRate, layer.desiredRefreshRate)) { + return kScoreForFractionalPairs; + } + + // Calculate how many display vsyncs we need to present a single frame for this + // layer + const auto [displayFramesQuotient, displayFramesRemainder] = + getDisplayFrames(layerPeriod, displayPeriod); + static constexpr size_t MAX_FRAMES_TO_FIT = 10; // Stop calculating when score < 0.1 + if (displayFramesRemainder == 0) { + // Layer desired refresh rate matches the display rate. + return 1.0f; + } + + if (displayFramesQuotient == 0) { + // Layer desired refresh rate is higher than the display rate. + return (static_cast(layerPeriod) / static_cast(displayPeriod)) * + (1.0f / (MAX_FRAMES_TO_FIT + 1)); + } + + // Layer desired refresh rate is lower than the display rate. Check how well it fits + // the cadence. + auto diff = std::abs(displayFramesRemainder - (displayPeriod - displayFramesRemainder)); + int iter = 2; + while (diff > MARGIN_FOR_PERIOD_CALCULATION && iter < MAX_FRAMES_TO_FIT) { + diff = diff - (displayPeriod - diff); + iter++; + } + + return (1.0f / iter); + } + + return 0; +} + +float RefreshRateSelector::calculateDistanceScoreFromMax(Fps refreshRate) const { + const auto& maxFps = mAppRequestFrameRates.back().fps; + const float ratio = refreshRate.getValue() / maxFps.getValue(); + // Use ratio^2 to get a lower score the more we get further from peak + return ratio * ratio; +} + +float RefreshRateSelector::calculateLayerScoreLocked(const LayerRequirement& layer, Fps refreshRate, + bool isSeamlessSwitch) const { + ATRACE_CALL(); + // Slightly prefer seamless switches. + constexpr float kSeamedSwitchPenalty = 0.95f; + const float seamlessness = isSeamlessSwitch ? 1.0f : kSeamedSwitchPenalty; + + // If the layer wants Max, give higher score to the higher refresh rate + if (layer.vote == LayerVoteType::Max) { + return calculateDistanceScoreFromMax(refreshRate); + } + + if (layer.vote == LayerVoteType::ExplicitExact) { + const int divisor = getFrameRateDivisor(refreshRate, layer.desiredRefreshRate); + if (supportsAppFrameRateOverrideByContent()) { + // Since we support frame rate override, allow refresh rates which are + // multiples of the layer's request, as those apps would be throttled + // down to run at the desired refresh rate. + return divisor > 0; + } + + return divisor == 1; + } + + // If the layer frame rate is a divisor of the refresh rate it should score + // the highest score. + if (getFrameRateDivisor(refreshRate, layer.desiredRefreshRate) > 0) { + return 1.0f * seamlessness; + } + + // The layer frame rate is not a divisor of the refresh rate, + // there is a small penalty attached to the score to favor the frame rates + // the exactly matches the display refresh rate or a multiple. + constexpr float kNonExactMatchingPenalty = 0.95f; + return calculateNonExactMatchingLayerScoreLocked(layer, refreshRate) * seamlessness * + kNonExactMatchingPenalty; +} + +auto RefreshRateSelector::getRankedFrameRates(const std::vector& layers, + GlobalSignals signals) const -> RankedFrameRates { + std::lock_guard lock(mLock); + + if (mGetRankedFrameRatesCache && + mGetRankedFrameRatesCache->arguments == std::make_pair(layers, signals)) { + return mGetRankedFrameRatesCache->result; + } + + const auto result = getRankedFrameRatesLocked(layers, signals); + mGetRankedFrameRatesCache = GetRankedFrameRatesCache{{layers, signals}, result}; + return result; +} + +auto RefreshRateSelector::getRankedFrameRatesLocked(const std::vector& layers, + GlobalSignals signals) const + -> RankedFrameRates { + using namespace fps_approx_ops; + ATRACE_CALL(); + ALOGV("%s: %zu layers", __func__, layers.size()); + + const auto& activeMode = *getActiveModeLocked().modePtr; + + // Keep the display at max frame rate for the duration of powering on the display. + if (signals.powerOnImminent) { + ALOGV("Power On Imminent"); + const auto ranking = rankFrameRates(activeMode.getGroup(), RefreshRateOrder::Descending); + ATRACE_FORMAT_INSTANT("%s (Power On Imminent)", + to_string(ranking.front().frameRateMode.fps).c_str()); + return {ranking, GlobalSignals{.powerOnImminent = true}}; + } + + int noVoteLayers = 0; + int minVoteLayers = 0; + int maxVoteLayers = 0; + int explicitDefaultVoteLayers = 0; + int explicitExactOrMultipleVoteLayers = 0; + int explicitExact = 0; + int seamedFocusedLayers = 0; + + for (const auto& layer : layers) { + switch (layer.vote) { + case LayerVoteType::NoVote: + noVoteLayers++; + break; + case LayerVoteType::Min: + minVoteLayers++; + break; + case LayerVoteType::Max: + maxVoteLayers++; + break; + case LayerVoteType::ExplicitDefault: + explicitDefaultVoteLayers++; + break; + case LayerVoteType::ExplicitExactOrMultiple: + explicitExactOrMultipleVoteLayers++; + break; + case LayerVoteType::ExplicitExact: + explicitExact++; + break; + case LayerVoteType::Heuristic: + break; + } + + if (layer.seamlessness == Seamlessness::SeamedAndSeamless && layer.focused) { + seamedFocusedLayers++; + } + } + + const bool hasExplicitVoteLayers = explicitDefaultVoteLayers > 0 || + explicitExactOrMultipleVoteLayers > 0 || explicitExact > 0; + + const Policy* policy = getCurrentPolicyLocked(); + const auto& defaultMode = mDisplayModes.get(policy->defaultMode)->get(); + + // If the default mode group is different from the group of current mode, + // this means a layer requesting a seamed mode switch just disappeared and + // we should switch back to the default group. + // However if a seamed layer is still present we anchor around the group + // of the current mode, in order to prevent unnecessary seamed mode switches + // (e.g. when pausing a video playback). + const auto anchorGroup = + seamedFocusedLayers > 0 ? activeMode.getGroup() : defaultMode->getGroup(); + + // Consider the touch event if there are no Explicit* layers. Otherwise wait until after we've + // selected a refresh rate to see if we should apply touch boost. + if (signals.touch && !hasExplicitVoteLayers) { + ALOGV("Touch Boost"); + const auto ranking = rankFrameRates(anchorGroup, RefreshRateOrder::Descending); + ATRACE_FORMAT_INSTANT("%s (Touch Boost)", + to_string(ranking.front().frameRateMode.fps).c_str()); + return {ranking, GlobalSignals{.touch = true}}; + } + + // If the primary range consists of a single refresh rate then we can only + // move out the of range if layers explicitly request a different refresh + // rate. + const bool primaryRangeIsSingleRate = + isApproxEqual(policy->primaryRanges.physical.min, policy->primaryRanges.physical.max); + + if (!signals.touch && signals.idle && !(primaryRangeIsSingleRate && hasExplicitVoteLayers)) { + ALOGV("Idle"); + const auto ranking = rankFrameRates(activeMode.getGroup(), RefreshRateOrder::Ascending); + ATRACE_FORMAT_INSTANT("%s (Idle)", to_string(ranking.front().frameRateMode.fps).c_str()); + return {ranking, GlobalSignals{.idle = true}}; + } + + if (layers.empty() || noVoteLayers == layers.size()) { + ALOGV("No layers with votes"); + const auto ranking = rankFrameRates(anchorGroup, RefreshRateOrder::Descending); + ATRACE_FORMAT_INSTANT("%s (No layers with votes)", + to_string(ranking.front().frameRateMode.fps).c_str()); + return {ranking, kNoSignals}; + } + + // Only if all layers want Min we should return Min + if (noVoteLayers + minVoteLayers == layers.size()) { + ALOGV("All layers Min"); + const auto ranking = rankFrameRates(activeMode.getGroup(), RefreshRateOrder::Ascending); + ATRACE_FORMAT_INSTANT("%s (All layers Min)", + to_string(ranking.front().frameRateMode.fps).c_str()); + return {ranking, kNoSignals}; + } + + // Find the best refresh rate based on score + std::vector scores; + scores.reserve(mAppRequestFrameRates.size()); + + for (const FrameRateMode& it : mAppRequestFrameRates) { + scores.emplace_back(RefreshRateScore{it, 0.0f}); + } + + for (const auto& layer : layers) { + ALOGV("Calculating score for %s (%s, weight %.2f, desired %.2f) ", layer.name.c_str(), + ftl::enum_string(layer.vote).c_str(), layer.weight, + layer.desiredRefreshRate.getValue()); + if (layer.vote == LayerVoteType::NoVote || layer.vote == LayerVoteType::Min) { + continue; + } + + const auto weight = layer.weight; + + for (auto& [mode, overallScore, fixedRateBelowThresholdLayersScore] : scores) { + const auto& [fps, modePtr] = mode; + const bool isSeamlessSwitch = modePtr->getGroup() == activeMode.getGroup(); + + if (layer.seamlessness == Seamlessness::OnlySeamless && !isSeamlessSwitch) { + ALOGV("%s ignores %s to avoid non-seamless switch. Current mode = %s", + formatLayerInfo(layer, weight).c_str(), to_string(*modePtr).c_str(), + to_string(activeMode).c_str()); + continue; + } + + if (layer.seamlessness == Seamlessness::SeamedAndSeamless && !isSeamlessSwitch && + !layer.focused) { + ALOGV("%s ignores %s because it's not focused and the switch is going to be seamed." + " Current mode = %s", + formatLayerInfo(layer, weight).c_str(), to_string(*modePtr).c_str(), + to_string(activeMode).c_str()); + continue; + } + + // Layers with default seamlessness vote for the current mode group if + // there are layers with seamlessness=SeamedAndSeamless and for the default + // mode group otherwise. In second case, if the current mode group is different + // from the default, this means a layer with seamlessness=SeamedAndSeamless has just + // disappeared. + const bool isInPolicyForDefault = modePtr->getGroup() == anchorGroup; + if (layer.seamlessness == Seamlessness::Default && !isInPolicyForDefault) { + ALOGV("%s ignores %s. Current mode = %s", formatLayerInfo(layer, weight).c_str(), + to_string(*modePtr).c_str(), to_string(activeMode).c_str()); + continue; + } + + const bool inPrimaryRange = policy->primaryRanges.render.includes(fps); + if ((primaryRangeIsSingleRate || !inPrimaryRange) && + !(layer.focused && + (layer.vote == LayerVoteType::ExplicitDefault || + layer.vote == LayerVoteType::ExplicitExact))) { + // Only focused layers with ExplicitDefault frame rate settings are allowed to score + // refresh rates outside the primary range. + continue; + } + + const float layerScore = calculateLayerScoreLocked(layer, fps, isSeamlessSwitch); + const float weightedLayerScore = weight * layerScore; + + // Layer with fixed source has a special consideration which depends on the + // mConfig.frameRateMultipleThreshold. We don't want these layers to score + // refresh rates above the threshold, but we also don't want to favor the lower + // ones by having a greater number of layers scoring them. Instead, we calculate + // the score independently for these layers and later decide which + // refresh rates to add it. For example, desired 24 fps with 120 Hz threshold should not + // score 120 Hz, but desired 60 fps should contribute to the score. + const bool fixedSourceLayer = [](LayerVoteType vote) { + switch (vote) { + case LayerVoteType::ExplicitExactOrMultiple: + case LayerVoteType::Heuristic: + return true; + case LayerVoteType::NoVote: + case LayerVoteType::Min: + case LayerVoteType::Max: + case LayerVoteType::ExplicitDefault: + case LayerVoteType::ExplicitExact: + return false; + } + }(layer.vote); + const bool layerBelowThreshold = mConfig.frameRateMultipleThreshold != 0 && + layer.desiredRefreshRate < + Fps::fromValue(mConfig.frameRateMultipleThreshold / 2); + if (fixedSourceLayer && layerBelowThreshold) { + const bool modeAboveThreshold = + modePtr->getFps() >= Fps::fromValue(mConfig.frameRateMultipleThreshold); + if (modeAboveThreshold) { + ALOGV("%s gives %s (%s) fixed source (above threshold) score of %.4f", + formatLayerInfo(layer, weight).c_str(), to_string(fps).c_str(), + to_string(modePtr->getFps()).c_str(), layerScore); + fixedRateBelowThresholdLayersScore.modeAboveThreshold += weightedLayerScore; + } else { + ALOGV("%s gives %s (%s) fixed source (below threshold) score of %.4f", + formatLayerInfo(layer, weight).c_str(), to_string(fps).c_str(), + to_string(modePtr->getFps()).c_str(), layerScore); + fixedRateBelowThresholdLayersScore.modeBelowThreshold += weightedLayerScore; + } + } else { + ALOGV("%s gives %s (%s) score of %.4f", formatLayerInfo(layer, weight).c_str(), + to_string(fps).c_str(), to_string(modePtr->getFps()).c_str(), layerScore); + overallScore += weightedLayerScore; + } + } + } + + // We want to find the best refresh rate without the fixed source layers, + // so we could know whether we should add the modeAboveThreshold scores or not. + // If the best refresh rate is already above the threshold, it means that + // some non-fixed source layers already scored it, so we can just add the score + // for all fixed source layers, even the ones that are above the threshold. + const bool maxScoreAboveThreshold = [&] { + if (mConfig.frameRateMultipleThreshold == 0 || scores.empty()) { + return false; + } + + const auto maxScoreIt = + std::max_element(scores.begin(), scores.end(), + [](RefreshRateScore max, RefreshRateScore current) { + return current.overallScore > max.overallScore; + }); + ALOGV("%s (%s) is the best refresh rate without fixed source layers. It is %s the " + "threshold for " + "refresh rate multiples", + to_string(maxScoreIt->frameRateMode.fps).c_str(), + to_string(maxScoreIt->frameRateMode.modePtr->getFps()).c_str(), + maxScoreAboveThreshold ? "above" : "below"); + return maxScoreIt->frameRateMode.modePtr->getFps() >= + Fps::fromValue(mConfig.frameRateMultipleThreshold); + }(); + + // Now we can add the fixed rate layers score + for (auto& [frameRateMode, overallScore, fixedRateBelowThresholdLayersScore] : scores) { + overallScore += fixedRateBelowThresholdLayersScore.modeBelowThreshold; + if (maxScoreAboveThreshold) { + overallScore += fixedRateBelowThresholdLayersScore.modeAboveThreshold; + } + ALOGV("%s (%s) adjusted overallScore is %.4f", to_string(frameRateMode.fps).c_str(), + to_string(frameRateMode.modePtr->getFps()).c_str(), overallScore); + } + + // Now that we scored all the refresh rates we need to pick the one that got the highest + // overallScore. Sort the scores based on their overallScore in descending order of priority. + const RefreshRateOrder refreshRateOrder = + maxVoteLayers > 0 ? RefreshRateOrder::Descending : RefreshRateOrder::Ascending; + std::sort(scores.begin(), scores.end(), + RefreshRateScoreComparator{.refreshRateOrder = refreshRateOrder}); + + FrameRateRanking ranking; + ranking.reserve(scores.size()); + + std::transform(scores.begin(), scores.end(), back_inserter(ranking), + [](const RefreshRateScore& score) { + return ScoredFrameRate{score.frameRateMode, score.overallScore}; + }); + + const bool noLayerScore = std::all_of(scores.begin(), scores.end(), [](RefreshRateScore score) { + return score.overallScore == 0; + }); + + if (primaryRangeIsSingleRate) { + // If we never scored any layers, then choose the rate from the primary + // range instead of picking a random score from the app range. + if (noLayerScore) { + ALOGV("Layers not scored"); + const auto descending = rankFrameRates(anchorGroup, RefreshRateOrder::Descending); + ATRACE_FORMAT_INSTANT("%s (Layers not scored)", + to_string(descending.front().frameRateMode.fps).c_str()); + return {descending, kNoSignals}; + } else { + ATRACE_FORMAT_INSTANT("%s (primaryRangeIsSingleRate)", + to_string(ranking.front().frameRateMode.fps).c_str()); + return {ranking, kNoSignals}; + } + } + + // Consider the touch event if there are no ExplicitDefault layers. ExplicitDefault are mostly + // interactive (as opposed to ExplicitExactOrMultiple) and therefore if those posted an explicit + // vote we should not change it if we get a touch event. Only apply touch boost if it will + // actually increase the refresh rate over the normal selection. + const bool touchBoostForExplicitExact = [&] { + if (supportsAppFrameRateOverrideByContent()) { + // Enable touch boost if there are other layers besides exact + return explicitExact + noVoteLayers != layers.size(); + } else { + // Enable touch boost if there are no exact layers + return explicitExact == 0; + } + }(); + + const auto touchRefreshRates = rankFrameRates(anchorGroup, RefreshRateOrder::Descending); + using fps_approx_ops::operator<; + + if (signals.touch && explicitDefaultVoteLayers == 0 && touchBoostForExplicitExact && + 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 + // current config + if (noLayerScore && refreshRateOrder == RefreshRateOrder::Ascending) { + const auto ascendingWithPreferred = + rankFrameRates(anchorGroup, RefreshRateOrder::Ascending, activeMode.getId()); + ATRACE_FORMAT_INSTANT("%s (preferredDisplayMode)", + to_string(ascendingWithPreferred.front().frameRateMode.fps).c_str()); + return {ascendingWithPreferred, kNoSignals}; + } + + ATRACE_FORMAT_INSTANT("%s (scored))", to_string(ranking.front().frameRateMode.fps).c_str()); + return {ranking, kNoSignals}; +} + +using LayerRequirementPtrs = std::vector; +using PerUidLayerRequirements = std::unordered_map; + +PerUidLayerRequirements groupLayersByUid( + const std::vector& layers) { + PerUidLayerRequirements layersByUid; + for (const auto& layer : layers) { + const auto it = layersByUid.emplace(layer.ownerUid, LayerRequirementPtrs()).first; + auto& layersWithSameUid = it->second; + layersWithSameUid.push_back(&layer); + } + + // Remove uids that can't have a frame rate override + for (auto it = layersByUid.begin(); it != layersByUid.end();) { + const auto& layersWithSameUid = it->second; + bool skipUid = false; + for (const auto& layer : layersWithSameUid) { + using LayerVoteType = RefreshRateSelector::LayerVoteType; + + if (layer->vote == LayerVoteType::Max || layer->vote == LayerVoteType::Heuristic) { + skipUid = true; + break; + } + } + if (skipUid) { + it = layersByUid.erase(it); + } else { + ++it; + } + } + + return layersByUid; +} + +auto RefreshRateSelector::getFrameRateOverrides(const std::vector& layers, + Fps displayRefreshRate, + GlobalSignals globalSignals) const + -> UidToFrameRateOverride { + ATRACE_CALL(); + if (mConfig.enableFrameRateOverride == Config::FrameRateOverride::Disabled) { + return {}; + } + + ALOGV("%s: %zu layers", __func__, layers.size()); + std::lock_guard lock(mLock); + + const auto* policyPtr = getCurrentPolicyLocked(); + // We don't want to run lower than 30fps + const Fps minFrameRate = std::max(policyPtr->appRequestRanges.render.min, 30_Hz, isApproxLess); + + using fps_approx_ops::operator/; + const unsigned numMultiples = displayRefreshRate / minFrameRate; + + std::vector> scoredFrameRates; + scoredFrameRates.reserve(numMultiples); + + for (unsigned n = numMultiples; n > 0; n--) { + const Fps divisor = displayRefreshRate / n; + if (mConfig.enableFrameRateOverride == + Config::FrameRateOverride::AppOverrideNativeRefreshRates && + !isNativeRefreshRate(divisor)) { + continue; + } + + if (policyPtr->appRequestRanges.render.includes(divisor)) { + ALOGV("%s: adding %s as a potential frame rate", __func__, to_string(divisor).c_str()); + scoredFrameRates.emplace_back(divisor, 0); + } + } + + const auto layersByUid = groupLayersByUid(layers); + UidToFrameRateOverride frameRateOverrides; + for (const auto& [uid, layersWithSameUid] : layersByUid) { + // Layers with ExplicitExactOrMultiple expect touch boost + const bool hasExplicitExactOrMultiple = + std::any_of(layersWithSameUid.cbegin(), layersWithSameUid.cend(), + [](const auto& layer) { + return layer->vote == LayerVoteType::ExplicitExactOrMultiple; + }); + + if (globalSignals.touch && hasExplicitExactOrMultiple) { + continue; + } + + for (auto& [_, score] : scoredFrameRates) { + score = 0; + } + + for (const auto& layer : layersWithSameUid) { + if (layer->vote == LayerVoteType::NoVote || layer->vote == LayerVoteType::Min) { + continue; + } + + LOG_ALWAYS_FATAL_IF(layer->vote != LayerVoteType::ExplicitDefault && + layer->vote != LayerVoteType::ExplicitExactOrMultiple && + layer->vote != LayerVoteType::ExplicitExact); + for (auto& [fps, score] : scoredFrameRates) { + constexpr bool isSeamlessSwitch = true; + const auto layerScore = calculateLayerScoreLocked(*layer, fps, isSeamlessSwitch); + score += layer->weight * layerScore; + } + } + + // If we never scored any layers, we don't have a preferred frame rate + if (std::all_of(scoredFrameRates.begin(), scoredFrameRates.end(), + [](const auto& scoredFrameRate) { + const auto [_, score] = scoredFrameRate; + return score == 0; + })) { + continue; + } + + // Now that we scored all the refresh rates we need to pick the lowest refresh rate + // that got the highest score. + const auto [overrideFps, _] = + *std::max_element(scoredFrameRates.begin(), scoredFrameRates.end(), + [](const auto& lhsPair, const auto& rhsPair) { + const float lhs = lhsPair.second; + const float rhs = rhsPair.second; + return lhs < rhs && !ScoredFrameRate::scoresEqual(lhs, rhs); + }); + ALOGV("%s: overriding to %s for uid=%d", __func__, to_string(overrideFps).c_str(), uid); + frameRateOverrides.emplace(uid, overrideFps); + } + + return frameRateOverrides; +} + +ftl::Optional RefreshRateSelector::onKernelTimerChanged( + std::optional desiredActiveModeId, bool timerExpired) const { + std::lock_guard lock(mLock); + + const auto current = [&]() REQUIRES(mLock) -> FrameRateMode { + if (desiredActiveModeId) { + const auto& modePtr = mDisplayModes.get(*desiredActiveModeId)->get(); + return FrameRateMode{modePtr->getFps(), ftl::as_non_null(modePtr)}; + } + + return getActiveModeLocked(); + }(); + + const DisplayModePtr& min = mMinRefreshRateModeIt->second; + if (current.modePtr->getId() == min->getId()) { + return {}; + } + + return timerExpired ? FrameRateMode{min->getFps(), ftl::as_non_null(min)} : current; +} + +const DisplayModePtr& RefreshRateSelector::getMinRefreshRateByPolicyLocked() const { + const auto& activeMode = *getActiveModeLocked().modePtr; + + for (const FrameRateMode& mode : mPrimaryFrameRates) { + if (activeMode.getGroup() == mode.modePtr->getGroup()) { + return mode.modePtr.get(); + } + } + + ALOGE("Can't find min refresh rate by policy with the same mode group as the current mode %s", + to_string(activeMode).c_str()); + + // Default to the lowest refresh rate. + return mPrimaryFrameRates.front().modePtr.get(); +} + +const DisplayModePtr& RefreshRateSelector::getMaxRefreshRateByPolicyLocked(int anchorGroup) const { + const ftl::NonNull* maxByAnchor = &mPrimaryFrameRates.back().modePtr; + const ftl::NonNull* max = &mPrimaryFrameRates.back().modePtr; + + bool maxByAnchorFound = false; + for (auto it = mPrimaryFrameRates.rbegin(); it != mPrimaryFrameRates.rend(); ++it) { + using namespace fps_approx_ops; + if (it->modePtr->getFps() > (*max)->getFps()) { + max = &it->modePtr; + } + + if (anchorGroup == it->modePtr->getGroup() && + it->modePtr->getFps() >= (*maxByAnchor)->getFps()) { + maxByAnchorFound = true; + maxByAnchor = &it->modePtr; + } + } + + if (maxByAnchorFound) { + return maxByAnchor->get(); + } + + ALOGE("Can't find max refresh rate by policy with the same group %d", anchorGroup); + + // Default to the highest refresh rate. + return max->get(); +} + +auto RefreshRateSelector::rankFrameRates(std::optional anchorGroupOpt, + RefreshRateOrder refreshRateOrder, + std::optional preferredDisplayModeOpt) const + -> FrameRateRanking { + using fps_approx_ops::operator<; + const char* const whence = __func__; + + // find the highest frame rate for each display mode + ftl::SmallMap maxRenderRateForMode; + const bool ascending = (refreshRateOrder == RefreshRateOrder::Ascending); + if (ascending) { + // TODO(b/266481656): Once this bug is fixed, we can remove this workaround and actually + // use a lower frame rate when we want Ascending frame rates. + for (const auto& frameRateMode : mPrimaryFrameRates) { + if (anchorGroupOpt && frameRateMode.modePtr->getGroup() != anchorGroupOpt) { + continue; + } + + const auto [iter, _] = maxRenderRateForMode.try_emplace(frameRateMode.modePtr->getId(), + frameRateMode.fps); + if (iter->second < frameRateMode.fps) { + iter->second = frameRateMode.fps; + } + } + } + + std::deque ranking; + const auto rankFrameRate = [&](const FrameRateMode& frameRateMode) REQUIRES(mLock) { + const auto& modePtr = frameRateMode.modePtr; + if (anchorGroupOpt && modePtr->getGroup() != anchorGroupOpt) { + return; + } + + const bool ascending = (refreshRateOrder == RefreshRateOrder::Ascending); + const auto id = modePtr->getId(); + if (ascending && frameRateMode.fps < *maxRenderRateForMode.get(id)) { + // TODO(b/266481656): Once this bug is fixed, we can remove this workaround and actually + // use a lower frame rate when we want Ascending frame rates. + return; + } + + float score = calculateDistanceScoreFromMax(frameRateMode.fps); + + if (ascending) { + score = 1.0f / score; + } + + constexpr float kScore = std::numeric_limits::max(); + if (preferredDisplayModeOpt) { + if (*preferredDisplayModeOpt == modePtr->getId()) { + ranking.emplace_front(ScoredFrameRate{frameRateMode, kScore}); + return; + } + constexpr float kNonPreferredModePenalty = 0.95f; + score *= kNonPreferredModePenalty; + } else if (ascending && id == getMinRefreshRateByPolicyLocked()->getId()) { + // TODO(b/266481656): Once this bug is fixed, we can remove this workaround + // and actually use a lower frame rate when we want Ascending frame rates. + ranking.emplace_front(ScoredFrameRate{frameRateMode, kScore}); + return; + } + + ALOGV("%s(%s) %s (%s) scored %.2f", whence, ftl::enum_string(refreshRateOrder).c_str(), + to_string(frameRateMode.fps).c_str(), to_string(modePtr->getFps()).c_str(), score); + ranking.emplace_back(ScoredFrameRate{frameRateMode, score}); + }; + + if (refreshRateOrder == RefreshRateOrder::Ascending) { + std::for_each(mPrimaryFrameRates.begin(), mPrimaryFrameRates.end(), rankFrameRate); + } else { + std::for_each(mPrimaryFrameRates.rbegin(), mPrimaryFrameRates.rend(), rankFrameRate); + } + + if (!ranking.empty() || !anchorGroupOpt) { + return {ranking.begin(), ranking.end()}; + } + + ALOGW("Can't find %s refresh rate by policy with the same mode group" + " as the mode group %d", + refreshRateOrder == RefreshRateOrder::Ascending ? "min" : "max", anchorGroupOpt.value()); + + constexpr std::optional kNoAnchorGroup = std::nullopt; + return rankFrameRates(kNoAnchorGroup, refreshRateOrder, preferredDisplayModeOpt); +} + +FrameRateMode RefreshRateSelector::getActiveMode() const { + std::lock_guard lock(mLock); + return getActiveModeLocked(); +} + +const FrameRateMode& RefreshRateSelector::getActiveModeLocked() const { + return *mActiveModeOpt; +} + +void RefreshRateSelector::setActiveMode(DisplayModeId modeId, Fps renderFrameRate) { + std::lock_guard lock(mLock); + + // Invalidate the cached invocation to getRankedFrameRates. This forces + // the refresh rate to be recomputed on the next call to getRankedFrameRates. + mGetRankedFrameRatesCache.reset(); + + const auto activeModeOpt = mDisplayModes.get(modeId); + LOG_ALWAYS_FATAL_IF(!activeModeOpt); + + mActiveModeOpt.emplace(FrameRateMode{renderFrameRate, ftl::as_non_null(activeModeOpt->get())}); +} + +RefreshRateSelector::RefreshRateSelector(DisplayModes modes, DisplayModeId activeModeId, + Config config) + : mKnownFrameRates(constructKnownFrameRates(modes)), mConfig(config) { + initializeIdleTimer(); + FTL_FAKE_GUARD(kMainThreadContext, updateDisplayModes(std::move(modes), activeModeId)); +} + +void RefreshRateSelector::initializeIdleTimer() { + if (mConfig.idleTimerTimeout > 0ms) { + mIdleTimer.emplace( + "IdleTimer", mConfig.idleTimerTimeout, + [this] { + std::scoped_lock lock(mIdleTimerCallbacksMutex); + if (const auto callbacks = getIdleTimerCallbacks()) { + callbacks->onReset(); + } + }, + [this] { + std::scoped_lock lock(mIdleTimerCallbacksMutex); + if (const auto callbacks = getIdleTimerCallbacks()) { + callbacks->onExpired(); + } + }); + } +} + +void RefreshRateSelector::updateDisplayModes(DisplayModes modes, DisplayModeId activeModeId) { + std::lock_guard lock(mLock); + + // Invalidate the cached invocation to getRankedFrameRates. This forces + // the refresh rate to be recomputed on the next call to getRankedFrameRates. + mGetRankedFrameRatesCache.reset(); + + mDisplayModes = std::move(modes); + const auto activeModeOpt = mDisplayModes.get(activeModeId); + LOG_ALWAYS_FATAL_IF(!activeModeOpt); + mActiveModeOpt = + FrameRateMode{activeModeOpt->get()->getFps(), ftl::as_non_null(activeModeOpt->get())}; + + const auto sortedModes = sortByRefreshRate(mDisplayModes); + mMinRefreshRateModeIt = sortedModes.front(); + mMaxRefreshRateModeIt = sortedModes.back(); + + // Reset the policy because the old one may no longer be valid. + mDisplayManagerPolicy = {}; + mDisplayManagerPolicy.defaultMode = activeModeId; + + mFrameRateOverrideConfig = [&] { + switch (mConfig.enableFrameRateOverride) { + case Config::FrameRateOverride::Disabled: + case Config::FrameRateOverride::AppOverride: + case Config::FrameRateOverride::Enabled: + return mConfig.enableFrameRateOverride; + case Config::FrameRateOverride::AppOverrideNativeRefreshRates: + return shouldEnableFrameRateOverride(sortedModes) + ? Config::FrameRateOverride::AppOverrideNativeRefreshRates + : Config::FrameRateOverride::Disabled; + } + }(); + + if (mConfig.enableFrameRateOverride == + Config::FrameRateOverride::AppOverrideNativeRefreshRates) { + for (const auto& [_, mode] : mDisplayModes) { + mAppOverrideNativeRefreshRates.try_emplace(mode->getFps(), ftl::unit); + } + } + + constructAvailableRefreshRates(); +} + +bool RefreshRateSelector::isPolicyValidLocked(const Policy& policy) const { + // defaultMode must be a valid mode, and within the given refresh rate range. + if (const auto mode = mDisplayModes.get(policy.defaultMode)) { + if (!policy.primaryRanges.physical.includes(mode->get()->getFps())) { + ALOGE("Default mode is not in the primary range."); + return false; + } + } else { + ALOGE("Default mode is not found."); + return false; + } + + const auto& primaryRanges = policy.primaryRanges; + const auto& appRequestRanges = policy.appRequestRanges; + ALOGE_IF(!appRequestRanges.physical.includes(primaryRanges.physical), + "Physical range is invalid: primary: %s appRequest: %s", + to_string(primaryRanges.physical).c_str(), + to_string(appRequestRanges.physical).c_str()); + ALOGE_IF(!appRequestRanges.render.includes(primaryRanges.render), + "Render range is invalid: primary: %s appRequest: %s", + to_string(primaryRanges.render).c_str(), to_string(appRequestRanges.render).c_str()); + + return primaryRanges.valid() && appRequestRanges.valid(); +} + +auto RefreshRateSelector::setPolicy(const PolicyVariant& policy) -> SetPolicyResult { + Policy oldPolicy; + PhysicalDisplayId displayId; + { + std::lock_guard lock(mLock); + oldPolicy = *getCurrentPolicyLocked(); + + const bool valid = ftl::match( + policy, + [this](const auto& policy) { + ftl::FakeGuard guard(mLock); + if (!isPolicyValidLocked(policy)) { + ALOGE("Invalid policy: %s", policy.toString().c_str()); + return false; + } + + using T = std::decay_t; + + if constexpr (std::is_same_v) { + mDisplayManagerPolicy = policy; + } else { + static_assert(std::is_same_v); + mOverridePolicy = policy; + } + return true; + }, + [this](NoOverridePolicy) { + ftl::FakeGuard guard(mLock); + mOverridePolicy.reset(); + return true; + }); + + if (!valid) { + return SetPolicyResult::Invalid; + } + + mGetRankedFrameRatesCache.reset(); + + if (*getCurrentPolicyLocked() == oldPolicy) { + return SetPolicyResult::Unchanged; + } + constructAvailableRefreshRates(); + + displayId = getActiveModeLocked().modePtr->getPhysicalDisplayId(); + } + + const unsigned numModeChanges = std::exchange(mNumModeSwitchesInPolicy, 0u); + + ALOGI("Display %s policy changed\n" + "Previous: %s\n" + "Current: %s\n" + "%u mode changes were performed under the previous policy", + to_string(displayId).c_str(), oldPolicy.toString().c_str(), toString(policy).c_str(), + numModeChanges); + + return SetPolicyResult::Changed; +} + +auto RefreshRateSelector::getCurrentPolicyLocked() const -> const Policy* { + return mOverridePolicy ? &mOverridePolicy.value() : &mDisplayManagerPolicy; +} + +auto RefreshRateSelector::getCurrentPolicy() const -> Policy { + std::lock_guard lock(mLock); + return *getCurrentPolicyLocked(); +} + +auto RefreshRateSelector::getDisplayManagerPolicy() const -> Policy { + std::lock_guard lock(mLock); + return mDisplayManagerPolicy; +} + +bool RefreshRateSelector::isModeAllowed(const FrameRateMode& mode) const { + std::lock_guard lock(mLock); + return std::find(mAppRequestFrameRates.begin(), mAppRequestFrameRates.end(), mode) != + mAppRequestFrameRates.end(); +} + +void RefreshRateSelector::constructAvailableRefreshRates() { + // Filter modes based on current policy and sort on refresh rate. + const Policy* policy = getCurrentPolicyLocked(); + ALOGV("%s: %s ", __func__, policy->toString().c_str()); + + const auto& defaultMode = mDisplayModes.get(policy->defaultMode)->get(); + + const auto filterRefreshRates = [&](const FpsRanges& ranges, + const char* rangeName) REQUIRES(mLock) { + const auto filterModes = [&](const DisplayMode& mode) { + return mode.getResolution() == defaultMode->getResolution() && + mode.getDpi() == defaultMode->getDpi() && + (policy->allowGroupSwitching || mode.getGroup() == defaultMode->getGroup()) && + ranges.physical.includes(mode.getFps()) && + (supportsFrameRateOverride() || ranges.render.includes(mode.getFps())); + }; + + const auto frameRateModes = createFrameRateModes(filterModes, ranges.render); + LOG_ALWAYS_FATAL_IF(frameRateModes.empty(), + "No matching frame rate modes for %s range. policy: %s", rangeName, + policy->toString().c_str()); + + const auto stringifyModes = [&] { + std::string str; + for (const auto& frameRateMode : frameRateModes) { + str += to_string(frameRateMode) + " "; + } + return str; + }; + ALOGV("%s render rates: %s", rangeName, stringifyModes().c_str()); + + return frameRateModes; + }; + + mPrimaryFrameRates = filterRefreshRates(policy->primaryRanges, "primary"); + mAppRequestFrameRates = filterRefreshRates(policy->appRequestRanges, "app request"); +} + +Fps RefreshRateSelector::findClosestKnownFrameRate(Fps frameRate) const { + using namespace fps_approx_ops; + + if (frameRate <= mKnownFrameRates.front()) { + return mKnownFrameRates.front(); + } + + if (frameRate >= mKnownFrameRates.back()) { + return mKnownFrameRates.back(); + } + + auto lowerBound = std::lower_bound(mKnownFrameRates.begin(), mKnownFrameRates.end(), frameRate, + isStrictlyLess); + + const auto distance1 = std::abs(frameRate.getValue() - lowerBound->getValue()); + const auto distance2 = std::abs(frameRate.getValue() - std::prev(lowerBound)->getValue()); + return distance1 < distance2 ? *lowerBound : *std::prev(lowerBound); +} + +auto RefreshRateSelector::getIdleTimerAction() const -> KernelIdleTimerAction { + std::lock_guard lock(mLock); + + const Fps deviceMinFps = mMinRefreshRateModeIt->second->getFps(); + const DisplayModePtr& minByPolicy = getMinRefreshRateByPolicyLocked(); + + // Kernel idle timer will set the refresh rate to the device min. If DisplayManager says that + // the min allowed refresh rate is higher than the device min, we do not want to enable the + // timer. + if (isStrictlyLess(deviceMinFps, minByPolicy->getFps())) { + return KernelIdleTimerAction::TurnOff; + } + + const DisplayModePtr& maxByPolicy = + getMaxRefreshRateByPolicyLocked(getActiveModeLocked().modePtr->getGroup()); + if (minByPolicy == maxByPolicy) { + // Turn on the timer when the min of the primary range is below the device min. + if (const Policy* currentPolicy = getCurrentPolicyLocked(); + isApproxLess(currentPolicy->primaryRanges.physical.min, deviceMinFps)) { + return KernelIdleTimerAction::TurnOn; + } + return KernelIdleTimerAction::TurnOff; + } + + // Turn on the timer in all other cases. + return KernelIdleTimerAction::TurnOn; +} + +int RefreshRateSelector::getFrameRateDivisor(Fps displayRefreshRate, Fps layerFrameRate) { + // This calculation needs to be in sync with the java code + // in DisplayManagerService.getDisplayInfoForFrameRateOverride + + // The threshold must be smaller than 0.001 in order to differentiate + // between the fractional pairs (e.g. 59.94 and 60). + constexpr float kThreshold = 0.0009f; + const auto numPeriods = displayRefreshRate.getValue() / layerFrameRate.getValue(); + const auto numPeriodsRounded = std::round(numPeriods); + if (std::abs(numPeriods - numPeriodsRounded) > kThreshold) { + return 0; + } + + return static_cast(numPeriodsRounded); +} + +bool RefreshRateSelector::isFractionalPairOrMultiple(Fps smaller, Fps bigger) { + if (isStrictlyLess(bigger, smaller)) { + return isFractionalPairOrMultiple(bigger, smaller); + } + + const auto multiplier = std::round(bigger.getValue() / smaller.getValue()); + constexpr float kCoef = 1000.f / 1001.f; + return isApproxEqual(bigger, Fps::fromValue(smaller.getValue() * multiplier / kCoef)) || + isApproxEqual(bigger, Fps::fromValue(smaller.getValue() * multiplier * kCoef)); +} + +void RefreshRateSelector::dump(utils::Dumper& dumper) const { + using namespace std::string_view_literals; + + std::lock_guard lock(mLock); + + const auto activeMode = getActiveModeLocked(); + dumper.dump("activeMode"sv, to_string(activeMode)); + + dumper.dump("displayModes"sv); + { + utils::Dumper::Indent indent(dumper); + for (const auto& [id, mode] : mDisplayModes) { + dumper.dump({}, to_string(*mode)); + } + } + + dumper.dump("displayManagerPolicy"sv, mDisplayManagerPolicy.toString()); + + if (const Policy& currentPolicy = *getCurrentPolicyLocked(); + mOverridePolicy && currentPolicy != mDisplayManagerPolicy) { + dumper.dump("overridePolicy"sv, currentPolicy.toString()); + } + + dumper.dump("frameRateOverrideConfig"sv, *ftl::enum_name(mFrameRateOverrideConfig)); + + dumper.dump("idleTimer"sv); + { + utils::Dumper::Indent indent(dumper); + dumper.dump("interval"sv, mIdleTimer.transform(&OneShotTimer::interval)); + dumper.dump("controller"sv, + mConfig.kernelIdleTimerController + .and_then(&ftl::enum_name) + .value_or("Platform"sv)); + } +} + +std::chrono::milliseconds RefreshRateSelector::getIdleTimerTimeout() { + return mConfig.idleTimerTimeout; +} + +} // namespace android::scheduler + +// TODO(b/129481165): remove the #pragma below and fix conversion issues +#pragma clang diagnostic pop // ignored "-Wextra" diff --git a/services/surfaceflinger/Scheduler/RefreshRateConfigs.h b/services/surfaceflinger/Scheduler/RefreshRateSelector.h similarity index 58% rename from services/surfaceflinger/Scheduler/RefreshRateConfigs.h rename to services/surfaceflinger/Scheduler/RefreshRateSelector.h index a79002e959b123011497736cab45d25e2951f35d..5052e6e257907cb91d7f334bbd44bcfb25c3695a 100644 --- a/services/surfaceflinger/Scheduler/RefreshRateConfigs.h +++ b/services/surfaceflinger/Scheduler/RefreshRateSelector.h @@ -18,19 +18,24 @@ #include #include -#include #include #include +#include +#include +#include +#include #include #include +#include #include #include "DisplayHardware/DisplayMode.h" -#include "DisplayHardware/HWComposer.h" #include "Scheduler/OneShotTimer.h" #include "Scheduler/StrongTyping.h" +#include "ThreadContext.h" +#include "Utils/Dumper.h" namespace android::scheduler { @@ -45,19 +50,19 @@ inline DisplayModeEvent operator|(DisplayModeEvent lhs, DisplayModeEvent rhs) { using FrameRateOverride = DisplayEventReceiver::Event::FrameRateOverride; -/** - * This class is used to encapsulate configuration for refresh rates. It holds information - * about available refresh rates on the device, and the mapping between the numbers and human - * readable names. - */ -class RefreshRateConfigs { +// Selects the refresh rate of a display by ranking its `DisplayModes` in accordance with +// the DisplayManager (or override) `Policy`, the `LayerRequirement` of each active layer, +// and `GlobalSignals`. +class RefreshRateSelector { public: // Margin used when matching refresh rates to the content desired ones. static constexpr nsecs_t MARGIN_FOR_PERIOD_CALCULATION = std::chrono::nanoseconds(800us).count(); - struct Policy { - private: + // The lowest Render Frame Rate that will ever be selected + static constexpr Fps kMinSupportedFrameRate = 20_Hz; + + class Policy { static constexpr int kAllowGroupSwitchingDefault = false; public: @@ -66,40 +71,31 @@ public: DisplayModeId defaultMode; // Whether or not we switch mode groups to get the best frame rate. bool allowGroupSwitching = kAllowGroupSwitchingDefault; - // The primary refresh rate range represents display manager's general guidance on the - // display modes we'll consider when switching refresh rates. Unless we get an explicit - // signal from an app, we should stay within this range. - FpsRange primaryRange; - // The app request refresh rate range allows us to consider more display modes when - // switching refresh rates. Although we should generally stay within the primary range, - // specific considerations, such as layer frame rate settings specified via the - // setFrameRate() api, may cause us to go outside the primary range. We never go outside the - // app request range. The app request range will be greater than or equal to the primary - // refresh rate range, never smaller. - FpsRange appRequestRange; + // The primary refresh rate ranges. @see DisplayModeSpecs.aidl for details. + // TODO(b/257072060): use the render range when selecting SF render rate + // or the app override frame rate + FpsRanges primaryRanges; + // The app request refresh rate ranges. @see DisplayModeSpecs.aidl for details. + FpsRanges appRequestRanges; Policy() = default; - Policy(DisplayModeId defaultMode, FpsRange range) - : Policy(defaultMode, kAllowGroupSwitchingDefault, range, range) {} - - Policy(DisplayModeId defaultMode, bool allowGroupSwitching, FpsRange range) - : Policy(defaultMode, allowGroupSwitching, range, range) {} - - Policy(DisplayModeId defaultMode, FpsRange primaryRange, FpsRange appRequestRange) - : Policy(defaultMode, kAllowGroupSwitchingDefault, primaryRange, appRequestRange) {} + Policy(DisplayModeId defaultMode, FpsRange range, + bool allowGroupSwitching = kAllowGroupSwitchingDefault) + : Policy(defaultMode, FpsRanges{range, range}, FpsRanges{range, range}, + allowGroupSwitching) {} - Policy(DisplayModeId defaultMode, bool allowGroupSwitching, FpsRange primaryRange, - FpsRange appRequestRange) + Policy(DisplayModeId defaultMode, FpsRanges primaryRanges, FpsRanges appRequestRanges, + bool allowGroupSwitching = kAllowGroupSwitchingDefault) : defaultMode(defaultMode), allowGroupSwitching(allowGroupSwitching), - primaryRange(primaryRange), - appRequestRange(appRequestRange) {} + primaryRanges(primaryRanges), + appRequestRanges(appRequestRanges) {} bool operator==(const Policy& other) const { using namespace fps_approx_ops; - return defaultMode == other.defaultMode && primaryRange == other.primaryRange && - appRequestRange == other.appRequestRange && + return defaultMode == other.defaultMode && primaryRanges == other.primaryRanges && + appRequestRanges == other.appRequestRanges && allowGroupSwitching == other.allowGroupSwitching; } @@ -107,23 +103,28 @@ public: std::string toString() const; }; - // Return code set*Policy() to indicate the current policy is unchanged. - static constexpr int CURRENT_POLICY_UNCHANGED = 1; + enum class SetPolicyResult { Invalid, Unchanged, Changed }; // We maintain the display manager policy and the override policy separately. The override // policy is used by CTS tests to get a consistent device state for testing. While the override // policy is set, it takes precedence over the display manager policy. Once the override policy // is cleared, we revert to using the display manager policy. + struct DisplayManagerPolicy : Policy { + using Policy::Policy; + }; + + struct OverridePolicy : Policy { + using Policy::Policy; + }; + + struct NoOverridePolicy {}; + + using PolicyVariant = std::variant; + + SetPolicyResult setPolicy(const PolicyVariant&) EXCLUDES(mLock) REQUIRES(kMainThreadContext); + + void onModeChangeInitiated() REQUIRES(kMainThreadContext) { mNumModeSwitchesInPolicy++; } - // Sets the display manager policy to choose refresh rates. The return value will be: - // - A negative value if the policy is invalid or another error occurred. - // - NO_ERROR if the policy was successfully updated, and the current policy is different from - // what it was before the call. - // - CURRENT_POLICY_UNCHANGED if the policy was successfully updated, but the current policy - // is the same as it was before the call. - status_t setDisplayManagerPolicy(const Policy& policy) EXCLUDES(mLock); - // Sets the override policy. See setDisplayManagerPolicy() for the meaning of the return value. - status_t setOverridePolicy(const std::optional& policy) EXCLUDES(mLock); // Gets the current policy, which will be the override policy if active, and the display manager // policy otherwise. Policy getCurrentPolicy() const EXCLUDES(mLock); @@ -131,7 +132,7 @@ public: Policy getDisplayManagerPolicy() const EXCLUDES(mLock); // Returns true if mode is allowed by the current policy. - bool isModeAllowed(DisplayModeId) const EXCLUDES(mLock); + bool isModeAllowed(const FrameRateMode&) const EXCLUDES(mLock); // Describes the different options the layer voted for refresh rate enum class LayerVoteType { @@ -184,31 +185,68 @@ public: bool touch = false; // True if the system hasn't seen any buffers posted to layers recently. bool idle = false; + // Whether the display is about to be powered on, or has been in PowerMode::ON + // within the timeout of DisplayPowerTimer. + bool powerOnImminent = false; bool operator==(GlobalSignals other) const { - return touch == other.touch && idle == other.idle; + return touch == other.touch && idle == other.idle && + powerOnImminent == other.powerOnImminent; + } + + auto toString() const { + return ftl::Concat("{touch=", touch, ", idle=", idle, + ", powerOnImminent=", powerOnImminent, '}'); } }; - // Returns the refresh rate that best fits the given layers, and whether the refresh rate was - // chosen based on touch boost and/or idle timer. - std::pair getBestRefreshRate( - const std::vector&, GlobalSignals) const EXCLUDES(mLock); + struct ScoredFrameRate { + FrameRateMode frameRateMode; + float score = 0.0f; + + bool operator==(const ScoredFrameRate& other) const { + return frameRateMode == other.frameRateMode && score == other.score; + } + + static bool scoresEqual(float lhs, float rhs) { + constexpr float kEpsilon = 0.0001f; + return std::abs(lhs - rhs) <= kEpsilon; + } + + struct DescendingScore { + bool operator()(const ScoredFrameRate& lhs, const ScoredFrameRate& rhs) const { + return lhs.score > rhs.score && !scoresEqual(lhs.score, rhs.score); + } + }; + }; + + using FrameRateRanking = std::vector; + + struct RankedFrameRates { + FrameRateRanking ranking; // Ordered by descending score. + GlobalSignals consideredSignals; + + bool operator==(const RankedFrameRates& other) const { + return ranking == other.ranking && consideredSignals == other.consideredSignals; + } + }; + + RankedFrameRates getRankedFrameRates(const std::vector&, GlobalSignals) const + EXCLUDES(mLock); FpsRange getSupportedRefreshRateRange() const EXCLUDES(mLock) { std::lock_guard lock(mLock); return {mMinRefreshRateModeIt->second->getFps(), mMaxRefreshRateModeIt->second->getFps()}; } - std::optional onKernelTimerChanged(std::optional desiredActiveModeId, - bool timerExpired) const EXCLUDES(mLock); + ftl::Optional onKernelTimerChanged( + std::optional desiredActiveModeId, bool timerExpired) const + EXCLUDES(mLock); - // Returns the highest refresh rate according to the current policy. May change at runtime. Only - // uses the primary range, not the app request range. - DisplayModePtr getMaxRefreshRateByPolicy() const EXCLUDES(mLock); + void setActiveMode(DisplayModeId, Fps renderFrameRate) EXCLUDES(mLock); - void setActiveModeId(DisplayModeId) EXCLUDES(mLock); - DisplayModePtr getActiveMode() const EXCLUDES(mLock); + // See mActiveModeOpt for thread safety. + FrameRateMode getActiveMode() const EXCLUDES(mLock); // Returns a known frame rate that is the closest to frameRate Fps findClosestKnownFrameRate(Fps frameRate) const; @@ -217,7 +255,23 @@ public: // Configuration flags. struct Config { - bool enableFrameRateOverride = false; + enum class FrameRateOverride { + // Do not override the frame rate for an app + Disabled, + + // Override the frame rate for an app to a value which is also + // a display refresh rate + AppOverrideNativeRefreshRates, + + // Override the frame rate for an app to any value + AppOverride, + + // Override the frame rate for all apps and all values. + Enabled, + + ftl_last = Enabled + }; + FrameRateOverride enableFrameRateOverride = FrameRateOverride::Disabled; // Specifies the upper refresh rate threshold (inclusive) for layer vote types of multiple // or heuristic, such that refresh rates higher than this value will not be voted for. 0 if @@ -229,37 +283,49 @@ public: // The controller representing how the kernel idle timer will be configured // either on the HWC api or sysprop. - std::optional kernelIdleTimerController; + ftl::Optional kernelIdleTimerController; }; - RefreshRateConfigs(DisplayModes, DisplayModeId activeModeId, - Config config = {.enableFrameRateOverride = false, - .frameRateMultipleThreshold = 0, - .idleTimerTimeout = 0ms, - .kernelIdleTimerController = {}}); + RefreshRateSelector( + DisplayModes, DisplayModeId activeModeId, + Config config = {.enableFrameRateOverride = Config::FrameRateOverride::Disabled, + .frameRateMultipleThreshold = 0, + .idleTimerTimeout = 0ms, + .kernelIdleTimerController = {}}); + + RefreshRateSelector(const RefreshRateSelector&) = delete; + RefreshRateSelector& operator=(const RefreshRateSelector&) = delete; - RefreshRateConfigs(const RefreshRateConfigs&) = delete; - RefreshRateConfigs& operator=(const RefreshRateConfigs&) = delete; + const DisplayModes& displayModes() const { return mDisplayModes; } // Returns whether switching modes (refresh rate or resolution) is possible. // TODO(b/158780872): Consider HAL support, and skip frame rate detection if the modes only - // differ in resolution. + // differ in resolution. Once Config::FrameRateOverride::Enabled becomes the default, + // we can probably remove canSwitch altogether since all devices will be able + // to switch to a frame rate divisor. bool canSwitch() const EXCLUDES(mLock) { std::lock_guard lock(mLock); - return mDisplayModes.size() > 1; + return mDisplayModes.size() > 1 || + mFrameRateOverrideConfig == Config::FrameRateOverride::Enabled; } // Class to enumerate options around toggling the kernel timer on and off. enum class KernelIdleTimerAction { - TurnOff, // Turn off the idle timer. - TurnOn // Turn on the idle timer. + TurnOff, // Turn off the idle timer. + TurnOn // Turn on the idle timer. }; // Checks whether kernel idle timer should be active depending the policy decisions around // refresh rates. KernelIdleTimerAction getIdleTimerAction() const; - bool supportsFrameRateOverrideByContent() const { return mSupportsFrameRateOverrideByContent; } + bool supportsAppFrameRateOverrideByContent() const { + return mFrameRateOverrideConfig != Config::FrameRateOverride::Disabled; + } + + bool supportsFrameRateOverride() const { + return mFrameRateOverrideConfig == Config::FrameRateOverride::Enabled; + } // Return the display refresh rate divisor to match the layer // frame rate, or 0 if the display refresh rate is not a multiple of the @@ -313,27 +379,32 @@ public: } } - void resetIdleTimer(bool kernelOnly) { - if (!mIdleTimer) { - return; + void resetKernelIdleTimer() { + if (mIdleTimer && mConfig.kernelIdleTimerController) { + mIdleTimer->reset(); } - if (kernelOnly && !mConfig.kernelIdleTimerController.has_value()) { - return; + } + + void resetIdleTimer() { + if (mIdleTimer) { + mIdleTimer->reset(); } - mIdleTimer->reset(); } - void dump(std::string& result) const EXCLUDES(mLock); + void dump(utils::Dumper&) const EXCLUDES(mLock); std::chrono::milliseconds getIdleTimerTimeout(); private: - friend struct TestableRefreshRateConfigs; + friend struct TestableRefreshRateSelector; void constructAvailableRefreshRates() REQUIRES(mLock); - std::pair getBestRefreshRateLocked( - const std::vector&, GlobalSignals) const REQUIRES(mLock); + // See mActiveModeOpt for thread safety. + const FrameRateMode& getActiveModeLocked() const REQUIRES(mLock); + + RankedFrameRates getRankedFrameRatesLocked(const std::vector& layers, + GlobalSignals signals) const REQUIRES(mLock); // Returns number of display frames and remainder when dividing the layer refresh period by // display refresh period. @@ -346,13 +417,27 @@ private: // Returns the highest refresh rate according to the current policy. May change at runtime. Only // uses the primary range, not the app request range. const DisplayModePtr& getMaxRefreshRateByPolicyLocked(int anchorGroup) const REQUIRES(mLock); - const DisplayModePtr& getMaxRefreshRateByPolicyLocked() const REQUIRES(mLock) { - return getMaxRefreshRateByPolicyLocked(mActiveModeIt->second->getGroup()); - } + + struct RefreshRateScoreComparator; + + enum class RefreshRateOrder { + Ascending, + Descending, + + ftl_last = Descending + }; + + // Only uses the primary range, not the app request range. + FrameRateRanking rankFrameRates( + std::optional anchorGroupOpt, RefreshRateOrder refreshRateOrder, + std::optional preferredDisplayModeOpt = std::nullopt) const + REQUIRES(mLock); const Policy* getCurrentPolicyLocked() const REQUIRES(mLock); bool isPolicyValidLocked(const Policy& policy) const REQUIRES(mLock); + // Returns the refresh rate score as a ratio to max refresh rate, which has a score of 1. + float calculateDistanceScoreFromMax(Fps refreshRate) const REQUIRES(mLock); // calculates a score for a layer. Used to determine the display refresh rate // and the frame rate override for certains applications. float calculateLayerScoreLocked(const LayerRequirement&, Fps refreshRate, @@ -361,7 +446,8 @@ private: float calculateNonExactMatchingLayerScoreLocked(const LayerRequirement&, Fps refreshRate) const REQUIRES(mLock); - void updateDisplayModes(DisplayModes, DisplayModeId activeModeId) EXCLUDES(mLock); + void updateDisplayModes(DisplayModes, DisplayModeId activeModeId) EXCLUDES(mLock) + REQUIRES(kMainThreadContext); void initializeIdleTimer(); @@ -372,22 +458,41 @@ private: : mIdleTimerCallbacks->platform; } + bool isNativeRefreshRate(Fps fps) const REQUIRES(mLock) { + LOG_ALWAYS_FATAL_IF(mConfig.enableFrameRateOverride != + Config::FrameRateOverride::AppOverrideNativeRefreshRates, + "should only be called when " + "Config::FrameRateOverride::AppOverrideNativeRefreshRates is used"); + return mAppOverrideNativeRefreshRates.contains(fps); + } + + std::vector createFrameRateModes( + std::function&& filterModes, const FpsRange&) const + REQUIRES(mLock); + // The display modes of the active display. The DisplayModeIterators below are pointers into // this container, so must be invalidated whenever the DisplayModes change. The Policy below // is also dependent, so must be reset as well. DisplayModes mDisplayModes GUARDED_BY(mLock); - DisplayModeIterator mActiveModeIt GUARDED_BY(mLock); + // Set of supported display refresh rates for easy lookup + // when FrameRateOverride::AppOverrideNativeRefreshRates is in use. + ftl::SmallMap mAppOverrideNativeRefreshRates; + + ftl::Optional mActiveModeOpt GUARDED_BY(mLock); + DisplayModeIterator mMinRefreshRateModeIt GUARDED_BY(mLock); DisplayModeIterator mMaxRefreshRateModeIt GUARDED_BY(mLock); // Display modes that satisfy the Policy's ranges, filtered and sorted by refresh rate. - std::vector mPrimaryRefreshRates GUARDED_BY(mLock); - std::vector mAppRequestRefreshRates GUARDED_BY(mLock); + std::vector mPrimaryFrameRates GUARDED_BY(mLock); + std::vector mAppRequestFrameRates GUARDED_BY(mLock); Policy mDisplayManagerPolicy GUARDED_BY(mLock); std::optional mOverridePolicy GUARDED_BY(mLock); + unsigned mNumModeSwitchesInPolicy GUARDED_BY(kMainThreadContext) = 0; + mutable std::mutex mLock; // A sorted list of known frame rates that a Heuristic layer will choose @@ -395,19 +500,19 @@ private: const std::vector mKnownFrameRates; const Config mConfig; - bool mSupportsFrameRateOverrideByContent; + Config::FrameRateOverride mFrameRateOverrideConfig; - struct GetBestRefreshRateCache { + struct GetRankedFrameRatesCache { std::pair, GlobalSignals> arguments; - std::pair result; + RankedFrameRates result; }; - mutable std::optional mGetBestRefreshRateCache GUARDED_BY(mLock); + mutable std::optional mGetRankedFrameRatesCache GUARDED_BY(mLock); // Declare mIdleTimer last to ensure its thread joins before the mutex/callbacks are destroyed. std::mutex mIdleTimerCallbacksMutex; std::optional mIdleTimerCallbacks GUARDED_BY(mIdleTimerCallbacksMutex); // Used to detect (lack of) frame activity. - std::optional mIdleTimer; + ftl::Optional mIdleTimer; }; } // namespace android::scheduler diff --git a/services/surfaceflinger/Scheduler/RefreshRateStats.h b/services/surfaceflinger/Scheduler/RefreshRateStats.h index ed65bc607d7ed7865d4c6751be8c46aaf49df13b..67e1b9c2ea9c4f050c02ca693d4766f8c0247c25 100644 --- a/services/surfaceflinger/Scheduler/RefreshRateStats.h +++ b/services/surfaceflinger/Scheduler/RefreshRateStats.h @@ -22,6 +22,7 @@ #include #include +#include #include #include @@ -82,12 +83,18 @@ public: flushTime(); TotalTimes totalTimes = ftl::init::map("ScreenOff", mScreenOffTime); - const auto zero = std::chrono::milliseconds::zero(); // Sum the times for modes that map to the same name, e.g. "60 Hz". for (const auto& [fps, time] : mFpsTotalTimes) { const auto string = to_string(fps); - const auto total = std::as_const(totalTimes).get(string).value_or(std::cref(zero)); + const auto total = std::as_const(totalTimes) + .get(string) + .or_else(ftl::static_ref([] { + using namespace std::chrono_literals; + return 0ms; + })) + .value(); + totalTimes.emplace_or_replace(string, total.get() + time); } @@ -114,15 +121,18 @@ private: mPreviousRecordedTime = currentTime; const auto duration = std::chrono::milliseconds{ns2ms(timeElapsed)}; - const auto zero = std::chrono::milliseconds::zero(); - uint32_t fps = 0; if (mCurrentPowerMode == PowerMode::ON) { // Normal power mode is counted under different config modes. const auto total = std::as_const(mFpsTotalTimes) .get(mCurrentRefreshRate) - .value_or(std::cref(zero)); + .or_else(ftl::static_ref([] { + using namespace std::chrono_literals; + return 0ms; + })) + .value(); + mFpsTotalTimes.emplace_or_replace(mCurrentRefreshRate, total.get() + duration); fps = static_cast(mCurrentRefreshRate.getIntValue()); diff --git a/services/surfaceflinger/Scheduler/Scheduler.cpp b/services/surfaceflinger/Scheduler/Scheduler.cpp index 8b1a5d9720a492b387797cfcd7de2b75f9183492..93195436669b94bd3878bbaa72ba22ee93b1f072 100644 --- a/services/surfaceflinger/Scheduler/Scheduler.cpp +++ b/services/surfaceflinger/Scheduler/Scheduler.cpp @@ -25,14 +25,17 @@ #include #include #include +#include #include +#include +#include #include #include -#include #include -#include #include +#include + #include #include #include @@ -41,14 +44,15 @@ #include #include "../Layer.h" -#include "DispSyncSource.h" +#include "Display/DisplayMap.h" #include "EventThread.h" #include "FrameRateOverrideMappings.h" -#include "InjectVSyncSource.h" +#include "FrontEnd/LayerHandle.h" #include "OneShotTimer.h" #include "SurfaceFlingerProperties.h" -#include "VSyncPredictor.h" -#include "VSyncReactor.h" +#include "VSyncTracker.h" +#include "VsyncController.h" +#include "VsyncSchedule.h" #define RETURN_IF_INVALID_HANDLE(handle, ...) \ do { \ @@ -60,16 +64,25 @@ namespace android::scheduler { -Scheduler::Scheduler(ICompositor& compositor, ISchedulerCallback& callback, FeatureFlags features) - : impl::MessageQueue(compositor), mFeatures(features), mSchedulerCallback(callback) {} +Scheduler::Scheduler(ICompositor& compositor, ISchedulerCallback& callback, FeatureFlags features, + sp modulatorPtr) + : impl::MessageQueue(compositor), + mFeatures(features), + mVsyncModulator(std::move(modulatorPtr)), + mSchedulerCallback(callback) {} Scheduler::~Scheduler() { + // MessageQueue depends on VsyncSchedule, so first destroy it. + // Otherwise, MessageQueue will get destroyed after Scheduler's dtor, + // which will cause a use-after-free issue. + Impl::destroyVsync(); + // Stop timers and wait for their threads to exit. mDisplayPowerTimer.reset(); mTouchTimer.reset(); - // Stop idle timer and clear callbacks, as the RefreshRateConfigs may outlive the Scheduler. - setRefreshRateConfigs(nullptr); + // Stop idle timer and clear callbacks, as the RefreshRateSelector may outlive the Scheduler. + demotePacesetterDisplay(); } void Scheduler::startTimers() { @@ -94,36 +107,48 @@ void Scheduler::startTimers() { } } -void Scheduler::setRefreshRateConfigs(std::shared_ptr configs) { - // The current RefreshRateConfigs instance may outlive this call, so unbind its idle timer. - { - // mRefreshRateConfigsLock is not locked here to avoid the deadlock - // as the callback can attempt to acquire the lock before stopIdleTimer can finish - // the execution. It's safe to FakeGuard as main thread is the only thread that - // writes to the mRefreshRateConfigs. - ftl::FakeGuard guard(mRefreshRateConfigsLock); - if (mRefreshRateConfigs) { - mRefreshRateConfigs->stopIdleTimer(); - mRefreshRateConfigs->clearIdleTimerCallbacks(); - } - } +void Scheduler::setPacesetterDisplay(std::optional pacesetterIdOpt) { + demotePacesetterDisplay(); + + promotePacesetterDisplay(pacesetterIdOpt); +} + +void Scheduler::registerDisplay(PhysicalDisplayId displayId, RefreshRateSelectorPtr selectorPtr) { + registerDisplayInternal(displayId, std::move(selectorPtr), + std::make_shared(displayId, mFeatures)); +} + +void Scheduler::registerDisplayInternal(PhysicalDisplayId displayId, + RefreshRateSelectorPtr selectorPtr, + VsyncSchedulePtr schedulePtr) { + demotePacesetterDisplay(); + + std::shared_ptr pacesetterVsyncSchedule; { - // Clear state that depends on the current instance. - std::scoped_lock lock(mPolicyLock); - mPolicy = {}; + std::scoped_lock lock(mDisplayLock); + mDisplays.emplace_or_replace(displayId, std::move(selectorPtr), std::move(schedulePtr)); + + pacesetterVsyncSchedule = promotePacesetterDisplayLocked(); } + applyNewVsyncSchedule(std::move(pacesetterVsyncSchedule)); +} - std::scoped_lock lock(mRefreshRateConfigsLock); - mRefreshRateConfigs = std::move(configs); - if (!mRefreshRateConfigs) return; +void Scheduler::unregisterDisplay(PhysicalDisplayId displayId) { + demotePacesetterDisplay(); - mRefreshRateConfigs->setIdleTimerCallbacks( - {.platform = {.onReset = [this] { idleTimerCallback(TimerState::Reset); }, - .onExpired = [this] { idleTimerCallback(TimerState::Expired); }}, - .kernel = {.onReset = [this] { kernelIdleTimerCallback(TimerState::Reset); }, - .onExpired = [this] { kernelIdleTimerCallback(TimerState::Expired); }}}); + std::shared_ptr pacesetterVsyncSchedule; + { + std::scoped_lock lock(mDisplayLock); + mDisplays.erase(displayId); - mRefreshRateConfigs->startIdleTimer(); + // Do not allow removing the final display. Code in the scheduler expects + // there to be at least one display. (This may be relaxed in the future with + // headless virtual display.) + LOG_ALWAYS_FATAL_IF(mDisplays.empty(), "Cannot unregister all displays!"); + + pacesetterVsyncSchedule = promotePacesetterDisplayLocked(); + } + applyNewVsyncSchedule(std::move(pacesetterVsyncSchedule)); } void Scheduler::run() { @@ -132,74 +157,84 @@ void Scheduler::run() { } } -void Scheduler::createVsyncSchedule(FeatureFlags features) { - mVsyncSchedule.emplace(features); -} +void Scheduler::onFrameSignal(ICompositor& compositor, VsyncId vsyncId, + TimePoint expectedVsyncTime) { + const TimePoint frameTime = SchedulerClock::now(); -std::unique_ptr Scheduler::makePrimaryDispSyncSource( - const char* name, std::chrono::nanoseconds workDuration, - std::chrono::nanoseconds readyDuration, bool traceVsync) { - return std::make_unique(mVsyncSchedule->getDispatch(), - mVsyncSchedule->getTracker(), workDuration, - readyDuration, traceVsync, name); + if (!compositor.commit(frameTime, vsyncId, expectedVsyncTime)) { + return; + } + + compositor.composite(frameTime, vsyncId); + compositor.sample(); } std::optional Scheduler::getFrameRateOverride(uid_t uid) const { - const auto refreshRateConfigs = holdRefreshRateConfigs(); const bool supportsFrameRateOverrideByContent = - refreshRateConfigs->supportsFrameRateOverrideByContent(); + pacesetterSelectorPtr()->supportsAppFrameRateOverrideByContent(); return mFrameRateOverrideMappings .getFrameRateOverrideForUid(uid, supportsFrameRateOverrideByContent); } -bool Scheduler::isVsyncValid(nsecs_t expectedVsyncTimestamp, uid_t uid) const { +bool Scheduler::isVsyncValid(TimePoint expectedVsyncTimestamp, uid_t uid) const { const auto frameRate = getFrameRateOverride(uid); if (!frameRate.has_value()) { return true; } - return mVsyncSchedule->getTracker().isVSyncInPhase(expectedVsyncTimestamp, *frameRate); + ATRACE_FORMAT("%s uid: %d frameRate: %s", __func__, uid, to_string(*frameRate).c_str()); + return getVsyncSchedule()->getTracker().isVSyncInPhase(expectedVsyncTimestamp.ns(), *frameRate); } -impl::EventThread::ThrottleVsyncCallback Scheduler::makeThrottleVsyncCallback() const { - std::scoped_lock lock(mRefreshRateConfigsLock); +bool Scheduler::isVsyncInPhase(TimePoint timePoint, const Fps frameRate) const { + return getVsyncSchedule()->getTracker().isVSyncInPhase(timePoint.ns(), frameRate); +} +impl::EventThread::ThrottleVsyncCallback Scheduler::makeThrottleVsyncCallback() const { return [this](nsecs_t expectedVsyncTimestamp, uid_t uid) { - return !isVsyncValid(expectedVsyncTimestamp, uid); + return !isVsyncValid(TimePoint::fromNs(expectedVsyncTimestamp), uid); }; } impl::EventThread::GetVsyncPeriodFunction Scheduler::makeGetVsyncPeriodFunction() const { return [this](uid_t uid) { - const Fps refreshRate = holdRefreshRateConfigs()->getActiveMode()->getFps(); - const auto currentPeriod = - mVsyncSchedule->getTracker().currentPeriod() ?: refreshRate.getPeriodNsecs(); + const auto [refreshRate, period] = [this] { + std::scoped_lock lock(mDisplayLock); + 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 Period currentPeriod = period != Period::zero() ? period : refreshRate.getPeriod(); const auto frameRate = getFrameRateOverride(uid); if (!frameRate.has_value()) { - return currentPeriod; + return currentPeriod.ns(); } - const auto divisor = RefreshRateConfigs::getFrameRateDivisor(refreshRate, *frameRate); + const auto divisor = RefreshRateSelector::getFrameRateDivisor(refreshRate, *frameRate); if (divisor <= 1) { - return currentPeriod; + return currentPeriod.ns(); } - return currentPeriod * divisor; + return currentPeriod.ns() * divisor; }; } -ConnectionHandle Scheduler::createConnection( - const char* connectionName, frametimeline::TokenManager* tokenManager, - std::chrono::nanoseconds workDuration, std::chrono::nanoseconds readyDuration, - impl::EventThread::InterceptVSyncsCallback interceptCallback) { - auto vsyncSource = makePrimaryDispSyncSource(connectionName, workDuration, readyDuration); - auto throttleVsync = makeThrottleVsyncCallback(); - auto getVsyncPeriod = makeGetVsyncPeriodFunction(); - auto eventThread = std::make_unique(std::move(vsyncSource), tokenManager, - std::move(interceptCallback), - std::move(throttleVsync), - std::move(getVsyncPeriod)); - return createConnection(std::move(eventThread)); +ConnectionHandle Scheduler::createEventThread(Cycle cycle, + frametimeline::TokenManager* tokenManager, + std::chrono::nanoseconds workDuration, + std::chrono::nanoseconds readyDuration) { + auto eventThread = std::make_unique(cycle == Cycle::Render ? "app" : "appSf", + getVsyncSchedule(), tokenManager, + makeThrottleVsyncCallback(), + makeGetVsyncPeriodFunction(), + workDuration, readyDuration); + + auto& handle = cycle == Cycle::Render ? mAppConnectionHandle : mSfConnectionHandle; + handle = createConnection(std::move(eventThread)); + return handle; } ConnectionHandle Scheduler::createConnection(std::unique_ptr eventThread) { @@ -214,15 +249,21 @@ ConnectionHandle Scheduler::createConnection(std::unique_ptr eventT } sp Scheduler::createConnectionInternal( - EventThread* eventThread, ISurfaceComposer::EventRegistrationFlags eventRegistration) { - return eventThread->createEventConnection([&] { resync(); }, eventRegistration); + EventThread* eventThread, EventRegistrationFlags eventRegistration, + const sp& layerHandle) { + int32_t layerId = static_cast(LayerHandle::getLayerId(layerHandle)); + auto connection = eventThread->createEventConnection([&] { resync(); }, eventRegistration); + mLayerHistory.attachChoreographer(layerId, connection); + return connection; } sp Scheduler::createDisplayEventConnection( - ConnectionHandle handle, ISurfaceComposer::EventRegistrationFlags eventRegistration) { + ConnectionHandle handle, EventRegistrationFlags eventRegistration, + const sp& layerHandle) { std::lock_guard lock(mConnectionsLock); RETURN_IF_INVALID_HANDLE(handle, nullptr); - return createConnectionInternal(mConnections[handle].thread.get(), eventRegistration); + return createConnectionInternal(mConnections[handle].thread.get(), eventRegistration, + layerHandle); } sp Scheduler::getEventConnection(ConnectionHandle handle) { @@ -243,32 +284,21 @@ void Scheduler::onHotplugReceived(ConnectionHandle handle, PhysicalDisplayId dis thread->onHotplugReceived(displayId, connected); } -void Scheduler::onScreenAcquired(ConnectionHandle handle) { +void Scheduler::enableSyntheticVsync(bool enable) { + // TODO(b/241285945): Remove connection handles. + const ConnectionHandle handle = mAppConnectionHandle; android::EventThread* thread; { std::lock_guard lock(mConnectionsLock); RETURN_IF_INVALID_HANDLE(handle); thread = mConnections[handle].thread.get(); } - thread->onScreenAcquired(); - mScreenAcquired = true; -} - -void Scheduler::onScreenReleased(ConnectionHandle handle) { - android::EventThread* thread; - { - std::lock_guard lock(mConnectionsLock); - RETURN_IF_INVALID_HANDLE(handle); - thread = mConnections[handle].thread.get(); - } - thread->onScreenReleased(); - mScreenAcquired = false; + thread->enableSyntheticVsync(enable); } void Scheduler::onFrameRateOverridesChanged(ConnectionHandle handle, PhysicalDisplayId displayId) { - const auto refreshRateConfigs = holdRefreshRateConfigs(); const bool supportsFrameRateOverrideByContent = - refreshRateConfigs->supportsFrameRateOverrideByContent(); + pacesetterSelectorPtr()->supportsAppFrameRateOverrideByContent(); std::vector overrides = mFrameRateOverrideMappings.getAllFrameRateOverrides(supportsFrameRateOverrideByContent); @@ -282,7 +312,7 @@ void Scheduler::onFrameRateOverridesChanged(ConnectionHandle handle, PhysicalDis thread->onFrameRateOverridesChanged(displayId, std::move(overrides)); } -void Scheduler::onPrimaryDisplayModeChanged(ConnectionHandle handle, DisplayModePtr mode) { +void Scheduler::onPrimaryDisplayModeChanged(ConnectionHandle handle, const FrameRateMode& mode) { { std::lock_guard lock(mPolicyLock); // Cache the last reported modes for primary display. @@ -297,7 +327,7 @@ void Scheduler::onPrimaryDisplayModeChanged(ConnectionHandle handle, DisplayMode void Scheduler::dispatchCachedReportedMode() { // Check optional fields first. - if (!mPolicy.mode) { + if (!mPolicy.modeOpt) { ALOGW("No mode ID found, not dispatching cached mode."); return; } @@ -309,22 +339,21 @@ void Scheduler::dispatchCachedReportedMode() { // If the mode is not the current mode, this means that a // mode change is in progress. In that case we shouldn't dispatch an event // as it will be dispatched when the current mode changes. - if (std::scoped_lock lock(mRefreshRateConfigsLock); - mRefreshRateConfigs->getActiveMode() != mPolicy.mode) { + if (pacesetterSelectorPtr()->getActiveMode() != mPolicy.modeOpt) { return; } // If there is no change from cached mode, there is no need to dispatch an event - if (mPolicy.mode == mPolicy.cachedModeChangedParams->mode) { + if (*mPolicy.modeOpt == mPolicy.cachedModeChangedParams->mode) { return; } - mPolicy.cachedModeChangedParams->mode = mPolicy.mode; + mPolicy.cachedModeChangedParams->mode = *mPolicy.modeOpt; onNonPrimaryDisplayModeChanged(mPolicy.cachedModeChangedParams->handle, mPolicy.cachedModeChangedParams->mode); } -void Scheduler::onNonPrimaryDisplayModeChanged(ConnectionHandle handle, DisplayModePtr mode) { +void Scheduler::onNonPrimaryDisplayModeChanged(ConnectionHandle handle, const FrameRateMode& mode) { android::EventThread* thread; { std::lock_guard lock(mConnectionsLock); @@ -361,83 +390,84 @@ void Scheduler::setDuration(ConnectionHandle handle, std::chrono::nanoseconds wo thread->setDuration(workDuration, readyDuration); } -DisplayStatInfo Scheduler::getDisplayStatInfo(nsecs_t now) { - const auto vsyncTime = mVsyncSchedule->getTracker().nextAnticipatedVSyncTimeFrom(now); - const auto vsyncPeriod = mVsyncSchedule->getTracker().currentPeriod(); - return DisplayStatInfo{.vsyncTime = vsyncTime, .vsyncPeriod = vsyncPeriod}; +void Scheduler::setVsyncConfigSet(const VsyncConfigSet& configs, Period vsyncPeriod) { + setVsyncConfig(mVsyncModulator->setVsyncConfigSet(configs), vsyncPeriod); } -ConnectionHandle Scheduler::enableVSyncInjection(bool enable) { - if (mInjectVSyncs == enable) { - return {}; - } - - ALOGV("%s VSYNC injection", enable ? "Enabling" : "Disabling"); +void Scheduler::setVsyncConfig(const VsyncConfig& config, Period vsyncPeriod) { + setDuration(mAppConnectionHandle, + /* workDuration */ config.appWorkDuration, + /* readyDuration */ config.sfWorkDuration); + setDuration(mSfConnectionHandle, + /* workDuration */ vsyncPeriod, + /* readyDuration */ config.sfWorkDuration); + setDuration(config.sfWorkDuration); +} - if (!mInjectorConnectionHandle) { - auto vsyncSource = std::make_unique(); - mVSyncInjector = vsyncSource.get(); +void Scheduler::enableHardwareVsync(PhysicalDisplayId id) { + auto schedule = getVsyncSchedule(id); + LOG_ALWAYS_FATAL_IF(!schedule); + schedule->enableHardwareVsync(mSchedulerCallback); +} - auto eventThread = - std::make_unique(std::move(vsyncSource), - /*tokenManager=*/nullptr, - impl::EventThread::InterceptVSyncsCallback(), - impl::EventThread::ThrottleVsyncCallback(), - impl::EventThread::GetVsyncPeriodFunction()); +void Scheduler::disableHardwareVsync(PhysicalDisplayId id, bool disallow) { + auto schedule = getVsyncSchedule(id); + LOG_ALWAYS_FATAL_IF(!schedule); + schedule->disableHardwareVsync(mSchedulerCallback, disallow); +} - // EventThread does not dispatch VSYNC unless the display is connected and powered on. - eventThread->onHotplugReceived(PhysicalDisplayId::fromPort(0), true); - eventThread->onScreenAcquired(); +void Scheduler::resyncAllToHardwareVsync(bool allowToEnable) { + ATRACE_CALL(); + std::scoped_lock lock(mDisplayLock); + ftl::FakeGuard guard(kMainThreadContext); - mInjectorConnectionHandle = createConnection(std::move(eventThread)); + for (const auto& [id, _] : mDisplays) { + resyncToHardwareVsyncLocked(id, allowToEnable); } - - mInjectVSyncs = enable; - return mInjectorConnectionHandle; } -bool Scheduler::injectVSync(nsecs_t when, nsecs_t expectedVSyncTime, nsecs_t deadlineTimestamp) { - if (!mInjectVSyncs || !mVSyncInjector) { - return false; +void Scheduler::resyncToHardwareVsyncLocked(PhysicalDisplayId id, bool allowToEnable, + std::optional refreshRate) { + const auto displayOpt = mDisplays.get(id); + if (!displayOpt) { + ALOGW("%s: Invalid display %s!", __func__, to_string(id).c_str()); + return; } + const Display& display = *displayOpt; - mVSyncInjector->onInjectSyncEvent(when, expectedVSyncTime, deadlineTimestamp); - return true; -} - -void Scheduler::enableHardwareVsync() { - std::lock_guard lock(mHWVsyncLock); - if (!mPrimaryHWVsyncEnabled && mHWVsyncAvailable) { - mVsyncSchedule->getTracker().resetModel(); - mSchedulerCallback.setVsyncEnabled(true); - mPrimaryHWVsyncEnabled = true; + if (display.schedulePtr->isHardwareVsyncAllowed(allowToEnable)) { + if (!refreshRate) { + refreshRate = display.selectorPtr->getActiveMode().modePtr->getFps(); + } + if (refreshRate->isValid()) { + display.schedulePtr->startPeriodTransition(mSchedulerCallback, refreshRate->getPeriod(), + false /* force */); + } } } -void Scheduler::disableHardwareVsync(bool makeUnavailable) { - std::lock_guard lock(mHWVsyncLock); - if (mPrimaryHWVsyncEnabled) { - mSchedulerCallback.setVsyncEnabled(false); - mPrimaryHWVsyncEnabled = false; - } - if (makeUnavailable) { - mHWVsyncAvailable = false; - } -} +void Scheduler::setRenderRate(PhysicalDisplayId id, Fps renderFrameRate) { + std::scoped_lock lock(mDisplayLock); + ftl::FakeGuard guard(kMainThreadContext); -void Scheduler::resyncToHardwareVsync(bool makeAvailable, Fps refreshRate) { - { - std::lock_guard lock(mHWVsyncLock); - if (makeAvailable) { - mHWVsyncAvailable = makeAvailable; - } else if (!mHWVsyncAvailable) { - // Hardware vsync is not currently available, so abort the resync - // attempt for now - return; - } + const auto displayOpt = mDisplays.get(id); + if (!displayOpt) { + ALOGW("%s: Invalid display %s!", __func__, to_string(id).c_str()); + return; } + const Display& display = *displayOpt; + const auto mode = display.selectorPtr->getActiveMode(); + + using fps_approx_ops::operator!=; + LOG_ALWAYS_FATAL_IF(renderFrameRate != mode.fps, + "Mismatch in render frame rates. Selector: %s, Scheduler: %s, Display: " + "%" PRIu64, + to_string(mode.fps).c_str(), to_string(renderFrameRate).c_str(), id.value); - setVsyncPeriod(refreshRate.getPeriodNsecs()); + ALOGV("%s %s (%s)", __func__, to_string(mode.fps).c_str(), + to_string(mode.modePtr->getFps()).c_str()); + + display.schedulePtr->getTracker().setRenderRate(renderFrameRate); } void Scheduler::resync() { @@ -447,124 +477,100 @@ void Scheduler::resync() { const nsecs_t last = mLastResyncTime.exchange(now); if (now - last > kIgnoreDelay) { - const auto refreshRate = [&] { - std::scoped_lock lock(mRefreshRateConfigsLock); - return mRefreshRateConfigs->getActiveMode()->getFps(); - }(); - resyncToHardwareVsync(false, refreshRate); + resyncAllToHardwareVsync(false /* allowToEnable */); } } -void Scheduler::setVsyncPeriod(nsecs_t period) { - if (period <= 0) return; - - std::lock_guard lock(mHWVsyncLock); - mVsyncSchedule->getController().startPeriodTransition(period); - - if (!mPrimaryHWVsyncEnabled) { - mVsyncSchedule->getTracker().resetModel(); - mSchedulerCallback.setVsyncEnabled(true); - mPrimaryHWVsyncEnabled = true; - } -} - -void Scheduler::addResyncSample(nsecs_t timestamp, std::optional hwcVsyncPeriod, - bool* periodFlushed) { - bool needsHwVsync = false; - *periodFlushed = false; - { // Scope for the lock - std::lock_guard lock(mHWVsyncLock); - if (mPrimaryHWVsyncEnabled) { - needsHwVsync = - mVsyncSchedule->getController().addHwVsyncTimestamp(timestamp, hwcVsyncPeriod, - periodFlushed); - } - } - - if (needsHwVsync) { - enableHardwareVsync(); - } else { - disableHardwareVsync(false); +bool Scheduler::addResyncSample(PhysicalDisplayId id, nsecs_t timestamp, + std::optional hwcVsyncPeriodIn) { + const auto hwcVsyncPeriod = ftl::Optional(hwcVsyncPeriodIn).transform([](nsecs_t nanos) { + return Period::fromNs(nanos); + }); + auto schedule = getVsyncSchedule(id); + if (!schedule) { + ALOGW("%s: Invalid display %s!", __func__, to_string(id).c_str()); + return false; } + return schedule->addResyncSample(mSchedulerCallback, TimePoint::fromNs(timestamp), + hwcVsyncPeriod); } -void Scheduler::addPresentFence(std::shared_ptr fence) { - if (mVsyncSchedule->getController().addPresentFence(std::move(fence))) { - enableHardwareVsync(); +void Scheduler::addPresentFence(PhysicalDisplayId id, std::shared_ptr fence) { + auto schedule = getVsyncSchedule(id); + LOG_ALWAYS_FATAL_IF(!schedule); + const bool needMoreSignals = schedule->getController().addPresentFence(std::move(fence)); + if (needMoreSignals) { + schedule->enableHardwareVsync(mSchedulerCallback); } else { - disableHardwareVsync(false); + schedule->disableHardwareVsync(mSchedulerCallback, false /* disallow */); } } void Scheduler::registerLayer(Layer* layer) { - using WindowType = gui::WindowInfo::Type; - - scheduler::LayerHistory::LayerVoteType voteType; - - if (!mFeatures.test(Feature::kContentDetection) || - layer->getWindowType() == WindowType::STATUS_BAR) { - voteType = scheduler::LayerHistory::LayerVoteType::NoVote; - } else if (layer->getWindowType() == WindowType::WALLPAPER) { - // Running Wallpaper at Min is considered as part of content detection. - voteType = scheduler::LayerHistory::LayerVoteType::Min; - } else { - voteType = scheduler::LayerHistory::LayerVoteType::Heuristic; - } - // If the content detection feature is off, we still keep the layer history, // since we use it for other features (like Frame Rate API), so layers // still need to be registered. - mLayerHistory.registerLayer(layer, voteType); + mLayerHistory.registerLayer(layer, mFeatures.test(Feature::kContentDetection)); } void Scheduler::deregisterLayer(Layer* layer) { mLayerHistory.deregisterLayer(layer); } -void Scheduler::recordLayerHistory(Layer* layer, nsecs_t presentTime, +void Scheduler::recordLayerHistory(int32_t id, const LayerProps& layerProps, nsecs_t presentTime, LayerHistory::LayerUpdateType updateType) { - { - std::scoped_lock lock(mRefreshRateConfigsLock); - if (!mRefreshRateConfigs->canSwitch()) return; + if (pacesetterSelectorPtr()->canSwitch()) { + mLayerHistory.record(id, layerProps, presentTime, systemTime(), updateType); } - - mLayerHistory.record(layer, presentTime, systemTime(), updateType); } void Scheduler::setModeChangePending(bool pending) { mLayerHistory.setModeChangePending(pending); } +void Scheduler::setDefaultFrameRateCompatibility(Layer* layer) { + mLayerHistory.setDefaultFrameRateCompatibility(layer, + mFeatures.test(Feature::kContentDetection)); +} + void Scheduler::chooseRefreshRateForContent() { - const auto configs = holdRefreshRateConfigs(); - if (!configs->canSwitch()) return; + const auto selectorPtr = pacesetterSelectorPtr(); + if (!selectorPtr->canSwitch()) return; ATRACE_CALL(); - LayerHistory::Summary summary = mLayerHistory.summarize(*configs, systemTime()); + LayerHistory::Summary summary = mLayerHistory.summarize(*selectorPtr, systemTime()); applyPolicy(&Policy::contentRequirements, std::move(summary)); } void Scheduler::resetIdleTimer() { - std::scoped_lock lock(mRefreshRateConfigsLock); - mRefreshRateConfigs->resetIdleTimer(/*kernelOnly*/ false); + pacesetterSelectorPtr()->resetIdleTimer(); } void Scheduler::onTouchHint() { if (mTouchTimer) { mTouchTimer->reset(); - - std::scoped_lock lock(mRefreshRateConfigsLock); - mRefreshRateConfigs->resetIdleTimer(/*kernelOnly*/ true); + pacesetterSelectorPtr()->resetKernelIdleTimer(); } } -void Scheduler::setDisplayPowerMode(hal::PowerMode powerMode) { - { +void Scheduler::setDisplayPowerMode(PhysicalDisplayId id, hal::PowerMode powerMode) { + const bool isPacesetter = [this, id]() REQUIRES(kMainThreadContext) { + ftl::FakeGuard guard(mDisplayLock); + return id == mPacesetterDisplayId; + }(); + if (isPacesetter) { + // TODO (b/255657128): This needs to be handled per display. std::lock_guard lock(mPolicyLock); mPolicy.displayPowerMode = powerMode; } - mVsyncSchedule->getController().setDisplayPowerMode(powerMode); + { + std::scoped_lock lock(mDisplayLock); + auto vsyncSchedule = getVsyncScheduleLocked(id); + LOG_ALWAYS_FATAL_IF(!vsyncSchedule); + vsyncSchedule->getController().setDisplayPowerMode(powerMode); + } + if (!isPacesetter) return; if (mDisplayPowerTimer) { mDisplayPowerTimer->reset(); @@ -575,15 +581,34 @@ void Scheduler::setDisplayPowerMode(hal::PowerMode powerMode) { mLayerHistory.clear(); } +auto Scheduler::getVsyncSchedule(std::optional idOpt) const + -> ConstVsyncSchedulePtr { + std::scoped_lock lock(mDisplayLock); + return getVsyncScheduleLocked(idOpt); +} + +auto Scheduler::getVsyncScheduleLocked(std::optional idOpt) const + -> ConstVsyncSchedulePtr { + ftl::FakeGuard guard(kMainThreadContext); + + if (!idOpt) { + LOG_ALWAYS_FATAL_IF(!mPacesetterDisplayId, "Missing a pacesetter!"); + idOpt = mPacesetterDisplayId; + } + + const auto displayOpt = mDisplays.get(*idOpt); + if (!displayOpt) { + return nullptr; + } + return displayOpt->get().schedulePtr; +} + void Scheduler::kernelIdleTimerCallback(TimerState state) { ATRACE_INT("ExpiredKernelIdleTimer", static_cast(state)); // TODO(145561154): cleanup the kernel idle timer implementation and the refresh rate // magic number - const Fps refreshRate = [&] { - std::scoped_lock lock(mRefreshRateConfigsLock); - return mRefreshRateConfigs->getActiveMode()->getFps(); - }(); + const Fps refreshRate = pacesetterSelectorPtr()->getActiveMode().modePtr->getFps(); constexpr Fps FPS_THRESHOLD_FOR_KERNEL_TIMER = 65_Hz; using namespace fps_approx_ops; @@ -592,12 +617,17 @@ void Scheduler::kernelIdleTimerCallback(TimerState state) { // If we're not in performance mode then the kernel timer shouldn't do // anything, as the refresh rate during DPU power collapse will be the // same. - resyncToHardwareVsync(true /* makeAvailable */, refreshRate); + resyncAllToHardwareVsync(true /* allowToEnable */); } else if (state == TimerState::Expired && refreshRate <= FPS_THRESHOLD_FOR_KERNEL_TIMER) { // Disable HW VSYNC if the timer expired, as we don't need it enabled if // we're not pushing frames, and if we're in PERFORMANCE mode then we'll // need to update the VsyncController model anyway. - disableHardwareVsync(false /* makeUnavailable */); + std::scoped_lock lock(mDisplayLock); + ftl::FakeGuard guard(kMainThreadContext); + for (const auto& [_, display] : mDisplays) { + constexpr bool kDisallow = false; + display.schedulePtr->disableHardwareVsync(mSchedulerCallback, kDisallow); + } } mSchedulerCallback.kernelTimerChanged(state == TimerState::Expired); @@ -625,78 +655,186 @@ void Scheduler::displayPowerTimerCallback(TimerState state) { ATRACE_INT("ExpiredDisplayPowerTimer", static_cast(state)); } -void Scheduler::dump(std::string& result) const { - using base::StringAppendF; - - StringAppendF(&result, "+ Touch timer: %s\n", - mTouchTimer ? mTouchTimer->dump().c_str() : "off"); - StringAppendF(&result, "+ Content detection: %s %s\n\n", - mFeatures.test(Feature::kContentDetection) ? "on" : "off", - mLayerHistory.dump().c_str()); +void Scheduler::dump(utils::Dumper& dumper) const { + using namespace std::string_view_literals; - mFrameRateOverrideMappings.dump(result); + { + utils::Dumper::Section section(dumper, "Features"sv); + for (Feature feature : ftl::enum_range()) { + if (const auto flagOpt = ftl::flag_name(feature)) { + dumper.dump(flagOpt->substr(1), mFeatures.test(feature)); + } + } + } { - std::lock_guard lock(mHWVsyncLock); - StringAppendF(&result, - "mScreenAcquired=%d mPrimaryHWVsyncEnabled=%d mHWVsyncAvailable=%d\n", - mScreenAcquired.load(), mPrimaryHWVsyncEnabled, mHWVsyncAvailable); + utils::Dumper::Section section(dumper, "Policy"sv); + { + std::scoped_lock lock(mDisplayLock); + ftl::FakeGuard guard(kMainThreadContext); + dumper.dump("pacesetterDisplayId"sv, mPacesetterDisplayId); + } + dumper.dump("layerHistory"sv, mLayerHistory.dump()); + dumper.dump("touchTimer"sv, mTouchTimer.transform(&OneShotTimer::interval)); + dumper.dump("displayPowerTimer"sv, mDisplayPowerTimer.transform(&OneShotTimer::interval)); } + + mFrameRateOverrideMappings.dump(dumper); + dumper.eol(); } void Scheduler::dumpVsync(std::string& out) const { - mVsyncSchedule->dump(out); + std::scoped_lock lock(mDisplayLock); + ftl::FakeGuard guard(kMainThreadContext); + if (mPacesetterDisplayId) { + base::StringAppendF(&out, "VsyncSchedule for pacesetter %s:\n", + to_string(*mPacesetterDisplayId).c_str()); + getVsyncScheduleLocked()->dump(out); + } + for (auto& [id, display] : mDisplays) { + if (id == mPacesetterDisplayId) { + continue; + } + base::StringAppendF(&out, "VsyncSchedule for follower %s:\n", to_string(id).c_str()); + display.schedulePtr->dump(out); + } } bool Scheduler::updateFrameRateOverrides(GlobalSignals consideredSignals, Fps displayRefreshRate) { - const auto refreshRateConfigs = holdRefreshRateConfigs(); + if (consideredSignals.idle) return false; + + const auto frameRateOverrides = + pacesetterSelectorPtr()->getFrameRateOverrides(mPolicy.contentRequirements, + displayRefreshRate, consideredSignals); - // we always update mFrameRateOverridesByContent here - // supportsFrameRateOverridesByContent will be checked - // when getting FrameRateOverrides from mFrameRateOverrideMappings - if (!consideredSignals.idle) { - const auto frameRateOverrides = - refreshRateConfigs->getFrameRateOverrides(mPolicy.contentRequirements, - displayRefreshRate, consideredSignals); - return mFrameRateOverrideMappings.updateFrameRateOverridesByContent(frameRateOverrides); + // Note that RefreshRateSelector::supportsFrameRateOverrideByContent is checked when querying + // the FrameRateOverrideMappings rather than here. + return mFrameRateOverrideMappings.updateFrameRateOverridesByContent(frameRateOverrides); +} + +void Scheduler::promotePacesetterDisplay(std::optional pacesetterIdOpt) { + std::shared_ptr pacesetterVsyncSchedule; + + { + std::scoped_lock lock(mDisplayLock); + pacesetterVsyncSchedule = promotePacesetterDisplayLocked(pacesetterIdOpt); } - return false; + + 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()); + + std::shared_ptr newVsyncSchedulePtr; + 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); }}}); + + pacesetter.selectorPtr->startIdleTimer(); + + newVsyncSchedulePtr = pacesetter.schedulePtr; + + const Fps refreshRate = pacesetter.selectorPtr->getActiveMode().modePtr->getFps(); + newVsyncSchedulePtr->startPeriodTransition(mSchedulerCallback, refreshRate.getPeriod(), + true /* force */); + } + return newVsyncSchedulePtr; +} + +void Scheduler::applyNewVsyncSchedule(std::shared_ptr vsyncSchedule) { + onNewVsyncSchedule(vsyncSchedule->getDispatch()); + std::vector threads; + { + std::lock_guard lock(mConnectionsLock); + threads.reserve(mConnections.size()); + for (auto& [_, connection] : mConnections) { + threads.push_back(connection.thread.get()); + } + } + for (auto* thread : threads) { + thread->onNewVsyncSchedule(vsyncSchedule); + } +} + +void Scheduler::demotePacesetterDisplay() { + // 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. + std::scoped_lock lock(mPolicyLock); + mPolicy = {}; } template auto Scheduler::applyPolicy(S Policy::*statePtr, T&& newState) -> GlobalSignals { - DisplayModePtr newMode; + ATRACE_CALL(); + std::vector modeRequests; GlobalSignals consideredSignals; bool refreshRateChanged = false; bool frameRateOverridesChanged; - const auto refreshRateConfigs = holdRefreshRateConfigs(); { - std::lock_guard lock(mPolicyLock); + std::scoped_lock lock(mPolicyLock); auto& currentState = mPolicy.*statePtr; if (currentState == newState) return {}; currentState = std::forward(newState); - std::tie(newMode, consideredSignals) = chooseDisplayMode(); - frameRateOverridesChanged = updateFrameRateOverrides(consideredSignals, newMode->getFps()); + DisplayModeChoiceMap modeChoices; + ftl::Optional modeOpt; + { + std::scoped_lock lock(mDisplayLock); + ftl::FakeGuard guard(kMainThreadContext); + + modeChoices = chooseDisplayModes(); + + // TODO(b/240743786): The pacesetter display's mode must change for any + // DisplayModeRequest to go through. Fix this by tracking per-display Scheduler::Policy + // and timers. + std::tie(modeOpt, consideredSignals) = + modeChoices.get(*mPacesetterDisplayId) + .transform([](const DisplayModeChoice& choice) { + return std::make_pair(choice.mode, choice.consideredSignals); + }) + .value(); + } + + modeRequests.reserve(modeChoices.size()); + for (auto& [id, choice] : modeChoices) { + modeRequests.emplace_back( + display::DisplayModeRequest{.mode = std::move(choice.mode), + .emitEvent = !choice.consideredSignals.idle}); + } + + frameRateOverridesChanged = updateFrameRateOverrides(consideredSignals, modeOpt->fps); - if (mPolicy.mode == newMode) { + if (mPolicy.modeOpt != modeOpt) { + mPolicy.modeOpt = modeOpt; + refreshRateChanged = true; + } else { // We don't need to change the display mode, but we might need to send an event // about a mode change, since it was suppressed if previously considered idle. if (!consideredSignals.idle) { dispatchCachedReportedMode(); } - } else { - mPolicy.mode = newMode; - refreshRateChanged = true; } } if (refreshRateChanged) { - mSchedulerCallback.requestDisplayMode(std::move(newMode), - consideredSignals.idle ? DisplayModeEvent::None - : DisplayModeEvent::Changed); + mSchedulerCallback.requestDisplayModes(std::move(modeRequests)); } if (frameRateOverridesChanged) { mSchedulerCallback.triggerOnFrameRateOverridesChanged(); @@ -704,31 +842,66 @@ auto Scheduler::applyPolicy(S Policy::*statePtr, T&& newState) -> GlobalSignals return consideredSignals; } -auto Scheduler::chooseDisplayMode() -> std::pair { +auto Scheduler::chooseDisplayModes() const -> DisplayModeChoiceMap { ATRACE_CALL(); - const auto configs = holdRefreshRateConfigs(); + using RankedRefreshRates = RefreshRateSelector::RankedFrameRates; + display::PhysicalDisplayVector perDisplayRanking; + const auto globalSignals = makeGlobalSignals(); + Fps pacesetterFps; + + for (const auto& [id, display] : mDisplays) { + auto rankedFrameRates = + display.selectorPtr->getRankedFrameRates(mPolicy.contentRequirements, + globalSignals); + if (id == *mPacesetterDisplayId) { + pacesetterFps = rankedFrameRates.ranking.front().frameRateMode.fps; + } + perDisplayRanking.push_back(std::move(rankedFrameRates)); + } + + DisplayModeChoiceMap modeChoices; + using fps_approx_ops::operator==; - // If Display Power is not in normal operation we want to be in performance mode. When coming - // back to normal mode, a grace period is given with DisplayPowerTimer. - if (mDisplayPowerTimer && - (mPolicy.displayPowerMode != hal::PowerMode::ON || - mPolicy.displayPowerTimer == TimerState::Reset)) { - constexpr GlobalSignals kNoSignals; - return {configs->getMaxRefreshRateByPolicy(), kNoSignals}; + 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}); } + return modeChoices; +} - const GlobalSignals signals{.touch = mTouchTimer && mPolicy.touch == TouchState::Active, - .idle = mPolicy.idleTimer == TimerState::Expired}; +GlobalSignals Scheduler::makeGlobalSignals() const { + const bool powerOnImminent = mDisplayPowerTimer && + (mPolicy.displayPowerMode != hal::PowerMode::ON || + mPolicy.displayPowerTimer == TimerState::Reset); - return configs->getBestRefreshRate(mPolicy.contentRequirements, signals); + return {.touch = mTouchTimer && mPolicy.touch == TouchState::Active, + .idle = mPolicy.idleTimer == TimerState::Expired, + .powerOnImminent = powerOnImminent}; } -DisplayModePtr Scheduler::getPreferredDisplayMode() { +FrameRateMode Scheduler::getPreferredDisplayMode() { std::lock_guard lock(mPolicyLock); + const auto frameRateMode = + pacesetterSelectorPtr() + ->getRankedFrameRates(mPolicy.contentRequirements, makeGlobalSignals()) + .ranking.front() + .frameRateMode; + // Make sure the stored mode is up to date. - mPolicy.mode = chooseDisplayMode().first; - return mPolicy.mode; + mPolicy.modeOpt = frameRateMode; + + return frameRateMode; } void Scheduler::onNewVsyncPeriodChangeTimeline(const hal::VsyncPeriodChangeTimeline& timeline) { @@ -774,11 +947,4 @@ void Scheduler::setPreferredRefreshRateForUid(FrameRateOverride frameRateOverrid mFrameRateOverrideMappings.setPreferredRefreshRateForUid(frameRateOverride); } -std::chrono::steady_clock::time_point Scheduler::getPreviousVsyncFrom( - nsecs_t expectedPresentTime) const { - const auto presentTime = std::chrono::nanoseconds(expectedPresentTime); - const auto vsyncPeriod = std::chrono::nanoseconds(mVsyncSchedule->getTracker().currentPeriod()); - return std::chrono::steady_clock::time_point(presentTime - vsyncPeriod); -} - } // namespace android::scheduler diff --git a/services/surfaceflinger/Scheduler/Scheduler.h b/services/surfaceflinger/Scheduler/Scheduler.h index a8043bf94cc69e3664b16a40a4452f9bdf48a168..f13c878b677b02ebc2619fde4a751a7a7d30361f 100644 --- a/services/surfaceflinger/Scheduler/Scheduler.h +++ b/services/surfaceflinger/Scheduler/Scheduler.h @@ -22,8 +22,8 @@ #include #include #include -#include #include +#include // TODO(b/129481165): remove the #pragma below and fix conversion issues #pragma clang diagnostic push @@ -32,15 +32,24 @@ #include #pragma clang diagnostic pop // ignored "-Wconversion -Wextra" +#include +#include #include +#include +#include +#include +#include "Display/DisplayMap.h" +#include "Display/DisplayModeRequest.h" #include "EventThread.h" #include "FrameRateOverrideMappings.h" +#include "ISchedulerCallback.h" #include "LayerHistory.h" #include "MessageQueue.h" #include "OneShotTimer.h" -#include "RefreshRateConfigs.h" -#include "VsyncSchedule.h" +#include "RefreshRateSelector.h" +#include "Utils/Dumper.h" +#include "VsyncModulator.h" namespace android::scheduler { @@ -74,7 +83,6 @@ struct hash { namespace android { class FenceTime; -class InjectVSyncSource; namespace frametimeline { class TokenManager; @@ -82,39 +90,40 @@ class TokenManager; namespace scheduler { -struct ISchedulerCallback { - using DisplayModeEvent = scheduler::DisplayModeEvent; +using GlobalSignals = RefreshRateSelector::GlobalSignals; - virtual void setVsyncEnabled(bool) = 0; - virtual void requestDisplayMode(DisplayModePtr, DisplayModeEvent) = 0; - virtual void kernelTimerChanged(bool expired) = 0; - virtual void triggerOnFrameRateOverridesChanged() = 0; +class VsyncSchedule; -protected: - ~ISchedulerCallback() = default; -}; - -class Scheduler : impl::MessageQueue { - using Impl = impl::MessageQueue; +class Scheduler : android::impl::MessageQueue { + using Impl = android::impl::MessageQueue; public: - Scheduler(ICompositor&, ISchedulerCallback&, FeatureFlags); + Scheduler(ICompositor&, ISchedulerCallback&, FeatureFlags, sp); virtual ~Scheduler(); void startTimers(); - void setRefreshRateConfigs(std::shared_ptr) - EXCLUDES(mRefreshRateConfigsLock); - void run(); + // TODO(b/241285191): Remove this API by promoting pacesetter in onScreen{Acquired,Released}. + void setPacesetterDisplay(std::optional) REQUIRES(kMainThreadContext) + EXCLUDES(mDisplayLock); + + using RefreshRateSelectorPtr = std::shared_ptr; - void createVsyncSchedule(FeatureFlags); + using ConstVsyncSchedulePtr = std::shared_ptr; + using VsyncSchedulePtr = std::shared_ptr; + + void registerDisplay(PhysicalDisplayId, RefreshRateSelectorPtr) REQUIRES(kMainThreadContext) + EXCLUDES(mDisplayLock); + void unregisterDisplay(PhysicalDisplayId) REQUIRES(kMainThreadContext) EXCLUDES(mDisplayLock); + + void run(); using Impl::initVsync; - using Impl::setInjector; using Impl::getScheduledFrameTime; using Impl::setDuration; + using Impl::scheduleConfigure; using Impl::scheduleFrame; // Schedule an asynchronous or synchronous task on the main thread. @@ -125,21 +134,33 @@ public: return std::move(future); } - ConnectionHandle createConnection(const char* connectionName, frametimeline::TokenManager*, - std::chrono::nanoseconds workDuration, - std::chrono::nanoseconds readyDuration, - impl::EventThread::InterceptVSyncsCallback); + template > + [[nodiscard]] std::future scheduleDelayed(F&& f, nsecs_t uptimeDelay) { + auto [task, future] = makeTask(std::move(f)); + postMessageDelayed(std::move(task), uptimeDelay); + return std::move(future); + } + + enum class Cycle { + Render, // Surface rendering. + LastComposite // Ahead of display compositing by one refresh period. + }; + + ConnectionHandle createEventThread(Cycle, frametimeline::TokenManager*, + std::chrono::nanoseconds workDuration, + std::chrono::nanoseconds readyDuration); sp createDisplayEventConnection( - ConnectionHandle, ISurfaceComposer::EventRegistrationFlags eventRegistration = {}); + ConnectionHandle, EventRegistrationFlags eventRegistration = {}, + const sp& layerHandle = nullptr); sp getEventConnection(ConnectionHandle); void onHotplugReceived(ConnectionHandle, PhysicalDisplayId, bool connected); - void onPrimaryDisplayModeChanged(ConnectionHandle, DisplayModePtr) EXCLUDES(mPolicyLock); - void onNonPrimaryDisplayModeChanged(ConnectionHandle, DisplayModePtr); - void onScreenAcquired(ConnectionHandle); - void onScreenReleased(ConnectionHandle); + void onPrimaryDisplayModeChanged(ConnectionHandle, const FrameRateMode&) EXCLUDES(mPolicyLock); + void onNonPrimaryDisplayModeChanged(ConnectionHandle, const FrameRateMode&); + + void enableSyntheticVsync(bool = true) REQUIRES(kMainThreadContext); void onFrameRateOverridesChanged(ConnectionHandle, PhysicalDisplayId) EXCLUDES(mConnectionsLock); @@ -148,60 +169,98 @@ public: void setDuration(ConnectionHandle, std::chrono::nanoseconds workDuration, std::chrono::nanoseconds readyDuration); - DisplayStatInfo getDisplayStatInfo(nsecs_t now); + VsyncModulator& vsyncModulator() { return *mVsyncModulator; } + + // In some cases, we should only modulate for the pacesetter display. In those + // cases, the caller should pass in the relevant display, and the method + // will no-op if it's not the pacesetter. Other cases are not specific to a + // display. + template (VsyncModulator::*)(Args...)> + void modulateVsync(std::optional id, Handler handler, Args... args) { + if (id) { + std::scoped_lock lock(mDisplayLock); + ftl::FakeGuard guard(kMainThreadContext); + if (id != mPacesetterDisplayId) { + return; + } + } + + if (const auto config = (*mVsyncModulator.*handler)(args...)) { + setVsyncConfig(*config, getPacesetterVsyncPeriod()); + } + } + + void setVsyncConfigSet(const VsyncConfigSet&, Period vsyncPeriod); - // Returns injector handle if injection has toggled, or an invalid handle otherwise. - ConnectionHandle enableVSyncInjection(bool enable); - // Returns false if injection is disabled. - bool injectVSync(nsecs_t when, nsecs_t expectedVSyncTime, nsecs_t deadlineTimestamp); - void enableHardwareVsync(); - void disableHardwareVsync(bool makeUnavailable); + // Sets the render rate for the scheduler to run at. + void setRenderRate(PhysicalDisplayId, Fps); + + void enableHardwareVsync(PhysicalDisplayId) REQUIRES(kMainThreadContext); + void disableHardwareVsync(PhysicalDisplayId, bool disallow) REQUIRES(kMainThreadContext); // Resyncs the scheduler to hardware vsync. - // If makeAvailable is true, then hardware vsync will be turned on. + // If allowToEnable is true, then hardware vsync will be turned on. // Otherwise, if hardware vsync is not already enabled then this method will // no-op. - void resyncToHardwareVsync(bool makeAvailable, Fps refreshRate); - void resync() EXCLUDES(mRefreshRateConfigsLock); + // If refreshRate is nullopt, use the existing refresh rate of the display. + void resyncToHardwareVsync(PhysicalDisplayId id, bool allowToEnable, + std::optional refreshRate = std::nullopt) + EXCLUDES(mDisplayLock) { + std::scoped_lock lock(mDisplayLock); + ftl::FakeGuard guard(kMainThreadContext); + resyncToHardwareVsyncLocked(id, allowToEnable, refreshRate); + } + void resync() EXCLUDES(mDisplayLock); void forceNextResync() { mLastResyncTime = 0; } - // Passes a vsync sample to VsyncController. periodFlushed will be true if - // VsyncController detected that the vsync period changed, and false otherwise. - void addResyncSample(nsecs_t timestamp, std::optional hwcVsyncPeriod, - bool* periodFlushed); - void addPresentFence(std::shared_ptr); + // Passes a vsync sample to VsyncController. Returns true if + // VsyncController detected that the vsync period changed and false + // otherwise. + bool addResyncSample(PhysicalDisplayId, nsecs_t timestamp, + std::optional hwcVsyncPeriod); + void addPresentFence(PhysicalDisplayId, std::shared_ptr) EXCLUDES(mDisplayLock) + REQUIRES(kMainThreadContext); // Layers are registered on creation, and unregistered when the weak reference expires. void registerLayer(Layer*); - void recordLayerHistory(Layer*, nsecs_t presentTime, LayerHistory::LayerUpdateType updateType) - EXCLUDES(mRefreshRateConfigsLock); + void recordLayerHistory(int32_t id, const LayerProps& layerProps, nsecs_t presentTime, + LayerHistory::LayerUpdateType) EXCLUDES(mDisplayLock); void setModeChangePending(bool pending); + void setDefaultFrameRateCompatibility(Layer*); void deregisterLayer(Layer*); // Detects content using layer history, and selects a matching refresh rate. - void chooseRefreshRateForContent() EXCLUDES(mRefreshRateConfigsLock); + void chooseRefreshRateForContent() EXCLUDES(mDisplayLock); void resetIdleTimer(); // Indicates that touch interaction is taking place. void onTouchHint(); - void setDisplayPowerMode(hal::PowerMode powerMode); + void setDisplayPowerMode(PhysicalDisplayId, hal::PowerMode powerMode) + REQUIRES(kMainThreadContext); - VSyncDispatch& getVsyncDispatch() { return mVsyncSchedule->getDispatch(); } + ConstVsyncSchedulePtr getVsyncSchedule(std::optional = std::nullopt) const + EXCLUDES(mDisplayLock); + + VsyncSchedulePtr getVsyncSchedule(std::optional idOpt = std::nullopt) + EXCLUDES(mDisplayLock) { + return std::const_pointer_cast(std::as_const(*this).getVsyncSchedule(idOpt)); + } // Returns true if a given vsync timestamp is considered valid vsync // for a given uid - bool isVsyncValid(nsecs_t expectedVsyncTimestamp, uid_t uid) const; + bool isVsyncValid(TimePoint expectedVsyncTimestamp, uid_t uid) const; - std::chrono::steady_clock::time_point getPreviousVsyncFrom(nsecs_t expectedPresentTime) const; + bool isVsyncInPhase(TimePoint expectedVsyncTime, Fps frameRate) const; - void dump(std::string&) const; + void dump(utils::Dumper&) const; void dump(ConnectionHandle, std::string&) const; - void dumpVsync(std::string&) const; + void dumpVsync(std::string&) const EXCLUDES(mDisplayLock); - // Get the appropriate refresh for current conditions. - DisplayModePtr getPreferredDisplayMode(); + // Returns the preferred refresh rate and frame rate for the pacesetter display. + FrameRateMode getPreferredDisplayMode(); // Notifies the scheduler about a refresh rate timeline change. void onNewVsyncPeriodChangeTimeline(const hal::VsyncPeriodChangeTimeline& timeline); @@ -214,11 +273,6 @@ public: size_t getEventThreadConnectionCount(ConnectionHandle handle); - std::unique_ptr makePrimaryDispSyncSource(const char* name, - std::chrono::nanoseconds workDuration, - std::chrono::nanoseconds readyDuration, - bool traceVsync = true); - // Stores the preferred refresh rate that an app should run at. // FrameRateOverride.refreshRateHz == 0 means no preference. void setPreferredRefreshRateForUid(FrameRateOverride); @@ -226,11 +280,14 @@ public: void setGameModeRefreshRateForUid(FrameRateOverride); // Retrieves the overridden refresh rate for a given uid. - std::optional getFrameRateOverride(uid_t uid) const EXCLUDES(mRefreshRateConfigsLock); + std::optional getFrameRateOverride(uid_t) const EXCLUDES(mDisplayLock); - nsecs_t getVsyncPeriodFromRefreshRateConfigs() const EXCLUDES(mRefreshRateConfigsLock) { - std::scoped_lock lock(mRefreshRateConfigsLock); - return mRefreshRateConfigs->getActiveMode()->getFps().getPeriodNsecs(); + Period getPacesetterVsyncPeriod() const EXCLUDES(mDisplayLock) { + return pacesetterSelectorPtr()->getActiveMode().fps.getPeriod(); + } + + Fps getPacesetterRefreshRate() const EXCLUDES(mDisplayLock) { + return pacesetterSelectorPtr()->getActiveMode().fps; } // Returns the framerate of the layer with the given sequence ID @@ -245,20 +302,49 @@ private: enum class TimerState { Reset, Expired }; enum class TouchState { Inactive, Active }; + // impl::MessageQueue overrides: + void onFrameSignal(ICompositor&, VsyncId, TimePoint expectedVsyncTime) override; + // Create a connection on the given EventThread. ConnectionHandle createConnection(std::unique_ptr); sp createConnectionInternal( - EventThread*, ISurfaceComposer::EventRegistrationFlags eventRegistration = {}); + EventThread*, EventRegistrationFlags eventRegistration = {}, + const sp& layerHandle = nullptr); // Update feature state machine to given state when corresponding timer resets or expires. - void kernelIdleTimerCallback(TimerState) EXCLUDES(mRefreshRateConfigsLock); + void kernelIdleTimerCallback(TimerState) EXCLUDES(mDisplayLock); void idleTimerCallback(TimerState); void touchTimerCallback(TimerState); void displayPowerTimerCallback(TimerState); - void setVsyncPeriod(nsecs_t period); - - using GlobalSignals = RefreshRateConfigs::GlobalSignals; + void resyncToHardwareVsyncLocked(PhysicalDisplayId, bool allowToEnable, + std::optional refreshRate = std::nullopt) + REQUIRES(kMainThreadContext, mDisplayLock); + 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); + + // 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 + // mDisplayLock is still locked. However, a new pacesetter means that + // 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) + 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); + + void registerDisplayInternal(PhysicalDisplayId, RefreshRateSelectorPtr, VsyncSchedulePtr) + REQUIRES(kMainThreadContext) EXCLUDES(mDisplayLock); struct Policy; @@ -267,22 +353,38 @@ private: template GlobalSignals applyPolicy(S Policy::*, T&&) EXCLUDES(mPolicyLock); - // Returns the display mode that fulfills the policy, and the signals that were considered. - std::pair chooseDisplayMode() REQUIRES(mPolicyLock); + struct DisplayModeChoice { + DisplayModeChoice(FrameRateMode mode, GlobalSignals consideredSignals) + : mode(std::move(mode)), consideredSignals(consideredSignals) {} - bool updateFrameRateOverrides(GlobalSignals, Fps displayRefreshRate) REQUIRES(mPolicyLock); + FrameRateMode mode; + GlobalSignals consideredSignals; - void dispatchCachedReportedMode() REQUIRES(mPolicyLock) EXCLUDES(mRefreshRateConfigsLock); + bool operator==(const DisplayModeChoice& other) const { + return mode == other.mode && consideredSignals == other.consideredSignals; + } - impl::EventThread::ThrottleVsyncCallback makeThrottleVsyncCallback() const - EXCLUDES(mRefreshRateConfigsLock); - impl::EventThread::GetVsyncPeriodFunction makeGetVsyncPeriodFunction() const; + // For tests. + friend std::ostream& operator<<(std::ostream& stream, const DisplayModeChoice& choice) { + return stream << '{' << to_string(*choice.mode.modePtr) << " considering " + << choice.consideredSignals.toString().c_str() << '}'; + } + }; - std::shared_ptr holdRefreshRateConfigs() const - EXCLUDES(mRefreshRateConfigsLock) { - std::scoped_lock lock(mRefreshRateConfigsLock); - return mRefreshRateConfigs; - } + using DisplayModeChoiceMap = display::PhysicalDisplayMap; + + // See mDisplayLock for thread safety. + DisplayModeChoiceMap chooseDisplayModes() const + REQUIRES(mPolicyLock, mDisplayLock, kMainThreadContext); + + GlobalSignals makeGlobalSignals() const REQUIRES(mPolicyLock); + + bool updateFrameRateOverrides(GlobalSignals, Fps displayRefreshRate) REQUIRES(mPolicyLock); + + void dispatchCachedReportedMode() REQUIRES(mPolicyLock) EXCLUDES(mDisplayLock); + + android::impl::EventThread::ThrottleVsyncCallback makeThrottleVsyncCallback() const; + android::impl::EventThread::GetVsyncPeriodFunction makeGetVsyncPeriodFunction() const; // Stores EventThread associated with a given VSyncSource, and an initial EventThreadConnection. struct Connection { @@ -294,31 +396,86 @@ private: mutable std::mutex mConnectionsLock; std::unordered_map mConnections GUARDED_BY(mConnectionsLock); - bool mInjectVSyncs = false; - InjectVSyncSource* mVSyncInjector = nullptr; - ConnectionHandle mInjectorConnectionHandle; - - mutable std::mutex mHWVsyncLock; - bool mPrimaryHWVsyncEnabled GUARDED_BY(mHWVsyncLock) = false; - bool mHWVsyncAvailable GUARDED_BY(mHWVsyncLock) = false; + ConnectionHandle mAppConnectionHandle; + ConnectionHandle mSfConnectionHandle; std::atomic mLastResyncTime = 0; const FeatureFlags mFeatures; - std::optional mVsyncSchedule; + + // Shifts the VSYNC phase during certain transactions and refresh rate changes. + const sp mVsyncModulator; // Used to choose refresh rate if content detection is enabled. LayerHistory mLayerHistory; // Timer used to monitor touch events. - std::optional mTouchTimer; + ftl::Optional mTouchTimer; // Timer used to monitor display power mode. - std::optional mDisplayPowerTimer; + ftl::Optional mDisplayPowerTimer; ISchedulerCallback& mSchedulerCallback; + // mDisplayLock may be locked while under mPolicyLock. mutable std::mutex mPolicyLock; + // Only required for reads outside kMainThreadContext. kMainThreadContext is the only writer, so + // must lock for writes but not reads. See also mPolicyLock for locking order. + mutable std::mutex mDisplayLock; + + struct Display { + Display(RefreshRateSelectorPtr selectorPtr, VsyncSchedulePtr schedulePtr) + : selectorPtr(std::move(selectorPtr)), schedulePtr(std::move(schedulePtr)) {} + + // Effectively const except in move constructor. + RefreshRateSelectorPtr selectorPtr; + VsyncSchedulePtr schedulePtr; + }; + + using DisplayRef = std::reference_wrapper; + using ConstDisplayRef = std::reference_wrapper; + + display::PhysicalDisplayMap mDisplays GUARDED_BY(mDisplayLock) + GUARDED_BY(kMainThreadContext); + + ftl::Optional mPacesetterDisplayId GUARDED_BY(mDisplayLock) + GUARDED_BY(kMainThreadContext); + + ftl::Optional pacesetterDisplayLocked() REQUIRES(mDisplayLock) { + return static_cast(this)->pacesetterDisplayLocked().transform( + [](const Display& display) { return std::ref(const_cast(display)); }); + } + + ftl::Optional pacesetterDisplayLocked() const REQUIRES(mDisplayLock) { + ftl::FakeGuard guard(kMainThreadContext); + return mPacesetterDisplayId.and_then([this](PhysicalDisplayId pacesetterId) + REQUIRES(mDisplayLock, kMainThreadContext) { + return mDisplays.get(pacesetterId); + }); + } + + RefreshRateSelectorPtr pacesetterSelectorPtr() const EXCLUDES(mDisplayLock) { + std::scoped_lock lock(mDisplayLock); + return pacesetterSelectorPtrLocked(); + } + + RefreshRateSelectorPtr pacesetterSelectorPtrLocked() const REQUIRES(mDisplayLock) { + ftl::FakeGuard guard(kMainThreadContext); + return pacesetterDisplayLocked() + .transform([](const Display& display) { return display.selectorPtr; }) + .or_else([] { return std::optional(nullptr); }) + .value(); + } + + ConstVsyncSchedulePtr getVsyncScheduleLocked( + std::optional = std::nullopt) const REQUIRES(mDisplayLock); + + VsyncSchedulePtr getVsyncScheduleLocked(std::optional idOpt = std::nullopt) + REQUIRES(mDisplayLock) { + return std::const_pointer_cast( + static_cast(this)->getVsyncScheduleLocked(idOpt)); + } + struct Policy { // Policy for choosing the display mode. LayerHistory::Summary contentRequirements; @@ -328,29 +485,23 @@ private: hal::PowerMode displayPowerMode = hal::PowerMode::ON; // Chosen display mode. - DisplayModePtr mode; + ftl::Optional modeOpt; struct ModeChangedParams { ConnectionHandle handle; - DisplayModePtr mode; + FrameRateMode mode; }; // Parameters for latest dispatch of mode change event. std::optional cachedModeChangedParams; } mPolicy GUARDED_BY(mPolicyLock); - mutable std::mutex mRefreshRateConfigsLock; - std::shared_ptr mRefreshRateConfigs GUARDED_BY(mRefreshRateConfigsLock); - std::mutex mVsyncTimelineLock; std::optional mLastVsyncPeriodChangeTimeline GUARDED_BY(mVsyncTimelineLock); static constexpr std::chrono::nanoseconds MAX_VSYNC_APPLIED_TIME = 200ms; FrameRateOverrideMappings mFrameRateOverrideMappings; - - // Keeps track of whether the screen is acquired for debug - std::atomic mScreenAcquired = false; }; } // namespace scheduler diff --git a/services/surfaceflinger/Scheduler/VSyncDispatch.h b/services/surfaceflinger/Scheduler/VSyncDispatch.h index 2bfe204a332aee2a3245564fc3d4a1497178b972..c3a952f6894dae54f67f159eb0f6cefeae381410 100644 --- a/services/surfaceflinger/Scheduler/VSyncDispatch.h +++ b/services/surfaceflinger/Scheduler/VSyncDispatch.h @@ -126,6 +126,17 @@ public: */ virtual ScheduleResult schedule(CallbackToken token, ScheduleTiming scheduleTiming) = 0; + /* + * Update the timing information for a scheduled callback. + * If the callback is not scheduled, then this function does nothing. + * + * \param [in] token The callback to schedule. + * \param [in] scheduleTiming The timing information for this schedule call + * \return The expected callback time if a callback was scheduled. + * std::nullopt if the callback is not registered. + */ + virtual ScheduleResult update(CallbackToken token, ScheduleTiming scheduleTiming) = 0; + /* Cancels a scheduled callback, if possible. * * \param [in] token The callback to cancel. @@ -144,13 +155,10 @@ protected: VSyncDispatch& operator=(const VSyncDispatch&) = delete; }; -/* - * Helper class to operate on registered callbacks. It is up to user of the class to ensure - * that VsyncDispatch lifetime exceeds the lifetime of VSyncCallbackRegistation. - */ class VSyncCallbackRegistration { public: - VSyncCallbackRegistration(VSyncDispatch&, VSyncDispatch::Callback, std::string callbackName); + VSyncCallbackRegistration(std::shared_ptr, VSyncDispatch::Callback, + std::string callbackName); ~VSyncCallbackRegistration(); VSyncCallbackRegistration(VSyncCallbackRegistration&&); @@ -159,13 +167,17 @@ public: // See documentation for VSyncDispatch::schedule. ScheduleResult schedule(VSyncDispatch::ScheduleTiming scheduleTiming); + // See documentation for VSyncDispatch::update. + ScheduleResult update(VSyncDispatch::ScheduleTiming scheduleTiming); + // See documentation for VSyncDispatch::cancel. CancelResult cancel(); private: - std::reference_wrapper mDispatch; - VSyncDispatch::CallbackToken mToken; - bool mValidToken; + friend class VSyncCallbackRegistrationTest; + + std::shared_ptr mDispatch; + std::optional mToken; }; } // namespace android::scheduler diff --git a/services/surfaceflinger/Scheduler/VSyncDispatchTimerQueue.cpp b/services/surfaceflinger/Scheduler/VSyncDispatchTimerQueue.cpp index 27f43115c19b5f321e3dfcc673146fe8bcabe1de..1f922f11fb888cc9435f0db6afc9a1a8dc6ee6bb 100644 --- a/services/surfaceflinger/Scheduler/VSyncDispatchTimerQueue.cpp +++ b/services/surfaceflinger/Scheduler/VSyncDispatchTimerQueue.cpp @@ -21,12 +21,16 @@ #include #include #include +#include #include #include "VSyncDispatchTimerQueue.h" #include "VSyncTracker.h" +#undef LOG_TAG +#define LOG_TAG "VSyncDispatch" + namespace android::scheduler { using base::StringAppendF; @@ -100,14 +104,8 @@ ScheduleResult VSyncDispatchTimerQueueEntry::schedule(VSyncDispatch::ScheduleTim return getExpectedCallbackTime(nextVsyncTime, timing); } - bool const alreadyDispatchedForVsync = mLastDispatchTime && - ((*mLastDispatchTime + mMinVsyncDistance) >= nextVsyncTime && - (*mLastDispatchTime - mMinVsyncDistance) <= nextVsyncTime); - if (alreadyDispatchedForVsync) { - nextVsyncTime = - tracker.nextAnticipatedVSyncTimeFrom(*mLastDispatchTime + mMinVsyncDistance); - nextWakeupTime = nextVsyncTime - timing.workDuration - timing.readyDuration; - } + nextVsyncTime = adjustVsyncIfNeeded(tracker, nextVsyncTime); + nextWakeupTime = nextVsyncTime - timing.workDuration - timing.readyDuration; auto const nextReadyTime = nextVsyncTime - timing.readyDuration; mScheduleTiming = timing; @@ -123,6 +121,25 @@ bool VSyncDispatchTimerQueueEntry::hasPendingWorkloadUpdate() const { return mWorkloadUpdateInfo.has_value(); } +nsecs_t VSyncDispatchTimerQueueEntry::adjustVsyncIfNeeded(VSyncTracker& tracker, + nsecs_t nextVsyncTime) const { + bool const alreadyDispatchedForVsync = mLastDispatchTime && + ((*mLastDispatchTime + mMinVsyncDistance) >= nextVsyncTime && + (*mLastDispatchTime - mMinVsyncDistance) <= nextVsyncTime); + const nsecs_t currentPeriod = tracker.currentPeriod(); + bool const nextVsyncTooClose = mLastDispatchTime && + (nextVsyncTime - *mLastDispatchTime + mMinVsyncDistance) <= currentPeriod; + if (alreadyDispatchedForVsync) { + return tracker.nextAnticipatedVSyncTimeFrom(*mLastDispatchTime + mMinVsyncDistance); + } + + if (nextVsyncTooClose) { + return tracker.nextAnticipatedVSyncTimeFrom(*mLastDispatchTime + currentPeriod); + } + + return nextVsyncTime; +} + void VSyncDispatchTimerQueueEntry::update(VSyncTracker& tracker, nsecs_t now) { if (!mArmedInfo && !mWorkloadUpdateInfo) { return; @@ -136,7 +153,9 @@ void VSyncDispatchTimerQueueEntry::update(VSyncTracker& tracker, nsecs_t now) { const auto earliestReadyBy = now + mScheduleTiming.workDuration + mScheduleTiming.readyDuration; const auto earliestVsync = std::max(earliestReadyBy, mScheduleTiming.earliestVsync); - const auto nextVsyncTime = tracker.nextAnticipatedVSyncTimeFrom(earliestVsync); + const auto nextVsyncTime = + adjustVsyncIfNeeded(tracker, /*nextVsyncTime*/ + tracker.nextAnticipatedVSyncTimeFrom(earliestVsync)); const auto nextReadyTime = nextVsyncTime - mScheduleTiming.readyDuration; const auto nextWakeupTime = nextReadyTime - mScheduleTiming.workDuration; @@ -200,16 +219,20 @@ void VSyncDispatchTimerQueueEntry::dump(std::string& result) const { } VSyncDispatchTimerQueue::VSyncDispatchTimerQueue(std::unique_ptr tk, - VSyncTracker& tracker, nsecs_t timerSlack, - nsecs_t minVsyncDistance) + VsyncSchedule::TrackerPtr tracker, + nsecs_t timerSlack, nsecs_t minVsyncDistance) : mTimeKeeper(std::move(tk)), - mTracker(tracker), + mTracker(std::move(tracker)), mTimerSlack(timerSlack), mMinVsyncDistance(minVsyncDistance) {} VSyncDispatchTimerQueue::~VSyncDispatchTimerQueue() { std::lock_guard lock(mMutex); cancelTimer(); + for (auto& [_, entry] : mCallbacks) { + ALOGE("Forgot to unregister a callback on VSyncDispatch!"); + entry->ensureNotRunning(); + } } void VSyncDispatchTimerQueue::cancelTimer() { @@ -240,7 +263,7 @@ void VSyncDispatchTimerQueue::rearmTimerSkippingUpdateFor( } if (it != skipUpdateIt) { - callback->update(mTracker, now); + callback->update(*mTracker, now); } auto const wakeupTime = *callback->wakeupTime(); if (!min || *min > wakeupTime) { @@ -332,38 +355,54 @@ void VSyncDispatchTimerQueue::unregisterCallback(CallbackToken token) { ScheduleResult VSyncDispatchTimerQueue::schedule(CallbackToken token, ScheduleTiming scheduleTiming) { - ScheduleResult result; - { - std::lock_guard lock(mMutex); - - auto it = mCallbacks.find(token); - if (it == mCallbacks.end()) { - return result; - } - auto& callback = it->second; - auto const now = mTimeKeeper->now(); + std::lock_guard lock(mMutex); + return scheduleLocked(token, scheduleTiming); +} - /* If the timer thread will run soon, we'll apply this work update via the callback - * timer recalculation to avoid cancelling a callback that is about to fire. */ - auto const rearmImminent = now > mIntendedWakeupTime; - if (CC_UNLIKELY(rearmImminent)) { - callback->addPendingWorkloadUpdate(scheduleTiming); - return getExpectedCallbackTime(mTracker, now, scheduleTiming); - } +ScheduleResult VSyncDispatchTimerQueue::scheduleLocked(CallbackToken token, + ScheduleTiming scheduleTiming) { + auto it = mCallbacks.find(token); + if (it == mCallbacks.end()) { + return {}; + } + auto& callback = it->second; + auto const now = mTimeKeeper->now(); + + /* If the timer thread will run soon, we'll apply this work update via the callback + * timer recalculation to avoid cancelling a callback that is about to fire. */ + auto const rearmImminent = now > mIntendedWakeupTime; + if (CC_UNLIKELY(rearmImminent)) { + callback->addPendingWorkloadUpdate(scheduleTiming); + return getExpectedCallbackTime(*mTracker, now, scheduleTiming); + } - result = callback->schedule(scheduleTiming, mTracker, now); - if (!result.has_value()) { - return result; - } + const ScheduleResult result = callback->schedule(scheduleTiming, *mTracker, now); + if (!result.has_value()) { + return {}; + } - if (callback->wakeupTime() < mIntendedWakeupTime - mTimerSlack) { - rearmTimerSkippingUpdateFor(now, it); - } + if (callback->wakeupTime() < mIntendedWakeupTime - mTimerSlack) { + rearmTimerSkippingUpdateFor(now, it); } return result; } +ScheduleResult VSyncDispatchTimerQueue::update(CallbackToken token, ScheduleTiming scheduleTiming) { + std::lock_guard lock(mMutex); + const auto it = mCallbacks.find(token); + if (it == mCallbacks.end()) { + return {}; + } + + auto& callback = it->second; + if (!callback->targetVsync().has_value()) { + return {}; + } + + return scheduleLocked(token, scheduleTiming); +} + CancelResult VSyncDispatchTimerQueue::cancel(CallbackToken token) { std::lock_guard lock(mMutex); @@ -403,44 +442,48 @@ void VSyncDispatchTimerQueue::dump(std::string& result) const { } } -VSyncCallbackRegistration::VSyncCallbackRegistration(VSyncDispatch& dispatch, +VSyncCallbackRegistration::VSyncCallbackRegistration(std::shared_ptr dispatch, VSyncDispatch::Callback callback, std::string callbackName) - : mDispatch(dispatch), - mToken(dispatch.registerCallback(std::move(callback), std::move(callbackName))), - mValidToken(true) {} + : mDispatch(std::move(dispatch)), + mToken(mDispatch->registerCallback(std::move(callback), std::move(callbackName))) {} VSyncCallbackRegistration::VSyncCallbackRegistration(VSyncCallbackRegistration&& other) - : mDispatch(other.mDispatch), - mToken(std::move(other.mToken)), - mValidToken(std::move(other.mValidToken)) { - other.mValidToken = false; -} + : mDispatch(std::move(other.mDispatch)), mToken(std::exchange(other.mToken, std::nullopt)) {} VSyncCallbackRegistration& VSyncCallbackRegistration::operator=(VSyncCallbackRegistration&& other) { + if (this == &other) return *this; + if (mToken) { + mDispatch->unregisterCallback(*mToken); + } mDispatch = std::move(other.mDispatch); - mToken = std::move(other.mToken); - mValidToken = std::move(other.mValidToken); - other.mValidToken = false; + mToken = std::exchange(other.mToken, std::nullopt); return *this; } VSyncCallbackRegistration::~VSyncCallbackRegistration() { - if (mValidToken) mDispatch.get().unregisterCallback(mToken); + if (mToken) mDispatch->unregisterCallback(*mToken); } ScheduleResult VSyncCallbackRegistration::schedule(VSyncDispatch::ScheduleTiming scheduleTiming) { - if (!mValidToken) { + if (!mToken) { + return std::nullopt; + } + return mDispatch->schedule(*mToken, scheduleTiming); +} + +ScheduleResult VSyncCallbackRegistration::update(VSyncDispatch::ScheduleTiming scheduleTiming) { + if (!mToken) { return std::nullopt; } - return mDispatch.get().schedule(mToken, scheduleTiming); + return mDispatch->update(*mToken, scheduleTiming); } CancelResult VSyncCallbackRegistration::cancel() { - if (!mValidToken) { + if (!mToken) { return CancelResult::Error; } - return mDispatch.get().cancel(mToken); + return mDispatch->cancel(*mToken); } } // namespace android::scheduler diff --git a/services/surfaceflinger/Scheduler/VSyncDispatchTimerQueue.h b/services/surfaceflinger/Scheduler/VSyncDispatchTimerQueue.h index 4923031098647ad7a63a49e874cf439c125600cf..6499d69969864c32387df77bb3a3ba9dddca3e28 100644 --- a/services/surfaceflinger/Scheduler/VSyncDispatchTimerQueue.h +++ b/services/surfaceflinger/Scheduler/VSyncDispatchTimerQueue.h @@ -26,11 +26,11 @@ #include #include "VSyncDispatch.h" +#include "VsyncSchedule.h" namespace android::scheduler { class TimeKeeper; -class VSyncTracker; // VSyncDispatchTimerQueueEntry is a helper class representing internal state for each entry in // VSyncDispatchTimerQueue hoisted to public for unit testing. @@ -84,6 +84,8 @@ public: void dump(std::string& result) const; private: + nsecs_t adjustVsyncIfNeeded(VSyncTracker& tracker, nsecs_t nextVsyncTime) const; + const std::string mName; const VSyncDispatch::Callback mCallback; @@ -118,13 +120,14 @@ public: // should be grouped into one wakeup. // \param[in] minVsyncDistance The minimum distance between two vsync estimates before the // vsyncs are considered the same vsync event. - VSyncDispatchTimerQueue(std::unique_ptr, VSyncTracker&, nsecs_t timerSlack, - nsecs_t minVsyncDistance); + VSyncDispatchTimerQueue(std::unique_ptr, VsyncSchedule::TrackerPtr, + nsecs_t timerSlack, nsecs_t minVsyncDistance); ~VSyncDispatchTimerQueue(); CallbackToken registerCallback(Callback, std::string callbackName) final; void unregisterCallback(CallbackToken) final; ScheduleResult schedule(CallbackToken, ScheduleTiming) final; + ScheduleResult update(CallbackToken, ScheduleTiming) final; CancelResult cancel(CallbackToken) final; void dump(std::string&) const final; @@ -141,10 +144,11 @@ private: void rearmTimerSkippingUpdateFor(nsecs_t now, CallbackMap::iterator const& skipUpdate) REQUIRES(mMutex); void cancelTimer() REQUIRES(mMutex); + ScheduleResult scheduleLocked(CallbackToken, ScheduleTiming) REQUIRES(mMutex); static constexpr nsecs_t kInvalidTime = std::numeric_limits::max(); std::unique_ptr const mTimeKeeper; - VSyncTracker& mTracker; + VsyncSchedule::TrackerPtr mTracker; nsecs_t const mTimerSlack; nsecs_t const mMinVsyncDistance; diff --git a/services/surfaceflinger/Scheduler/VSyncPredictor.cpp b/services/surfaceflinger/Scheduler/VSyncPredictor.cpp index 77782e9c723d05d44d18bf2271a8c0197252bd33..e969fdc6799e13ef8b32bbf0a918116bac7a06a9 100644 --- a/services/surfaceflinger/Scheduler/VSyncPredictor.cpp +++ b/services/surfaceflinger/Scheduler/VSyncPredictor.cpp @@ -31,10 +31,11 @@ #include #include #include +#include +#include #include -#include -#include "RefreshRateConfigs.h" +#include "RefreshRateSelector.h" #include "VSyncPredictor.h" namespace android::scheduler { @@ -45,9 +46,10 @@ static auto constexpr kMaxPercent = 100u; VSyncPredictor::~VSyncPredictor() = default; -VSyncPredictor::VSyncPredictor(nsecs_t idealPeriod, size_t historySize, +VSyncPredictor::VSyncPredictor(PhysicalDisplayId id, nsecs_t idealPeriod, size_t historySize, size_t minimumSamplesForPrediction, uint32_t outlierTolerancePercent) - : mTraceOn(property_get_bool("debug.sf.vsp_trace", true)), + : mId(id), + mTraceOn(property_get_bool("debug.sf.vsp_trace", false)), kHistorySize(historySize), kMinimumSamplesForPrediction(minimumSamplesForPrediction), kOutlierTolerancePercent(std::min(outlierTolerancePercent, kMaxPercent)), @@ -57,10 +59,14 @@ VSyncPredictor::VSyncPredictor(nsecs_t idealPeriod, size_t historySize, inline void VSyncPredictor::traceInt64If(const char* name, int64_t value) const { if (CC_UNLIKELY(mTraceOn)) { - ATRACE_INT64(name, value); + traceInt64(name, value); } } +inline void VSyncPredictor::traceInt64(const char* name, int64_t value) const { + ATRACE_INT64(ftl::Concat(ftl::truncated<14>(name), " ", mId.value).c_str(), value); +} + inline size_t VSyncPredictor::next(size_t i) const { return (i + 1) % mTimestamps.size(); } @@ -124,6 +130,8 @@ bool VSyncPredictor::addVsyncTimestamp(nsecs_t timestamp) { mTimestamps[mLastTimestampIndex] = timestamp; } + traceInt64If("VSP-ts", timestamp); + const size_t numSamples = mTimestamps.size(); if (numSamples < kMinimumSamplesForPrediction) { mRateMap[mIdealPeriod] = {mIdealPeriod, 0}; @@ -161,13 +169,13 @@ bool VSyncPredictor::addVsyncTimestamp(nsecs_t timestamp) { nsecs_t meanOrdinal = 0; for (size_t i = 0; i < numSamples; i++) { - traceInt64If("VSP-ts", mTimestamps[i]); - const auto timestamp = mTimestamps[i] - oldestTS; vsyncTS[i] = timestamp; meanTS += timestamp; - const auto ordinal = (vsyncTS[i] + currentPeriod / 2) / currentPeriod * kScalingFactor; + const auto ordinal = currentPeriod == 0 + ? 0 + : (vsyncTS[i] + currentPeriod / 2) / currentPeriod * kScalingFactor; ordinals[i] = ordinal; meanOrdinal += ordinal; } @@ -208,16 +216,27 @@ bool VSyncPredictor::addVsyncTimestamp(nsecs_t timestamp) { it->second = {anticipatedPeriod, intercept}; - ALOGV("model update ts: %" PRId64 " slope: %" PRId64 " intercept: %" PRId64, timestamp, - anticipatedPeriod, intercept); + ALOGV("model update ts %" PRIu64 ": %" PRId64 " slope: %" PRId64 " intercept: %" PRId64, + mId.value, timestamp, anticipatedPeriod, intercept); return true; } +auto VSyncPredictor::getVsyncSequenceLocked(nsecs_t timestamp) const -> VsyncSequence { + const auto vsync = nextAnticipatedVSyncTimeFromLocked(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::nextAnticipatedVSyncTimeFromLocked(nsecs_t timePoint) const { auto const [slope, intercept] = getVSyncPredictionModelLocked(); if (mTimestamps.empty()) { - traceInt64If("VSP-mode", 1); + traceInt64("VSP-mode", 1); auto const knownTimestamp = mKnownTimestamp ? *mKnownTimestamp : timePoint; auto const numPeriodsOut = ((timePoint - knownTimestamp) / mIdealPeriod) + 1; return knownTimestamp + numPeriodsOut * mIdealPeriod; @@ -230,7 +249,7 @@ nsecs_t VSyncPredictor::nextAnticipatedVSyncTimeFromLocked(nsecs_t timePoint) co auto const ordinalRequest = (timePoint - zeroPoint + slope) / slope; auto const prediction = (ordinalRequest * slope) + intercept + oldest; - traceInt64If("VSP-mode", 0); + traceInt64("VSP-mode", 0); traceInt64If("VSP-timePoint", timePoint); traceInt64If("VSP-prediction", prediction); @@ -251,7 +270,31 @@ nsecs_t VSyncPredictor::nextAnticipatedVSyncTimeFromLocked(nsecs_t timePoint) co nsecs_t VSyncPredictor::nextAnticipatedVSyncTimeFrom(nsecs_t timePoint) const { std::lock_guard lock(mMutex); - return nextAnticipatedVSyncTimeFromLocked(timePoint); + + // update the mLastVsyncSequence for reference point + mLastVsyncSequence = getVsyncSequenceLocked(timePoint); + + const auto renderRatePhase = [&]() REQUIRES(mMutex) -> int { + if (!mRenderRate) return 0; + + const auto divisor = + RefreshRateSelector::getFrameRateDivisor(Fps::fromPeriodNsecs(mIdealPeriod), + *mRenderRate); + if (divisor <= 1) return 0; + + const int mod = mLastVsyncSequence->seq % divisor; + if (mod == 0) return 0; + + return divisor - mod; + }(); + + if (renderRatePhase == 0) { + return mLastVsyncSequence->vsyncTime; + } + + auto const [slope, intercept] = getVSyncPredictionModelLocked(); + const auto approximateNextVsync = mLastVsyncSequence->vsyncTime + slope * renderRatePhase; + return nextAnticipatedVSyncTimeFromLocked(approximateNextVsync - slope / 2); } /* @@ -263,51 +306,36 @@ nsecs_t VSyncPredictor::nextAnticipatedVSyncTimeFrom(nsecs_t timePoint) const { * isVSyncInPhase(50.0, 30) = true */ bool VSyncPredictor::isVSyncInPhase(nsecs_t timePoint, Fps frameRate) const { - struct VsyncError { - nsecs_t vsyncTimestamp; - float error; + std::lock_guard lock(mMutex); + const auto divisor = + RefreshRateSelector::getFrameRateDivisor(Fps::fromPeriodNsecs(mIdealPeriod), frameRate); + return isVSyncInPhaseLocked(timePoint, static_cast(divisor)); +} - bool operator<(const VsyncError& other) const { return error < other.error; } +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); - std::lock_guard lock(mMutex); - const auto divisor = - RefreshRateConfigs::getFrameRateDivisor(Fps::fromPeriodNsecs(mIdealPeriod), frameRate); if (divisor <= 1 || timePoint == 0) { return true; } const nsecs_t period = mRateMap[mIdealPeriod].slope; const nsecs_t justBeforeTimePoint = timePoint - period / 2; - const nsecs_t dividedPeriod = mIdealPeriod / divisor; - - // If this is the first time we have asked about this divisor with the - // current vsync period, it is considered in phase and we store the closest - // vsync timestamp - const auto knownTimestampIter = mRateDivisorKnownTimestampMap.find(dividedPeriod); - if (knownTimestampIter == mRateDivisorKnownTimestampMap.end()) { - const auto vsync = nextAnticipatedVSyncTimeFromLocked(justBeforeTimePoint); - mRateDivisorKnownTimestampMap[dividedPeriod] = vsync; - return true; - } - - // Find the next N vsync timestamp where N is the divisor. - // One of these vsyncs will be in phase. We return the one which is - // the most aligned with the last known in phase vsync - std::vector vsyncs(static_cast(divisor)); - const nsecs_t knownVsync = knownTimestampIter->second; - nsecs_t point = justBeforeTimePoint; - for (size_t i = 0; i < divisor; i++) { - const nsecs_t vsync = nextAnticipatedVSyncTimeFromLocked(point); - const auto numPeriods = static_cast(vsync - knownVsync) / (period * divisor); - const auto error = std::abs(std::round(numPeriods) - numPeriods); - vsyncs[i] = {vsync, error}; - point = vsync + 1; - } + const auto vsyncSequence = getVsyncSequenceLocked(justBeforeTimePoint); + ATRACE_FORMAT_INSTANT("vsync in: %.2f sequence: %" PRId64, + getTimePointIn(now, vsyncSequence.vsyncTime), vsyncSequence.seq); + return vsyncSequence.seq % divisor == 0; +} - const auto minVsyncError = std::min_element(vsyncs.begin(), vsyncs.end()); - mRateDivisorKnownTimestampMap[dividedPeriod] = minVsyncError->vsyncTimestamp; - return std::abs(minVsyncError->vsyncTimestamp - timePoint) < period / 2; +void VSyncPredictor::setRenderRate(Fps fps) { + ALOGV("%s %s: %s", __func__, to_string(mId).c_str(), to_string(fps).c_str()); + std::lock_guard lock(mMutex); + mRenderRate = fps; } VSyncPredictor::Model VSyncPredictor::getVSyncPredictionModel() const { @@ -321,7 +349,8 @@ VSyncPredictor::Model VSyncPredictor::getVSyncPredictionModelLocked() const { } void VSyncPredictor::setPeriod(nsecs_t period) { - ATRACE_CALL(); + ATRACE_FORMAT("%s %s", __func__, to_string(mId).c_str()); + traceInt64("VSP-setPeriod", period); std::lock_guard lock(mMutex); static constexpr size_t kSizeLimit = 30; diff --git a/services/surfaceflinger/Scheduler/VSyncPredictor.h b/services/surfaceflinger/Scheduler/VSyncPredictor.h index 3181102663e87ba0fabd1073e5e232d171e276a5..c01c44dc6b5886fdb988d394374e1cbb04d209c1 100644 --- a/services/surfaceflinger/Scheduler/VSyncPredictor.h +++ b/services/surfaceflinger/Scheduler/VSyncPredictor.h @@ -21,6 +21,7 @@ #include #include +#include #include "VSyncTracker.h" @@ -29,14 +30,15 @@ namespace android::scheduler { class VSyncPredictor : public VSyncTracker { public: /* + * \param [in] PhysicalDisplayid The display this corresponds to. * \param [in] idealPeriod The initial ideal period to use. * \param [in] historySize The internal amount of entries to store in the model. * \param [in] minimumSamplesForPrediction The minimum number of samples to collect before * 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(nsecs_t idealPeriod, size_t historySize, size_t minimumSamplesForPrediction, - uint32_t outlierTolerancePercent); + VSyncPredictor(PhysicalDisplayId, nsecs_t idealPeriod, size_t historySize, + size_t minimumSamplesForPrediction, uint32_t outlierTolerancePercent); ~VSyncPredictor(); bool addVsyncTimestamp(nsecs_t timestamp) final EXCLUDES(mMutex); @@ -67,6 +69,8 @@ public: bool isVSyncInPhase(nsecs_t timePoint, Fps frameRate) const final EXCLUDES(mMutex); + void setRenderRate(Fps) final EXCLUDES(mMutex); + void dump(std::string& result) const final EXCLUDES(mMutex); private: @@ -74,20 +78,28 @@ private: VSyncPredictor& operator=(VSyncPredictor const&) = delete; void clearTimestamps() REQUIRES(mMutex); - inline void traceInt64If(const char* name, int64_t value) const; - bool const mTraceOn; + const PhysicalDisplayId mId; - size_t const kHistorySize; - size_t const kMinimumSamplesForPrediction; - size_t const kOutlierTolerancePercent; + inline void traceInt64If(const char* name, int64_t value) const; + inline void traceInt64(const char* name, int64_t value) const; - std::mutex mutable mMutex; size_t next(size_t i) const REQUIRES(mMutex); bool validate(nsecs_t timestamp) const REQUIRES(mMutex); - Model getVSyncPredictionModelLocked() const REQUIRES(mMutex); - nsecs_t nextAnticipatedVSyncTimeFromLocked(nsecs_t timePoint) const REQUIRES(mMutex); + bool isVSyncInPhaseLocked(nsecs_t timePoint, unsigned divisor) const REQUIRES(mMutex); + + struct VsyncSequence { + nsecs_t vsyncTime; + int64_t seq; + }; + VsyncSequence getVsyncSequenceLocked(nsecs_t timestamp) const REQUIRES(mMutex); + + bool const mTraceOn; + size_t const kHistorySize; + size_t const kMinimumSamplesForPrediction; + size_t const kOutlierTolerancePercent; + std::mutex mutable mMutex; nsecs_t mIdealPeriod GUARDED_BY(mMutex); std::optional mKnownTimestamp GUARDED_BY(mMutex); @@ -95,11 +107,12 @@ private: // Map between ideal vsync period and the calculated model std::unordered_map mutable mRateMap GUARDED_BY(mMutex); - // Map between the divided vsync period and the last known vsync timestamp - std::unordered_map mutable mRateDivisorKnownTimestampMap GUARDED_BY(mMutex); - size_t mLastTimestampIndex GUARDED_BY(mMutex) = 0; std::vector mTimestamps GUARDED_BY(mMutex); + + std::optional mRenderRate GUARDED_BY(mMutex); + + mutable std::optional mLastVsyncSequence GUARDED_BY(mMutex); }; } // namespace android::scheduler diff --git a/services/surfaceflinger/Scheduler/VSyncReactor.cpp b/services/surfaceflinger/Scheduler/VSyncReactor.cpp index e23945dc8a03c5fd9414fdc004bf7918173e0bfa..2938aa3fb34efc1da57307305b4544584875c3ec 100644 --- a/services/surfaceflinger/Scheduler/VSyncReactor.cpp +++ b/services/surfaceflinger/Scheduler/VSyncReactor.cpp @@ -21,6 +21,8 @@ #include #include +#include +#include #include #include @@ -39,12 +41,13 @@ nsecs_t SystemClock::now() const { return systemTime(SYSTEM_TIME_MONOTONIC); } -VSyncReactor::VSyncReactor(std::unique_ptr clock, VSyncTracker& tracker, - size_t pendingFenceLimit, bool supportKernelIdleTimer) - : mClock(std::move(clock)), +VSyncReactor::VSyncReactor(PhysicalDisplayId id, std::unique_ptr clock, + VSyncTracker& tracker, size_t pendingFenceLimit, + bool supportKernelIdleTimer) + : mId(id), + mClock(std::move(clock)), mTracker(tracker), mPendingLimit(pendingFenceLimit), - // TODO(adyabr): change mSupportKernelIdleTimer when the active display changes mSupportKernelIdleTimer(supportKernelIdleTimer) {} VSyncReactor::~VSyncReactor() = default; @@ -114,7 +117,7 @@ void VSyncReactor::updateIgnorePresentFencesInternal() { } void VSyncReactor::startPeriodTransitionInternal(nsecs_t newPeriod) { - ATRACE_CALL(); + ATRACE_FORMAT("%s %" PRIu64, __func__, mId.value); mPeriodConfirmationInProgress = true; mPeriodTransitioningTo = newPeriod; mMoreSamplesNeeded = true; @@ -122,18 +125,18 @@ void VSyncReactor::startPeriodTransitionInternal(nsecs_t newPeriod) { } void VSyncReactor::endPeriodTransition() { - ATRACE_CALL(); + ATRACE_FORMAT("%s %" PRIu64, __func__, mId.value); mPeriodTransitioningTo.reset(); mPeriodConfirmationInProgress = false; mLastHwVsync.reset(); } -void VSyncReactor::startPeriodTransition(nsecs_t period) { - ATRACE_INT64("VSR-setPeriod", period); +void VSyncReactor::startPeriodTransition(nsecs_t period, bool force) { + ATRACE_INT64(ftl::Concat("VSR-", __func__, " ", mId.value).c_str(), period); std::lock_guard lock(mMutex); mLastHwVsync.reset(); - if (!mSupportKernelIdleTimer && period == mTracker.currentPeriod()) { + if (!mSupportKernelIdleTimer && period == mTracker.currentPeriod() && !force) { endPeriodTransition(); setIgnorePresentFencesInternal(false); mMoreSamplesNeeded = false; @@ -181,7 +184,7 @@ bool VSyncReactor::addHwVsyncTimestamp(nsecs_t timestamp, std::optional std::lock_guard lock(mMutex); if (periodConfirmed(timestamp, hwcVsyncPeriod)) { - ATRACE_NAME("VSR: period confirmed"); + ATRACE_FORMAT("VSR %" PRIu64 ": period confirmed", mId.value); if (mPeriodTransitioningTo) { mTracker.setPeriod(*mPeriodTransitioningTo); *periodFlushed = true; @@ -195,12 +198,12 @@ bool VSyncReactor::addHwVsyncTimestamp(nsecs_t timestamp, std::optional endPeriodTransition(); mMoreSamplesNeeded = mTracker.needsMoreSamples(); } else if (mPeriodConfirmationInProgress) { - ATRACE_NAME("VSR: still confirming period"); + ATRACE_FORMAT("VSR %" PRIu64 ": still confirming period", mId.value); mLastHwVsync = timestamp; mMoreSamplesNeeded = true; *periodFlushed = false; } else { - ATRACE_NAME("VSR: adding sample"); + ATRACE_FORMAT("VSR %" PRIu64 ": adding sample", mId.value); *periodFlushed = false; mTracker.addVsyncTimestamp(timestamp); mMoreSamplesNeeded = mTracker.needsMoreSamples(); diff --git a/services/surfaceflinger/Scheduler/VSyncReactor.h b/services/surfaceflinger/Scheduler/VSyncReactor.h index 45014873927fdcabf523466d1f2b2a56b5e07316..f2302422ad512203d1fa841e20a47abfd8d9db6e 100644 --- a/services/surfaceflinger/Scheduler/VSyncReactor.h +++ b/services/surfaceflinger/Scheduler/VSyncReactor.h @@ -22,6 +22,7 @@ #include #include +#include #include #include @@ -37,14 +38,14 @@ class VSyncTracker; // TODO (b/145217110): consider renaming. class VSyncReactor : public VsyncController { public: - VSyncReactor(std::unique_ptr clock, VSyncTracker& tracker, size_t pendingFenceLimit, - bool supportKernelIdleTimer); + VSyncReactor(PhysicalDisplayId, std::unique_ptr clock, VSyncTracker& tracker, + size_t pendingFenceLimit, bool supportKernelIdleTimer); ~VSyncReactor(); bool addPresentFence(std::shared_ptr) final; void setIgnorePresentFences(bool ignore) final; - void startPeriodTransition(nsecs_t period) final; + void startPeriodTransition(nsecs_t period, bool force) final; bool addHwVsyncTimestamp(nsecs_t timestamp, std::optional hwcVsyncPeriod, bool* periodFlushed) final; @@ -61,6 +62,7 @@ private: bool periodConfirmed(nsecs_t vsync_timestamp, std::optional hwcVsyncPeriod) REQUIRES(mMutex); + const PhysicalDisplayId mId; std::unique_ptr const mClock; VSyncTracker& mTracker; size_t const mPendingLimit; diff --git a/services/surfaceflinger/Scheduler/VSyncTracker.h b/services/surfaceflinger/Scheduler/VSyncTracker.h index 76315d2b5b34fb48ed28629ba7b6a17c471fe5c7..bc0e3bcbb2d7fa92bb60a42d735ae5209fb42f3c 100644 --- a/services/surfaceflinger/Scheduler/VSyncTracker.h +++ b/services/surfaceflinger/Scheduler/VSyncTracker.h @@ -79,6 +79,18 @@ public: */ virtual bool isVSyncInPhase(nsecs_t timePoint, Fps frameRate) const = 0; + /* + * Sets a render rate on the tracker. If the render rate is not a divisor + * of the period, the render rate is ignored until the period changes. + * The tracker will continue to track the vsync timeline and expect it + * to match the current period, however, nextAnticipatedVSyncTimeFrom will + * return vsyncs according to the render rate set. Setting a render rate is useful + * 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. + */ + virtual void setRenderRate(Fps) = 0; + virtual void dump(std::string& result) const = 0; protected: diff --git a/services/surfaceflinger/Scheduler/VsyncConfiguration.cpp b/services/surfaceflinger/Scheduler/VsyncConfiguration.cpp index ff316515b6069272014f61fd7bd403ac02cc462e..6ae10f3d312c7eecce53c3fb05f9ac0a56b411fd 100644 --- a/services/surfaceflinger/Scheduler/VsyncConfiguration.cpp +++ b/services/surfaceflinger/Scheduler/VsyncConfiguration.cpp @@ -42,12 +42,12 @@ namespace android::scheduler::impl { VsyncConfiguration::VsyncConfiguration(Fps currentFps) : mRefreshRateFps(currentFps) {} -PhaseOffsets::VsyncConfigSet VsyncConfiguration::getConfigsForRefreshRate(Fps fps) const { +VsyncConfigSet VsyncConfiguration::getConfigsForRefreshRate(Fps fps) const { std::lock_guard lock(mLock); return getConfigsForRefreshRateLocked(fps); } -PhaseOffsets::VsyncConfigSet VsyncConfiguration::getConfigsForRefreshRateLocked(Fps fps) const { +VsyncConfigSet VsyncConfiguration::getConfigsForRefreshRateLocked(Fps fps) const { if (const auto offsets = mOffsetsCache.get(fps)) { return offsets->get(); } @@ -134,7 +134,7 @@ PhaseOffsets::PhaseOffsets(Fps currentFps, nsecs_t vsyncPhaseOffsetNs, nsecs_t s mThresholdForNextVsync(thresholdForNextVsync), mHwcMinWorkDuration(hwcMinWorkDuration) {} -PhaseOffsets::VsyncConfigSet PhaseOffsets::constructOffsets(nsecs_t vsyncDuration) const { +VsyncConfigSet PhaseOffsets::constructOffsets(nsecs_t vsyncDuration) const { if (vsyncDuration < std::chrono::nanoseconds(15ms).count()) { return getHighFpsOffsets(vsyncDuration); } else { @@ -158,7 +158,7 @@ std::chrono::nanoseconds appOffsetToDuration(nsecs_t appOffset, nsecs_t sfOffset } } // namespace -PhaseOffsets::VsyncConfigSet PhaseOffsets::getDefaultOffsets(nsecs_t vsyncDuration) const { +VsyncConfigSet PhaseOffsets::getDefaultOffsets(nsecs_t vsyncDuration) const { const auto earlySfOffset = mEarlySfOffsetNs.value_or(mSfVSyncPhaseOffsetNs) < mThresholdForNextVsync @@ -196,7 +196,7 @@ PhaseOffsets::VsyncConfigSet PhaseOffsets::getDefaultOffsets(nsecs_t vsyncDurati }; } -PhaseOffsets::VsyncConfigSet PhaseOffsets::getHighFpsOffsets(nsecs_t vsyncDuration) const { +VsyncConfigSet PhaseOffsets::getHighFpsOffsets(nsecs_t vsyncDuration) const { const auto earlySfOffset = mHighFpsEarlySfOffsetNs.value_or(mHighFpsSfVSyncPhaseOffsetNs) < mThresholdForNextVsync ? mHighFpsEarlySfOffsetNs.value_or(mHighFpsSfVSyncPhaseOffsetNs) @@ -286,7 +286,7 @@ nsecs_t appDurationToOffset(std::chrono::nanoseconds appDuration, } } // namespace -WorkDuration::VsyncConfigSet WorkDuration::constructOffsets(nsecs_t vsyncDuration) const { +VsyncConfigSet WorkDuration::constructOffsets(nsecs_t vsyncDuration) const { const auto sfDurationFixup = [vsyncDuration](nsecs_t duration) { return duration == -1 ? std::chrono::nanoseconds(vsyncDuration) - 1ms : std::chrono::nanoseconds(duration); diff --git a/services/surfaceflinger/Scheduler/VsyncConfiguration.h b/services/surfaceflinger/Scheduler/VsyncConfiguration.h index 02ebd702722ea117cfdb6050bbc5befb7e7177c1..a24e43f9d67a7ddd5ffefc02b8d5d16e4ff9a8a2 100644 --- a/services/surfaceflinger/Scheduler/VsyncConfiguration.h +++ b/services/surfaceflinger/Scheduler/VsyncConfiguration.h @@ -20,12 +20,12 @@ #include #include +#include #include #include #include - -#include "VsyncModulator.h" +#include namespace android::scheduler { @@ -37,8 +37,6 @@ namespace android::scheduler { */ class VsyncConfiguration { public: - using VsyncConfigSet = VsyncModulator::VsyncConfigSet; - virtual ~VsyncConfiguration() = default; virtual VsyncConfigSet getCurrentConfigs() const = 0; virtual VsyncConfigSet getConfigsForRefreshRate(Fps fps) const = 0; @@ -85,7 +83,7 @@ public: void dump(std::string& result) const override; protected: - virtual VsyncConfiguration::VsyncConfigSet constructOffsets(nsecs_t vsyncDuration) const = 0; + virtual VsyncConfigSet constructOffsets(nsecs_t vsyncDuration) const = 0; VsyncConfigSet getConfigsForRefreshRateLocked(Fps fps) const REQUIRES(mLock); @@ -115,7 +113,7 @@ protected: nsecs_t hwcMinWorkDuration); private: - VsyncConfiguration::VsyncConfigSet constructOffsets(nsecs_t vsyncDuration) const override; + VsyncConfigSet constructOffsets(nsecs_t vsyncDuration) const override; VsyncConfigSet getDefaultOffsets(nsecs_t vsyncPeriod) const; VsyncConfigSet getHighFpsOffsets(nsecs_t vsyncPeriod) const; @@ -154,7 +152,7 @@ protected: nsecs_t hwcMinWorkDuration); private: - VsyncConfiguration::VsyncConfigSet constructOffsets(nsecs_t vsyncDuration) const override; + VsyncConfigSet constructOffsets(nsecs_t vsyncDuration) const override; const nsecs_t mSfDuration; const nsecs_t mAppDuration; diff --git a/services/surfaceflinger/Scheduler/VsyncController.h b/services/surfaceflinger/Scheduler/VsyncController.h index 726a42064922261251a721b55c9c8759790a4190..917789934a8498f3a4a6e0b4690d618ba338419e 100644 --- a/services/surfaceflinger/Scheduler/VsyncController.h +++ b/services/surfaceflinger/Scheduler/VsyncController.h @@ -63,8 +63,9 @@ public: * itself. The controller will end the period transition internally. * * \param [in] period The period that the system is changing into. + * \param [in] force True to recalibrate even if period matches the existing period. */ - virtual void startPeriodTransition(nsecs_t period) = 0; + virtual void startPeriodTransition(nsecs_t period, bool force) = 0; /* * Tells the tracker to stop using present fences to get a vsync signal. diff --git a/services/surfaceflinger/Scheduler/VsyncModulator.cpp b/services/surfaceflinger/Scheduler/VsyncModulator.cpp index be57b2acd75da0a89956ea3e738c6b1d46d653cc..586357f50adfef8ac6dcaa81a8b7e1ef0b7a9fe4 100644 --- a/services/surfaceflinger/Scheduler/VsyncModulator.cpp +++ b/services/surfaceflinger/Scheduler/VsyncModulator.cpp @@ -40,7 +40,7 @@ VsyncModulator::VsyncModulator(const VsyncConfigSet& config, Now now) mNow(now), mTraceDetailedInfo(base::GetBoolProperty("debug.sf.vsync_trace_detailed_info", false)) {} -VsyncModulator::VsyncConfig VsyncModulator::setVsyncConfigSet(const VsyncConfigSet& config) { +VsyncConfig VsyncModulator::setVsyncConfigSet(const VsyncConfigSet& config) { std::lock_guard lock(mMutex); mVsyncConfigSet = config; return updateVsyncConfigLocked(); @@ -53,14 +53,14 @@ VsyncModulator::VsyncConfigOpt VsyncModulator::setTransactionSchedule(Transactio case Schedule::EarlyStart: if (token) { mEarlyWakeupRequests.emplace(token); - token->linkToDeath(this); + token->linkToDeath(sp::fromExisting(this)); } else { ALOGW("%s: EarlyStart requested without a valid token", __func__); } break; case Schedule::EarlyEnd: { if (token && mEarlyWakeupRequests.erase(token) > 0) { - token->unlinkToDeath(this); + token->unlinkToDeath(sp::fromExisting(this)); } else { ALOGW("%s: Unexpected EarlyEnd", __func__); } @@ -129,7 +129,7 @@ VsyncModulator::VsyncConfigOpt VsyncModulator::onDisplayRefresh(bool usedGpuComp return updateVsyncConfig(); } -VsyncModulator::VsyncConfig VsyncModulator::getVsyncConfig() const { +VsyncConfig VsyncModulator::getVsyncConfig() const { std::lock_guard lock(mMutex); return mVsyncConfig; } @@ -147,7 +147,7 @@ auto VsyncModulator::getNextVsyncConfigType() const -> VsyncConfigType { } } -const VsyncModulator::VsyncConfig& VsyncModulator::getNextVsyncConfig() const { +const VsyncConfig& VsyncModulator::getNextVsyncConfig() const { switch (getNextVsyncConfigType()) { case VsyncConfigType::Early: return mVsyncConfigSet.early; @@ -158,12 +158,12 @@ const VsyncModulator::VsyncConfig& VsyncModulator::getNextVsyncConfig() const { } } -VsyncModulator::VsyncConfig VsyncModulator::updateVsyncConfig() { +VsyncConfig VsyncModulator::updateVsyncConfig() { std::lock_guard lock(mMutex); return updateVsyncConfigLocked(); } -VsyncModulator::VsyncConfig VsyncModulator::updateVsyncConfigLocked() { +VsyncConfig VsyncModulator::updateVsyncConfigLocked() { const VsyncConfig& offsets = getNextVsyncConfig(); mVsyncConfig = offsets; @@ -187,9 +187,9 @@ void VsyncModulator::binderDied(const wp& who) { static_cast(updateVsyncConfigLocked()); } -bool VsyncModulator::isVsyncConfigDefault() const { +bool VsyncModulator::isVsyncConfigEarly() const { std::lock_guard lock(mMutex); - return getNextVsyncConfigType() == VsyncConfigType::Late; + return getNextVsyncConfigType() != VsyncConfigType::Late; } } // namespace android::scheduler diff --git a/services/surfaceflinger/Scheduler/VsyncModulator.h b/services/surfaceflinger/Scheduler/VsyncModulator.h index 537cae1d7c61dda87e3553a1f7cc67a99d688edb..be0d3348b5479bdc81a800b57e8f060afc704863 100644 --- a/services/surfaceflinger/Scheduler/VsyncModulator.h +++ b/services/surfaceflinger/Scheduler/VsyncModulator.h @@ -25,19 +25,13 @@ #include #include +#include +#include + #include "../WpHash.h" namespace android::scheduler { -// State machine controlled by transaction flags. VsyncModulator switches to early phase offsets -// when a transaction is flagged EarlyStart or Early, lasting until an EarlyEnd transaction or a -// fixed number of frames, respectively. -enum class TransactionSchedule { - Late, // Default. - EarlyStart, - EarlyEnd -}; - // Modulates VSYNC phase depending on transaction schedule and refresh rate changes. class VsyncModulator : public IBinder::DeathRecipient { public: @@ -51,47 +45,20 @@ public: // This may keep early offsets for an extra frame, but avoids a race with transaction commit. static const std::chrono::nanoseconds MIN_EARLY_TRANSACTION_TIME; - // Phase offsets and work durations for SF and app deadlines from VSYNC. - struct VsyncConfig { - nsecs_t sfOffset; - nsecs_t appOffset; - std::chrono::nanoseconds sfWorkDuration; - std::chrono::nanoseconds appWorkDuration; - - bool operator==(const VsyncConfig& other) const { - return sfOffset == other.sfOffset && appOffset == other.appOffset && - sfWorkDuration == other.sfWorkDuration && - appWorkDuration == other.appWorkDuration; - } - - bool operator!=(const VsyncConfig& other) const { return !(*this == other); } - }; - using VsyncConfigOpt = std::optional; - struct VsyncConfigSet { - VsyncConfig early; // Used for early transactions, and during refresh rate change. - VsyncConfig earlyGpu; // Used during GPU composition. - VsyncConfig late; // Default. - std::chrono::nanoseconds hwcMinWorkDuration; // Used for calculating the - // earliest present time - - bool operator==(const VsyncConfigSet& other) const { - return early == other.early && earlyGpu == other.earlyGpu && late == other.late && - hwcMinWorkDuration == other.hwcMinWorkDuration; - } - - bool operator!=(const VsyncConfigSet& other) const { return !(*this == other); } - }; - using Clock = std::chrono::steady_clock; using TimePoint = Clock::time_point; using Now = TimePoint (*)(); explicit VsyncModulator(const VsyncConfigSet&, Now = Clock::now); + bool isVsyncConfigEarly() const EXCLUDES(mMutex); + VsyncConfig getVsyncConfig() const EXCLUDES(mMutex); + void cancelRefreshRateChange() { mRefreshRateChangePending = false; } + [[nodiscard]] VsyncConfig setVsyncConfigSet(const VsyncConfigSet&) EXCLUDES(mMutex); // Changes offsets in response to transaction flags or commit. @@ -109,8 +76,6 @@ public: [[nodiscard]] VsyncConfigOpt onDisplayRefresh(bool usedGpuComposition); - [[nodiscard]] bool isVsyncConfigDefault() const; - protected: // Called from unit tests as well void binderDied(const wp&) override EXCLUDES(mMutex); diff --git a/services/surfaceflinger/Scheduler/VsyncSchedule.cpp b/services/surfaceflinger/Scheduler/VsyncSchedule.cpp index 3a918a1660f51ea19869e99a49060c17b6b65d99..84671aea0da3f34b862e7b8c370c4846599e939e 100644 --- a/services/surfaceflinger/Scheduler/VsyncSchedule.cpp +++ b/services/surfaceflinger/Scheduler/VsyncSchedule.cpp @@ -16,11 +16,14 @@ #define ATRACE_TAG ATRACE_TAG_GRAPHICS +#include #include #include #include "VsyncSchedule.h" +#include "ISchedulerCallback.h" +#include "Utils/Dumper.h" #include "VSyncDispatchTimerQueue.h" #include "VSyncPredictor.h" #include "VSyncReactor.h" @@ -39,8 +42,8 @@ class VsyncSchedule::PredictedVsyncTracer { } public: - explicit PredictedVsyncTracer(VsyncDispatch& dispatch) - : mRegistration(dispatch, makeVsyncCallback(), __func__) { + explicit PredictedVsyncTracer(std::shared_ptr dispatch) + : mRegistration(std::move(dispatch), makeVsyncCallback(), __func__) { schedule(); } @@ -51,24 +54,43 @@ private: VSyncCallbackRegistration mRegistration; }; -VsyncSchedule::VsyncSchedule(FeatureFlags features) - : mTracker(createTracker()), - mDispatch(createDispatch(*mTracker)), - mController(createController(*mTracker, features)) { - if (features.test(Feature::kTracePredictedVsync)) { - mTracer = std::make_unique(*mDispatch); - } -} - -VsyncSchedule::VsyncSchedule(TrackerPtr tracker, DispatchPtr dispatch, ControllerPtr controller) - : mTracker(std::move(tracker)), +VsyncSchedule::VsyncSchedule(PhysicalDisplayId id, FeatureFlags features) + : mId(id), + mTracker(createTracker(id)), + mDispatch(createDispatch(mTracker)), + mController(createController(id, *mTracker, features)), + mTracer(features.test(Feature::kTracePredictedVsync) + ? std::make_unique(mDispatch) + : nullptr) {} + +VsyncSchedule::VsyncSchedule(PhysicalDisplayId id, TrackerPtr tracker, DispatchPtr dispatch, + ControllerPtr controller) + : mId(id), + mTracker(std::move(tracker)), mDispatch(std::move(dispatch)), mController(std::move(controller)) {} -VsyncSchedule::VsyncSchedule(VsyncSchedule&&) = default; VsyncSchedule::~VsyncSchedule() = default; +Period VsyncSchedule::period() const { + return Period::fromNs(mTracker->currentPeriod()); +} + +TimePoint VsyncSchedule::vsyncDeadlineAfter(TimePoint timePoint) const { + return TimePoint::fromNs(mTracker->nextAnticipatedVSyncTimeFrom(timePoint.ns())); +} + void VsyncSchedule::dump(std::string& out) const { + utils::Dumper dumper(out); + { + std::lock_guard lock(mHwVsyncLock); + dumper.dump("hwVsyncState", ftl::enum_string(mHwVsyncState)); + + ftl::FakeGuard guard(kMainThreadContext); + dumper.dump("pendingHwVsyncState", ftl::enum_string(mPendingHwVsyncState)); + dumper.eol(); + } + out.append("VsyncController:\n"); mController->dump(out); @@ -76,40 +98,110 @@ void VsyncSchedule::dump(std::string& out) const { mDispatch->dump(out); } -VsyncSchedule::TrackerPtr VsyncSchedule::createTracker() { +VsyncSchedule::TrackerPtr VsyncSchedule::createTracker(PhysicalDisplayId id) { // TODO(b/144707443): Tune constants. constexpr nsecs_t kInitialPeriod = (60_Hz).getPeriodNsecs(); constexpr size_t kHistorySize = 20; constexpr size_t kMinSamplesForPrediction = 6; constexpr uint32_t kDiscardOutlierPercent = 20; - return std::make_unique(kInitialPeriod, kHistorySize, kMinSamplesForPrediction, - kDiscardOutlierPercent); + return std::make_unique(id, kInitialPeriod, kHistorySize, + kMinSamplesForPrediction, kDiscardOutlierPercent); } -VsyncSchedule::DispatchPtr VsyncSchedule::createDispatch(VsyncTracker& tracker) { +VsyncSchedule::DispatchPtr VsyncSchedule::createDispatch(TrackerPtr tracker) { using namespace std::chrono_literals; // TODO(b/144707443): Tune constants. constexpr std::chrono::nanoseconds kGroupDispatchWithin = 500us; constexpr std::chrono::nanoseconds kSnapToSameVsyncWithin = 3ms; - return std::make_unique(std::make_unique(), tracker, + return std::make_unique(std::make_unique(), std::move(tracker), kGroupDispatchWithin.count(), kSnapToSameVsyncWithin.count()); } -VsyncSchedule::ControllerPtr VsyncSchedule::createController(VsyncTracker& tracker, +VsyncSchedule::ControllerPtr VsyncSchedule::createController(PhysicalDisplayId id, + VsyncTracker& tracker, FeatureFlags features) { // TODO(b/144707443): Tune constants. constexpr size_t kMaxPendingFences = 20; const bool hasKernelIdleTimer = features.test(Feature::kKernelIdleTimer); - auto reactor = std::make_unique(std::make_unique(), tracker, + auto reactor = std::make_unique(id, std::make_unique(), tracker, kMaxPendingFences, hasKernelIdleTimer); reactor->setIgnorePresentFences(!features.test(Feature::kPresentFences)); return reactor; } +void VsyncSchedule::startPeriodTransition(ISchedulerCallback& callback, Period period, bool force) { + std::lock_guard lock(mHwVsyncLock); + mController->startPeriodTransition(period.ns(), force); + enableHardwareVsyncLocked(callback); +} + +bool VsyncSchedule::addResyncSample(ISchedulerCallback& callback, TimePoint timestamp, + ftl::Optional hwcVsyncPeriod) { + bool needsHwVsync = false; + bool periodFlushed = false; + { + std::lock_guard lock(mHwVsyncLock); + if (mHwVsyncState == HwVsyncState::Enabled) { + needsHwVsync = mController->addHwVsyncTimestamp(timestamp.ns(), + hwcVsyncPeriod.transform(&Period::ns), + &periodFlushed); + } + } + if (needsHwVsync) { + enableHardwareVsync(callback); + } else { + disableHardwareVsync(callback, false /* disallow */); + } + return periodFlushed; +} + +void VsyncSchedule::enableHardwareVsync(ISchedulerCallback& callback) { + std::lock_guard lock(mHwVsyncLock); + enableHardwareVsyncLocked(callback); +} + +void VsyncSchedule::enableHardwareVsyncLocked(ISchedulerCallback& callback) { + if (mHwVsyncState == HwVsyncState::Disabled) { + getTracker().resetModel(); + callback.setVsyncEnabled(mId, true); + mHwVsyncState = HwVsyncState::Enabled; + } +} + +void VsyncSchedule::disableHardwareVsync(ISchedulerCallback& callback, bool disallow) { + std::lock_guard lock(mHwVsyncLock); + switch (mHwVsyncState) { + case HwVsyncState::Enabled: + callback.setVsyncEnabled(mId, false); + [[fallthrough]]; + case HwVsyncState::Disabled: + mHwVsyncState = disallow ? HwVsyncState::Disallowed : HwVsyncState::Disabled; + break; + case HwVsyncState::Disallowed: + break; + } +} + +bool VsyncSchedule::isHardwareVsyncAllowed(bool makeAllowed) { + std::lock_guard lock(mHwVsyncLock); + if (makeAllowed && mHwVsyncState == HwVsyncState::Disallowed) { + mHwVsyncState = HwVsyncState::Disabled; + } + return mHwVsyncState != HwVsyncState::Disallowed; +} + +void VsyncSchedule::setPendingHardwareVsyncState(bool enabled) { + mPendingHwVsyncState = enabled ? HwVsyncState::Enabled : HwVsyncState::Disabled; +} + +bool VsyncSchedule::getPendingHardwareVsyncState() const { + return mPendingHwVsyncState == HwVsyncState::Enabled; +} + } // namespace android::scheduler diff --git a/services/surfaceflinger/Scheduler/VsyncSchedule.h b/services/surfaceflinger/Scheduler/VsyncSchedule.h index 0d9b114875083891dd245a6f36af03394688657c..763d058e287cc5a659848f9050294f86069e488c 100644 --- a/services/surfaceflinger/Scheduler/VsyncSchedule.h +++ b/services/surfaceflinger/Scheduler/VsyncSchedule.h @@ -19,10 +19,27 @@ #include #include +#include +#include +#include +#include #include +#include +#include + +namespace android { +class EventThreadTest; +class VsyncScheduleTest; +} + +namespace android::fuzz { +class SchedulerFuzzer; +} namespace android::scheduler { +struct ISchedulerCallback; + // TODO(b/185535769): Rename classes, and remove aliases. class VSyncDispatch; class VSyncTracker; @@ -34,42 +51,106 @@ using VsyncTracker = VSyncTracker; // Schedule that synchronizes to hardware VSYNC of a physical display. class VsyncSchedule { public: - explicit VsyncSchedule(FeatureFlags); - VsyncSchedule(VsyncSchedule&&); + VsyncSchedule(PhysicalDisplayId, FeatureFlags); ~VsyncSchedule(); + Period period() const; + TimePoint vsyncDeadlineAfter(TimePoint) const; + + // Inform the schedule that the period is changing and the schedule needs to recalibrate + // itself. The schedule will end the period transition internally. This will + // enable hardware VSYNCs in order to calibrate. + // + // \param [in] period The period that the system is changing into. + // \param [in] force True to force a transition even if it is not a + // change. + void startPeriodTransition(ISchedulerCallback&, Period period, bool force); + + // Pass a VSYNC sample to VsyncController. Return true if + // VsyncController detected that the VSYNC period changed. Enable or disable + // hardware VSYNCs depending on whether more samples are needed. + bool addResyncSample(ISchedulerCallback&, TimePoint timestamp, + ftl::Optional hwcVsyncPeriod); + // TODO(b/185535769): Hide behind API. const VsyncTracker& getTracker() const { return *mTracker; } VsyncTracker& getTracker() { return *mTracker; } VsyncController& getController() { return *mController; } + // TODO(b/185535769): Once these are hidden behind the API, they may no + // longer need to be shared_ptrs. + using DispatchPtr = std::shared_ptr; + using TrackerPtr = std::shared_ptr; + // TODO(b/185535769): Remove once VsyncSchedule owns all registrations. - VsyncDispatch& getDispatch() { return *mDispatch; } + DispatchPtr getDispatch() { return mDispatch; } void dump(std::string&) const; -private: - friend class TestableScheduler; + // Turn on hardware VSYNCs, unless mHwVsyncState is Disallowed, in which + // case this call is ignored. + void enableHardwareVsync(ISchedulerCallback&) EXCLUDES(mHwVsyncLock); + + // Disable hardware VSYNCs. If `disallow` is true, future calls to + // enableHardwareVsync are ineffective until isHardwareVsyncAllowed is + // called with `makeAllowed` set to true. + void disableHardwareVsync(ISchedulerCallback&, bool disallow) EXCLUDES(mHwVsyncLock); + + // If true, enableHardwareVsync can enable hardware VSYNC (if not already + // enabled). If false, enableHardwareVsync does nothing. + bool isHardwareVsyncAllowed(bool makeAllowed) EXCLUDES(mHwVsyncLock); - using TrackerPtr = std::unique_ptr; - using DispatchPtr = std::unique_ptr; + void setPendingHardwareVsyncState(bool enabled) REQUIRES(kMainThreadContext); + + bool getPendingHardwareVsyncState() const REQUIRES(kMainThreadContext); + +protected: using ControllerPtr = std::unique_ptr; // For tests. - VsyncSchedule(TrackerPtr, DispatchPtr, ControllerPtr); + VsyncSchedule(PhysicalDisplayId, TrackerPtr, DispatchPtr, ControllerPtr); + +private: + friend class TestableScheduler; + friend class android::EventThreadTest; + friend class android::VsyncScheduleTest; + friend class android::fuzz::SchedulerFuzzer; + + static TrackerPtr createTracker(PhysicalDisplayId); + static DispatchPtr createDispatch(TrackerPtr); + static ControllerPtr createController(PhysicalDisplayId, VsyncTracker&, FeatureFlags); + + void enableHardwareVsyncLocked(ISchedulerCallback&) REQUIRES(mHwVsyncLock); + + mutable std::mutex mHwVsyncLock; + enum class HwVsyncState { + // Hardware VSYNCs are currently enabled. + Enabled, + + // Hardware VSYNCs are currently disabled. They can be enabled by a call + // to `enableHardwareVsync`. + Disabled, + + // Hardware VSYNCs are not currently allowed (e.g. because the display + // is off). + Disallowed, + + ftl_last = Disallowed, + }; + HwVsyncState mHwVsyncState GUARDED_BY(mHwVsyncLock) = HwVsyncState::Disallowed; - static TrackerPtr createTracker(); - static DispatchPtr createDispatch(VsyncTracker&); - static ControllerPtr createController(VsyncTracker&, FeatureFlags); + // Pending state, in case an attempt is made to set the state while the + // device is off. + HwVsyncState mPendingHwVsyncState GUARDED_BY(kMainThreadContext) = HwVsyncState::Disabled; class PredictedVsyncTracer; using TracerPtr = std::unique_ptr; - // Effectively const except in move constructor. - TrackerPtr mTracker; - DispatchPtr mDispatch; - ControllerPtr mController; - TracerPtr mTracer; + const PhysicalDisplayId mId; + const TrackerPtr mTracker; + const DispatchPtr mDispatch; + const ControllerPtr mController; + const TracerPtr mTracer; }; } // namespace android::scheduler diff --git a/services/surfaceflinger/Scheduler/include/scheduler/Fps.h b/services/surfaceflinger/Scheduler/include/scheduler/Fps.h index bd4f40989d39fcfe188111bfe36b9f17ad35207a..d6329e246c0982aa5d74b5b0d3d11259b1e59b4a 100644 --- a/services/surfaceflinger/Scheduler/include/scheduler/Fps.h +++ b/services/surfaceflinger/Scheduler/include/scheduler/Fps.h @@ -23,7 +23,7 @@ #include #include -#include +#include namespace android { @@ -52,6 +52,7 @@ public: constexpr float getValue() const { return mFrequency; } int getIntValue() const { return static_cast(std::round(mFrequency)); } + constexpr Period getPeriod() const { return Period::fromNs(mPeriod); } constexpr nsecs_t getPeriodNsecs() const { return mPeriod; } private: @@ -66,6 +67,18 @@ struct FpsRange { Fps max = Fps::fromValue(std::numeric_limits::max()); bool includes(Fps) const; + bool includes(FpsRange) const; +}; + +struct FpsRanges { + // The range of refresh rates that refers to the display mode setting. + FpsRange physical; + + // the range of frame rates that refers to the render rate, which is + // the rate that frames are swapped. + FpsRange render; + + bool valid() const; }; static_assert(std::is_trivially_copyable_v); @@ -127,13 +140,39 @@ inline bool operator!=(FpsRange lhs, FpsRange rhs) { return !(lhs == rhs); } +inline bool operator==(const FpsRanges& lhs, const FpsRanges& rhs) { + return lhs.physical == rhs.physical && lhs.render == rhs.render; +} + +inline bool operator!=(const FpsRanges& lhs, const FpsRanges& rhs) { + return !(lhs == rhs); +} + +inline unsigned operator/(Fps lhs, Fps rhs) { + return static_cast(std::ceil(lhs.getValue() / rhs.getValue())); +} + } // namespace fps_approx_ops +constexpr Fps operator/(Fps fps, unsigned divisor) { + return Fps::fromPeriodNsecs(fps.getPeriodNsecs() * static_cast(divisor)); +} + inline bool FpsRange::includes(Fps fps) const { using fps_approx_ops::operator<=; return min <= fps && fps <= max; } +inline bool FpsRange::includes(FpsRange range) const { + using namespace fps_approx_ops; + return min <= range.min && max >= range.max; +} + +inline bool FpsRanges::valid() const { + using fps_approx_ops::operator>=; + return physical.max >= render.max; +} + struct FpsApproxEqual { bool operator()(Fps lhs, Fps rhs) const { return isApproxEqual(lhs, rhs); } }; @@ -151,4 +190,10 @@ inline std::string to_string(FpsRange range) { return base::StringPrintf("[%s, %s]", to_string(min).c_str(), to_string(max).c_str()); } +inline std::string to_string(FpsRanges ranges) { + const auto& [physical, render] = ranges; + return base::StringPrintf("{physical=%s, render=%s}", to_string(physical).c_str(), + to_string(render).c_str()); +} + } // namespace android diff --git a/services/surfaceflinger/Scheduler/include/scheduler/FrameRateMode.h b/services/surfaceflinger/Scheduler/include/scheduler/FrameRateMode.h new file mode 100644 index 0000000000000000000000000000000000000000..db38ebe65836b5c16651ac2cea62987c350ed007 --- /dev/null +++ b/services/surfaceflinger/Scheduler/include/scheduler/FrameRateMode.h @@ -0,0 +1,42 @@ +/* + * 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 + +// TODO(b/241285191): Pull this to +#include "DisplayHardware/DisplayMode.h" + +namespace android::scheduler { + +struct FrameRateMode { + Fps fps; // The render frame rate, which is a divisor of modePtr->getFps(). + ftl::NonNull modePtr; + + bool operator==(const FrameRateMode& other) const { + return isApproxEqual(fps, other.fps) && modePtr == other.modePtr; + } + + bool operator!=(const FrameRateMode& other) const { return !(*this == other); } +}; + +inline std::string to_string(const FrameRateMode& mode) { + return to_string(mode.fps) + " (" + to_string(mode.modePtr->getFps()) + ")"; +} + +} // namespace android::scheduler diff --git a/services/surfaceflinger/Scheduler/include/scheduler/PresentLatencyTracker.h b/services/surfaceflinger/Scheduler/include/scheduler/PresentLatencyTracker.h new file mode 100644 index 0000000000000000000000000000000000000000..23ae83fc4bdfa338730ee74ce40a4976bc6c3a06 --- /dev/null +++ b/services/surfaceflinger/Scheduler/include/scheduler/PresentLatencyTracker.h @@ -0,0 +1,54 @@ +/* + * 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 +#include + +#include + +namespace android { + +class FenceTime; + +namespace scheduler { + +// Computes composite-to-present latency by tracking recently composited frames pending to present. +class PresentLatencyTracker { +public: + // For tests. + static constexpr size_t kMaxPendingFrames = 4; + + // Returns the present latency of the latest frame. + Duration trackPendingFrame(TimePoint compositeTime, + std::shared_ptr presentFenceTime); + +private: + struct PendingFrame { + PendingFrame(TimePoint compositeTime, std::shared_ptr presentFenceTime) + : compositeTime(compositeTime), presentFenceTime(std::move(presentFenceTime)) {} + + const TimePoint compositeTime; + const std::shared_ptr presentFenceTime; + }; + + std::queue mPendingFrames; +}; + +} // namespace scheduler +} // namespace android diff --git a/services/surfaceflinger/Scheduler/include/scheduler/Time.h b/services/surfaceflinger/Scheduler/include/scheduler/Time.h new file mode 100644 index 0000000000000000000000000000000000000000..ba1459a5627118c370a8355f9f6db0d5dd4cd5fb --- /dev/null +++ b/services/surfaceflinger/Scheduler/include/scheduler/Time.h @@ -0,0 +1,88 @@ +/* + * 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 + +#include +#include + +namespace android { +namespace scheduler { + +// TODO(b/185535769): Pull Clock.h to libscheduler to reuse this. +using SchedulerClock = std::chrono::steady_clock; +static_assert(SchedulerClock::is_steady); + +} // namespace scheduler + +struct Duration; + +struct TimePoint : scheduler::SchedulerClock::time_point { + constexpr TimePoint() = default; + explicit constexpr TimePoint(const Duration&); + + // Implicit conversion from std::chrono counterpart. + constexpr TimePoint(scheduler::SchedulerClock::time_point p) + : scheduler::SchedulerClock::time_point(p) {} + + static constexpr TimePoint fromNs(nsecs_t); + + static TimePoint now() { return scheduler::SchedulerClock::now(); }; + + nsecs_t ns() const; +}; + +struct Duration : TimePoint::duration { + // Implicit conversion from std::chrono counterpart. + template + constexpr Duration(std::chrono::duration d) : TimePoint::duration(d) {} + + static constexpr Duration fromNs(nsecs_t ns) { return {std::chrono::nanoseconds(ns)}; } + + nsecs_t ns() const { return std::chrono::nanoseconds(*this).count(); } +}; + +using Period = Duration; + +constexpr TimePoint::TimePoint(const Duration& d) : scheduler::SchedulerClock::time_point(d) {} + +constexpr TimePoint TimePoint::fromNs(nsecs_t ns) { + return TimePoint(Duration::fromNs(ns)); +} + +inline nsecs_t TimePoint::ns() const { + return Duration(time_since_epoch()).ns(); +} + +// Shorthand to convert the tick count of a Duration to Period and Rep. For example: +// +// const auto i = ticks>(d); // Integer seconds. +// const auto f = ticks(d); // Floating-point milliseconds. +// +template +constexpr Rep ticks(Duration d) { + using D = std::chrono::duration; + return std::chrono::duration_cast(d).count(); +} + +inline std::string to_string(Duration d) { + return base::StringPrintf("%.3f ms", ticks(d)); +} + +} // namespace android diff --git a/services/surfaceflinger/Scheduler/include/scheduler/TransactionSchedule.h b/services/surfaceflinger/Scheduler/include/scheduler/TransactionSchedule.h new file mode 100644 index 0000000000000000000000000000000000000000..6fc44dde23f3d4837eb63b6b8aade08afa86b34f --- /dev/null +++ b/services/surfaceflinger/Scheduler/include/scheduler/TransactionSchedule.h @@ -0,0 +1,30 @@ +/* + * 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. + */ + +#pragma once + +namespace android::scheduler { + +// State machine controlled by transaction flags. VsyncModulator switches to early phase offsets +// when a transaction is flagged EarlyStart or Early, lasting until an EarlyEnd transaction or a +// fixed number of frames, respectively. +enum class TransactionSchedule { + Late, // Default. + EarlyStart, + EarlyEnd +}; + +} // namespace android::scheduler diff --git a/services/surfaceflinger/Scheduler/include/scheduler/VsyncConfig.h b/services/surfaceflinger/Scheduler/include/scheduler/VsyncConfig.h new file mode 100644 index 0000000000000000000000000000000000000000..47d95a8a9a3defa68b4eb1ff3ac82c05c1005104 --- /dev/null +++ b/services/surfaceflinger/Scheduler/include/scheduler/VsyncConfig.h @@ -0,0 +1,60 @@ +/* + * 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. + */ + +#pragma once + +#include + +#include + +namespace android::scheduler { + +using namespace std::chrono_literals; + +// Phase offsets and work durations for SF and app deadlines from VSYNC. +struct VsyncConfig { + nsecs_t sfOffset; + nsecs_t appOffset; + std::chrono::nanoseconds sfWorkDuration; + std::chrono::nanoseconds appWorkDuration; + + bool operator==(const VsyncConfig& other) const { + return sfOffset == other.sfOffset && appOffset == other.appOffset && + sfWorkDuration == other.sfWorkDuration && appWorkDuration == other.appWorkDuration; + } + + bool operator!=(const VsyncConfig& other) const { return !(*this == other); } + + // The duration for which SF can delay a frame if it is considered early based on the + // VsyncConfig::appWorkDuration. + static constexpr std::chrono::nanoseconds kEarlyLatchMaxThreshold = 100ms; +}; + +struct VsyncConfigSet { + VsyncConfig early; // Used for early transactions, and during refresh rate change. + VsyncConfig earlyGpu; // Used during GPU composition. + VsyncConfig late; // Default. + std::chrono::nanoseconds hwcMinWorkDuration; // Used for calculating the earliest present time. + + bool operator==(const VsyncConfigSet& other) const { + return early == other.early && earlyGpu == other.earlyGpu && late == other.late && + hwcMinWorkDuration == other.hwcMinWorkDuration; + } + + bool operator!=(const VsyncConfigSet& other) const { return !(*this == other); } +}; + +} // namespace android::scheduler diff --git a/services/surfaceflinger/Scheduler/include/scheduler/VsyncId.h b/services/surfaceflinger/Scheduler/include/scheduler/VsyncId.h new file mode 100644 index 0000000000000000000000000000000000000000..c64a3cdc6f0ddc609c7fd3fab5a49cdfd241568e --- /dev/null +++ b/services/surfaceflinger/Scheduler/include/scheduler/VsyncId.h @@ -0,0 +1,34 @@ +/* + * 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 { + +// TODO(b/185536303): Import StrongTyping.h into FTL so it can be used here. + +// Sequential frame identifier, also known as FrameTimeline token. +struct VsyncId { + int64_t value = -1; +}; + +inline bool operator==(VsyncId lhs, VsyncId rhs) { + return lhs.value == rhs.value; +} + +} // namespace android diff --git a/libs/gui/include/gui/TransactionTracing.h b/services/surfaceflinger/Scheduler/include/scheduler/interface/CompositionCoverage.h similarity index 55% rename from libs/gui/include/gui/TransactionTracing.h rename to services/surfaceflinger/Scheduler/include/scheduler/interface/CompositionCoverage.h index 9efba47a18231c9e2ebfb83ab6beab4a32e82c19..3d0f1a9d3326b909e8db7d560df1fc277e240546 100644 --- a/libs/gui/include/gui/TransactionTracing.h +++ b/services/surfaceflinger/Scheduler/include/scheduler/interface/CompositionCoverage.h @@ -1,5 +1,5 @@ /* - * Copyright 2020 The Android Open Source Project + * 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. @@ -16,26 +16,22 @@ #pragma once -#include -#include +#include -namespace android { - -class TransactionTraceListener : public gui::BnTransactionTraceListener { - static std::mutex sMutex; - static sp sInstance; - - TransactionTraceListener(); +#include -public: - static sp getInstance(); - - binder::Status onToggled(bool enabled) override; +namespace android { - bool isTracingEnabled(); +// Whether composition was covered by HWC and/or GPU. +enum class CompositionCoverage : std::uint8_t { + Hwc = 1 << 0, -private: - bool mTracingEnabled = false; + // Mutually exclusive: The composition either used the GPU, or reused a buffer that had been + // composited on the GPU. + Gpu = 1 << 1, + GpuReuse = 1 << 2, }; +using CompositionCoverageFlags = ftl::Flags; + } // namespace android diff --git a/services/surfaceflinger/Scheduler/include/scheduler/interface/ICompositor.h b/services/surfaceflinger/Scheduler/include/scheduler/interface/ICompositor.h new file mode 100644 index 0000000000000000000000000000000000000000..cc419259ef2c41ed84ab91a2dcbbaa7adbeecd54 --- /dev/null +++ b/services/surfaceflinger/Scheduler/include/scheduler/interface/ICompositor.h @@ -0,0 +1,43 @@ +/* + * 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. + */ + +#pragma once + +#include +#include + +namespace android { + +struct ICompositor { + // Configures physical displays, processing hotplug and/or mode setting via the Composer HAL. + virtual void configure() = 0; + + // Commits transactions for layers and displays. Returns whether any state has been invalidated, + // i.e. whether a frame should be composited for each display. + virtual bool commit(TimePoint frameTime, VsyncId, TimePoint expectedVsyncTime) = 0; + + // Composites a frame for each display. CompositionEngine performs GPU and/or HAL composition + // via RenderEngine and the Composer HAL, respectively. + virtual void composite(TimePoint frameTime, VsyncId) = 0; + + // Samples the composited frame via RegionSamplingThread. + virtual void sample() = 0; + +protected: + ~ICompositor() = default; +}; + +} // namespace android diff --git a/services/surfaceflinger/Scheduler/src/PresentLatencyTracker.cpp b/services/surfaceflinger/Scheduler/src/PresentLatencyTracker.cpp new file mode 100644 index 0000000000000000000000000000000000000000..8f3e0818f8dc65f069875ac1b5c25e2a7a3bb9f0 --- /dev/null +++ b/services/surfaceflinger/Scheduler/src/PresentLatencyTracker.cpp @@ -0,0 +1,56 @@ +/* + * 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::scheduler { + +Duration PresentLatencyTracker::trackPendingFrame(TimePoint compositeTime, + std::shared_ptr presentFenceTime) { + Duration presentLatency = Duration::zero(); + while (!mPendingFrames.empty()) { + const auto& pendingFrame = mPendingFrames.front(); + const auto presentTime = + TimePoint::fromNs(pendingFrame.presentFenceTime->getCachedSignalTime()); + + if (presentTime == TimePoint::fromNs(Fence::SIGNAL_TIME_PENDING)) { + break; + } + + if (presentTime == TimePoint::fromNs(Fence::SIGNAL_TIME_INVALID)) { + ALOGE("%s: Invalid present fence", __func__); + } else { + presentLatency = presentTime - pendingFrame.compositeTime; + } + + mPendingFrames.pop(); + } + + mPendingFrames.emplace(compositeTime, std::move(presentFenceTime)); + + if (CC_UNLIKELY(mPendingFrames.size() > kMaxPendingFrames)) { + ALOGE("%s: Too many pending frames", __func__); + mPendingFrames.pop(); + } + + return presentLatency; +} + +} // namespace android::scheduler diff --git a/services/surfaceflinger/Scheduler/tests/PresentLatencyTrackerTest.cpp b/services/surfaceflinger/Scheduler/tests/PresentLatencyTrackerTest.cpp new file mode 100644 index 0000000000000000000000000000000000000000..8952ca99ab9c405b48a0422f81a3e36ac8e0c4c6 --- /dev/null +++ b/services/surfaceflinger/Scheduler/tests/PresentLatencyTrackerTest.cpp @@ -0,0 +1,81 @@ +/* + * 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::scheduler { +namespace { + +using FencePair = std::pair, std::shared_ptr>; + +FencePair makePendingFence(FenceToFenceTimeMap& fenceMap) { + const auto fence = sp::make(); + return {fence, fenceMap.createFenceTimeForTest(fence)}; +} + +} // namespace + +TEST(PresentLatencyTrackerTest, skipsInvalidFences) { + PresentLatencyTracker tracker; + + const TimePoint kCompositeTime = TimePoint::fromNs(999); + EXPECT_EQ(tracker.trackPendingFrame(kCompositeTime, FenceTime::NO_FENCE), Duration::zero()); + EXPECT_EQ(tracker.trackPendingFrame(kCompositeTime, FenceTime::NO_FENCE), Duration::zero()); + EXPECT_EQ(tracker.trackPendingFrame(kCompositeTime, FenceTime::NO_FENCE), Duration::zero()); + + FenceToFenceTimeMap fenceMap; + const auto [fence, fenceTime] = makePendingFence(fenceMap); + EXPECT_EQ(tracker.trackPendingFrame(kCompositeTime, fenceTime), Duration::zero()); + + fenceTime->signalForTest(9999); + + EXPECT_EQ(tracker.trackPendingFrame(kCompositeTime, FenceTime::NO_FENCE), + Duration::fromNs(9000)); +} + +TEST(PresentLatencyTrackerTest, tracksPendingFrames) { + PresentLatencyTracker tracker; + + FenceToFenceTimeMap fenceMap; + std::array fences; + std::generate(fences.begin(), fences.end(), [&fenceMap] { return makePendingFence(fenceMap); }); + + // The present latency is 0 if all fences are pending. + const TimePoint kCompositeTime = TimePoint::fromNs(1234); + for (const auto& [fence, fenceTime] : fences) { + EXPECT_EQ(tracker.trackPendingFrame(kCompositeTime, fenceTime), Duration::zero()); + } + + // If multiple frames have been presented... + constexpr size_t kPresentCount = fences.size() / 2; + for (size_t i = 0; i < kPresentCount; i++) { + fences[i].second->signalForTest(kCompositeTime.ns() + static_cast(i)); + } + + const auto fence = makePendingFence(fenceMap); + + // ...then the present latency is measured using the latest frame. + constexpr Duration kPresentLatency = Duration::fromNs(static_cast(kPresentCount) - 1); + EXPECT_EQ(tracker.trackPendingFrame(kCompositeTime, fence.second), kPresentLatency); +} + +} // namespace android::scheduler diff --git a/services/surfaceflinger/ScreenCaptureOutput.cpp b/services/surfaceflinger/ScreenCaptureOutput.cpp new file mode 100644 index 0000000000000000000000000000000000000000..09dac23410106fd2c5a648d416d983c852b60c43 --- /dev/null +++ b/services/surfaceflinger/ScreenCaptureOutput.cpp @@ -0,0 +1,120 @@ +/* + * 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 "ScreenCaptureOutput.h" +#include "ScreenCaptureRenderSurface.h" + +#include +#include +#include +#include + +namespace android { + +std::shared_ptr createScreenCaptureOutput(ScreenCaptureOutputArgs args) { + std::shared_ptr output = compositionengine::impl::createOutputTemplated< + ScreenCaptureOutput, compositionengine::CompositionEngine, const RenderArea&, + const compositionengine::Output::ColorProfile&, bool>(args.compositionEngine, + args.renderArea, + args.colorProfile, + args.regionSampling); + output->editState().isSecure = args.renderArea.isSecure(); + output->setCompositionEnabled(true); + output->setLayerFilter({args.layerStack}); + output->setRenderSurface(std::make_unique(std::move(args.buffer))); + output->setDisplayBrightness(args.sdrWhitePointNits, args.displayBrightnessNits); + output->editState().clientTargetBrightness = args.targetBrightness; + + output->setDisplayColorProfile(std::make_unique( + compositionengine::DisplayColorProfileCreationArgsBuilder() + .setHasWideColorGamut(true) + .Build())); + + const Rect& sourceCrop = args.renderArea.getSourceCrop(); + const ui::Rotation orientation = ui::Transform::toRotation(args.renderArea.getRotationFlags()); + const Rect orientedDisplaySpaceRect{args.renderArea.getReqWidth(), + args.renderArea.getReqHeight()}; + output->setProjection(orientation, sourceCrop, orientedDisplaySpaceRect); + output->setDisplaySize({sourceCrop.getWidth(), sourceCrop.getHeight()}); + + { + std::string name = args.regionSampling ? "RegionSampling" : "ScreenCaptureOutput"; + if (auto displayDevice = args.renderArea.getDisplayDevice()) { + base::StringAppendF(&name, " for %" PRIu64, displayDevice->getId().value); + } + output->setName(name); + } + return output; +} + +ScreenCaptureOutput::ScreenCaptureOutput( + const RenderArea& renderArea, const compositionengine::Output::ColorProfile& colorProfile, + bool regionSampling) + : mRenderArea(renderArea), mColorProfile(colorProfile), mRegionSampling(regionSampling) {} + +void ScreenCaptureOutput::updateColorProfile(const compositionengine::CompositionRefreshArgs&) { + auto& outputState = editState(); + outputState.dataspace = mColorProfile.dataspace; + outputState.renderIntent = mColorProfile.renderIntent; +} + +renderengine::DisplaySettings ScreenCaptureOutput::generateClientCompositionDisplaySettings() + const { + auto clientCompositionDisplay = + compositionengine::impl::Output::generateClientCompositionDisplaySettings(); + clientCompositionDisplay.clip = mRenderArea.getSourceCrop(); + return clientCompositionDisplay; +} + +std::vector +ScreenCaptureOutput::generateClientCompositionRequests( + bool supportsProtectedContent, ui::Dataspace outputDataspace, + std::vector& outLayerFEs) { + auto clientCompositionLayers = compositionengine::impl::Output:: + generateClientCompositionRequests(supportsProtectedContent, outputDataspace, + outLayerFEs); + + if (mRegionSampling) { + for (auto& layer : clientCompositionLayers) { + layer.backgroundBlurRadius = 0; + layer.blurRegions.clear(); + } + } + + if (outputDataspace == ui::Dataspace::BT2020_HLG) { + for (auto& layer : clientCompositionLayers) { + auto transfer = layer.sourceDataspace & ui::Dataspace::TRANSFER_MASK; + if (transfer != static_cast(ui::Dataspace::TRANSFER_HLG) && + transfer != static_cast(ui::Dataspace::TRANSFER_ST2084)) { + layer.whitePointNits *= (1000.0f / 203.0f); + } + } + } + + Rect sourceCrop = mRenderArea.getSourceCrop(); + compositionengine::LayerFE::LayerSettings fillLayer; + fillLayer.source.buffer.buffer = nullptr; + fillLayer.source.solidColor = half3(0.0f, 0.0f, 0.0f); + fillLayer.geometry.boundaries = + FloatRect(static_cast(sourceCrop.left), static_cast(sourceCrop.top), + static_cast(sourceCrop.right), static_cast(sourceCrop.bottom)); + fillLayer.alpha = half(RenderArea::getCaptureFillValue(mRenderArea.getCaptureFill())); + clientCompositionLayers.insert(clientCompositionLayers.begin(), fillLayer); + + return clientCompositionLayers; +} + +} // namespace android diff --git a/services/surfaceflinger/ScreenCaptureOutput.h b/services/surfaceflinger/ScreenCaptureOutput.h new file mode 100644 index 0000000000000000000000000000000000000000..3c307b0733e29c83cf041af83ef3bd05414db54e --- /dev/null +++ b/services/surfaceflinger/ScreenCaptureOutput.h @@ -0,0 +1,69 @@ +/* + * 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 +#include +#include + +#include "RenderArea.h" + +namespace android { + +struct ScreenCaptureOutputArgs { + const compositionengine::CompositionEngine& compositionEngine; + const compositionengine::Output::ColorProfile& colorProfile; + const RenderArea& renderArea; + ui::LayerStack layerStack; + std::shared_ptr buffer; + float sdrWhitePointNits; + float displayBrightnessNits; + // Counterintuitively, when targetBrightness > 1.0 then dim the scene. + float targetBrightness; + bool regionSampling; +}; + +// ScreenCaptureOutput is used to compose a set of layers into a preallocated buffer. +// +// SurfaceFlinger passes instances of ScreenCaptureOutput to CompositionEngine in calls to +// SurfaceFlinger::captureLayers and SurfaceFlinger::captureDisplay. +class ScreenCaptureOutput : public compositionengine::impl::Output { +public: + ScreenCaptureOutput(const RenderArea& renderArea, + const compositionengine::Output::ColorProfile& colorProfile, + bool regionSampling); + + void updateColorProfile(const compositionengine::CompositionRefreshArgs&) override; + + std::vector generateClientCompositionRequests( + bool supportsProtectedContent, ui::Dataspace outputDataspace, + std::vector& outLayerFEs) override; + +protected: + bool getSkipColorTransform() const override { return false; } + renderengine::DisplaySettings generateClientCompositionDisplaySettings() const override; + +private: + const RenderArea& mRenderArea; + const compositionengine::Output::ColorProfile& mColorProfile; + const bool mRegionSampling; +}; + +std::shared_ptr createScreenCaptureOutput(ScreenCaptureOutputArgs); + +} // namespace android diff --git a/services/surfaceflinger/ScreenCaptureRenderSurface.h b/services/surfaceflinger/ScreenCaptureRenderSurface.h new file mode 100644 index 0000000000000000000000000000000000000000..20973003d5e119b7a7ecd3f67d52c75030cc597c --- /dev/null +++ b/services/surfaceflinger/ScreenCaptureRenderSurface.h @@ -0,0 +1,81 @@ +/* + * 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 +#include +#include +#include + +namespace android { + +// ScreenCaptureRenderSurface is a RenderSurface that returns a preallocated buffer used by +// ScreenCaptureOutput. +class ScreenCaptureRenderSurface : public compositionengine::RenderSurface { +public: + ScreenCaptureRenderSurface(std::shared_ptr buffer) + : mBuffer(std::move(buffer)){}; + + std::shared_ptr dequeueBuffer( + base::unique_fd* /* bufferFence */) override { + return mBuffer; + } + + void queueBuffer(base::unique_fd readyFence) override { + mRenderFence = sp::make(readyFence.release()); + } + + const sp& getClientTargetAcquireFence() const override { return mRenderFence; } + + bool supportsCompositionStrategyPrediction() const override { return false; } + + bool isValid() const override { return true; } + + void initialize() override {} + + const ui::Size& getSize() const override { return mSize; } + + bool isProtected() const override { return mBuffer->getUsage() & GRALLOC_USAGE_PROTECTED; } + + void setDisplaySize(const ui::Size&) override {} + + void setBufferDataspace(ui::Dataspace) override {} + + void setBufferPixelFormat(ui::PixelFormat) override {} + + void setProtected(bool /* useProtected */) override {} + + status_t beginFrame(bool /* mustRecompose */) override { return OK; } + + void prepareFrame(bool /* usesClientComposition */, bool /* usesDeviceComposition */) override { + } + + void onPresentDisplayCompleted() override {} + + void dump(std::string& /* result */) const override {} + +private: + std::shared_ptr mBuffer; + + sp mRenderFence = Fence::NO_FENCE; + + ui::Size mSize; +}; + +} // namespace android diff --git a/services/surfaceflinger/SurfaceFlinger.cpp b/services/surfaceflinger/SurfaceFlinger.cpp index 314b4cd57baeadd27d089ae1bad087f4a06924e6..ee3505d7c8ba9540849801317c2bbc939f07a0c3 100644 --- a/services/surfaceflinger/SurfaceFlinger.cpp +++ b/services/surfaceflinger/SurfaceFlinger.cpp @@ -30,6 +30,7 @@ #include #include #include +#include #include #include #include @@ -43,18 +44,23 @@ #include #include #include +#include #include #include #include #include +#include #include #include #include #include #include +#include +#include #include #include -#include +#include +#include #include #include #include @@ -81,6 +87,7 @@ #include #include #include +#include #include #include #include @@ -98,17 +105,18 @@ #include #include #include +#include #include #include +#include +#include #include #include "BackgroundExecutor.h" -#include "BufferLayer.h" -#include "BufferQueueLayer.h" -#include "BufferStateLayer.h" #include "Client.h" +#include "ClientCache.h" #include "Colorizer.h" -#include "ContainerLayer.h" +#include "Display/DisplayMap.h" #include "DisplayDevice.h" #include "DisplayHardware/ComposerHal.h" #include "DisplayHardware/FramebufferSurface.h" @@ -117,33 +125,34 @@ #include "DisplayHardware/PowerAdvisor.h" #include "DisplayHardware/VirtualDisplaySurface.h" #include "DisplayRenderArea.h" -#include "EffectLayer.h" #include "Effects/Daltonizer.h" #include "FlagManager.h" #include "FpsReporter.h" #include "FrameTimeline/FrameTimeline.h" #include "FrameTracer/FrameTracer.h" +#include "FrontEnd/LayerCreationArgs.h" +#include "FrontEnd/LayerHandle.h" +#include "FrontEnd/LayerLifecycleManager.h" +#include "FrontEnd/LayerSnapshot.h" #include "HdrLayerInfoReporter.h" #include "Layer.h" #include "LayerProtoHelper.h" #include "LayerRenderArea.h" #include "LayerVector.h" -#include "MonitoredProducer.h" #include "MutexUtils.h" #include "NativeWindowSurface.h" -#include "RefreshRateOverlay.h" #include "RegionSamplingThread.h" -#include "Scheduler/DispSyncSource.h" #include "Scheduler/EventThread.h" #include "Scheduler/LayerHistory.h" #include "Scheduler/Scheduler.h" #include "Scheduler/VsyncConfiguration.h" -#include "Scheduler/VsyncController.h" +#include "Scheduler/VsyncModulator.h" +#include "ScreenCaptureOutput.h" #include "StartPropertySetThread.h" #include "SurfaceFlingerProperties.h" -#include "SurfaceInterceptor.h" #include "TimeStats/TimeStats.h" #include "TunnelModeEnabledReporter.h" +#include "Utils/Dumper.h" #include "WindowInfosListenerInvoker.h" #include @@ -154,13 +163,21 @@ #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; +using namespace std::string_view_literals; using namespace hardware::configstore; using namespace hardware::configstore::V1_0; using namespace sysprop; +using ftl::Flags; +using namespace ftl::flag_operators; using aidl::android::hardware::graphics::common::DisplayDecorationSupport; using aidl::android::hardware::graphics::composer3::Capability; @@ -169,47 +186,29 @@ using CompositionStrategyPredictionState = android::compositionengine::impl:: OutputCompositionState::CompositionStrategyPredictionState; using base::StringAppendF; +using display::PhysicalDisplay; +using display::PhysicalDisplays; +using frontend::TransactionHandler; using gui::DisplayInfo; +using gui::GameMode; using gui::IDisplayEventConnection; using gui::IWindowInfosListener; +using gui::LayerMetadata; using gui::WindowInfo; -using ui::ColorMode; +using gui::aidl_utils::binderStatusFromStatusT; +using scheduler::VsyncModulator; using ui::Dataspace; using ui::DisplayPrimaries; using ui::RenderIntent; -using KernelIdleTimerController = scheduler::RefreshRateConfigs::KernelIdleTimerController; +using KernelIdleTimerController = scheduler::RefreshRateSelector::KernelIdleTimerController; namespace hal = android::hardware::graphics::composer::hal; namespace { -#pragma clang diagnostic push -#pragma clang diagnostic error "-Wswitch-enum" - -bool isWideColorMode(const ColorMode colorMode) { - switch (colorMode) { - case ColorMode::DISPLAY_P3: - case ColorMode::ADOBE_RGB: - case ColorMode::DCI_P3: - case ColorMode::BT2020: - case ColorMode::DISPLAY_BT2020: - case ColorMode::BT2100_PQ: - case ColorMode::BT2100_HLG: - return true; - case ColorMode::NATIVE: - case ColorMode::STANDARD_BT601_625: - case ColorMode::STANDARD_BT601_625_UNADJUSTED: - case ColorMode::STANDARD_BT601_525: - case ColorMode::STANDARD_BT601_525_UNADJUSTED: - case ColorMode::STANDARD_BT709: - case ColorMode::SRGB: - return false; - } - return false; -} - -#pragma clang diagnostic pop +static constexpr int FOUR_K_WIDTH = 3840; +static constexpr int FOUR_K_HEIGHT = 2160; // TODO(b/141333600): Consolidate with DisplayMode::Builder::getDefaultDensity. constexpr float FALLBACK_DENSITY = ACONFIGURATION_DENSITY_TV; @@ -258,6 +257,51 @@ bool getKernelIdleTimerSyspropConfig(DisplayId displayId) { return displaySupportKernelIdleTimer || sysprop::support_kernel_idle_timer(false); } +bool isAbove4k30(const ui::DisplayMode& outMode) { + using fps_approx_ops::operator>; + Fps refreshRate = Fps::fromValue(outMode.refreshRate); + return outMode.resolution.getWidth() >= FOUR_K_WIDTH && + outMode.resolution.getHeight() >= FOUR_K_HEIGHT && refreshRate > 30_Hz; +} + +void excludeDolbyVisionIf4k30Present(const std::vector& displayHdrTypes, + ui::DisplayMode& outMode) { + if (isAbove4k30(outMode) && + std::any_of(displayHdrTypes.begin(), displayHdrTypes.end(), + [](ui::Hdr type) { return type == ui::Hdr::DOLBY_VISION_4K30; })) { + for (ui::Hdr type : displayHdrTypes) { + if (type != ui::Hdr::DOLBY_VISION_4K30 && type != ui::Hdr::DOLBY_VISION) { + outMode.supportedHdrTypes.push_back(type); + } + } + } else { + for (ui::Hdr type : displayHdrTypes) { + if (type != ui::Hdr::DOLBY_VISION_4K30) { + outMode.supportedHdrTypes.push_back(type); + } + } + } +} + +HdrCapabilities filterOut4k30(const HdrCapabilities& displayHdrCapabilities) { + std::vector hdrTypes; + for (ui::Hdr type : displayHdrCapabilities.getSupportedHdrTypes()) { + if (type != ui::Hdr::DOLBY_VISION_4K30) { + hdrTypes.push_back(type); + } + } + return {hdrTypes, displayHdrCapabilities.getDesiredMaxLuminance(), + displayHdrCapabilities.getDesiredMaxAverageLuminance(), + displayHdrCapabilities.getDesiredMinLuminance()}; +} + +uint32_t getLayerIdFromSurfaceControl(sp surfaceControl) { + if (!surfaceControl) { + return UNASSIGNED_LAYER_ID; + } + return LayerHandle::getLayerId(surfaceControl->getHandle()); +} + } // namespace anonymous // --------------------------------------------------------------------------- @@ -270,9 +314,12 @@ const String16 sControlDisplayBrightness("android.permission.CONTROL_DISPLAY_BRI const String16 sDump("android.permission.DUMP"); const String16 sCaptureBlackoutContent("android.permission.CAPTURE_BLACKOUT_CONTENT"); const String16 sInternalSystemWindow("android.permission.INTERNAL_SYSTEM_WINDOW"); +const String16 sWakeupSurfaceFlinger("android.permission.WAKEUP_SURFACE_FLINGER"); const char* KERNEL_IDLE_TIMER_PROP = "graphics.display.kernel_idle_timer.enabled"; +static const int MAX_TRACING_MEMORY = 1024 * 1024 * 1024; // 1GB + // --------------------------------------------------------------------------- int64_t SurfaceFlinger::dispSyncPresentTimeOffset; bool SurfaceFlinger::useHwcForRgbToYuv; @@ -280,7 +327,6 @@ bool SurfaceFlinger::hasSyncFramework; int64_t SurfaceFlinger::maxFrameBufferAcquiredBuffers; uint32_t SurfaceFlinger::maxGraphicsWidth; uint32_t SurfaceFlinger::maxGraphicsHeight; -bool SurfaceFlinger::hasWideColorDisplay; bool SurfaceFlinger::useContextPriority; Dataspace SurfaceFlinger::defaultCompositionDataspace = Dataspace::V0_SRGB; ui::PixelFormat SurfaceFlinger::defaultCompositionPixelFormat = ui::PixelFormat::RGBA_8888; @@ -302,37 +348,30 @@ std::string decodeDisplayColorSetting(DisplayColorSetting displayColorSetting) { } } -bool callingThreadHasRotateSurfaceFlingerAccess() { +bool callingThreadHasPermission(const String16& permission) { IPCThreadState* ipc = IPCThreadState::self(); const int pid = ipc->getCallingPid(); const int uid = ipc->getCallingUid(); return uid == AID_GRAPHICS || uid == AID_SYSTEM || - PermissionCache::checkPermission(sRotateSurfaceFlinger, pid, uid); + PermissionCache::checkPermission(permission, pid, uid); } -bool callingThreadHasInternalSystemWindowAccess() { - IPCThreadState* ipc = IPCThreadState::self(); - const int pid = ipc->getCallingPid(); - const int uid = ipc->getCallingUid(); - return uid == AID_GRAPHICS || uid == AID_SYSTEM || - PermissionCache::checkPermission(sInternalSystemWindow, pid, uid); -} +ui::Transform::RotationFlags SurfaceFlinger::sActiveDisplayRotationFlags = ui::Transform::ROT_0; SurfaceFlinger::SurfaceFlinger(Factory& factory, SkipInitializationTag) : mFactory(factory), mPid(getpid()), - mInterceptor(mFactory.createSurfaceInterceptor()), mTimeStats(std::make_shared()), mFrameTracer(mFactory.createFrameTracer()), mFrameTimeline(mFactory.createFrameTimeline(mTimeStats, mPid)), mCompositionEngine(mFactory.createCompositionEngine()), mHwcServiceName(base::GetProperty("debug.sf.hwc_service_name"s, "default"s)), - mTunnelModeEnabledReporter(new TunnelModeEnabledReporter()), + mTunnelModeEnabledReporter(sp::make()), mEmulatedDisplayDensity(getDensityFromProperty("qemu.sf.lcd_density", false)), mInternalDisplayDensity( getDensityFromProperty("ro.sf.lcd_density", !mEmulatedDisplayDensity)), mPowerAdvisor(std::make_unique(*this)), - mWindowInfosListenerInvoker(sp::make(*this)) { + mWindowInfosListenerInvoker(sp::make()) { ALOGI("Using HWComposer service: %s", mHwcServiceName.c_str()); } @@ -350,11 +389,11 @@ SurfaceFlinger::SurfaceFlinger(Factory& factory) : SurfaceFlinger(factory, SkipI maxGraphicsWidth = std::max(max_graphics_width(0), 0); maxGraphicsHeight = std::max(max_graphics_height(0), 0); - hasWideColorDisplay = has_wide_color_display(false); + mSupportsWideColor = has_wide_color_display(false); mDefaultCompositionDataspace = static_cast(default_composition_dataspace(Dataspace::V0_SRGB)); mWideColorGamutCompositionDataspace = static_cast(wcg_composition_dataspace( - hasWideColorDisplay ? Dataspace::DISPLAY_P3 : Dataspace::V0_SRGB)); + mSupportsWideColor ? Dataspace::DISPLAY_P3 : Dataspace::V0_SRGB)); defaultCompositionDataspace = mDefaultCompositionDataspace; wideColorGamutCompositionDataspace = mWideColorGamutCompositionDataspace; defaultCompositionPixelFormat = static_cast( @@ -378,32 +417,20 @@ SurfaceFlinger::SurfaceFlinger(Factory& factory) : SurfaceFlinger(factory, SkipI // debugging stuff... char value[PROPERTY_VALUE_MAX]; - property_get("ro.bq.gpu_to_cpu_unsupported", value, "0"); - mGpuToCpuSupported = !atoi(value); - property_get("ro.build.type", value, "user"); mIsUserBuild = strcmp(value, "user") == 0; mDebugFlashDelay = base::GetUintProperty("debug.sf.showupdates"s, 0u); - // DDMS debugging deprecated (b/120782499) - property_get("debug.sf.ddms", value, "0"); - int debugDdms = atoi(value); - ALOGI_IF(debugDdms, "DDMS debugging not supported"); - - property_get("debug.sf.enable_gl_backpressure", value, "0"); - mPropagateBackpressureClientComposition = atoi(value); - ALOGI_IF(mPropagateBackpressureClientComposition, - "Enabling backpressure propagation for Client Composition"); + mBackpressureGpuComposition = base::GetBoolProperty("debug.sf.enable_gl_backpressure"s, true); + ALOGI_IF(mBackpressureGpuComposition, "Enabling backpressure for GPU composition"); property_get("ro.surface_flinger.supports_background_blur", value, "0"); bool supportsBlurs = atoi(value); mSupportsBlur = supportsBlurs; ALOGI_IF(!mSupportsBlur, "Disabling blur effects, they are not supported."); - property_get("ro.sf.blurs_are_expensive", value, "0"); - mBlursAreExpensive = atoi(value); - const size_t defaultListSize = ISurfaceComposer::MAX_LAYERS; + const size_t defaultListSize = MAX_LAYERS; auto listSize = property_get_int32("debug.sf.max_igbp_list_size", int32_t(defaultListSize)); mMaxGraphicBufferProducerListSize = (listSize > 0) ? size_t(listSize) : defaultListSize; mGraphicBufferProducerListSizeLogThreshold = @@ -423,6 +450,9 @@ SurfaceFlinger::SurfaceFlinger(Factory& factory) : SurfaceFlinger(factory, SkipI property_get("debug.sf.treat_170m_as_sRGB", value, "0"); mTreat170mAsSrgb = atoi(value); + mIgnoreHwcPhysicalDisplayOrientation = + base::GetBoolProperty("debug.sf.ignore_hwc_physical_display_orientation"s, false); + // We should be reading 'persist.sys.sf.color_saturation' here // but since /data may be encrypted, we need to wait until after vold // comes online to attempt to read the property. The property is @@ -438,7 +468,12 @@ SurfaceFlinger::SurfaceFlinger(Factory& factory) : SurfaceFlinger(factory, SkipI android::hardware::details::setTrebleTestingOverride(true); } - mRefreshRateOverlaySpinner = property_get_bool("sf.debug.show_refresh_rate_overlay_spinner", 0); + // TODO (b/270966065) Update the HWC based refresh rate overlay to support spinner + mRefreshRateOverlaySpinner = property_get_bool("debug.sf.show_refresh_rate_overlay_spinner", 0); + mRefreshRateOverlayRenderRate = + property_get_bool("debug.sf.show_refresh_rate_overlay_render_rate", 0); + mRefreshRateOverlayShowInMiddle = + property_get_bool("debug.sf.show_refresh_rate_overlay_in_middle", 0); if (!mIsUserBuild && base::GetBoolProperty("debug.sf.enable_transaction_tracing"s, true)) { mTransactionTracing.emplace(); @@ -446,21 +481,21 @@ SurfaceFlinger::SurfaceFlinger(Factory& factory) : SurfaceFlinger(factory, SkipI mIgnoreHdrCameraLayers = ignore_hdr_camera_layers(false); - // Power hint session mode, representing which hint(s) to send: early, late, or both) - mPowerHintSessionMode = - {.late = base::GetBoolProperty("debug.sf.send_late_power_session_hint"s, true), - .early = base::GetBoolProperty("debug.sf.send_early_power_session_hint"s, false)}; + mLayerLifecycleManagerEnabled = + base::GetBoolProperty("persist.debug.sf.enable_layer_lifecycle_manager"s, false); + mLegacyFrontEndEnabled = !mLayerLifecycleManagerEnabled || + base::GetBoolProperty("persist.debug.sf.enable_legacy_frontend"s, false); } LatchUnsignaledConfig SurfaceFlinger::getLatchUnsignaledConfig() { - if (base::GetBoolProperty("debug.sf.latch_unsignaled"s, false)) { - return LatchUnsignaledConfig::Always; - } - if (base::GetBoolProperty("debug.sf.auto_latch_unsignaled"s, true)) { return LatchUnsignaledConfig::AutoSingleLayer; } + if (base::GetBoolProperty("debug.sf.latch_unsignaled"s, false)) { + return LatchUnsignaledConfig::Always; + } + return LatchUnsignaledConfig::Disabled; } @@ -470,13 +505,13 @@ void SurfaceFlinger::binderDied(const wp&) { // the window manager died on us. prepare its eulogy. mBootFinished = false; - // Sever the link to inputflinger since it's gone as well. - static_cast(mScheduler->schedule([=] { mInputFlinger = nullptr; })); + static_cast(mScheduler->schedule([this]() FTL_FAKE_GUARD(kMainThreadContext) { + // Sever the link to inputflinger since it's gone as well. + mInputFlinger.clear(); - // restore initial conditions (default device unblank, etc) - initializeDisplays(); + initializeDisplays(); + })); - // restart the boot-animation startBootAnim(); } @@ -484,12 +519,8 @@ void SurfaceFlinger::run() { mScheduler->run(); } -sp SurfaceFlinger::createConnection() { - const sp client = new Client(this); - return client->initCheck() == NO_ERROR ? client : nullptr; -} - -sp SurfaceFlinger::createDisplay(const String8& displayName, bool secure) { +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 // display. Secure displays can show secure content so we add an additional restriction on it. @@ -504,7 +535,7 @@ sp SurfaceFlinger::createDisplay(const String8& displayName, bool secur virtual ~DisplayToken() { // no more references, this display must be terminated Mutex::Autolock _l(flinger->mStateLock); - flinger->mCurrentState.displays.removeItem(this); + flinger->mCurrentState.displays.removeItem(wp::fromExisting(this)); flinger->setTransactionFlags(eDisplayTransactionNeeded); } public: @@ -513,15 +544,15 @@ sp SurfaceFlinger::createDisplay(const String8& displayName, bool secur } }; - sp token = new DisplayToken(this); + sp token = sp::make(sp::fromExisting(this)); Mutex::Autolock _l(mStateLock); // Display ID is assigned when virtual display is allocated by HWC. DisplayDeviceState state; state.isSecure = secure; state.displayName = displayName; + state.requestedRefreshRate = Fps::fromValue(requestedRefreshRate); mCurrentState.displays.add(token, state); - mInterceptor->saveDisplayCreation(state); return token; } @@ -539,7 +570,6 @@ void SurfaceFlinger::destroyDisplay(const sp& displayToken) { ALOGE("%s: Invalid operation on physical display", __func__); return; } - mInterceptor->saveDisplayDeletion(state.sequenceId); mCurrentState.displays.removeItemsAt(index); setTransactionFlags(eDisplayTransactionNeeded); } @@ -591,12 +621,12 @@ void SurfaceFlinger::releaseVirtualDisplay(VirtualDisplayId displayId) { std::vector SurfaceFlinger::getPhysicalDisplayIdsLocked() const { std::vector displayIds; - displayIds.reserve(mPhysicalDisplayTokens.size()); + displayIds.reserve(mPhysicalDisplays.size()); const auto defaultDisplayId = getDefaultDisplayDeviceLocked()->getPhysicalId(); displayIds.push_back(defaultDisplayId); - for (const auto& [id, token] : mPhysicalDisplayTokens) { + for (const auto& [id, display] : mPhysicalDisplays) { if (id != defaultDisplayId) { displayIds.push_back(id); } @@ -605,10 +635,10 @@ std::vector SurfaceFlinger::getPhysicalDisplayIdsLocked() con return displayIds; } -status_t SurfaceFlinger::getPrimaryPhysicalDisplayId(PhysicalDisplayId* id) const { - Mutex::Autolock lock(mStateLock); - *id = getPrimaryDisplayIdLocked(); - return NO_ERROR; +std::optional SurfaceFlinger::getPhysicalDisplayIdLocked( + const sp& displayToken) const { + return ftl::find_if(mPhysicalDisplays, PhysicalDisplay::hasToken(displayToken)) + .transform(&ftl::to_key); } sp SurfaceFlinger::getPhysicalDisplayToken(PhysicalDisplayId displayId) const { @@ -629,7 +659,7 @@ HWComposer& SurfaceFlinger::getHwComposer() const { } renderengine::RenderEngine& SurfaceFlinger::getRenderEngine() const { - return mCompositionEngine->getRenderEngine(); + return *mRenderEngine; } compositionengine::CompositionEngine& SurfaceFlinger::getCompositionEngine() const { @@ -661,7 +691,7 @@ void SurfaceFlinger::bootFinished() { const String16 name("window"); mWindowManager = defaultServiceManager()->getService(name); if (mWindowManager != 0) { - mWindowManager->linkToDeath(static_cast(this)); + mWindowManager->linkToDeath(sp::fromExisting(this)); } // stop boot animation @@ -675,7 +705,7 @@ void SurfaceFlinger::bootFinished() { sp input(defaultServiceManager()->getService(String16("inputflinger"))); - static_cast(mScheduler->schedule([=] { + static_cast(mScheduler->schedule([=]() FTL_FAKE_GUARD(kMainThreadContext) { if (input == nullptr) { ALOGE("Failed to link to input service"); } else { @@ -684,12 +714,12 @@ void SurfaceFlinger::bootFinished() { readPersistentProperties(); mPowerAdvisor->onBootFinished(); - const bool powerHintEnabled = mFlagManager.use_adpf_cpu_hint(); - mPowerAdvisor->enablePowerHint(powerHintEnabled); - const bool powerHintUsed = mPowerAdvisor->usePowerHintSession(); + const bool hintSessionEnabled = mFlagManager.use_adpf_cpu_hint(); + mPowerAdvisor->enablePowerHintSession(hintSessionEnabled); + const bool hintSessionUsed = mPowerAdvisor->usePowerHintSession(); ALOGD("Power hint is %s", - powerHintUsed ? "supported" : (powerHintEnabled ? "unsupported" : "disabled")); - if (powerHintUsed) { + hintSessionUsed ? "supported" : (hintSessionEnabled ? "unsupported" : "disabled")); + if (hintSessionUsed) { std::optional renderEngineTid = getRenderEngine().getRenderEngineTid(); std::vector tidList; tidList.emplace_back(gettid()); @@ -703,8 +733,9 @@ void SurfaceFlinger::bootFinished() { mBootStage = BootStage::FINISHED; - if (property_get_bool("sf.debug.show_refresh_rate_overlay", false)) { - FTL_FAKE_GUARD(mStateLock, enableRefreshRateOverlay(true)); + if (base::GetBoolProperty("sf.debug.show_refresh_rate_overlay"s, false)) { + ftl::FakeGuard guard(mStateLock); + enableRefreshRateOverlay(true); } })); } @@ -758,6 +789,10 @@ chooseRenderEngineTypeViaSysProp() { return renderengine::RenderEngine::RenderEngineType::SKIA_GL; } else if (strcmp(prop, "skiaglthreaded") == 0) { return renderengine::RenderEngine::RenderEngineType::SKIA_GL_THREADED; + } else if (strcmp(prop, "skiavk") == 0) { + return renderengine::RenderEngine::RenderEngineType::SKIA_VK; + } else if (strcmp(prop, "skiavkthreaded") == 0) { + return renderengine::RenderEngine::RenderEngineType::SKIA_VK_THREADED; } else { ALOGE("Unrecognized RenderEngineType %s; ignoring!", prop); return {}; @@ -766,10 +801,11 @@ chooseRenderEngineTypeViaSysProp() { // Do not call property_set on main thread which will be blocked by init // Use StartPropertySetThread instead. -void SurfaceFlinger::init() { +void SurfaceFlinger::init() FTL_FAKE_GUARD(kMainThreadContext) { ALOGI( "SurfaceFlinger's main thread ready to run. " "Initializing graphics H/W..."); - Mutex::Autolock _l(mStateLock); + addTransactionReadyFilters(); + Mutex::Autolock lock(mStateLock); // Get a RenderEngine for the given display / config (can't fail) // TODO(b/77156734): We need to stop casting and use HAL types when possible. @@ -788,7 +824,8 @@ void SurfaceFlinger::init() { if (auto type = chooseRenderEngineTypeViaSysProp()) { builder.setRenderEngineType(type.value()); } - mCompositionEngine->setRenderEngine(renderengine::RenderEngine::create(builder.build())); + mRenderEngine = renderengine::RenderEngine::create(builder.build()); + mCompositionEngine->setRenderEngine(mRenderEngine.get()); mMaxRenderTargetSize = std::min(getRenderEngine().getMaxTextureSize(), getRenderEngine().getMaxViewportDims()); @@ -808,19 +845,44 @@ void SurfaceFlinger::init() { enableHalVirtualDisplays(true); } - // Process any initial hotplug and resulting display changes. - processDisplayHotplugEventsLocked(); - const auto display = getDefaultDisplayDeviceLocked(); - LOG_ALWAYS_FATAL_IF(!display, "Missing primary display after registering composer callback."); - const auto displayId = display->getPhysicalId(); - LOG_ALWAYS_FATAL_IF(!getHwComposer().isConnected(displayId), - "Primary display is disconnected."); + // Process hotplug for displays connected at boot. + LOG_ALWAYS_FATAL_IF(!configureLocked(), + "Initial display configuration failed: HWC did not hotplug"); + + // Commit primary display. + sp display; + if (const auto indexOpt = mCurrentState.getDisplayIndex(getPrimaryDisplayIdLocked())) { + const auto& displays = mCurrentState.displays; + + const auto& token = displays.keyAt(*indexOpt); + const auto& state = displays.valueAt(*indexOpt); + + processDisplayAdded(token, state); + mDrawingState.displays.add(token, state); + + display = getDefaultDisplayDeviceLocked(); + } + + LOG_ALWAYS_FATAL_IF(!display, "Failed to configure the primary display"); + LOG_ALWAYS_FATAL_IF(!getHwComposer().isConnected(display->getPhysicalId()), + "Primary display is disconnected"); + + // TODO(b/241285876): The Scheduler needlessly depends on creating the CompositionEngine part of + // the DisplayDevice, hence the above commit of the primary display. Remove that special case by + // initializing the Scheduler after configureLocked, once decoupled from DisplayDevice. + initScheduler(display); + dispatchDisplayHotplugEvent(display->getPhysicalId(), true); + + // Commit secondary display(s). + processDisplayChangesLocked(); // initialize our drawing state mDrawingState = mCurrentState; - // set initial conditions (e.g. unblank default device) - initializeDisplays(); + onActiveDisplayChangedLocked(nullptr, *display); + + static_cast(mScheduler->schedule( + [this]() FTL_FAKE_GUARD(kMainThreadContext) { initializeDisplays(); })); mPowerAdvisor->init(); @@ -838,8 +900,6 @@ void SurfaceFlinger::init() { } } - onActiveDisplaySizeChanged(display); - // Inform native graphics APIs whether the present timestamp is supported: const bool presentFenceReliable = @@ -866,8 +926,8 @@ void SurfaceFlinger::readPersistentProperties() { property_get("persist.sys.sf.native_mode", value, "0"); mDisplayColorSetting = static_cast(atoi(value)); - property_get("persist.sys.sf.color_mode", value, "0"); - mForceColorMode = static_cast(atoi(value)); + mForceColorMode = + static_cast(base::GetIntProperty("persist.sys.sf.color_mode"s, 0)); } void SurfaceFlinger::startBootAnim() { @@ -882,17 +942,6 @@ void SurfaceFlinger::startBootAnim() { // ---------------------------------------------------------------------------- -bool SurfaceFlinger::authenticateSurfaceTexture( - const sp& bufferProducer) const { - Mutex::Autolock _l(mStateLock); - return authenticateSurfaceTextureLocked(bufferProducer); -} - -bool SurfaceFlinger::authenticateSurfaceTextureLocked( - const sp& /* bufferProducer */) const { - return false; -} - status_t SurfaceFlinger::getSupportedFrameTimestamps( std::vector* outSupported) const { *outSupported = { @@ -936,24 +985,24 @@ status_t SurfaceFlinger::getDisplayState(const sp& displayToken, ui::Di return NO_ERROR; } -status_t SurfaceFlinger::getStaticDisplayInfo(const sp& displayToken, - ui::StaticDisplayInfo* info) { - if (!displayToken || !info) { +status_t SurfaceFlinger::getStaticDisplayInfo(int64_t displayId, ui::StaticDisplayInfo* info) { + if (!info) { return BAD_VALUE; } Mutex::Autolock lock(mStateLock); + const auto id = DisplayId::fromValue(static_cast(displayId)); + const auto displayOpt = mPhysicalDisplays.get(*id).and_then(getDisplayDeviceAndSnapshot()); - const auto display = getDisplayDeviceLocked(displayToken); - if (!display) { + if (!displayOpt) { return NAME_NOT_FOUND; } - if (const auto connectionType = display->getConnectionType()) - info->connectionType = *connectionType; - else { - return INVALID_OPERATION; - } + const auto& [display, snapshotRef] = *displayOpt; + const auto& snapshot = snapshotRef.get(); + + info->connectionType = snapshot.connectionType(); + info->deviceProductInfo = snapshot.deviceProductInfo(); if (mEmulatedDisplayDensity) { info->density = mEmulatedDisplayDensity; @@ -965,37 +1014,19 @@ status_t SurfaceFlinger::getStaticDisplayInfo(const sp& displayToken, info->density /= ACONFIGURATION_DENSITY_MEDIUM; info->secure = display->isSecure(); - info->deviceProductInfo = display->getDeviceProductInfo(); info->installOrientation = display->getPhysicalOrientation(); return NO_ERROR; } -status_t SurfaceFlinger::getDynamicDisplayInfo(const sp& displayToken, - ui::DynamicDisplayInfo* info) { - if (!displayToken || !info) { - return BAD_VALUE; - } - - Mutex::Autolock lock(mStateLock); - - const auto display = getDisplayDeviceLocked(displayToken); - if (!display) { - return NAME_NOT_FOUND; - } - - const auto displayId = PhysicalDisplayId::tryCast(display->getId()); - if (!displayId) { - return INVALID_OPERATION; - } - - info->activeDisplayModeId = display->getActiveMode()->getId().value(); - - const auto& supportedModes = display->getSupportedModes(); +void SurfaceFlinger::getDynamicDisplayInfoInternal(ui::DynamicDisplayInfo*& info, + const sp& display, + const display::DisplaySnapshot& snapshot) { + const auto& displayModes = snapshot.displayModes(); info->supportedDisplayModes.clear(); - info->supportedDisplayModes.reserve(supportedModes.size()); + info->supportedDisplayModes.reserve(displayModes.size()); - for (const auto& [id, mode] : supportedModes) { + for (const auto& [id, mode] : displayModes) { ui::DisplayMode outMode; outMode.id = static_cast(id.value()); @@ -1035,106 +1066,206 @@ status_t SurfaceFlinger::getDynamicDisplayInfo(const sp& displayToken, // We add an additional 1ms to allow for processing time and // differences between the ideal and actual refresh rate. outMode.presentationDeadline = period - outMode.sfVsyncOffset + 1000000; - + excludeDolbyVisionIf4k30Present(display->getHdrCapabilities().getSupportedHdrTypes(), + outMode); info->supportedDisplayModes.push_back(outMode); } + info->supportedColorModes = snapshot.filterColorModes(mSupportsWideColor); + + const PhysicalDisplayId displayId = snapshot.displayId(); + + const auto mode = display->refreshRateSelector().getActiveMode(); + info->activeDisplayModeId = mode.modePtr->getId().value(); + info->renderFrameRate = mode.fps.getValue(); info->activeColorMode = display->getCompositionDisplay()->getState().colorMode; - info->supportedColorModes = getDisplayColorModes(*display); - info->hdrCapabilities = display->getHdrCapabilities(); + info->hdrCapabilities = filterOut4k30(display->getHdrCapabilities()); info->autoLowLatencyModeSupported = - getHwComposer().hasDisplayCapability(*displayId, + getHwComposer().hasDisplayCapability(displayId, DisplayCapability::AUTO_LOW_LATENCY_MODE); info->gameContentTypeSupported = - getHwComposer().supportsContentType(*displayId, hal::ContentType::GAME); + getHwComposer().supportsContentType(displayId, hal::ContentType::GAME); info->preferredBootDisplayMode = static_cast(-1); if (getHwComposer().hasCapability(Capability::BOOT_DISPLAY_CONFIG)) { - if (const auto hwcId = getHwComposer().getPreferredBootDisplayMode(*displayId)) { - if (const auto modeId = display->translateModeId(*hwcId)) { + if (const auto hwcId = getHwComposer().getPreferredBootDisplayMode(displayId)) { + if (const auto modeId = snapshot.translateModeId(*hwcId)) { info->preferredBootDisplayMode = modeId->value(); } } } +} + +status_t SurfaceFlinger::getDynamicDisplayInfoFromId(int64_t physicalDisplayId, + ui::DynamicDisplayInfo* info) { + if (!info) { + return BAD_VALUE; + } + + Mutex::Autolock lock(mStateLock); + + const auto id_ = + DisplayId::fromValue(static_cast(physicalDisplayId)); + const auto displayOpt = mPhysicalDisplays.get(*id_).and_then(getDisplayDeviceAndSnapshot()); + + if (!displayOpt) { + return NAME_NOT_FOUND; + } + const auto& [display, snapshotRef] = *displayOpt; + getDynamicDisplayInfoInternal(info, display, snapshotRef.get()); return NO_ERROR; } -status_t SurfaceFlinger::getDisplayStats(const sp&, DisplayStatInfo* stats) { - if (!stats) { +status_t SurfaceFlinger::getDynamicDisplayInfoFromToken(const sp& displayToken, + ui::DynamicDisplayInfo* info) { + if (!displayToken || !info) { return BAD_VALUE; } - *stats = mScheduler->getDisplayStatInfo(systemTime()); + Mutex::Autolock lock(mStateLock); + + const auto displayOpt = ftl::find_if(mPhysicalDisplays, PhysicalDisplay::hasToken(displayToken)) + .transform(&ftl::to_mapped_ref) + .and_then(getDisplayDeviceAndSnapshot()); + + if (!displayOpt) { + return NAME_NOT_FOUND; + } + + const auto& [display, snapshotRef] = *displayOpt; + getDynamicDisplayInfoInternal(info, display, snapshotRef.get()); return NO_ERROR; } -void SurfaceFlinger::setDesiredActiveMode(const ActiveModeInfo& info, bool force) { - ATRACE_CALL(); +status_t SurfaceFlinger::getDisplayStats(const sp& displayToken, + DisplayStatInfo* outStats) { + if (!outStats) { + return BAD_VALUE; + } - if (!info.mode) { - ALOGW("requested display mode is null"); - return; + std::optional displayIdOpt; + { + Mutex::Autolock lock(mStateLock); + if (displayToken) { + displayIdOpt = getPhysicalDisplayIdLocked(displayToken); + if (!displayIdOpt) { + ALOGW("%s: Invalid physical display token %p", __func__, displayToken.get()); + return NAME_NOT_FOUND; + } + } else { + // TODO (b/277364366): Clients should be updated to pass in the display they + // want, rather than us picking an arbitrary one (the active display, in this + // case). + displayIdOpt = mActiveDisplayId; + } + } + + const auto schedule = mScheduler->getVsyncSchedule(displayIdOpt); + if (!schedule) { + ALOGE("%s: Missing VSYNC schedule for display %s!", __func__, + to_string(*displayIdOpt).c_str()); + return NAME_NOT_FOUND; } - auto display = getDisplayDeviceLocked(info.mode->getPhysicalDisplayId()); + outStats->vsyncTime = schedule->vsyncDeadlineAfter(TimePoint::now()).ns(); + outStats->vsyncPeriod = schedule->period().ns(); + return NO_ERROR; +} + +void SurfaceFlinger::setDesiredActiveMode(display::DisplayModeRequest&& request, bool force) { + ATRACE_CALL(); + + const auto displayId = request.mode.modePtr->getPhysicalDisplayId(); + const auto display = getDisplayDeviceLocked(displayId); if (!display) { ALOGW("%s: display is no longer valid", __func__); return; } - if (display->setDesiredActiveMode(info, force)) { - scheduleComposite(FrameHint::kNone); + const auto mode = request.mode; + const bool emitEvent = request.emitEvent; + + switch (display->setDesiredActiveMode(DisplayDevice::ActiveModeInfo(std::move(request)), + force)) { + case DisplayDevice::DesiredActiveModeAction::InitiateDisplayModeSwitch: + // Set the render rate as setDesiredActiveMode updated it. + mScheduler->setRenderRate(displayId, + display->refreshRateSelector().getActiveMode().fps); + + // Schedule a new frame to initiate the display mode switch. + scheduleComposite(FrameHint::kNone); + + // Start receiving vsync samples now, so that we can detect a period + // switch. + mScheduler->resyncToHardwareVsync(displayId, true /* allowToEnable */, + mode.modePtr->getFps()); - // Start receiving vsync samples now, so that we can detect a period - // switch. - mScheduler->resyncToHardwareVsync(true, info.mode->getFps()); - // As we called to set period, we will call to onRefreshRateChangeCompleted once - // VsyncController model is locked. - modulateVsync(&VsyncModulator::onRefreshRateChangeInitiated); + // As we called to set period, we will call to onRefreshRateChangeCompleted once + // VsyncController model is locked. + mScheduler->modulateVsync(displayId, &VsyncModulator::onRefreshRateChangeInitiated); + updatePhaseConfiguration(mode.fps); + mScheduler->setModeChangePending(true); + break; + case DisplayDevice::DesiredActiveModeAction::InitiateRenderRateSwitch: + mScheduler->setRenderRate(displayId, mode.fps); + updatePhaseConfiguration(mode.fps); + mRefreshRateStats->setRefreshRate(mode.fps); + if (display->getPhysicalId() == mActiveDisplayId && emitEvent) { + mScheduler->onPrimaryDisplayModeChanged(mAppConnectionHandle, mode); + } - updatePhaseConfiguration(info.mode->getFps()); - mScheduler->setModeChangePending(true); + break; + case DisplayDevice::DesiredActiveModeAction::None: + break; } } -status_t SurfaceFlinger::setActiveModeFromBackdoor(const sp& displayToken, int modeId) { +status_t SurfaceFlinger::setActiveModeFromBackdoor(const sp& displayToken, + DisplayModeId modeId) { ATRACE_CALL(); if (!displayToken) { return BAD_VALUE; } - auto future = mScheduler->schedule([=]() -> status_t { - const auto display = FTL_FAKE_GUARD(mStateLock, getDisplayDeviceLocked(displayToken)); - if (!display) { - ALOGE("Attempt to set allowed display modes for invalid display token %p", - displayToken.get()); + const char* const whence = __func__; + auto future = mScheduler->schedule([=]() FTL_FAKE_GUARD(kMainThreadContext) -> status_t { + const auto displayOpt = + FTL_FAKE_GUARD(mStateLock, + ftl::find_if(mPhysicalDisplays, + PhysicalDisplay::hasToken(displayToken)) + .transform(&ftl::to_mapped_ref) + .and_then(getDisplayDeviceAndSnapshot())); + if (!displayOpt) { + ALOGE("%s: Invalid physical display token %p", whence, displayToken.get()); return NAME_NOT_FOUND; } - if (display->isVirtual()) { - ALOGW("Attempt to set allowed display modes for virtual display"); - return INVALID_OPERATION; - } + const auto& [display, snapshotRef] = *displayOpt; + const auto& snapshot = snapshotRef.get(); + + const auto fpsOpt = snapshot.displayModes().get(modeId).transform( + [](const DisplayModePtr& mode) { return mode->getFps(); }); - const auto mode = display->getMode(DisplayModeId{modeId}); - if (!mode) { - ALOGW("Attempt to switch to an unsupported mode %d.", modeId); + if (!fpsOpt) { + ALOGE("%s: Invalid mode %d for display %s", whence, modeId.value(), + to_string(snapshot.displayId()).c_str()); return BAD_VALUE; } - const auto fps = mode->getFps(); + const Fps fps = *fpsOpt; + // Keep the old switching type. - const auto allowGroupSwitching = - display->refreshRateConfigs().getCurrentPolicy().allowGroupSwitching; - const scheduler::RefreshRateConfigs::Policy policy{mode->getId(), - allowGroupSwitching, - {fps, fps}}; - constexpr bool kOverridePolicy = false; - - return setDesiredDisplayModeSpecsInternal(display, policy, kOverridePolicy); + const bool allowGroupSwitching = + display->refreshRateSelector().getCurrentPolicy().allowGroupSwitching; + + const scheduler::RefreshRateSelector::DisplayManagerPolicy policy{modeId, + {fps, fps}, + allowGroupSwitching}; + + return setDesiredDisplayModeSpecsInternal(display, policy); }); return future.get(); @@ -1148,52 +1279,61 @@ void SurfaceFlinger::updateInternalStateWithChangedMode() { return; } - const auto upcomingModeInfo = - FTL_FAKE_GUARD(kMainThreadContext, display->getUpcomingActiveMode()); - - if (!upcomingModeInfo.mode) { + const auto upcomingModeInfo = display->getUpcomingActiveMode(); + if (!upcomingModeInfo.modeOpt) { // There is no pending mode change. This can happen if the active // display changed and the mode change happened on a different display. return; } - if (display->getActiveMode()->getResolution() != upcomingModeInfo.mode->getResolution()) { + if (display->getActiveMode().modePtr->getResolution() != + upcomingModeInfo.modeOpt->modePtr->getResolution()) { auto& state = mCurrentState.displays.editValueFor(display->getDisplayToken()); // We need to generate new sequenceId in order to recreate the display (and this // way the framebuffer). state.sequenceId = DisplayDeviceState{}.sequenceId; - state.physical->activeMode = upcomingModeInfo.mode; + state.physical->activeMode = upcomingModeInfo.modeOpt->modePtr.get(); processDisplayChangesLocked(); // processDisplayChangesLocked will update all necessary components so we're done here. return; } - // We just created this display so we can call even if we are not on the main thread. - ftl::FakeGuard guard(kMainThreadContext); - display->setActiveMode(upcomingModeInfo.mode->getId()); + mPhysicalDisplays.get(display->getPhysicalId()) + .transform(&PhysicalDisplay::snapshotRef) + .transform(ftl::unit_fn([&](const display::DisplaySnapshot& snapshot) { + FTL_FAKE_GUARD(kMainThreadContext, + display->setActiveMode(upcomingModeInfo.modeOpt->modePtr->getId(), + upcomingModeInfo.modeOpt->modePtr->getFps(), + upcomingModeInfo.modeOpt->fps)); + })); - const Fps refreshRate = upcomingModeInfo.mode->getFps(); + const Fps refreshRate = upcomingModeInfo.modeOpt->fps; mRefreshRateStats->setRefreshRate(refreshRate); updatePhaseConfiguration(refreshRate); - if (upcomingModeInfo.event != DisplayModeEvent::None) { - mScheduler->onPrimaryDisplayModeChanged(mAppConnectionHandle, upcomingModeInfo.mode); + if (upcomingModeInfo.event != scheduler::DisplayModeEvent::None) { + mScheduler->onPrimaryDisplayModeChanged(mAppConnectionHandle, *upcomingModeInfo.modeOpt); } } void SurfaceFlinger::clearDesiredActiveModeState(const sp& display) { display->clearDesiredActiveModeState(); - if (isDisplayActiveLocked(display)) { + if (display->getPhysicalId() == mActiveDisplayId) { mScheduler->setModeChangePending(false); } } void SurfaceFlinger::desiredActiveModeChangeDone(const sp& display) { - const auto refreshRate = display->getDesiredActiveMode()->mode->getFps(); + const auto desiredActiveMode = display->getDesiredActiveMode(); + const auto& modeOpt = desiredActiveMode->modeOpt; + const auto displayId = modeOpt->modePtr->getPhysicalDisplayId(); + const auto displayFps = modeOpt->modePtr->getFps(); + const auto renderFps = modeOpt->fps; clearDesiredActiveModeState(display); - mScheduler->resyncToHardwareVsync(true, refreshRate); - updatePhaseConfiguration(refreshRate); + mScheduler->resyncToHardwareVsync(displayId, true /* allowToEnable */, displayFps); + mScheduler->setRenderRate(displayId, renderFps); + updatePhaseConfiguration(renderFps); } void SurfaceFlinger::setActiveModeInHwcIfNeeded() { @@ -1201,39 +1341,44 @@ void SurfaceFlinger::setActiveModeInHwcIfNeeded() { std::optional displayToUpdateImmediately; - for (const auto& iter : mDisplays) { - const auto& display = iter.second; - if (!display || !display->isInternal()) { + for (const auto& [id, physical] : mPhysicalDisplays) { + const auto& snapshot = physical.snapshot(); + + if (snapshot.connectionType() != ui::DisplayConnectionType::Internal) { continue; } + const auto display = getDisplayDeviceLocked(id); + if (!display) continue; + // Store the local variable to release the lock. const auto desiredActiveMode = display->getDesiredActiveMode(); if (!desiredActiveMode) { - // No desired active mode pending to be applied + // No desired active mode pending to be applied. continue; } - if (!isDisplayActiveLocked(display)) { - // display is no longer the active display, so abort the mode change + if (id != mActiveDisplayId) { + // Display is no longer the active display, so abort the mode change. clearDesiredActiveModeState(display); continue; } - const auto desiredMode = display->getMode(desiredActiveMode->mode->getId()); - if (!desiredMode) { + const auto desiredModeId = desiredActiveMode->modeOpt->modePtr->getId(); + const auto displayModePtrOpt = snapshot.displayModes().get(desiredModeId); + + if (!displayModePtrOpt) { ALOGW("Desired display mode is no longer supported. Mode ID = %d", - desiredActiveMode->mode->getId().value()); + desiredModeId.value()); clearDesiredActiveModeState(display); continue; } - const auto refreshRate = desiredMode->getFps(); - ALOGV("%s changing active mode to %d(%s) for display %s", __func__, - desiredMode->getId().value(), to_string(refreshRate).c_str(), + ALOGV("%s changing active mode to %d(%s) for display %s", __func__, desiredModeId.value(), + to_string(displayModePtrOpt->get()->getFps()).c_str(), to_string(display->getId()).c_str()); - if (display->getActiveMode()->getId() == desiredActiveMode->mode->getId()) { + if (display->getActiveMode() == desiredActiveMode->modeOpt) { // we are already in the requested mode, there is nothing left to do desiredActiveModeChangeDone(display); continue; @@ -1243,7 +1388,7 @@ void SurfaceFlinger::setActiveModeInHwcIfNeeded() { // allowed modes might have changed by the time we process the refresh. // Make sure the desired mode is still allowed const auto displayModeAllowed = - display->refreshRateConfigs().isModeAllowed(desiredActiveMode->mode->getId()); + display->refreshRateSelector().isModeAllowed(*desiredActiveMode->modeOpt); if (!displayModeAllowed) { clearDesiredActiveModeState(display); continue; @@ -1255,9 +1400,8 @@ void SurfaceFlinger::setActiveModeInHwcIfNeeded() { constraints.seamlessRequired = false; hal::VsyncPeriodChangeTimeline outTimeline; - const auto status = FTL_FAKE_GUARD(kMainThreadContext, - display->initiateModeChange(*desiredActiveMode, - constraints, &outTimeline)); + const auto status = + display->initiateModeChange(*desiredActiveMode, constraints, &outTimeline); if (status != NO_ERROR) { // initiateModeChange may fail if a hotplug event is just about @@ -1265,6 +1409,8 @@ void SurfaceFlinger::setActiveModeInHwcIfNeeded() { ALOGW("initiateModeChange failed: %d", status); continue; } + + display->refreshRateSelector().onModeChangeInitiated(); mScheduler->onNewVsyncPeriodChangeTimeline(outTimeline); if (outTimeline.refreshRequired) { @@ -1283,8 +1429,7 @@ void SurfaceFlinger::setActiveModeInHwcIfNeeded() { const auto display = getDisplayDeviceLocked(*displayToUpdateImmediately); const auto desiredActiveMode = display->getDesiredActiveMode(); - if (desiredActiveMode && - display->getActiveMode()->getId() == desiredActiveMode->mode->getId()) { + if (desiredActiveMode && display->getActiveMode() == desiredActiveMode->modeOpt) { desiredActiveModeChangeDone(display); } } @@ -1305,22 +1450,6 @@ void SurfaceFlinger::disableExpensiveRendering() { future.wait(); } -std::vector SurfaceFlinger::getDisplayColorModes(const DisplayDevice& display) { - auto modes = getHwComposer().getColorModes(display.getPhysicalId()); - - // If the display is internal and the configuration claims it's not wide color capable, - // filter out all wide color modes. The typical reason why this happens is that the - // hardware is not good enough to support GPU composition of wide color, and thus the - // OEMs choose to disable this capability. - if (display.getConnectionType() == ui::DisplayConnectionType::Internal && - !hasWideColorDisplay) { - const auto newEnd = std::remove_if(modes.begin(), modes.end(), isWideColorMode); - modes.erase(newEnd, modes.end()); - } - - return modes; -} - status_t SurfaceFlinger::getDisplayNativePrimaries(const sp& displayToken, ui::DisplayPrimaries& primaries) { if (!displayToken) { @@ -1329,13 +1458,13 @@ status_t SurfaceFlinger::getDisplayNativePrimaries(const sp& displayTok Mutex::Autolock lock(mStateLock); - const auto display = getDisplayDeviceLocked(displayToken); + const auto display = ftl::find_if(mPhysicalDisplays, PhysicalDisplay::hasToken(displayToken)) + .transform(&ftl::to_mapped_ref); if (!display) { return NAME_NOT_FOUND; } - const auto connectionType = display->getConnectionType(); - if (connectionType != ui::DisplayConnectionType::Internal) { + if (!display.transform(&PhysicalDisplay::isInternal).value()) { return INVALID_OPERATION; } @@ -1344,31 +1473,32 @@ status_t SurfaceFlinger::getDisplayNativePrimaries(const sp& displayTok return NO_ERROR; } -status_t SurfaceFlinger::setActiveColorMode(const sp& displayToken, ColorMode mode) { +status_t SurfaceFlinger::setActiveColorMode(const sp& displayToken, ui::ColorMode mode) { if (!displayToken) { return BAD_VALUE; } + const char* const whence = __func__; auto future = mScheduler->schedule([=]() FTL_FAKE_GUARD(mStateLock) -> status_t { - const auto display = getDisplayDeviceLocked(displayToken); - if (!display) { - ALOGE("Attempt to set active color mode %s (%d) for invalid display token %p", - decodeColorMode(mode).c_str(), mode, displayToken.get()); + const auto displayOpt = + ftl::find_if(mPhysicalDisplays, PhysicalDisplay::hasToken(displayToken)) + .transform(&ftl::to_mapped_ref) + .and_then(getDisplayDeviceAndSnapshot()); + + if (!displayOpt) { + ALOGE("%s: Invalid physical display token %p", whence, displayToken.get()); return NAME_NOT_FOUND; } - if (display->isVirtual()) { - ALOGW("Attempt to set active color mode %s (%d) for virtual display", - decodeColorMode(mode).c_str(), mode); - return INVALID_OPERATION; - } + const auto& [display, snapshotRef] = *displayOpt; + const auto& snapshot = snapshotRef.get(); - const auto modes = getDisplayColorModes(*display); + const auto modes = snapshot.filterColorModes(mSupportsWideColor); const bool exists = std::find(modes.begin(), modes.end(), mode) != modes.end(); - if (mode < ColorMode::NATIVE || !exists) { - ALOGE("Attempt to set invalid active color mode %s (%d) for display token %p", - decodeColorMode(mode).c_str(), mode, displayToken.get()); + if (mode < ui::ColorMode::NATIVE || !exists) { + ALOGE("%s: Invalid color mode %s (%d) for display %s", whence, + decodeColorMode(mode).c_str(), mode, to_string(snapshot.displayId()).c_str()); return BAD_VALUE; } @@ -1391,30 +1521,67 @@ status_t SurfaceFlinger::getBootDisplayModeSupport(bool* outSupport) const { return NO_ERROR; } -status_t SurfaceFlinger::setBootDisplayMode(const sp& displayToken, - ui::DisplayModeId modeId) { +status_t SurfaceFlinger::getOverlaySupport(gui::OverlayProperties* outProperties) const { + const auto& aidlProperties = getHwComposer().getOverlaySupport(); + // convert aidl OverlayProperties to gui::OverlayProperties + outProperties->combinations.reserve(aidlProperties.combinations.size()); + for (const auto& combination : aidlProperties.combinations) { + std::vector pixelFormats; + pixelFormats.reserve(combination.pixelFormats.size()); + std::transform(combination.pixelFormats.cbegin(), combination.pixelFormats.cend(), + std::back_inserter(pixelFormats), + [](const auto& val) { return static_cast(val); }); + std::vector standards; + standards.reserve(combination.standards.size()); + std::transform(combination.standards.cbegin(), combination.standards.cend(), + std::back_inserter(standards), + [](const auto& val) { return static_cast(val); }); + std::vector transfers; + transfers.reserve(combination.transfers.size()); + std::transform(combination.transfers.cbegin(), combination.transfers.cend(), + std::back_inserter(transfers), + [](const auto& val) { return static_cast(val); }); + std::vector ranges; + ranges.reserve(combination.ranges.size()); + std::transform(combination.ranges.cbegin(), combination.ranges.cend(), + std::back_inserter(ranges), + [](const auto& val) { return static_cast(val); }); + gui::OverlayProperties::SupportedBufferCombinations outCombination; + outCombination.pixelFormats = std::move(pixelFormats); + outCombination.standards = std::move(standards); + outCombination.transfers = std::move(transfers); + outCombination.ranges = std::move(ranges); + outProperties->combinations.emplace_back(outCombination); + } + outProperties->supportMixedColorSpaces = aidlProperties.supportMixedColorSpaces; + return NO_ERROR; +} + +status_t SurfaceFlinger::setBootDisplayMode(const sp& displayToken, + DisplayModeId modeId) { const char* const whence = __func__; auto future = mScheduler->schedule([=]() FTL_FAKE_GUARD(mStateLock) -> status_t { - const auto display = getDisplayDeviceLocked(displayToken); - if (!display) { - ALOGE("%s: Invalid display token %p", whence, displayToken.get()); + const auto snapshotOpt = + ftl::find_if(mPhysicalDisplays, PhysicalDisplay::hasToken(displayToken)) + .transform(&ftl::to_mapped_ref) + .transform(&PhysicalDisplay::snapshotRef); + + if (!snapshotOpt) { + ALOGE("%s: Invalid physical display token %p", whence, displayToken.get()); return NAME_NOT_FOUND; } - if (display->isVirtual()) { - ALOGE("%s: Invalid operation on virtual display", whence); - return INVALID_OPERATION; - } + const auto& snapshot = snapshotOpt->get(); + const auto hwcIdOpt = snapshot.displayModes().get(modeId).transform( + [](const DisplayModePtr& mode) { return mode->getHwcId(); }); - const auto displayId = display->getPhysicalId(); - const auto mode = display->getMode(DisplayModeId{modeId}); - if (!mode) { - ALOGE("%s: Invalid mode %d for display %s", whence, modeId, - to_string(displayId).c_str()); + if (!hwcIdOpt) { + ALOGE("%s: Invalid mode %d for display %s", whence, modeId.value(), + to_string(snapshot.displayId()).c_str()); return BAD_VALUE; } - return getHwComposer().setBootDisplayMode(displayId, mode->getHwcId()); + return getHwComposer().setBootDisplayMode(snapshot.displayId(), *hwcIdOpt); }); return future.get(); } @@ -1432,6 +1599,92 @@ status_t SurfaceFlinger::clearBootDisplayMode(const sp& displayToken) { return future.get(); } +status_t SurfaceFlinger::getHdrConversionCapabilities( + std::vector* hdrConversionCapabilities) const { + bool hdrOutputConversionSupport; + getHdrOutputConversionSupport(&hdrOutputConversionSupport); + if (hdrOutputConversionSupport == false) { + ALOGE("hdrOutputConversion is not supported by this device."); + return INVALID_OPERATION; + } + const auto aidlConversionCapability = getHwComposer().getHdrConversionCapabilities(); + for (auto capability : aidlConversionCapability) { + gui::HdrConversionCapability tempCapability; + tempCapability.sourceType = static_cast(capability.sourceType); + tempCapability.outputType = static_cast(capability.outputType); + tempCapability.addsLatency = capability.addsLatency; + hdrConversionCapabilities->push_back(tempCapability); + } + return NO_ERROR; +} + +status_t SurfaceFlinger::setHdrConversionStrategy( + const gui::HdrConversionStrategy& hdrConversionStrategy, + int32_t* outPreferredHdrOutputType) { + bool hdrOutputConversionSupport; + getHdrOutputConversionSupport(&hdrOutputConversionSupport); + if (hdrOutputConversionSupport == false) { + ALOGE("hdrOutputConversion is not supported by this device."); + return INVALID_OPERATION; + } + auto future = mScheduler->schedule([=]() FTL_FAKE_GUARD(mStateLock) mutable -> status_t { + using AidlHdrConversionStrategy = + aidl::android::hardware::graphics::common::HdrConversionStrategy; + using GuiHdrConversionStrategyTag = gui::HdrConversionStrategy::Tag; + AidlHdrConversionStrategy aidlConversionStrategy; + status_t status; + aidl::android::hardware::graphics::common::Hdr aidlPreferredHdrOutputType; + switch (hdrConversionStrategy.getTag()) { + case GuiHdrConversionStrategyTag::passthrough: { + aidlConversionStrategy.set( + hdrConversionStrategy.get()); + status = getHwComposer().setHdrConversionStrategy(aidlConversionStrategy, + &aidlPreferredHdrOutputType); + *outPreferredHdrOutputType = static_cast(aidlPreferredHdrOutputType); + return status; + } + case GuiHdrConversionStrategyTag::autoAllowedHdrTypes: { + auto autoHdrTypes = + hdrConversionStrategy + .get(); + std::vector aidlAutoHdrTypes; + for (auto type : autoHdrTypes) { + aidlAutoHdrTypes.push_back( + static_cast(type)); + } + aidlConversionStrategy.set( + aidlAutoHdrTypes); + status = getHwComposer().setHdrConversionStrategy(aidlConversionStrategy, + &aidlPreferredHdrOutputType); + *outPreferredHdrOutputType = static_cast(aidlPreferredHdrOutputType); + return status; + } + case GuiHdrConversionStrategyTag::forceHdrConversion: { + auto forceHdrConversion = + hdrConversionStrategy + .get(); + aidlConversionStrategy.set( + static_cast( + forceHdrConversion)); + status = getHwComposer().setHdrConversionStrategy(aidlConversionStrategy, + &aidlPreferredHdrOutputType); + *outPreferredHdrOutputType = static_cast(aidlPreferredHdrOutputType); + return status; + } + } + }); + return future.get(); +} + +status_t SurfaceFlinger::getHdrOutputConversionSupport(bool* outSupport) const { + auto future = mScheduler->schedule([this] { + return getHwComposer().hasCapability(Capability::HDR_OUTPUT_CONVERSION_CONFIG); + }); + + *outSupport = future.get(); + return NO_ERROR; +} + void SurfaceFlinger::setAutoLowLatencyMode(const sp& displayToken, bool on) { const char* const whence = __func__; static_cast(mScheduler->schedule([=]() FTL_FAKE_GUARD(mStateLock) { @@ -1455,18 +1708,6 @@ void SurfaceFlinger::setGameContentType(const sp& displayToken, bool on })); } -status_t SurfaceFlinger::clearAnimationFrameStats() { - Mutex::Autolock _l(mStateLock); - mAnimFrameTracker.clearStats(); - return NO_ERROR; -} - -status_t SurfaceFlinger::getAnimationFrameStats(FrameStats* outStats) const { - Mutex::Autolock _l(mStateLock); - mAnimFrameTracker.getStats(outStats); - return NO_ERROR; -} - status_t SurfaceFlinger::overrideHdrTypes(const sp& displayToken, const std::vector& hdrTypes) { Mutex::Autolock lock(mStateLock); @@ -1482,7 +1723,8 @@ status_t SurfaceFlinger::overrideHdrTypes(const sp& displayToken, return NO_ERROR; } -status_t SurfaceFlinger::onPullAtom(const int32_t atomId, std::string* pulledData, bool* success) { +status_t SurfaceFlinger::onPullAtom(const int32_t atomId, std::vector* pulledData, + bool* success) { *success = mTimeStats->onPullAtom(atomId, pulledData); return NO_ERROR; } @@ -1557,34 +1799,11 @@ status_t SurfaceFlinger::isWideColorDisplay(const sp& displayToken, } *outIsWideColorDisplay = - display->isPrimary() ? hasWideColorDisplay : display->hasWideColorGamut(); - return NO_ERROR; -} - -status_t SurfaceFlinger::enableVSyncInjections(bool enable) { - auto future = mScheduler->schedule([=] { - Mutex::Autolock lock(mStateLock); - - if (const auto handle = mScheduler->enableVSyncInjection(enable)) { - mScheduler->setInjector(enable ? mScheduler->getEventConnection(handle) : nullptr); - } - }); - - future.wait(); + display->isPrimary() ? mSupportsWideColor : display->hasWideColorGamut(); return NO_ERROR; } -status_t SurfaceFlinger::injectVSync(nsecs_t when) { - Mutex::Autolock lock(mStateLock); - const DisplayStatInfo stats = mScheduler->getDisplayStatInfo(when); - const auto expectedPresent = calculateExpectedPresentTime(stats); - return mScheduler->injectVSync(when, /*expectedVSyncTime=*/expectedPresent, - /*deadlineTimestamp=*/expectedPresent) - ? NO_ERROR - : BAD_VALUE; -} - -status_t SurfaceFlinger::getLayerDebugInfo(std::vector* outLayers) { +status_t SurfaceFlinger::getLayerDebugInfo(std::vector* outLayers) { outLayers->clear(); auto future = mScheduler->schedule([=] { const auto display = FTL_FAKE_GUARD(mStateLock, getDefaultDisplayDeviceLocked()); @@ -1615,8 +1834,12 @@ status_t SurfaceFlinger::addRegionSamplingListener(const Rect& samplingArea, return BAD_VALUE; } - const wp stopLayer = fromHandle(stopLayerHandle); - mRegionSamplingThread->addListener(samplingArea, stopLayer, listener); + // LayerHandle::getLayer promotes the layer object in a binder thread but we will not destroy + // the layer here since the caller has a strong ref to the layer's handle. + const sp stopLayer = LayerHandle::getLayer(stopLayerHandle); + mRegionSamplingThread->addListener(samplingArea, + stopLayer ? stopLayer->getSequence() : UNASSIGNED_LAYER_ID, + listener); return NO_ERROR; } @@ -1681,17 +1904,6 @@ status_t SurfaceFlinger::getDisplayBrightnessSupport(const sp& displayT return NO_ERROR; } -bool SurfaceFlinger::hasVisibleHdrLayer(const sp& display) { - bool hasHdrLayers = false; - mDrawingState.traverse([&, - compositionDisplay = display->getCompositionDisplay()](Layer* layer) { - hasHdrLayers |= (layer->isVisible() && - compositionDisplay->includesLayer(layer->getCompositionEngineLayerFE()) && - isHdrDataspace(layer->getDataSpace())); - }); - return hasHdrLayers; -} - status_t SurfaceFlinger::setDisplayBrightness(const sp& displayToken, const gui::DisplayBrightness& brightness) { if (!displayToken) { @@ -1816,19 +2028,21 @@ status_t SurfaceFlinger::getDisplayDecorationSupport( // ---------------------------------------------------------------------------- sp SurfaceFlinger::createDisplayEventConnection( - ISurfaceComposer::VsyncSource vsyncSource, - ISurfaceComposer::EventRegistrationFlags eventRegistration) { + gui::ISurfaceComposer::VsyncSource vsyncSource, EventRegistrationFlags eventRegistration, + const sp& layerHandle) { const auto& handle = - vsyncSource == eVsyncSourceSurfaceFlinger ? mSfConnectionHandle : mAppConnectionHandle; + vsyncSource == gui::ISurfaceComposer::VsyncSource::eVsyncSourceSurfaceFlinger + ? mSfConnectionHandle + : mAppConnectionHandle; - return mScheduler->createDisplayEventConnection(handle, eventRegistration); + return mScheduler->createDisplayEventConnection(handle, eventRegistration, layerHandle); } void SurfaceFlinger::scheduleCommit(FrameHint hint) { if (hint == FrameHint::kActive) { mScheduler->resetIdleTimer(); } - mPowerAdvisor->notifyDisplayUpdateImminent(); + mPowerAdvisor->notifyDisplayUpdateImminentAndCpuReset(); mScheduler->scheduleFrame(); } @@ -1856,66 +2070,29 @@ nsecs_t SurfaceFlinger::getVsyncPeriodFromHWC() const { void SurfaceFlinger::onComposerHalVsync(hal::HWDisplayId hwcDisplayId, int64_t timestamp, std::optional vsyncPeriod) { - const std::string tracePeriod = [vsyncPeriod]() { - if (ATRACE_ENABLED() && vsyncPeriod) { - std::stringstream ss; - ss << "(" << *vsyncPeriod << ")"; - return ss.str(); - } - return std::string(); - }(); - ATRACE_FORMAT("onComposerHalVsync%s", tracePeriod.c_str()); + ATRACE_NAME(vsyncPeriod + ? ftl::Concat(__func__, ' ', hwcDisplayId, ' ', *vsyncPeriod, "ns").c_str() + : ftl::Concat(__func__, ' ', hwcDisplayId).c_str()); Mutex::Autolock lock(mStateLock); - const auto displayId = getHwComposer().toPhysicalDisplayId(hwcDisplayId); - if (displayId) { - const auto token = getPhysicalDisplayTokenLocked(*displayId); - const auto display = getDisplayDeviceLocked(token); - display->onVsync(timestamp); - } - - if (!getHwComposer().onVsync(hwcDisplayId, timestamp)) { - return; - } - - const bool isActiveDisplay = - displayId && getPhysicalDisplayTokenLocked(*displayId) == mActiveDisplayToken; - if (!isActiveDisplay) { - // For now, we don't do anything with non active display vsyncs. - return; - } - - bool periodFlushed = false; - mScheduler->addResyncSample(timestamp, vsyncPeriod, &periodFlushed); - if (periodFlushed) { - modulateVsync(&VsyncModulator::onRefreshRateChangeCompleted); + if (const auto displayIdOpt = getHwComposer().onVsync(hwcDisplayId, timestamp)) { + if (mScheduler->addResyncSample(*displayIdOpt, timestamp, vsyncPeriod)) { + // period flushed + mScheduler->modulateVsync(displayIdOpt, &VsyncModulator::onRefreshRateChangeCompleted); + } } } -void SurfaceFlinger::getCompositorTiming(CompositorTiming* compositorTiming) { - std::lock_guard lock(getBE().mCompositorTimingLock); - *compositorTiming = getBE().mCompositorTiming; -} - void SurfaceFlinger::onComposerHalHotplug(hal::HWDisplayId hwcDisplayId, hal::Connection connection) { - const bool connected = connection == hal::Connection::CONNECTED; - ALOGI("%s HAL display %" PRIu64, connected ? "Connecting" : "Disconnecting", hwcDisplayId); - - // Only lock if we're not on the main thread. This function is normally - // called on a hwbinder thread, but for the primary display it's called on - // the main thread with the state lock already held, so don't attempt to - // acquire it here. - ConditionalLock lock(mStateLock, std::this_thread::get_id() != mMainThreadId); - - mPendingHotplugEvents.emplace_back(HotplugEvent{hwcDisplayId, connection}); - - if (std::this_thread::get_id() == mMainThreadId) { - // Process all pending hot plug events immediately if we are on the main thread. - processDisplayHotplugEventsLocked(); + { + std::lock_guard lock(mHotplugMutex); + mPendingHotplugEvents.push_back(HotplugEvent{hwcDisplayId, connection}); } - setTransactionFlags(eDisplayTransactionNeeded); + if (mScheduler) { + mScheduler->scheduleConfigure(); + } } void SurfaceFlinger::onComposerHalVsyncPeriodTimingChanged( @@ -1943,40 +2120,62 @@ void SurfaceFlinger::onComposerHalVsyncIdle(hal::HWDisplayId) { mScheduler->forceNextResync(); } -void SurfaceFlinger::setVsyncEnabled(bool enabled) { +void SurfaceFlinger::onRefreshRateChangedDebug(const RefreshRateChangedDebugData& data) { ATRACE_CALL(); + if (const auto displayId = getHwComposer().toPhysicalDisplayId(data.display); displayId) { + const Fps fps = Fps::fromPeriodNsecs(data.vsyncPeriodNanos); + ATRACE_FORMAT("%s Fps %d", __func__, fps.getIntValue()); + static_cast(mScheduler->schedule([=]() FTL_FAKE_GUARD(mStateLock) { + { + { + const auto display = getDisplayDeviceLocked(*displayId); + FTL_FAKE_GUARD(kMainThreadContext, + display->updateRefreshRateOverlayRate(fps, + display->getActiveMode() + .fps, + /* setByHwc */ true)); + } + } + })); + } +} + +void SurfaceFlinger::setVsyncEnabled(PhysicalDisplayId id, bool enabled) { + const char* const whence = __func__; + ATRACE_FORMAT("%s (%d) for %" PRIu64, whence, enabled, id.value); // On main thread to avoid race conditions with display power state. static_cast(mScheduler->schedule([=]() FTL_FAKE_GUARD(mStateLock) { - mHWCVsyncPendingState = enabled ? hal::Vsync::ENABLE : hal::Vsync::DISABLE; + { + ftl::FakeGuard guard(kMainThreadContext); + if (auto schedule = mScheduler->getVsyncSchedule(id)) { + schedule->setPendingHardwareVsyncState(enabled); + } + } - if (const auto display = getDefaultDisplayDeviceLocked(); - display && display->isPoweredOn()) { - setHWCVsyncEnabled(display->getPhysicalId(), mHWCVsyncPendingState); + ATRACE_FORMAT("%s (%d) for %" PRIu64 " (main thread)", whence, enabled, id.value); + if (const auto display = getDisplayDeviceLocked(id); display && display->isPoweredOn()) { + setHWCVsyncEnabled(id, enabled); } })); } -SurfaceFlinger::FenceWithFenceTime SurfaceFlinger::previousFrameFence() { - const auto now = systemTime(); - const auto vsyncPeriod = mScheduler->getDisplayStatInfo(now).vsyncPeriod; - const bool expectedPresentTimeIsTheNextVsync = mExpectedPresentTime - now <= vsyncPeriod; +bool SurfaceFlinger::wouldPresentEarly(TimePoint frameTime, Period vsyncPeriod) const { + const bool isThreeVsyncsAhead = mExpectedPresentTime - frameTime > 2 * vsyncPeriod; + return isThreeVsyncsAhead || + getPreviousPresentFence(frameTime, vsyncPeriod)->getSignalTime() != + Fence::SIGNAL_TIME_PENDING; +} - size_t shift = 0; - if (!expectedPresentTimeIsTheNextVsync) { - shift = static_cast((mExpectedPresentTime - now) / vsyncPeriod); - if (shift >= mPreviousPresentFences.size()) { - shift = mPreviousPresentFences.size() - 1; - } - } - ATRACE_FORMAT("previousFrameFence shift=%zu", shift); - return mPreviousPresentFences[shift]; +auto SurfaceFlinger::getPreviousPresentFence(TimePoint frameTime, Period vsyncPeriod) const + -> const FenceTimePtr& { + const bool isTwoVsyncsAhead = mExpectedPresentTime - frameTime > vsyncPeriod; + const size_t i = static_cast(isTwoVsyncsAhead); + return mPreviousPresentFences[i].fenceTime; } -bool SurfaceFlinger::previousFramePending(int graceTimeMs) { +bool SurfaceFlinger::isFencePending(const FenceTimePtr& fence, int graceTimeMs) { ATRACE_CALL(); - const std::shared_ptr& fence = previousFrameFence().fenceTime; - if (fence == FenceTime::NO_FENCE) { return false; } @@ -1987,53 +2186,221 @@ bool SurfaceFlinger::previousFramePending(int graceTimeMs) { return status == -ETIME; } -nsecs_t SurfaceFlinger::previousFramePresentTime() { - const std::shared_ptr& fence = previousFrameFence().fenceTime; +TimePoint SurfaceFlinger::calculateExpectedPresentTime(TimePoint frameTime) const { + const auto& schedule = mScheduler->getVsyncSchedule(); - if (fence == FenceTime::NO_FENCE) { - return Fence::SIGNAL_TIME_INVALID; + const TimePoint vsyncDeadline = schedule->vsyncDeadlineAfter(frameTime); + if (mScheduler->vsyncModulator().getVsyncConfig().sfOffset > 0) { + return vsyncDeadline; } - return fence->getSignalTime(); + // Inflate the expected present time if we're targeting the next vsync. + return vsyncDeadline + schedule->period(); } -nsecs_t SurfaceFlinger::calculateExpectedPresentTime(DisplayStatInfo stats) const { - // Inflate the expected present time if we're targetting the next vsync. - return mVsyncModulator->getVsyncConfig().sfOffset > 0 ? stats.vsyncTime - : stats.vsyncTime + stats.vsyncPeriod; +void SurfaceFlinger::configure() FTL_FAKE_GUARD(kMainThreadContext) { + Mutex::Autolock lock(mStateLock); + if (configureLocked()) { + setTransactionFlags(eDisplayTransactionNeeded); + } } -bool SurfaceFlinger::commit(nsecs_t frameTime, int64_t vsyncId, nsecs_t expectedVsyncTime) - FTL_FAKE_GUARD(kMainThreadContext) { - // calculate the expected present time once and use the cached - // value throughout this frame to make sure all layers are - // seeing this same value. - if (expectedVsyncTime >= frameTime) { - mExpectedPresentTime = expectedVsyncTime; - } else { - const DisplayStatInfo stats = mScheduler->getDisplayStatInfo(frameTime); - mExpectedPresentTime = calculateExpectedPresentTime(stats); +bool SurfaceFlinger::updateLayerSnapshotsLegacy(VsyncId vsyncId, frontend::Update& update, + bool transactionsFlushed, + bool& outTransactionsAreEmpty) { + bool needsTraversal = false; + if (transactionsFlushed) { + needsTraversal |= commitMirrorDisplays(vsyncId); + needsTraversal |= commitCreatedLayers(vsyncId, update.layerCreatedStates); + needsTraversal |= applyTransactions(update.transactions, vsyncId); + } + outTransactionsAreEmpty = !needsTraversal; + const bool shouldCommit = (getTransactionFlags() & ~eTransactionFlushNeeded) || needsTraversal; + if (shouldCommit) { + commitTransactions(); + } + + bool mustComposite = latchBuffers() || shouldCommit; + updateLayerGeometry(); + return mustComposite; +} + +void SurfaceFlinger::updateLayerHistory(const frontend::LayerSnapshot& snapshot) { + using Changes = frontend::RequestedLayerState::Changes; + if (snapshot.path.isClone() || + !snapshot.changes.any(Changes::FrameRate | Changes::Buffer | Changes::Animation)) { + return; + } + + const auto layerProps = scheduler::LayerProps{ + .visible = snapshot.isVisible, + .bounds = snapshot.geomLayerBounds, + .transform = snapshot.geomLayerTransform, + .setFrameRateVote = snapshot.frameRate, + .frameRateSelectionPriority = snapshot.frameRateSelectionPriority, + }; + + auto it = mLegacyLayers.find(snapshot.sequence); + LOG_ALWAYS_FATAL_IF(it == mLegacyLayers.end(), "Couldnt find layer object for %s", + snapshot.getDebugString().c_str()); + + if (snapshot.changes.test(Changes::Animation)) { + it->second->recordLayerHistoryAnimationTx(layerProps); + } + + if (snapshot.changes.test(Changes::FrameRate)) { + it->second->setFrameRateForLayerTree(snapshot.frameRate, layerProps); + } + + if (snapshot.changes.test(Changes::Buffer)) { + it->second->recordLayerHistoryBufferUpdate(layerProps); + } +} + +bool SurfaceFlinger::updateLayerSnapshots(VsyncId vsyncId, frontend::Update& update, + bool transactionsFlushed, bool& outTransactionsAreEmpty) { + using Changes = frontend::RequestedLayerState::Changes; + ATRACE_NAME("updateLayerSnapshots"); + { + mLayerLifecycleManager.addLayers(std::move(update.newLayers)); + mLayerLifecycleManager.applyTransactions(update.transactions); + mLayerLifecycleManager.onHandlesDestroyed(update.destroyedHandles); + for (auto& legacyLayer : update.layerCreatedStates) { + sp layer = legacyLayer.layer.promote(); + if (layer) { + mLegacyLayers[layer->sequence] = layer; + } + } + } + if (mLayerLifecycleManager.getGlobalChanges().test(Changes::Hierarchy)) { + ATRACE_NAME("LayerHierarchyBuilder:update"); + mLayerHierarchyBuilder.update(mLayerLifecycleManager.getLayers(), + mLayerLifecycleManager.getDestroyedLayers()); + } + + bool mustComposite = false; + mustComposite |= applyAndCommitDisplayTransactionStates(update.transactions); + + { + ATRACE_NAME("LayerSnapshotBuilder:update"); + frontend::LayerSnapshotBuilder::Args + args{.root = mLayerHierarchyBuilder.getHierarchy(), + .layerLifecycleManager = mLayerLifecycleManager, + .displays = mFrontEndDisplayInfos, + .displayChanges = mFrontEndDisplayInfosChanged, + .globalShadowSettings = mDrawingState.globalShadowSettings, + .supportsBlur = mSupportsBlur, + .forceFullDamage = mForceFullDamage, + .supportedLayerGenericMetadata = + getHwComposer().getSupportedLayerGenericMetadata(), + .genericLayerMetadataKeyMap = getGenericLayerMetadataKeyMap()}; + mLayerSnapshotBuilder.update(args); + } + + if (mLayerLifecycleManager.getGlobalChanges().any(Changes::Geometry | Changes::Input | + Changes::Hierarchy | Changes::Visibility)) { + mUpdateInputInfo = true; + } + if (mLayerLifecycleManager.getGlobalChanges().any(Changes::VisibleRegion | Changes::Hierarchy | + Changes::Visibility)) { + mVisibleRegionsDirty = true; + } + outTransactionsAreEmpty = mLayerLifecycleManager.getGlobalChanges().get() == 0; + mustComposite |= mLayerLifecycleManager.getGlobalChanges().get() != 0; + + bool newDataLatched = false; + if (!mLegacyFrontEndEnabled) { + ATRACE_NAME("DisplayCallbackAndStatsUpdates"); + applyTransactions(update.transactions, vsyncId); + 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(); + if (!layer->hasReadyFrame() && !willReleaseBufferOnLatch) continue; + + auto it = mLegacyLayers.find(layer->id); + LOG_ALWAYS_FATAL_IF(it == mLegacyLayers.end(), "Couldnt find layer object for %s", + layer->getDebugString().c_str()); + const bool bgColorOnly = + !layer->externalTexture && (layer->bgColorLayerId != UNASSIGNED_LAYER_ID); + if (willReleaseBufferOnLatch) { + mLayersWithBuffersRemoved.emplace(it->second); + } + it->second->latchBufferImpl(unused, latchTime, bgColorOnly); + mLayersWithQueuedFrames.emplace(it->second); + } + + for (auto& snapshot : mLayerSnapshotBuilder.getSnapshots()) { + updateLayerHistory(*snapshot); + if (!snapshot->hasReadyFrame) continue; + newDataLatched = true; + if (!snapshot->isVisible) break; + + Region visibleReg; + visibleReg.set(snapshot->transformedBoundsWithoutTransparentRegion); + invalidateLayerStack(snapshot->outputFilter, visibleReg); + } + + for (auto& destroyedLayer : mLayerLifecycleManager.getDestroyedLayers()) { + mLegacyLayers.erase(destroyedLayer->id); + } + + { + ATRACE_NAME("LLM:commitChanges"); + mLayerLifecycleManager.commitChanges(); + } + + commitTransactions(); + + // 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; + return mustComposite; +} - const nsecs_t lastScheduledPresentTime = mScheduledPresentTime; +bool SurfaceFlinger::commit(TimePoint frameTime, VsyncId vsyncId, TimePoint expectedVsyncTime) + FTL_FAKE_GUARD(kMainThreadContext) { + // The expectedVsyncTime, which was predicted when this frame was scheduled, is normally in the + // future relative to frameTime, but may not be for delayed frames. Adjust mExpectedPresentTime + // accordingly, but not mScheduledPresentTime. + const TimePoint lastScheduledPresentTime = mScheduledPresentTime; mScheduledPresentTime = expectedVsyncTime; - const auto vsyncIn = [&] { - if (!ATRACE_ENABLED()) return 0.f; - return (mExpectedPresentTime - systemTime()) / 1e6f; - }(); - ATRACE_FORMAT("%s %" PRId64 " vsyncIn %.2fms%s", __func__, vsyncId, vsyncIn, + // Calculate the expected present time once and use the cached value throughout this frame to + // make sure all layers are seeing this same value. + mExpectedPresentTime = expectedVsyncTime >= frameTime ? expectedVsyncTime + : calculateExpectedPresentTime(frameTime); + + ATRACE_FORMAT("%s %" PRId64 " vsyncIn %.2fms%s", __func__, vsyncId.value, + ticks(mExpectedPresentTime - TimePoint::now()), mExpectedPresentTime == expectedVsyncTime ? "" : " (adjusted)"); - // When Backpressure propagation is enabled we want to give a small grace period + const Period vsyncPeriod = mScheduler->getVsyncSchedule()->period(); + const FenceTimePtr& previousPresentFence = getPreviousPresentFence(frameTime, vsyncPeriod); + + // When backpressure propagation is enabled, we want to give a small grace period of 1ms // for the present fence to fire instead of just giving up on this frame to handle cases // where present fence is just about to get signaled. - const int graceTimeForPresentFenceMs = - (mPropagateBackpressureClientComposition || !mHadClientComposition) ? 1 : 0; + const int graceTimeForPresentFenceMs = static_cast( + mBackpressureGpuComposition || !mCompositionCoverage.test(CompositionCoverage::Gpu)); // Pending frames may trigger backpressure propagation. const TracedOrdinal framePending = {"PrevFramePending", - previousFramePending(graceTimeForPresentFenceMs)}; + isFencePending(previousPresentFence, + graceTimeForPresentFenceMs)}; // Frame missed counts for metrics tracking. // A frame is missed if the prior frame is still pending. If no longer pending, @@ -2043,18 +2410,22 @@ bool SurfaceFlinger::commit(nsecs_t frameTime, int64_t vsyncId, nsecs_t expected // Add some slop to correct for drift. This should generally be // smaller than a typical frame duration, but should not be so small // that it reports reasonable drift as a missed frame. - const DisplayStatInfo stats = mScheduler->getDisplayStatInfo(systemTime()); - const nsecs_t frameMissedSlop = stats.vsyncPeriod / 2; - const nsecs_t previousPresentTime = previousFramePresentTime(); + const nsecs_t frameMissedSlop = vsyncPeriod.ns() / 2; + const nsecs_t previousPresentTime = previousPresentFence->getSignalTime(); const TracedOrdinal frameMissed = {"PrevFrameMissed", framePending || (previousPresentTime >= 0 && - (lastScheduledPresentTime < + (lastScheduledPresentTime.ns() < previousPresentTime - frameMissedSlop))}; const TracedOrdinal hwcFrameMissed = {"PrevHwcFrameMissed", - mHadDeviceComposition && frameMissed}; + frameMissed && + mCompositionCoverage.test( + CompositionCoverage::Hwc)}; + const TracedOrdinal gpuFrameMissed = {"PrevGpuFrameMissed", - mHadClientComposition && frameMissed}; + frameMissed && + mCompositionCoverage.test( + CompositionCoverage::Gpu)}; if (frameMissed) { mFrameMissedCount++; @@ -2069,6 +2440,11 @@ bool SurfaceFlinger::commit(nsecs_t frameTime, int64_t vsyncId, nsecs_t expected mGpuFrameMissedCount++; } + if (mTracingEnabledChanged) { + mLayerTracingEnabled = mLayerTracing.isEnabled(); + mTracingEnabledChanged = false; + } + // If we are in the middle of a mode change and the fence hasn't // fired yet just wait for the next commit. if (mSetActiveModePending) { @@ -2087,7 +2463,7 @@ bool SurfaceFlinger::commit(nsecs_t frameTime, int64_t vsyncId, nsecs_t expected } if (framePending) { - if ((hwcFrameMissed && !gpuFrameMissed) || mPropagateBackpressureClientComposition) { + if (mBackpressureGpuComposition || (hwcFrameMissed && !gpuFrameMissed)) { scheduleCommit(FrameHint::kNone); return false; } @@ -2095,33 +2471,24 @@ bool SurfaceFlinger::commit(nsecs_t frameTime, int64_t vsyncId, nsecs_t expected // Save this once per commit + composite to ensure consistency // TODO (b/240619471): consider removing active display check once AOD is fixed - const auto activeDisplay = - FTL_FAKE_GUARD(mStateLock, getDisplayDeviceLocked(mActiveDisplayToken)); + const auto activeDisplay = FTL_FAKE_GUARD(mStateLock, getDisplayDeviceLocked(mActiveDisplayId)); mPowerHintSessionEnabled = mPowerAdvisor->usePowerHintSession() && activeDisplay && activeDisplay->getPowerMode() == hal::PowerMode::ON; if (mPowerHintSessionEnabled) { - const auto& display = FTL_FAKE_GUARD(mStateLock, getDefaultDisplayDeviceLocked()).get(); - // get stable vsync period from display mode - const nsecs_t vsyncPeriod = display->getActiveMode()->getVsyncPeriod(); mPowerAdvisor->setCommitStart(frameTime); mPowerAdvisor->setExpectedPresentTime(mExpectedPresentTime); - const nsecs_t idealSfWorkDuration = - mVsyncModulator->getVsyncConfig().sfWorkDuration.count(); - // Frame delay is how long we should have minus how long we actually have - mPowerAdvisor->setFrameDelay(idealSfWorkDuration - (mExpectedPresentTime - frameTime)); - mPowerAdvisor->setTotalFrameTargetWorkDuration(idealSfWorkDuration); - mPowerAdvisor->setTargetWorkDuration(vsyncPeriod); - // Send early hint here to make sure there's not another frame pending - if (mPowerHintSessionMode.early) { - // Send a rough prediction for this frame based on last frame's timing info - mPowerAdvisor->sendPredictedWorkDuration(); - } - } + // Frame delay is how long we should have minus how long we actually have. + const Duration idealSfWorkDuration = + mScheduler->vsyncModulator().getVsyncConfig().sfWorkDuration; + const Duration frameDelay = idealSfWorkDuration - (mExpectedPresentTime - frameTime); - if (mTracingEnabledChanged) { - mLayerTracingEnabled = mLayerTracing.isEnabled(); - mTracingEnabledChanged = false; + mPowerAdvisor->setFrameDelay(frameDelay); + mPowerAdvisor->setTotalFrameTargetWorkDuration(idealSfWorkDuration); + + const auto& display = FTL_FAKE_GUARD(mStateLock, getDefaultDisplayDeviceLocked()).get(); + const Period idealVsyncPeriod = display->getActiveMode().fps.getPeriod(); + mPowerAdvisor->updateTargetWorkDuration(idealVsyncPeriod); } if (mRefreshRateOverlaySpinner) { @@ -2134,46 +2501,42 @@ bool SurfaceFlinger::commit(nsecs_t frameTime, int64_t vsyncId, nsecs_t expected // Composite if transactions were committed, or if requested by HWC. bool mustComposite = mMustComposite.exchange(false); { - mFrameTimeline->setSfWakeUp(vsyncId, frameTime, Fps::fromPeriodNsecs(stats.vsyncPeriod)); - - bool needsTraversal = false; - if (clearTransactionFlags(eTransactionFlushNeeded)) { - // Locking: - // 1. to prevent onHandleDestroyed from being called while the state lock is held, - // we must keep a copy of the transactions (specifically the composer - // states) around outside the scope of the lock - // 2. Transactions and created layers do not share a lock. To prevent applying - // transactions with layers still in the createdLayer queue, flush the transactions - // before committing the created layers. - std::vector transactions = flushTransactions(); - needsTraversal |= commitCreatedLayers(); - needsTraversal |= applyTransactions(transactions, vsyncId); + mFrameTimeline->setSfWakeUp(vsyncId.value, frameTime.ns(), + Fps::fromPeriodNsecs(vsyncPeriod.ns())); + + const bool flushTransactions = clearTransactionFlags(eTransactionFlushNeeded); + frontend::Update updates; + if (flushTransactions) { + updates = flushLifecycleUpdates(); + if (mTransactionTracing) { + mTransactionTracing->addCommittedTransactions(vsyncId.value, frameTime.ns(), + updates, mFrontEndDisplayInfos, + mFrontEndDisplayInfosChanged); + } } - - const bool shouldCommit = - (getTransactionFlags() & ~eTransactionFlushNeeded) || needsTraversal; - if (shouldCommit) { - commitTransactions(); + bool transactionsAreEmpty; + if (mLegacyFrontEndEnabled) { + mustComposite |= updateLayerSnapshotsLegacy(vsyncId, updates, flushTransactions, + transactionsAreEmpty); + } + if (mLayerLifecycleManagerEnabled) { + mustComposite |= + updateLayerSnapshots(vsyncId, updates, flushTransactions, transactionsAreEmpty); } if (transactionFlushNeeded()) { setTransactionFlags(eTransactionFlushNeeded); } - mustComposite |= shouldCommit; - mustComposite |= latchBuffers(); - // This has to be called after latchBuffers because we want to include the layers that have // been latched in the commit callback - if (!needsTraversal) { + if (transactionsAreEmpty) { // Invoke empty transaction callbacks early. mTransactionCallbackInvoker.sendCallbacks(false /* onCommitOnly */); } else { // Invoke OnCommit callbacks. mTransactionCallbackInvoker.sendCallbacks(true /* onCommitOnly */); } - - updateLayerGeometry(); } // Layers need to get updated (in the previous line) before we can use them for @@ -2181,41 +2544,72 @@ bool SurfaceFlinger::commit(nsecs_t frameTime, int64_t vsyncId, nsecs_t expected // Hold mStateLock as chooseRefreshRateForContent promotes wp to sp // and may eventually call to ~Layer() if it holds the last reference { - Mutex::Autolock _l(mStateLock); + Mutex::Autolock lock(mStateLock); mScheduler->chooseRefreshRateForContent(); setActiveModeInHwcIfNeeded(); } updateCursorAsync(); - updateInputFlinger(); + updateInputFlinger(vsyncId, frameTime); if (mLayerTracingEnabled && !mLayerTracing.flagIsSet(LayerTracing::TRACE_COMPOSITION)) { // This will block and tracing should only be enabled for debugging. - mLayerTracing.notify(mVisibleRegionsDirty, frameTime); + addToLayerTracing(mVisibleRegionsDirty, frameTime.ns(), vsyncId.value); } + mLastCommittedVsyncId = vsyncId; persistDisplayBrightness(mustComposite); return mustComposite && CC_LIKELY(mBootStage != BootStage::BOOTLOADER); } -void SurfaceFlinger::composite(nsecs_t frameTime, int64_t vsyncId) +void SurfaceFlinger::composite(TimePoint frameTime, VsyncId vsyncId) FTL_FAKE_GUARD(kMainThreadContext) { - ATRACE_FORMAT("%s %" PRId64, __func__, vsyncId); + ATRACE_FORMAT("%s %" PRId64, __func__, vsyncId.value); compositionengine::CompositionRefreshArgs refreshArgs; const auto& displays = FTL_FAKE_GUARD(mStateLock, mDisplays); refreshArgs.outputs.reserve(displays.size()); std::vector displayIds; for (const auto& [_, display] : displays) { - refreshArgs.outputs.push_back(display->getCompositionDisplay()); + bool dropFrame = false; + if (display->isVirtual()) { + Fps refreshRate = display->getAdjustedRefreshRate(); + using fps_approx_ops::operator>; + dropFrame = (refreshRate > 0_Hz) && !mScheduler->isVsyncInPhase(frameTime, refreshRate); + } + if (!dropFrame) { + refreshArgs.outputs.push_back(display->getCompositionDisplay()); + } + display->tracePowerMode(); displayIds.push_back(display->getId()); } mPowerAdvisor->setDisplays(displayIds); - mDrawingState.traverseInZOrder([&refreshArgs](Layer* layer) { - if (auto layerFE = layer->getCompositionEngineLayerFE()) - refreshArgs.layers.push_back(layerFE); - }); + + const bool updateTaskMetadata = mCompositionEngine->getFeatureFlags().test( + compositionengine::Feature::kSnapshotLayerMetadata); + if (updateTaskMetadata && (mVisibleRegionsDirty || mLayerMetadataSnapshotNeeded)) { + updateLayerMetadataSnapshot(); + 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()); for (auto layer : mLayersWithQueuedFrames) { if (auto layerFE = layer->getCompositionEngineLayerFE()) @@ -2230,8 +2624,7 @@ void SurfaceFlinger::composite(nsecs_t frameTime, int64_t vsyncId) refreshArgs.updatingOutputGeometryThisFrame = mVisibleRegionsDirty; refreshArgs.updatingGeometryThisFrame = mGeometryDirty.exchange(false) || mVisibleRegionsDirty; - refreshArgs.blursAreExpensive = mBlursAreExpensive; - refreshArgs.internalDisplayRotationFlags = DisplayDevice::getPrimaryDisplayRotationFlags(); + refreshArgs.internalDisplayRotationFlags = getActiveDisplayRotationFlags(); if (CC_UNLIKELY(mDrawingState.colorMatrixChanged)) { refreshArgs.colorTransformMatrix = mDrawingState.colorMatrix; @@ -2245,69 +2638,108 @@ void SurfaceFlinger::composite(nsecs_t frameTime, int64_t vsyncId) refreshArgs.devOptFlashDirtyRegionsDelay = std::chrono::milliseconds(mDebugFlashDelay); } - const auto expectedPresentTime = mExpectedPresentTime.load(); - const auto prevVsyncTime = mScheduler->getPreviousVsyncFrom(expectedPresentTime); - const auto hwcMinWorkDuration = mVsyncConfiguration->getCurrentConfigs().hwcMinWorkDuration; - refreshArgs.earliestPresentTime = prevVsyncTime - hwcMinWorkDuration; - refreshArgs.previousPresentFence = mPreviousPresentFences[0].fenceTime; + const Period vsyncPeriod = mScheduler->getVsyncSchedule()->period(); + + if (!getHwComposer().getComposer()->isSupported( + Hwc2::Composer::OptionalFeature::ExpectedPresentTime) && + wouldPresentEarly(frameTime, vsyncPeriod)) { + const auto prevVsyncTime = mExpectedPresentTime - vsyncPeriod; + const auto hwcMinWorkDuration = mVsyncConfiguration->getCurrentConfigs().hwcMinWorkDuration; + + refreshArgs.earliestPresentTime = prevVsyncTime - hwcMinWorkDuration; + } + refreshArgs.scheduledFrameTime = mScheduler->getScheduledFrameTime(); - refreshArgs.expectedPresentTime = expectedPresentTime; + refreshArgs.expectedPresentTime = mExpectedPresentTime.ns(); + refreshArgs.hasTrustedPresentationListener = mNumTrustedPresentationListeners > 0; // Store the present time just before calling to the composition engine so we could notify // the scheduler. const auto presentTime = systemTime(); + std::vector> layers = + moveSnapshotsToCompositionArgs(refreshArgs, /*cursorOnly=*/false, vsyncId.value); 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; + owningLayer->onLayerDisplayed(std::move(releaseFence), layerStack); + } + if (compositionResult.lastClientCompositionFence) { + layer->setWasClientComposed(compositionResult.lastClientCompositionFence); + } + } - mTimeStats->recordFrameDuration(frameTime, systemTime()); + mTimeStats->recordFrameDuration(frameTime.ns(), systemTime()); - // Send a power hint hint after presentation is finished + // Send a power hint after presentation is finished. if (mPowerHintSessionEnabled) { - mPowerAdvisor->setSfPresentTiming(mPreviousPresentFences[0].fenceTime->getSignalTime(), - systemTime()); - if (mPowerHintSessionMode.late) { - mPowerAdvisor->sendActualWorkDuration(); - } + // Now that the current frame has been presented above, PowerAdvisor needs the present time + // of the previous frame (whose fence is signaled by now) to determine how long the HWC had + // waited on that fence to retire before presenting. + const auto& previousPresentFence = mPreviousPresentFences[0].fenceTime; + + mPowerAdvisor->setSfPresentTiming(TimePoint::fromNs(previousPresentFence->getSignalTime()), + TimePoint::now()); + mPowerAdvisor->reportActualWorkDuration(); } if (mScheduler->onPostComposition(presentTime)) { scheduleComposite(FrameHint::kNone); } - postFrame(); - postComposition(); + postComposition(presentTime); - const bool prevFrameHadClientComposition = mHadClientComposition; + const bool hadGpuComposited = mCompositionCoverage.test(CompositionCoverage::Gpu); + mCompositionCoverage.clear(); - mHadClientComposition = mHadDeviceComposition = mReusedClientComposition = false; TimeStats::ClientCompositionRecord clientCompositionRecord; for (const auto& [_, display] : displays) { const auto& state = display->getCompositionDisplay()->getState(); - mHadClientComposition |= state.usesClientComposition && !state.reusedClientComposition; - mHadDeviceComposition |= state.usesDeviceComposition; - mReusedClientComposition |= state.reusedClientComposition; + + if (state.usesDeviceComposition) { + mCompositionCoverage |= CompositionCoverage::Hwc; + } + + if (state.reusedClientComposition) { + mCompositionCoverage |= CompositionCoverage::GpuReuse; + } else if (state.usesClientComposition) { + mCompositionCoverage |= CompositionCoverage::Gpu; + } + clientCompositionRecord.predicted |= (state.strategyPrediction != CompositionStrategyPredictionState::DISABLED); clientCompositionRecord.predictionSucceeded |= (state.strategyPrediction == CompositionStrategyPredictionState::SUCCESS); } - clientCompositionRecord.hadClientComposition = mHadClientComposition; - clientCompositionRecord.reused = mReusedClientComposition; - clientCompositionRecord.changed = prevFrameHadClientComposition != mHadClientComposition; + const bool hasGpuComposited = mCompositionCoverage.test(CompositionCoverage::Gpu); + + clientCompositionRecord.hadClientComposition = hasGpuComposited; + clientCompositionRecord.reused = mCompositionCoverage.test(CompositionCoverage::GpuReuse); + clientCompositionRecord.changed = hadGpuComposited != hasGpuComposited; + mTimeStats->pushCompositionStrategyState(clientCompositionRecord); - // TODO: b/160583065 Enable skip validation when SF caches all client composition layers - const bool usedGpuComposition = mHadClientComposition || mReusedClientComposition; - modulateVsync(&VsyncModulator::onDisplayRefresh, usedGpuComposition); + using namespace ftl::flag_operators; + + // TODO(b/160583065): Enable skip validation when SF caches all client composition layers. + const bool hasGpuUseOrReuse = + mCompositionCoverage.any(CompositionCoverage::Gpu | CompositionCoverage::GpuReuse); + mScheduler->modulateVsync({}, &VsyncModulator::onDisplayRefresh, hasGpuUseOrReuse); mLayersWithQueuedFrames.clear(); if (mLayerTracingEnabled && mLayerTracing.flagIsSet(LayerTracing::TRACE_COMPOSITION)) { // This will block and should only be used for debugging. - mLayerTracing.notify(mVisibleRegionsDirty, frameTime); + addToLayerTracing(mVisibleRegionsDirty, frameTime.ns(), vsyncId.value); } - mVisibleRegionsWereDirtyThisFrame = mVisibleRegionsDirty; // Cache value for use in post-comp + if (mVisibleRegionsDirty) mHdrLayerInfoChanged = true; mVisibleRegionsDirty = false; if (mCompositionEngine->needsAnotherUpdate()) { @@ -2315,7 +2747,7 @@ void SurfaceFlinger::composite(nsecs_t frameTime, int64_t vsyncId) } if (mPowerHintSessionEnabled) { - mPowerAdvisor->setCompositeEnd(systemTime()); + mPowerAdvisor->setCompositeEnd(TimePoint::now()); } } @@ -2329,87 +2761,38 @@ void SurfaceFlinger::updateLayerGeometry() { for (auto& layer : mLayersPendingRefresh) { Region visibleReg; visibleReg.set(layer->getScreenBounds()); - invalidateLayerStack(layer, visibleReg); + invalidateLayerStack(layer->getOutputFilter(), visibleReg); } mLayersPendingRefresh.clear(); } -void SurfaceFlinger::updateCompositorTiming(const DisplayStatInfo& stats, nsecs_t compositeTime, - std::shared_ptr& presentFenceTime) { - // Update queue of past composite+present times and determine the - // most recently known composite to present latency. - getBE().mCompositePresentTimes.push({compositeTime, presentFenceTime}); - nsecs_t compositeToPresentLatency = -1; - while (!getBE().mCompositePresentTimes.empty()) { - SurfaceFlingerBE::CompositePresentTime& cpt = getBE().mCompositePresentTimes.front(); - // Cached values should have been updated before calling this method, - // which helps avoid duplicate syscalls. - nsecs_t displayTime = cpt.display->getCachedSignalTime(); - if (displayTime == Fence::SIGNAL_TIME_PENDING) { - break; +bool SurfaceFlinger::isHdrLayer(const frontend::LayerSnapshot& snapshot) const { + // Even though the camera layer may be using an HDR transfer function or otherwise be "HDR" + // the device may need to avoid boosting the brightness as a result of these layers to + // reduce power consumption during camera recording + if (mIgnoreHdrCameraLayers) { + if (snapshot.externalTexture && + (snapshot.externalTexture->getUsage() & GRALLOC_USAGE_HW_CAMERA_WRITE) != 0) { + return false; } - compositeToPresentLatency = displayTime - cpt.composite; - getBE().mCompositePresentTimes.pop(); - } - - // Don't let mCompositePresentTimes grow unbounded, just in case. - while (getBE().mCompositePresentTimes.size() > 16) { - getBE().mCompositePresentTimes.pop(); } - - setCompositorTimingSnapped(stats, compositeToPresentLatency); -} - -void SurfaceFlinger::setCompositorTimingSnapped(const DisplayStatInfo& stats, - nsecs_t compositeToPresentLatency) { - // Avoid division by 0 by defaulting to 60Hz - const auto vsyncPeriod = stats.vsyncPeriod ?: (60_Hz).getPeriodNsecs(); - - // Integer division and modulo round toward 0 not -inf, so we need to - // treat negative and positive offsets differently. - nsecs_t idealLatency = (mVsyncConfiguration->getCurrentConfigs().late.sfOffset > 0) - ? (vsyncPeriod - - (mVsyncConfiguration->getCurrentConfigs().late.sfOffset % vsyncPeriod)) - : ((-mVsyncConfiguration->getCurrentConfigs().late.sfOffset) % vsyncPeriod); - - // Just in case mVsyncConfiguration->getCurrentConfigs().late.sf == -vsyncInterval. - if (idealLatency <= 0) { - idealLatency = vsyncPeriod; + if (isHdrDataspace(snapshot.dataspace)) { + return true; } - - // Snap the latency to a value that removes scheduling jitter from the - // composition and present times, which often have >1ms of jitter. - // Reducing jitter is important if an app attempts to extrapolate - // something (such as user input) to an accurate diasplay time. - // Snapping also allows an app to precisely calculate - // mVsyncConfiguration->getCurrentConfigs().late.sf with (presentLatency % interval). - const nsecs_t bias = vsyncPeriod / 2; - const int64_t extraVsyncs = ((compositeToPresentLatency - idealLatency + bias) / vsyncPeriod); - const nsecs_t snappedCompositeToPresentLatency = - (extraVsyncs > 0) ? idealLatency + (extraVsyncs * vsyncPeriod) : idealLatency; - - std::lock_guard lock(getBE().mCompositorTimingLock); - getBE().mCompositorTiming.deadline = stats.vsyncTime - idealLatency; - getBE().mCompositorTiming.interval = vsyncPeriod; - getBE().mCompositorTiming.presentLatency = snappedCompositeToPresentLatency; -} - -bool SurfaceFlinger::isHdrLayer(Layer* layer) const { - // Treat all layers as non-HDR if: - // 1. They do not have a valid HDR dataspace. Currently we treat those as PQ or HLG. and - // 2. The layer is allowed to be dimmed. WindowManager may disable dimming in order to - // keep animations invoking SDR screenshots of HDR layers seamless. Treat such tagged - // layers as HDR so that DisplayManagerService does not try to change the screen brightness - if (!isHdrDataspace(layer->getDataSpace()) && layer->isDimmingEnabled()) { - return false; + // If the layer is not allowed to be dimmed, treat it as HDR. WindowManager may disable + // dimming in order to keep animations invoking SDR screenshots of HDR layers seamless. + // Treat such tagged layers as HDR so that DisplayManagerService does not try to change + // the screen brightness + if (!snapshot.dimmingEnabled) { + return true; } - if (mIgnoreHdrCameraLayers) { - auto buffer = layer->getBuffer(); - if (buffer && (buffer->getUsage() & GRALLOC_USAGE_HW_CAMERA_WRITE) != 0) { - return false; - } + // RANGE_EXTENDED layers may identify themselves as being "HDR" via a desired sdr/hdr ratio + if ((snapshot.dataspace & (int32_t)Dataspace::RANGE_MASK) == + (int32_t)Dataspace::RANGE_EXTENDED && + snapshot.desiredHdrSdrRatio > 1.01f) { + return true; } - return true; + return false; } ui::Rotation SurfaceFlinger::getPhysicalDisplayOrientation(DisplayId displayId, @@ -2418,7 +2801,8 @@ ui::Rotation SurfaceFlinger::getPhysicalDisplayOrientation(DisplayId displayId, if (!id) { return ui::ROTATION_0; } - if (getHwComposer().getComposer()->isSupported( + if (!mIgnoreHwcPhysicalDisplayOrientation && + getHwComposer().getComposer()->isSupported( Hwc2::Composer::OptionalFeature::PhysicalDisplayOrientation)) { switch (getHwComposer().getPhysicalDisplayOrientation(*id)) { case Hwc2::AidlTransform::ROT_90: @@ -2448,56 +2832,87 @@ ui::Rotation SurfaceFlinger::getPhysicalDisplayOrientation(DisplayId displayId, return ui::ROTATION_0; } -void SurfaceFlinger::postComposition() { +void SurfaceFlinger::postComposition(nsecs_t callTime) { ATRACE_CALL(); - ALOGV("postComposition"); + ALOGV(__func__); - const auto* display = FTL_FAKE_GUARD(mStateLock, getDefaultDisplayDeviceLocked()).get(); + const auto* defaultDisplay = FTL_FAKE_GUARD(mStateLock, getDefaultDisplayDeviceLocked()).get(); std::shared_ptr glCompositionDoneFenceTime; - if (display && display->getCompositionDisplay()->getState().usesClientComposition) { + if (defaultDisplay && + defaultDisplay->getCompositionDisplay()->getState().usesClientComposition) { glCompositionDoneFenceTime = - std::make_shared(display->getCompositionDisplay() + std::make_shared(defaultDisplay->getCompositionDisplay() ->getRenderSurface() ->getClientTargetAcquireFence()); } else { glCompositionDoneFenceTime = FenceTime::NO_FENCE; } - for (size_t i = mPreviousPresentFences.size()-1; i >= 1; i--) { - mPreviousPresentFences[i] = mPreviousPresentFences[i-1]; - } + mPreviousPresentFences[1] = mPreviousPresentFences[0]; + + auto presentFence = defaultDisplay + ? getHwComposer().getPresentFence(defaultDisplay->getPhysicalId()) + : Fence::NO_FENCE; - mPreviousPresentFences[0].fence = - display ? getHwComposer().getPresentFence(display->getPhysicalId()) : Fence::NO_FENCE; - mPreviousPresentFences[0].fenceTime = - std::make_shared(mPreviousPresentFences[0].fence); + auto presentFenceTime = std::make_shared(presentFence); + mPreviousPresentFences[0] = {presentFence, presentFenceTime}; - nsecs_t now = systemTime(); + const TimePoint presentTime = TimePoint::now(); // Set presentation information before calling Layer::releasePendingBuffer, such that jank // information from previous' frame classification is already available when sending jank info // to clients, so they get jank classification as early as possible. - mFrameTimeline->setSfPresent(/* sfPresentTime */ now, mPreviousPresentFences[0].fenceTime, - glCompositionDoneFenceTime); - - const DisplayStatInfo stats = mScheduler->getDisplayStatInfo(now); + mFrameTimeline->setSfPresent(presentTime.ns(), presentFenceTime, glCompositionDoneFenceTime); // We use the CompositionEngine::getLastFrameRefreshTimestamp() which might // be sampled a little later than when we started doing work for this frame, - // but that should be okay since updateCompositorTiming has snapping logic. - updateCompositorTiming(stats, mCompositionEngine->getLastFrameRefreshTimestamp(), - mPreviousPresentFences[0].fenceTime); - CompositorTiming compositorTiming; + // 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) + ? mPresentLatencyTracker.trackPendingFrame(compositeTime, presentFenceTime) + : Duration::zero(); + + const auto schedule = mScheduler->getVsyncSchedule(); + const TimePoint vsyncDeadline = schedule->vsyncDeadlineAfter(presentTime); + const Period vsyncPeriod = schedule->period(); + const nsecs_t vsyncPhase = mVsyncConfiguration->getCurrentConfigs().late.sfOffset; + + const CompositorTiming compositorTiming(vsyncDeadline.ns(), vsyncPeriod.ns(), vsyncPhase, + presentLatency.ns()); + + display::DisplayMap layerStackToDisplay; { - std::lock_guard lock(getBE().mCompositorTimingLock); - compositorTiming = getBE().mCompositorTiming; + if (!mLayersWithBuffersRemoved.empty() || mNumTrustedPresentationListeners > 0) { + Mutex::Autolock lock(mStateLock); + for (const auto& [token, display] : mDisplays) { + layerStackToDisplay.emplace_or_replace(display->getLayerStack(), display.get()); + } + } + } + + for (auto layer : mLayersWithBuffersRemoved) { + std::vector previouslyPresentedLayerStacks = + std::move(layer->mPreviouslyPresentedLayerStacks); + layer->mPreviouslyPresentedLayerStacks.clear(); + for (auto layerStack : previouslyPresentedLayerStacks) { + 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); + } + } + layer->releasePendingBuffer(presentTime.ns()); } + mLayersWithBuffersRemoved.clear(); for (const auto& layer: mLayersWithQueuedFrames) { - layer->onPostComposition(display, glCompositionDoneFenceTime, - mPreviousPresentFences[0].fenceTime, compositorTiming); - layer->releasePendingBuffer(/*dequeueReadyTime*/ now); + layer->onPostComposition(defaultDisplay, glCompositionDoneFenceTime, presentFenceTime, + compositorTiming); + layer->releasePendingBuffer(presentTime.ns()); } std::vector, sp>> @@ -2524,17 +2939,23 @@ void SurfaceFlinger::postComposition() { mAddingHDRLayerInfoListener = false; } - if (haveNewListeners || mSomeDataspaceChanged || mVisibleRegionsWereDirtyThisFrame) { + if (haveNewListeners || mHdrLayerInfoChanged) { for (auto& [compositionDisplay, listener] : hdrInfoListeners) { HdrLayerInfoReporter::HdrLayerInfo info; int32_t maxArea = 0; mDrawingState.traverse([&, compositionDisplay = compositionDisplay](Layer* layer) { const auto layerFe = layer->getCompositionEngineLayerFE(); - if (layer->isVisible() && compositionDisplay->includesLayer(layerFe)) { - if (isHdrLayer(layer)) { + const frontend::LayerSnapshot& snapshot = *layer->getLayerSnapshot(); + if (snapshot.isVisible && + compositionDisplay->includesLayer(snapshot.outputFilter)) { + if (isHdrLayer(snapshot)) { const auto* outputLayer = compositionDisplay->getOutputLayerForLayer(layerFe); if (outputLayer) { + const float desiredHdrSdrRatio = snapshot.desiredHdrSdrRatio <= 1.f + ? std::numeric_limits::infinity() + : snapshot.desiredHdrSdrRatio; + info.mergeDesiredRatio(desiredHdrSdrRatio); info.numberOfHdrLayers++; const auto displayFrame = outputLayer->getState().displayFrame; const int32_t area = displayFrame.width() * displayFrame.height(); @@ -2551,69 +2972,48 @@ void SurfaceFlinger::postComposition() { } } - mSomeDataspaceChanged = false; - mVisibleRegionsWereDirtyThisFrame = false; + mHdrLayerInfoChanged = false; - mTransactionCallbackInvoker.addPresentFence(mPreviousPresentFences[0].fence); + mTransactionCallbackInvoker.addPresentFence(std::move(presentFence)); mTransactionCallbackInvoker.sendCallbacks(false /* onCommitOnly */); mTransactionCallbackInvoker.clearCompletedTransactions(); - if (display && display->isInternal() && display->getPowerMode() == hal::PowerMode::ON && - mPreviousPresentFences[0].fenceTime->isValid()) { - mScheduler->addPresentFence(mPreviousPresentFences[0].fenceTime); - } - - const bool isDisplayConnected = - display && getHwComposer().isConnected(display->getPhysicalId()); + mTimeStats->incrementTotalFrames(); + mTimeStats->setPresentFenceGlobal(presentFenceTime); - if (!hasSyncFramework) { - if (isDisplayConnected && display->isPoweredOn()) { - mScheduler->enableHardwareVsync(); + { + ftl::FakeGuard guard(mStateLock); + for (const auto& [id, physicalDisplay] : mPhysicalDisplays) { + if (auto displayDevice = getDisplayDeviceLocked(id); + displayDevice && displayDevice->isPoweredOn() && physicalDisplay.isInternal()) { + auto presentFenceTimeI = defaultDisplay && defaultDisplay->getPhysicalId() == id + ? std::move(presentFenceTime) + : std::make_shared(getHwComposer().getPresentFence(id)); + if (presentFenceTimeI->isValid()) { + mScheduler->addPresentFence(id, std::move(presentFenceTimeI)); + } + } } } - if (mAnimCompositionPending) { - mAnimCompositionPending = false; + const bool isDisplayConnected = + defaultDisplay && getHwComposer().isConnected(defaultDisplay->getPhysicalId()); - if (mPreviousPresentFences[0].fenceTime->isValid()) { - mAnimFrameTracker.setActualPresentFence(mPreviousPresentFences[0].fenceTime); - } else if (isDisplayConnected) { - // The HWC doesn't support present fences, so use the refresh - // timestamp instead. - const nsecs_t presentTime = display->getRefreshTimestamp(); - mAnimFrameTracker.setActualPresentTime(presentTime); + if (!hasSyncFramework) { + if (isDisplayConnected && defaultDisplay->isPoweredOn()) { + mScheduler->enableHardwareVsync(defaultDisplay->getPhysicalId()); } - mAnimFrameTracker.advanceFrame(); } - mTimeStats->incrementTotalFrames(); - - mTimeStats->setPresentFenceGlobal(mPreviousPresentFences[0].fenceTime); - const size_t sfConnections = mScheduler->getEventThreadConnectionCount(mSfConnectionHandle); const size_t appConnections = mScheduler->getEventThreadConnectionCount(mAppConnectionHandle); mTimeStats->recordDisplayEventConnectionCount(sfConnections + appConnections); - if (isDisplayConnected && !display->isPoweredOn()) { + if (isDisplayConnected && !defaultDisplay->isPoweredOn()) { getRenderEngine().cleanupPostRender(); return; } - nsecs_t currentTime = systemTime(); - if (mHasPoweredOff) { - mHasPoweredOff = false; - } else { - nsecs_t elapsedTime = currentTime - getBE().mLastSwapTime; - size_t numPeriods = static_cast(elapsedTime / stats.vsyncPeriod); - if (numPeriods < SurfaceFlingerBE::NUM_BUCKETS - 1) { - getBE().mFrameBuckets[numPeriods] += elapsedTime; - } else { - getBE().mFrameBuckets[SurfaceFlingerBE::NUM_BUCKETS - 1] += elapsedTime; - } - getBE().mTotalTime += elapsedTime; - } - getBE().mLastSwapTime = currentTime; - // Cleanup any outstanding resources due to rendering a prior frame. getRenderEngine().cleanupPostRender(); @@ -2634,12 +3034,33 @@ void SurfaceFlinger::postComposition() { } } + if (mNumTrustedPresentationListeners > 0) { + // We avoid any reverse traversal upwards so this shouldn't be too expensive + traverseLegacyLayers([&](Layer* layer) { + if (!layer->hasTrustedPresentationListener()) { + return; + } + const frontend::LayerSnapshot* snapshot = (mLayerLifecycleManagerEnabled) + ? mLayerSnapshotBuilder.getSnapshot(layer->sequence) + : layer->getLayerSnapshot(); + std::optional displayOpt = std::nullopt; + if (snapshot) { + displayOpt = layerStackToDisplay.get(snapshot->outputFilter.layerStack); + } + const DisplayDevice* display = displayOpt.value_or(nullptr); + layer->updateTrustedPresentationState(display, snapshot, + nanoseconds_to_milliseconds(callTime), false); + }); + } + // Even though ATRACE_INT64 already checks if tracing is enabled, it doesn't prevent the // side-effect of getTotalSize(), so we check that again here if (ATRACE_ENABLED()) { // getTotalSize returns the total number of buffers that were allocated by SurfaceFlinger ATRACE_INT64("Total Buffer Size", GraphicBufferAllocator::get().getTotalSize()); } + + logFrameStats(presentTime); } FloatRect SurfaceFlinger::getMaxDisplayBounds() { @@ -2672,16 +3093,6 @@ void SurfaceFlinger::computeLayerBounds() { } } -void SurfaceFlinger::postFrame() { - const auto display = FTL_FAKE_GUARD(mStateLock, getDefaultDisplayDeviceLocked()); - if (display && getHwComposer().isConnected(display->getPhysicalId())) { - uint32_t flipCount = display->getPageFlipCount(); - if (flipCount % LOG_FRAME_STATS_PERIOD == 0) { - logFrameStats(); - } - } -} - void SurfaceFlinger::commitTransactions() { ATRACE_CALL(); @@ -2698,7 +3109,7 @@ void SurfaceFlinger::commitTransactions() { // 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. - modulateVsync(&VsyncModulator::onTransactionCommit); + mScheduler->modulateVsync({}, &VsyncModulator::onTransactionCommit); commitTransactionsLocked(clearTransactionFlags(eTransactionMask)); mDebugInTransaction = 0; @@ -2714,10 +3125,9 @@ std::pair SurfaceFlinger::loadDisplayModes( do { hwcModes = getHwComposer().getModes(displayId); activeModeHwcId = getHwComposer().getActiveMode(displayId); - LOG_ALWAYS_FATAL_IF(!activeModeHwcId, "HWC returned no active mode"); const auto isActiveMode = [activeModeHwcId](const HWComposer::HWCDisplayMode& mode) { - return mode.hwcId == *activeModeHwcId; + return mode.hwcId == activeModeHwcId; }; if (std::any_of(hwcModes.begin(), hwcModes.end(), isActiveMode)) { @@ -2725,16 +3135,21 @@ std::pair SurfaceFlinger::loadDisplayModes( } } while (++attempt < kMaxAttempts); - LOG_ALWAYS_FATAL_IF(attempt == kMaxAttempts, - "After %d attempts HWC still returns an active mode which is not" - " supported. Active mode ID = %" PRIu64 ". Supported modes = %s", - kMaxAttempts, *activeModeHwcId, base::Join(hwcModes, ", ").c_str()); - - DisplayModes oldModes; - if (const auto token = getPhysicalDisplayTokenLocked(displayId)) { - oldModes = getDisplayDeviceLocked(token)->getSupportedModes(); + if (attempt == kMaxAttempts) { + const std::string activeMode = + activeModeHwcId ? std::to_string(*activeModeHwcId) : "unknown"s; + ALOGE("HWC failed to report an active mode that is supported: activeModeHwcId=%s, " + "hwcModes={%s}", + activeMode.c_str(), base::Join(hwcModes, ", ").c_str()); + return {}; } + const DisplayModes oldModes = mPhysicalDisplays.get(displayId) + .transform([](const PhysicalDisplay& display) { + return display.snapshot().displayModes(); + }) + .value_or(DisplayModes{}); + ui::DisplayModeId nextModeId = 1 + std::accumulate(oldModes.begin(), oldModes.end(), static_cast(-1), [](ui::DisplayModeId max, const auto& pair) { @@ -2772,70 +3187,96 @@ std::pair SurfaceFlinger::loadDisplayModes( return {modes, activeMode}; } -void SurfaceFlinger::processDisplayHotplugEventsLocked() { - for (const auto& event : mPendingHotplugEvents) { - std::optional info = - getHwComposer().onHotplug(event.hwcDisplayId, event.connection); +bool SurfaceFlinger::configureLocked() { + std::vector events; + { + std::lock_guard lock(mHotplugMutex); + events = std::move(mPendingHotplugEvents); + } + + for (const auto [hwcDisplayId, connection] : events) { + if (auto info = getHwComposer().onHotplug(hwcDisplayId, connection)) { + const auto displayId = info->id; + const bool connected = connection == hal::Connection::CONNECTED; - if (!info) { - continue; + if (const char* const log = + processHotplug(displayId, hwcDisplayId, connected, std::move(*info))) { + ALOGI("%s display %s (HAL ID %" PRIu64 ")", log, to_string(displayId).c_str(), + hwcDisplayId); + } } + } - const auto displayId = info->id; - const auto token = mPhysicalDisplayTokens.get(displayId); + return !events.empty(); +} - if (event.connection == hal::Connection::CONNECTED) { - auto [supportedModes, activeMode] = loadDisplayModes(displayId); +const char* SurfaceFlinger::processHotplug(PhysicalDisplayId displayId, + hal::HWDisplayId hwcDisplayId, bool connected, + DisplayIdentificationInfo&& info) { + const auto displayOpt = mPhysicalDisplays.get(displayId); + if (!connected) { + LOG_ALWAYS_FATAL_IF(!displayOpt); + const auto& display = displayOpt->get(); - if (!token) { - ALOGV("Creating display %s", to_string(displayId).c_str()); + if (const ssize_t index = mCurrentState.displays.indexOfKey(display.token()); index >= 0) { + mCurrentState.displays.removeItemsAt(index); + } - DisplayDeviceState state; - state.physical = {.id = displayId, - .type = getHwComposer().getDisplayConnectionType(displayId), - .hwcDisplayId = event.hwcDisplayId, - .deviceProductInfo = std::move(info->deviceProductInfo), - .supportedModes = std::move(supportedModes), - .activeMode = std::move(activeMode)}; - state.isSecure = true; // All physical displays are currently considered secure. - state.displayName = std::move(info->name); + mPhysicalDisplays.erase(displayId); + return "Disconnecting"; + } - sp token = new BBinder(); - mCurrentState.displays.add(token, state); - mPhysicalDisplayTokens.try_emplace(displayId, std::move(token)); - mInterceptor->saveDisplayCreation(state); - } else { - ALOGV("Recreating display %s", to_string(displayId).c_str()); - - auto& state = mCurrentState.displays.editValueFor(token->get()); - state.sequenceId = DisplayDeviceState{}.sequenceId; // Generate new sequenceId. - state.physical->supportedModes = std::move(supportedModes); - state.physical->activeMode = std::move(activeMode); - if (getHwComposer().updatesDeviceProductInfoOnHotplugReconnect()) { - state.physical->deviceProductInfo = std::move(info->deviceProductInfo); - } - } - } else { - ALOGV("Removing display %s", to_string(displayId).c_str()); + auto [displayModes, activeMode] = loadDisplayModes(displayId); + if (!activeMode) { + // TODO(b/241286153): Report hotplug failure to the framework. + ALOGE("Failed to hotplug display %s", to_string(displayId).c_str()); + getHwComposer().disconnectDisplay(displayId); + return nullptr; + } - if (const ssize_t index = mCurrentState.displays.indexOfKey(token->get()); index >= 0) { - const DisplayDeviceState& state = mCurrentState.displays.valueAt(index); - mInterceptor->saveDisplayDeletion(state.sequenceId); - mCurrentState.displays.removeItemsAt(index); - } + ui::ColorModes colorModes = getHwComposer().getColorModes(displayId); - mPhysicalDisplayTokens.erase(displayId); + if (displayOpt) { + const auto& display = displayOpt->get(); + const auto& snapshot = display.snapshot(); + + std::optional deviceProductInfo; + if (getHwComposer().updatesDeviceProductInfoOnHotplugReconnect()) { + deviceProductInfo = std::move(info.deviceProductInfo); + } else { + deviceProductInfo = snapshot.deviceProductInfo(); } - processDisplayChangesLocked(); + const auto it = + mPhysicalDisplays.try_replace(displayId, display.token(), displayId, + snapshot.connectionType(), std::move(displayModes), + std::move(colorModes), std::move(deviceProductInfo)); + + auto& state = mCurrentState.displays.editValueFor(it->second.token()); + state.sequenceId = DisplayDeviceState{}.sequenceId; // Generate new sequenceId. + state.physical->activeMode = std::move(activeMode); + return "Reconnecting"; } - mPendingHotplugEvents.clear(); + const sp token = sp::make(); + + mPhysicalDisplays.try_emplace(displayId, token, displayId, + getHwComposer().getDisplayConnectionType(displayId), + std::move(displayModes), std::move(colorModes), + std::move(info.deviceProductInfo)); + + DisplayDeviceState state; + state.physical = {.id = displayId, + .hwcDisplayId = hwcDisplayId, + .activeMode = std::move(activeMode)}; + state.isSecure = true; // All physical displays are currently considered secure. + state.displayName = std::move(info.name); + + mCurrentState.displays.add(token, state); + return "Connecting"; } void SurfaceFlinger::dispatchDisplayHotplugEvent(PhysicalDisplayId displayId, bool connected) { - ALOGI("Dispatching display hotplug event displayId=%s, connected=%d", - to_string(displayId).c_str(), connected); mScheduler->onHotplugReceived(mAppConnectionHandle, displayId, connected); mScheduler->onHotplugReceived(mSfConnectionHandle, displayId, connected); } @@ -2846,7 +3287,8 @@ sp SurfaceFlinger::setupNewDisplayDeviceInternal( const DisplayDeviceState& state, const sp& displaySurface, const sp& producer) { - DisplayDeviceCreationArgs creationArgs(this, getHwComposer(), displayToken, compositionDisplay); + DisplayDeviceCreationArgs creationArgs(sp::fromExisting(this), getHwComposer(), + displayToken, compositionDisplay); creationArgs.sequenceId = state.sequenceId; creationArgs.isSecure = state.isSecure; creationArgs.displaySurface = displaySurface; @@ -2854,37 +3296,45 @@ sp SurfaceFlinger::setupNewDisplayDeviceInternal( creationArgs.supportedPerFrameMetadata = 0; if (const auto& physical = state.physical) { - creationArgs.connectionType = physical->type; - creationArgs.supportedModes = physical->supportedModes; creationArgs.activeModeId = physical->activeMode->getId(); const auto [kernelIdleTimerController, idleTimerTimeoutMs] = getKernelIdleTimerProperties(compositionDisplay->getId()); - scheduler::RefreshRateConfigs::Config config = - {.enableFrameRateOverride = android::sysprop::enable_frame_rate_override(false), + using Config = scheduler::RefreshRateSelector::Config; + const auto enableFrameRateOverride = sysprop::enable_frame_rate_override(true) + ? Config::FrameRateOverride::Enabled + : Config::FrameRateOverride::Disabled; + Config config = + {.enableFrameRateOverride = enableFrameRateOverride, .frameRateMultipleThreshold = base::GetIntProperty("debug.sf.frame_rate_multiple_threshold", 0), .idleTimerTimeout = idleTimerTimeoutMs, .kernelIdleTimerController = kernelIdleTimerController}; - creationArgs.refreshRateConfigs = - std::make_shared(creationArgs.supportedModes, - creationArgs.activeModeId, config); - } - if (const auto id = PhysicalDisplayId::tryCast(compositionDisplay->getId())) { - creationArgs.isPrimary = id == 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); - if (useColorManagement) { - std::vector modes = getHwComposer().getColorModes(*id); - for (ColorMode colorMode : modes) { - if (isWideColorMode(colorMode)) { - creationArgs.hasWideColorGamut = true; - } + creationArgs.isPrimary = physical->id == getPrimaryDisplayIdLocked(); - std::vector renderIntents = - getHwComposer().getRenderIntents(*id, colorMode); - creationArgs.hwcColorModes.emplace(colorMode, renderIntents); - } + if (useColorManagement) { + mPhysicalDisplays.get(physical->id) + .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)); + } + })); } } @@ -2912,27 +3362,35 @@ sp SurfaceFlinger::setupNewDisplayDeviceInternal( creationArgs.initialPowerMode = state.isVirtual() ? std::make_optional(hal::PowerMode::ON) : std::nullopt; + creationArgs.requestedRefreshRate = state.requestedRefreshRate; + sp display = getFactory().createDisplayDevice(creationArgs); nativeWindowSurface->preallocateBuffers(); - ColorMode defaultColorMode = ColorMode::NATIVE; + ui::ColorMode defaultColorMode = ui::ColorMode::NATIVE; Dataspace defaultDataSpace = Dataspace::UNKNOWN; if (display->hasWideColorGamut()) { - defaultColorMode = ColorMode::SRGB; + defaultColorMode = ui::ColorMode::SRGB; defaultDataSpace = Dataspace::V0_SRGB; } display->getCompositionDisplay()->setColorProfile( compositionengine::Output::ColorProfile{defaultColorMode, defaultDataSpace, RenderIntent::COLORIMETRIC, Dataspace::UNKNOWN}); - if (!state.isVirtual()) { - FTL_FAKE_GUARD(kMainThreadContext, - display->setActiveMode(state.physical->activeMode->getId())); - display->setDeviceProductInfo(state.physical->deviceProductInfo); + + if (const auto& physical = state.physical) { + mPhysicalDisplays.get(physical->id) + .transform(&PhysicalDisplay::snapshotRef) + .transform(ftl::unit_fn([&](const display::DisplaySnapshot& snapshot) { + FTL_FAKE_GUARD(kMainThreadContext, + display->setActiveMode(physical->activeMode->getId(), + physical->activeMode->getFps(), + physical->activeMode->getFps())); + })); } - display->setLayerStack(state.layerStack); + display->setLayerFilter(makeLayerFilterForDisplay(display->getId(), state.layerStack)); display->setProjection(state.orientation, state.layerStackSpaceRect, state.orientedDisplaySpaceRect); display->setDisplayName(state.displayName); @@ -3008,11 +3466,22 @@ void SurfaceFlinger::processDisplayAdded(const wp& displayToken, LOG_FATAL_IF(!displaySurface); auto display = setupNewDisplayDeviceInternal(displayToken, std::move(compositionDisplay), state, displaySurface, producer); - if (display->isPrimary()) { - initScheduler(display); + + if (mScheduler && !display->isVirtual()) { + const auto displayId = display->getPhysicalId(); + { + // TODO(b/241285876): Annotate `processDisplayAdded` instead. + ftl::FakeGuard guard(kMainThreadContext); + + // For hotplug reconnect, renew the registration since display modes have been reloaded. + mScheduler->registerDisplay(displayId, display->holdRefreshRateSelector()); + } + + dispatchDisplayHotplugEvent(displayId, true); } - if (!state.isVirtual()) { - dispatchDisplayHotplugEvent(display->getPhysicalId(), true); + + if (display->isVirtual()) { + display->adjustRefreshRate(mScheduler->getPacesetterRefreshRate()); } mDisplays.try_emplace(displayToken, std::move(display)); @@ -3027,6 +3496,7 @@ void SurfaceFlinger::processDisplayRemoved(const wp& displayToken) { releaseVirtualDisplay(display->getVirtualId()); } else { dispatchDisplayHotplugEvent(display->getPhysicalId(), false); + mScheduler->unregisterDisplay(display->getPhysicalId()); } } @@ -3080,7 +3550,7 @@ void SurfaceFlinger::processDisplayChanged(const wp& displayToken, // TODO(b/175678251) Call a listener instead. if (currentState.physical->hwcDisplayId == getHwComposer().getPrimaryHwcDisplayId()) { - updateInternalDisplayVsyncLocked(display); + resetPhaseConfiguration(display->getActiveMode().fps); } } return; @@ -3088,7 +3558,8 @@ void SurfaceFlinger::processDisplayChanged(const wp& displayToken, if (const auto display = getDisplayDeviceLocked(displayToken)) { if (currentState.layerStack != drawingState.layerStack) { - display->setLayerStack(currentState.layerStack); + display->setLayerFilter( + makeLayerFilterForDisplay(display->getId(), currentState.layerStack)); } if (currentState.flags != drawingState.flags) { display->setFlags(currentState.flags); @@ -3098,23 +3569,28 @@ void SurfaceFlinger::processDisplayChanged(const wp& displayToken, (currentState.orientedDisplaySpaceRect != drawingState.orientedDisplaySpaceRect)) { display->setProjection(currentState.orientation, currentState.layerStackSpaceRect, currentState.orientedDisplaySpaceRect); - if (isDisplayActiveLocked(display)) { + if (display->getId() == mActiveDisplayId) { mActiveDisplayTransformHint = display->getTransformHint(); + sActiveDisplayRotationFlags = + ui::Transform::toRotationFlags(display->getOrientation()); } } if (currentState.width != drawingState.width || currentState.height != drawingState.height) { display->setDisplaySize(currentState.width, currentState.height); - if (isDisplayActiveLocked(display)) { - onActiveDisplaySizeChanged(display); + if (display->getId() == mActiveDisplayId) { + onActiveDisplaySizeChanged(*display); } } } } -void SurfaceFlinger::updateInternalDisplayVsyncLocked(const sp& activeDisplay) { + +void SurfaceFlinger::resetPhaseConfiguration(Fps refreshRate) { + // Cancel the pending refresh rate change, if any, before updating the phase configuration. + mScheduler->vsyncModulator().cancelRefreshRateChange(); + mVsyncConfiguration->reset(); - const Fps refreshRate = activeDisplay->refreshRateConfigs().getActiveMode()->getFps(); updatePhaseConfiguration(refreshRate); mRefreshRateStats->setRefreshRate(refreshRate); } @@ -3127,6 +3603,7 @@ void SurfaceFlinger::processDisplayChangesLocked() { const KeyedVector, DisplayDeviceState>& draw(mDrawingState.displays); if (!curr.isIdenticalTo(draw)) { mVisibleRegionsDirty = true; + mUpdateInputInfo = true; // find the displays that were removed // (ie: in drawing state but not in current state) @@ -3162,15 +3639,20 @@ void SurfaceFlinger::processDisplayChangesLocked() { void SurfaceFlinger::commitTransactionsLocked(uint32_t transactionFlags) { // Commit display transactions. const bool displayTransactionNeeded = transactionFlags & eDisplayTransactionNeeded; - if (displayTransactionNeeded) { + mFrontEndDisplayInfosChanged = displayTransactionNeeded; + if (displayTransactionNeeded && !mLayerLifecycleManagerEnabled) { processDisplayChangesLocked(); - processDisplayHotplugEventsLocked(); + mFrontEndDisplayInfos.clear(); + for (const auto& [_, display] : mDisplays) { + mFrontEndDisplayInfos.try_emplace(display->getLayerStack(), display->getFrontEndInfo()); + } } mForceTransactionDisplayChange = displayTransactionNeeded; if (mSomeChildrenChanged) { mVisibleRegionsDirty = true; mSomeChildrenChanged = false; + mUpdateInputInfo = true; } // Update transform hint. @@ -3215,14 +3697,19 @@ void SurfaceFlinger::commitTransactionsLocked(uint32_t transactionFlags) { if (!hintDisplay) { // NOTE: TEMPORARY FIX ONLY. Real fix should cause layers to // redraw after transform hint changes. See bug 8508397. - // could be null when this layer is using a layerStack // that is not visible on any display. Also can occur at // screen off/on times. - hintDisplay = getDefaultDisplayDeviceLocked(); + // U Update: Don't provide stale hints to the clients. For + // special cases where we want the app to draw its + // first frame before the display is available, we rely + // on WMS and DMS to provide the right information + // so the client can calculate the hint. + ALOGV("Skipping reporting transform hint update for %s", layer->getDebugName()); + layer->skipReportingTransformHint(); + } else { + layer->updateTransformHint(hintDisplay->getTransformHint()); } - - layer->updateTransformHint(hintDisplay->getTransformHint()); }); } @@ -3230,6 +3717,7 @@ void SurfaceFlinger::commitTransactionsLocked(uint32_t transactionFlags) { mLayersAdded = false; // Layers have been added. mVisibleRegionsDirty = true; + mUpdateInputInfo = true; } // some layers might have been removed, so @@ -3237,49 +3725,49 @@ void SurfaceFlinger::commitTransactionsLocked(uint32_t transactionFlags) { if (mLayersRemoved) { mLayersRemoved = false; mVisibleRegionsDirty = true; + mUpdateInputInfo = true; mDrawingState.traverseInZOrder([&](Layer* layer) { - if (mLayersPendingRemoval.indexOf(layer) >= 0) { + if (mLayersPendingRemoval.indexOf(sp::fromExisting(layer)) >= 0) { // this layer is not visible anymore Region visibleReg; visibleReg.set(layer->getScreenBounds()); - invalidateLayerStack(layer, visibleReg); + invalidateLayerStack(layer->getOutputFilter(), visibleReg); } }); } + if (transactionFlags & eInputInfoUpdateNeeded) { + mUpdateInputInfo = true; + } + doCommitTransactions(); - signalSynchronousTransactions(CountDownLatch::eSyncTransaction); - mAnimTransactionPending = false; } -void SurfaceFlinger::updateInputFlinger() { - ATRACE_CALL(); - if (!mInputFlinger) { +void SurfaceFlinger::updateInputFlinger(VsyncId vsyncId, TimePoint frameTime) { + if (!mInputFlinger || (!mUpdateInputInfo && mInputWindowCommands.empty())) { return; } + ATRACE_CALL(); std::vector windowInfos; std::vector displayInfos; bool updateWindowInfo = false; - if (mVisibleRegionsDirty || mInputInfoChanged) { - mInputInfoChanged = false; + if (mUpdateInputInfo) { + mUpdateInputInfo = false; updateWindowInfo = true; buildWindowInfos(windowInfos, displayInfos); } - if (!updateWindowInfo && mInputWindowCommands.empty()) { - return; - } - std::unordered_set visibleLayers; - mDrawingState.traverse([&visibleLayers](Layer* layer) { - if (layer->isVisibleForInput()) { - visibleLayers.insert(layer); + std::unordered_set visibleWindowIds; + for (WindowInfo& windowInfo : windowInfos) { + if (!windowInfo.inputConfig.test(WindowInfo::InputConfig::NOT_VISIBLE)) { + visibleWindowIds.insert(windowInfo.id); } - }); - bool visibleLayersChanged = false; - if (visibleLayers != mVisibleLayers) { - visibleLayersChanged = true; - mVisibleLayers = std::move(visibleLayers); + } + bool visibleWindowsChanged = false; + if (visibleWindowIds != mVisibleWindowIds) { + visibleWindowsChanged = true; + mVisibleWindowIds = std::move(visibleWindowIds); } BackgroundExecutor::getInstance().sendCallbacks({[updateWindowInfo, @@ -3288,19 +3776,25 @@ void SurfaceFlinger::updateInputFlinger() { inputWindowCommands = std::move(mInputWindowCommands), inputFlinger = mInputFlinger, this, - visibleLayersChanged]() { + visibleWindowsChanged, vsyncId, frameTime]() { ATRACE_NAME("BackgroundExecutor::updateInputFlinger"); if (updateWindowInfo) { mWindowInfosListenerInvoker - ->windowInfosChanged(std::move(windowInfos), std::move(displayInfos), - /* shouldSync= */ inputWindowCommands.syncInputWindows, - /* forceImmediateCall= */ - visibleLayersChanged || + ->windowInfosChanged(gui::WindowInfosUpdate{std::move(windowInfos), + std::move(displayInfos), + vsyncId.value, frameTime.ns()}, + std::move( + inputWindowCommands.windowInfosReportedListeners), + /* forceImmediateCall= */ visibleWindowsChanged || !inputWindowCommands.focusRequests.empty()); - } else if (inputWindowCommands.syncInputWindows) { - // If the caller requested to sync input windows, but there are no - // changes to input windows, notify immediately. - windowInfosReported(); + } else { + // If there are listeners but no changes to input windows, call the listeners + // immediately. + for (const auto& listener : inputWindowCommands.windowInfosReportedListeners) { + if (IInterface::asBinder(listener)->isBinderAlive()) { + listener->onWindowInfosReported(); + } + } } for (const auto& focusRequest : inputWindowCommands.focusRequests) { inputFlinger->setFocusedWindow(focusRequest); @@ -3332,7 +3826,7 @@ void SurfaceFlinger::persistDisplayBrightness(bool needsComposite) { ALOGE_IF(error != NO_ERROR, "Error setting display brightness for display %s: %d (%s)", - display->getDebugName().c_str(), error, strerror(error)); + to_string(display->getId()).c_str(), error, strerror(error)); } display->persistBrightness(needsComposite); } @@ -3341,47 +3835,32 @@ void SurfaceFlinger::persistDisplayBrightness(bool needsComposite) { void SurfaceFlinger::buildWindowInfos(std::vector& outWindowInfos, std::vector& outDisplayInfos) { - ftl::SmallMap displayInputInfos; - - for (const auto& [_, display] : FTL_FAKE_GUARD(mStateLock, mDisplays)) { - const auto layerStack = display->getLayerStack(); - const auto info = display->getInputInfo(); - - const auto [it, emplaced] = displayInputInfos.try_emplace(layerStack, info); - if (emplaced) { - continue; - } - - // If the layer stack is mirrored on multiple displays, the first display that is configured - // to receive input takes precedence. - auto& otherInfo = it->second; - if (otherInfo.receivesInput) { - ALOGW_IF(display->receivesInput(), - "Multiple displays claim to accept input for the same layer stack: %u", - layerStack.id); - } else { - otherInfo = info; - } - } - static size_t sNumWindowInfos = 0; outWindowInfos.reserve(sNumWindowInfos); sNumWindowInfos = 0; - mDrawingState.traverseInReverseZOrder([&](Layer* layer) { - if (!layer->needsInputInfo()) return; - - const auto opt = displayInputInfos.get(layer->getLayerStack(), - [](const auto& info) -> Layer::InputDisplayArgs { - return {&info.transform, info.isSecure}; - }); - outWindowInfos.push_back(layer->fillInputInfo(opt.value_or(Layer::InputDisplayArgs{}))); - }); + if (mLayerLifecycleManagerEnabled) { + mLayerSnapshotBuilder.forEachInputSnapshot( + [&outWindowInfos](const frontend::LayerSnapshot& snapshot) { + outWindowInfos.push_back(snapshot.inputInfo); + }); + } else { + mDrawingState.traverseInReverseZOrder([&](Layer* layer) { + if (!layer->needsInputInfo()) return; + const auto opt = + mFrontEndDisplayInfos.get(layer->getLayerStack()) + .transform([](const frontend::DisplayInfo& info) { + return Layer::InputDisplayArgs{&info.transform, info.isSecure}; + }); + + outWindowInfos.push_back(layer->fillInputInfo(opt.value_or(Layer::InputDisplayArgs{}))); + }); + } sNumWindowInfos = outWindowInfos.size(); - outDisplayInfos.reserve(displayInputInfos.size()); - for (const auto& [_, info] : displayInputInfos) { + outDisplayInfos.reserve(mFrontEndDisplayInfos.size()); + for (const auto& [_, info] : mFrontEndDisplayInfos) { outDisplayInfos.push_back(info.info); } } @@ -3393,35 +3872,49 @@ void SurfaceFlinger::updateCursorAsync() { refreshArgs.outputs.push_back(display->getCompositionDisplay()); } } - + auto layers = moveSnapshotsToCompositionArgs(refreshArgs, /*cursorOnly=*/true, 0); mCompositionEngine->updateCursorAsync(refreshArgs); + moveSnapshotsFromCompositionArgs(refreshArgs, layers); } -void SurfaceFlinger::requestDisplayMode(DisplayModePtr mode, DisplayModeEvent event) { +void SurfaceFlinger::requestDisplayModes(std::vector modeRequests) { + if (mBootStage != BootStage::FINISHED) { + ALOGV("Currently in the boot stage, skipping display mode changes"); + return; + } + + ATRACE_CALL(); + // If this is called from the main thread mStateLock must be locked before // Currently the only way to call this function from the main thread is from // Scheduler::chooseRefreshRateForContent ConditionalLock lock(mStateLock, std::this_thread::get_id() != mMainThreadId); - const auto display = getDefaultDisplayDeviceLocked(); - if (!display || mBootStage != BootStage::FINISHED) { - return; - } - ATRACE_CALL(); + for (auto& request : modeRequests) { + const auto& modePtr = request.mode.modePtr; - if (display->isInternal() && !isDisplayActiveLocked(display)) { - ALOGV("%s(%s): Inactive display", __func__, to_string(display->getId()).c_str()); - return; - } + const auto displayId = modePtr->getPhysicalDisplayId(); + const auto display = getDisplayDeviceLocked(displayId); - if (!display->refreshRateConfigs().isModeAllowed(mode->getId())) { - ALOGV("%s(%s): Disallowed mode %d", __func__, to_string(display->getId()).c_str(), - mode->getId().value()); - return; - } + if (!display) continue; + + const bool isInternalDisplay = mPhysicalDisplays.get(displayId) + .transform(&PhysicalDisplay::isInternal) + .value_or(false); + + if (isInternalDisplay && displayId != mActiveDisplayId) { + ALOGV("%s(%s): Inactive display", __func__, to_string(displayId).c_str()); + continue; + } - setDesiredActiveMode({std::move(mode), event}); + if (display->refreshRateSelector().isModeAllowed(request.mode)) { + setDesiredActiveMode(std::move(request)); + } else { + ALOGV("%s: Mode %d is disallowed for display %s", __func__, modePtr->getId().value(), + to_string(display->getId()).c_str()); + } + } } void SurfaceFlinger::triggerOnFrameRateOverridesChanged() { @@ -3433,24 +3926,19 @@ void SurfaceFlinger::triggerOnFrameRateOverridesChanged() { mScheduler->onFrameRateOverridesChanged(mAppConnectionHandle, displayId); } -void SurfaceFlinger::initScheduler(const sp& display) { - if (mScheduler) { - // If the scheduler is already initialized, this means that we received - // a hotplug(connected) on the primary display. In that case we should - // update the scheduler with the most recent display information. - ALOGW("Scheduler already initialized, updating instead"); - mScheduler->setRefreshRateConfigs(display->holdRefreshRateConfigs()); - return; - } - const auto currRefreshRate = display->getActiveMode()->getFps(); - mRefreshRateStats = std::make_unique(*mTimeStats, currRefreshRate, - hal::PowerMode::OFF); +void SurfaceFlinger::initScheduler(const sp& display) { + using namespace scheduler; - mVsyncConfiguration = getFactory().createVsyncConfiguration(currRefreshRate); - mVsyncModulator = sp::make(mVsyncConfiguration->getCurrentConfigs()); + LOG_ALWAYS_FATAL_IF(mScheduler); - using Feature = scheduler::Feature; - scheduler::FeatureFlags features; + const auto activeMode = display->refreshRateSelector().getActiveMode(); + const Fps activeRefreshRate = activeMode.fps; + mRefreshRateStats = + std::make_unique(*mTimeStats, activeRefreshRate, hal::PowerMode::OFF); + + mVsyncConfiguration = getFactory().createVsyncConfiguration(activeRefreshRate); + + FeatureFlags features; if (sysprop::use_content_detection_for_refresh_rate(false)) { features |= Feature::kContentDetection; @@ -3462,68 +3950,46 @@ void SurfaceFlinger::initScheduler(const sp& display) { !getHwComposer().hasCapability(Capability::PRESENT_FENCE_IS_NOT_RELIABLE)) { features |= Feature::kPresentFences; } + if (display->refreshRateSelector().kernelIdleTimerController()) { + features |= Feature::kKernelIdleTimer; + } - mScheduler = std::make_unique(static_cast(*this), - static_cast(*this), - features); - { - auto configs = display->holdRefreshRateConfigs(); - if (configs->kernelIdleTimerController().has_value()) { - features |= Feature::kKernelIdleTimer; - } + auto modulatorPtr = sp::make(mVsyncConfiguration->getCurrentConfigs()); - mScheduler->createVsyncSchedule(features); - mScheduler->setRefreshRateConfigs(std::move(configs)); - } - setVsyncEnabled(false); + mScheduler = std::make_unique(static_cast(*this), + static_cast(*this), features, + std::move(modulatorPtr)); + mScheduler->registerDisplay(display->getPhysicalId(), display->holdRefreshRateSelector()); + + setVsyncEnabled(display->getPhysicalId(), false); mScheduler->startTimers(); const auto configs = mVsyncConfiguration->getCurrentConfigs(); - const nsecs_t vsyncPeriod = currRefreshRate.getPeriodNsecs(); + mAppConnectionHandle = - mScheduler->createConnection("app", mFrameTimeline->getTokenManager(), - /*workDuration=*/configs.late.appWorkDuration, - /*readyDuration=*/configs.late.sfWorkDuration, - impl::EventThread::InterceptVSyncsCallback()); + mScheduler->createEventThread(Scheduler::Cycle::Render, + mFrameTimeline->getTokenManager(), + /* workDuration */ configs.late.appWorkDuration, + /* readyDuration */ configs.late.sfWorkDuration); mSfConnectionHandle = - mScheduler->createConnection("appSf", mFrameTimeline->getTokenManager(), - /*workDuration=*/std::chrono::nanoseconds(vsyncPeriod), - /*readyDuration=*/configs.late.sfWorkDuration, - [this](nsecs_t timestamp) { - mInterceptor->saveVSyncEvent(timestamp); - }); + mScheduler->createEventThread(Scheduler::Cycle::LastComposite, + mFrameTimeline->getTokenManager(), + /* workDuration */ activeRefreshRate.getPeriod(), + /* readyDuration */ configs.late.sfWorkDuration); - mScheduler->initVsync(mScheduler->getVsyncDispatch(), *mFrameTimeline->getTokenManager(), - configs.late.sfWorkDuration); + mScheduler->initVsync(mScheduler->getVsyncSchedule()->getDispatch(), + *mFrameTimeline->getTokenManager(), configs.late.sfWorkDuration); mRegionSamplingThread = - new RegionSamplingThread(*this, RegionSamplingThread::EnvironmentTimingTunables()); - mFpsReporter = new FpsReporter(*mFrameTimeline, *this); - // Dispatch a mode change request for the primary display on scheduler - // initialization, so that the EventThreads always contain a reference to a - // prior configuration. - // - // This is a bit hacky, but this avoids a back-pointer into the main SF - // classes from EventThread, and there should be no run-time binder cost - // anyway since there are no connected apps at this point. - mScheduler->onPrimaryDisplayModeChanged(mAppConnectionHandle, display->getActiveMode()); + sp::make(*this, + RegionSamplingThread::EnvironmentTimingTunables()); + mFpsReporter = sp::make(*mFrameTimeline, *this); } -void SurfaceFlinger::updatePhaseConfiguration(const Fps& refreshRate) { +void SurfaceFlinger::updatePhaseConfiguration(Fps refreshRate) { mVsyncConfiguration->setRefreshRateFps(refreshRate); - setVsyncConfig(mVsyncModulator->setVsyncConfigSet(mVsyncConfiguration->getCurrentConfigs()), - refreshRate.getPeriodNsecs()); -} - -void SurfaceFlinger::setVsyncConfig(const VsyncModulator::VsyncConfig& config, - nsecs_t vsyncPeriod) { - mScheduler->setDuration(mAppConnectionHandle, - /*workDuration=*/config.appWorkDuration, - /*readyDuration=*/config.sfWorkDuration); - mScheduler->setDuration(mSfConnectionHandle, - /*workDuration=*/std::chrono::nanoseconds(vsyncPeriod), - /*readyDuration=*/config.sfWorkDuration); - mScheduler->setDuration(config.sfWorkDuration); + mScheduler->setVsyncConfigSet(mVsyncConfiguration->getCurrentConfigs(), + refreshRate.getPeriod()); } void SurfaceFlinger::doCommitTransactions() { @@ -3556,10 +4022,6 @@ void SurfaceFlinger::doCommitTransactions() { mLayersPendingRemoval.clear(); } - // If this transaction is part of a window animation then the next frame - // we composite should be considered an animation as well. - mAnimCompositionPending = mAnimTransactionPending; - mDrawingState = mCurrentState; // clear the "changed" flags in current state mCurrentState.colorMatrixChanged = false; @@ -3571,8 +4033,24 @@ void SurfaceFlinger::doCommitTransactions() { } commitOffscreenLayers(); - if (mNumClones > 0) { - mDrawingState.traverse([&](Layer* layer) { layer->updateMirrorInfo(); }); + if (mLayerMirrorRoots.size() > 0) { + std::deque pendingUpdates; + pendingUpdates.insert(pendingUpdates.end(), mLayerMirrorRoots.begin(), + mLayerMirrorRoots.end()); + std::vector needsUpdating; + for (Layer* cloneRoot : mLayerMirrorRoots) { + pendingUpdates.pop_front(); + if (cloneRoot->isRemovedFromCurrentState()) { + continue; + } + if (cloneRoot->updateMirrorInfo(pendingUpdates)) { + } else { + needsUpdating.push_back(cloneRoot); + } + } + for (Layer* cloneRoot : needsUpdating) { + cloneRoot->updateMirrorInfo({}); + } } } @@ -3587,10 +4065,10 @@ void SurfaceFlinger::commitOffscreenLayers() { } } -void SurfaceFlinger::invalidateLayerStack(const sp& layer, const Region& dirty) { +void SurfaceFlinger::invalidateLayerStack(const ui::LayerFilter& layerFilter, const Region& dirty) { for (const auto& [token, displayDevice] : FTL_FAKE_GUARD(mStateLock, mDisplays)) { auto display = displayDevice->getCompositionDisplay(); - if (display->includesLayer(layer->getOutputFilter())) { + if (display->includesLayer(layerFilter)) { display->editState().dirtyRegion.orSelf(dirty); } } @@ -3605,8 +4083,6 @@ bool SurfaceFlinger::latchBuffers() { bool frameQueued = false; bool newDataLatched = false; - const nsecs_t expectedPresentTime = mExpectedPresentTime.load(); - // Store the set of layers that need updates. This set must not change as // buffers are being latched, as this could result in a deadlock. // Example: Two producers share the same command stream and: @@ -3624,16 +4100,19 @@ bool SurfaceFlinger::latchBuffers() { } } - if (layer->hasReadyFrame()) { + if (layer->hasReadyFrame() || layer->willReleaseBufferOnLatch()) { frameQueued = true; - if (layer->shouldPresentNow(expectedPresentTime)) { - mLayersWithQueuedFrames.emplace(layer); - } else { - ATRACE_NAME("!layer->shouldPresentNow()"); - layer->useEmptyDamage(); - } + mLayersWithQueuedFrames.emplace(sp::fromExisting(layer)); } else { layer->useEmptyDamage(); + if (!layer->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. + layer->updateLastLatchTime(latchTime); + } } }); mForceTransactionDisplayChange = false; @@ -3652,7 +4131,10 @@ bool SurfaceFlinger::latchBuffers() { Mutex::Autolock lock(mStateLock); for (const auto& layer : mLayersWithQueuedFrames) { - if (layer->latchBuffer(visibleRegions, latchTime, expectedPresentTime)) { + if (layer->willReleaseBufferOnLatch()) { + mLayersWithBuffersRemoved.emplace(layer); + } + if (layer->latchBuffer(visibleRegions, latchTime)) { mLayersPendingRefresh.push_back(layer); newDataLatched = true; } @@ -3675,7 +4157,7 @@ bool SurfaceFlinger::latchBuffers() { mBootStage = BootStage::BOOTANIMATION; } - if (mNumClones > 0) { + if (mLayerMirrorRoots.size() > 0) { mDrawingState.traverse([&](Layer* layer) { layer->updateCloneBufferInfo(); }); } @@ -3683,44 +4165,80 @@ bool SurfaceFlinger::latchBuffers() { return !mLayersWithQueuedFrames.empty() && newDataLatched; } -status_t SurfaceFlinger::addClientLayer(const sp& client, const sp& handle, +status_t SurfaceFlinger::addClientLayer(LayerCreationArgs& args, const sp& handle, const sp& layer, const wp& parent, - bool addToRoot, uint32_t* outTransformHint) { - if (mNumLayers >= ISurfaceComposer::MAX_LAYERS) { + uint32_t* outTransformHint) { + if (mNumLayers >= MAX_LAYERS) { ALOGE("AddClientLayer failed, mNumLayers (%zu) >= MAX_LAYERS (%zu)", mNumLayers.load(), - ISurfaceComposer::MAX_LAYERS); + MAX_LAYERS); static_cast(mScheduler->schedule([=] { - ALOGE("Dumping random sampling of on-screen layers: "); - mDrawingState.traverse([&](Layer *layer) { + ALOGE("Dumping layer keeping > 20 children alive:"); + bool leakingParentLayerFound = false; + mDrawingState.traverse([&](Layer* layer) { + if (leakingParentLayerFound) { + return; + } + if (layer->getChildrenCount() > 20) { + leakingParentLayerFound = true; + sp parent = sp::fromExisting(layer); + while (parent) { + ALOGE("Parent Layer: %s%s", parent->getName().c_str(), + (parent->isHandleAlive() ? "handleAlive" : "")); + parent = parent->getParent(); + } + // Sample up to 100 layers + ALOGE("Dumping random sampling of child layers total(%zu): ", + layer->getChildrenCount()); + int sampleSize = (layer->getChildrenCount() / 100) + 1; + layer->traverseChildren([&](Layer* layer) { + if (rand() % sampleSize == 0) { + ALOGE("Child Layer: %s", layer->getName().c_str()); + } + }); + } + }); + + int numLayers = 0; + mDrawingState.traverse([&](Layer* layer) { numLayers++; }); + + ALOGE("Dumping random sampling of on-screen layers total(%u):", numLayers); + mDrawingState.traverse([&](Layer* layer) { // Aim to dump about 200 layers to avoid totally trashing // logcat. On the other hand, if there really are 4096 layers // something has gone totally wrong its probably the most // useful information in logcat. if (rand() % 20 == 13) { - ALOGE("Layer: %s", layer->getName().c_str()); + ALOGE("Layer: %s%s", layer->getName().c_str(), + (layer->isHandleAlive() ? "handleAlive" : "")); + std::this_thread::sleep_for(std::chrono::milliseconds(5)); } }); + ALOGE("Dumping random sampling of off-screen layers total(%zu): ", + mOffscreenLayers.size()); for (Layer* offscreenLayer : mOffscreenLayers) { if (rand() % 20 == 13) { - ALOGE("Offscreen-layer: %s", offscreenLayer->getName().c_str()); + ALOGE("Offscreen-layer: %s%s", offscreenLayer->getName().c_str(), + (offscreenLayer->isHandleAlive() ? "handleAlive" : "")); + std::this_thread::sleep_for(std::chrono::milliseconds(5)); } } })); return NO_MEMORY; } - { - std::scoped_lock lock(mCreatedLayersLock); - mCreatedLayers.emplace_back(layer, parent, addToRoot); - } - layer->updateTransformHint(mActiveDisplayTransformHint); if (outTransformHint) { *outTransformHint = mActiveDisplayTransformHint; } - // attach this layer to the client - if (client != nullptr) { - client->attachLayer(handle, layer); + args.parentId = LayerHandle::getLayerId(args.parentHandle.promote()); + args.layerIdToMirror = LayerHandle::getLayerId(args.mirrorLayerHandle.promote()); + { + std::scoped_lock lock(mCreatedLayersLock); + mCreatedLayers.emplace_back(layer, parent, args.addToRoot); + mNewLayers.emplace_back(std::make_unique(args)); + args.mirrorLayerHandle.clear(); + args.parentHandle.clear(); + mNewLayerArgs.emplace_back(std::move(args)); } setTransactionFlags(eTransactionNeeded); @@ -3732,217 +4250,171 @@ uint32_t SurfaceFlinger::getTransactionFlags() const { } uint32_t SurfaceFlinger::clearTransactionFlags(uint32_t mask) { - return mTransactionFlags.fetch_and(~mask) & mask; + uint32_t transactionFlags = mTransactionFlags.fetch_and(~mask); + ATRACE_INT("mTransactionFlags", transactionFlags); + return transactionFlags & mask; } void SurfaceFlinger::setTransactionFlags(uint32_t mask, TransactionSchedule schedule, const sp& applyToken, FrameHint frameHint) { - modulateVsync(&VsyncModulator::setTransactionSchedule, schedule, applyToken); + mScheduler->modulateVsync({}, &VsyncModulator::setTransactionSchedule, schedule, applyToken); + uint32_t transactionFlags = mTransactionFlags.fetch_or(mask); + ATRACE_INT("mTransactionFlags", transactionFlags); - if (const bool scheduled = mTransactionFlags.fetch_or(mask) & mask; !scheduled) { + if (const bool scheduled = transactionFlags & mask; !scheduled) { scheduleCommit(frameHint); + } else if (frameHint == FrameHint::kActive) { + // Even if the next frame is already scheduled, we should reset the idle timer + // as a new activity just happened. + mScheduler->resetIdleTimer(); } } -bool SurfaceFlinger::stopTransactionProcessing( - const std::unordered_set, SpHash>& - applyTokensWithUnsignaledTransactions) const { - if (enableLatchUnsignaledConfig == LatchUnsignaledConfig::AutoSingleLayer) { - // if we are in LatchUnsignaledConfig::AutoSingleLayer - // then we should have only one applyToken for processing. - // so we can stop further transactions on this applyToken. - return !applyTokensWithUnsignaledTransactions.empty(); +TransactionHandler::TransactionReadiness SurfaceFlinger::transactionReadyTimelineCheck( + const TransactionHandler::TransactionFlushState& flushState) { + using TransactionReadiness = TransactionHandler::TransactionReadiness; + const auto& transaction = *flushState.transaction; + TimePoint desiredPresentTime = TimePoint::fromNs(transaction.desiredPresentTime); + // Do not present if the desiredPresentTime has not passed unless it is more than + // one second in the future. We ignore timestamps more than 1 second in the future + // for stability reasons. + if (!transaction.isAutoTimestamp && desiredPresentTime >= mExpectedPresentTime && + desiredPresentTime < mExpectedPresentTime + 1s) { + ATRACE_FORMAT("not current desiredPresentTime: %" PRId64 " expectedPresentTime: %" PRId64, + desiredPresentTime, mExpectedPresentTime); + return TransactionReadiness::NotReady; } - return false; -} + if (!mScheduler->isVsyncValid(mExpectedPresentTime, transaction.originUid)) { + ATRACE_FORMAT("!isVsyncValid expectedPresentTime: %" PRId64 " uid: %d", + mExpectedPresentTime, transaction.originUid); + return TransactionReadiness::NotReady; + } -int SurfaceFlinger::flushUnsignaledPendingTransactionQueues( - std::vector& transactions, - std::unordered_map, uint64_t, SpHash>& bufferLayersReadyToPresent, - std::unordered_set, SpHash>& applyTokensWithUnsignaledTransactions) { - return flushPendingTransactionQueues(transactions, bufferLayersReadyToPresent, - applyTokensWithUnsignaledTransactions, - /*tryApplyUnsignaled*/ true); -} - -int SurfaceFlinger::flushPendingTransactionQueues( - std::vector& transactions, - std::unordered_map, uint64_t, SpHash>& bufferLayersReadyToPresent, - std::unordered_set, SpHash>& applyTokensWithUnsignaledTransactions, - bool tryApplyUnsignaled) { - int transactionsPendingBarrier = 0; - auto it = mPendingTransactionQueues.begin(); - while (it != mPendingTransactionQueues.end()) { - auto& [applyToken, transactionQueue] = *it; - while (!transactionQueue.empty()) { - if (stopTransactionProcessing(applyTokensWithUnsignaledTransactions)) { - ATRACE_NAME("stopTransactionProcessing"); - break; + // If the client didn't specify desiredPresentTime, use the vsyncId to determine the + // expected present time of this transaction. + if (transaction.isAutoTimestamp && + frameIsEarly(mExpectedPresentTime, VsyncId{transaction.frameTimelineInfo.vsyncId})) { + ATRACE_FORMAT("frameIsEarly vsyncId: %" PRId64 " expectedPresentTime: %" PRId64, + transaction.frameTimelineInfo.vsyncId, mExpectedPresentTime); + return TransactionReadiness::NotReady; + } + return TransactionReadiness::Ready; +} + +TransactionHandler::TransactionReadiness SurfaceFlinger::transactionReadyBufferCheck( + const TransactionHandler::TransactionFlushState& flushState) { + using TransactionReadiness = TransactionHandler::TransactionReadiness; + auto ready = TransactionReadiness::Ready; + flushState.transaction->traverseStatesWithBuffersWhileTrue([&](const layer_state_t& s, + const std::shared_ptr< + renderengine:: + ExternalTexture>& + externalTexture) + -> bool { + sp layer = LayerHandle::getLayer(s.surface); + const auto& transaction = *flushState.transaction; + // check for barrier frames + if (s.bufferData->hasBarrier) { + // The current producerId is already a newer producer than the buffer that has a + // barrier. This means the incoming buffer is older and we can release it here. We + // don't wait on the barrier since we know that's stale information. + if (layer->getDrawingState().barrierProducerId > s.bufferData->producerId) { + layer->callReleaseBufferCallback(s.bufferData->releaseBufferListener, + externalTexture->getBuffer(), + s.bufferData->frameNumber, + s.bufferData->acquireFence); + // Delete the entire state at this point and not just release the buffer because + // everything associated with the Layer in this Transaction is now out of date. + ATRACE_FORMAT("DeleteStaleBuffer %s barrierProducerId:%d > %d", + layer->getDebugName(), layer->getDrawingState().barrierProducerId, + s.bufferData->producerId); + return TraverseBuffersReturnValues::DELETE_AND_CONTINUE_TRAVERSAL; } - auto& transaction = transactionQueue.front(); - const auto ready = - transactionIsReadyToBeApplied(transaction, - transaction.frameTimelineInfo, - transaction.isAutoTimestamp, - transaction.desiredPresentTime, - transaction.originUid, transaction.states, - bufferLayersReadyToPresent, transactions.size(), - tryApplyUnsignaled); - ATRACE_INT("TransactionReadiness", static_cast(ready)); - if (ready == TransactionReadiness::NotReady) { - setTransactionFlags(eTransactionFlushNeeded); - break; - } - if (ready == TransactionReadiness::NotReadyBarrier) { - transactionsPendingBarrier++; - setTransactionFlags(eTransactionFlushNeeded); - break; - } - transaction.traverseStatesWithBuffers([&](const layer_state_t& state) { - const bool frameNumberChanged = state.bufferData->flags.test( - BufferData::BufferDataChange::frameNumberChanged); - if (frameNumberChanged) { - bufferLayersReadyToPresent[state.surface] = state.bufferData->frameNumber; - } else { - // Barrier function only used for BBQ which always includes a frame number - bufferLayersReadyToPresent[state.surface] = - std::numeric_limits::max(); + if (layer->getDrawingState().barrierFrameNumber < s.bufferData->barrierFrameNumber) { + const bool willApplyBarrierFrame = + flushState.bufferLayersReadyToPresent.contains(s.surface.get()) && + ((flushState.bufferLayersReadyToPresent.get(s.surface.get()) >= + s.bufferData->barrierFrameNumber)); + if (!willApplyBarrierFrame) { + ATRACE_FORMAT("NotReadyBarrier %s barrierFrameNumber:%" PRId64 " > %" PRId64, + layer->getDebugName(), + layer->getDrawingState().barrierFrameNumber, + s.bufferData->barrierFrameNumber); + ready = TransactionReadiness::NotReadyBarrier; + return TraverseBuffersReturnValues::STOP_TRAVERSAL; } - }); - const bool appliedUnsignaled = (ready == TransactionReadiness::ReadyUnsignaled); - if (appliedUnsignaled) { - applyTokensWithUnsignaledTransactions.insert(transaction.applyToken); } - - transactions.emplace_back(std::move(transaction)); - transactionQueue.pop(); } - if (transactionQueue.empty()) { - it = mPendingTransactionQueues.erase(it); - mTransactionQueueCV.broadcast(); - } else { - it = std::next(it, 1); + // If backpressure is enabled and we already have a buffer to commit, keep + // the transaction in the queue. + const bool hasPendingBuffer = + flushState.bufferLayersReadyToPresent.contains(s.surface.get()); + if (layer->backpressureEnabled() && hasPendingBuffer && transaction.isAutoTimestamp) { + ATRACE_FORMAT("hasPendingBuffer %s", layer->getDebugName()); + ready = TransactionReadiness::NotReady; + return TraverseBuffersReturnValues::STOP_TRAVERSAL; } - } - return transactionsPendingBarrier; -} - -std::vector SurfaceFlinger::flushTransactions() { - // to prevent onHandleDestroyed from being called while the lock is held, - // we must keep a copy of the transactions (specifically the composer - // states) around outside the scope of the lock - std::vector transactions; - // Layer handles that have transactions with buffers that are ready to be applied. - std::unordered_map, uint64_t, SpHash> bufferLayersReadyToPresent; - std::unordered_set, SpHash> applyTokensWithUnsignaledTransactions; - { - Mutex::Autolock _l(mStateLock); - { - Mutex::Autolock _l(mQueueLock); - - int lastTransactionsPendingBarrier = 0; - int transactionsPendingBarrier = 0; - // First collect transactions from the pending transaction queues. - // We are not allowing unsignaled buffers here as we want to - // collect all the transactions from applyTokens that are ready first. - transactionsPendingBarrier = - flushPendingTransactionQueues(transactions, bufferLayersReadyToPresent, - applyTokensWithUnsignaledTransactions, /*tryApplyUnsignaled*/ false); - - // Second, collect transactions from the transaction queue. - // Here as well we are not allowing unsignaled buffers for the same - // reason as above. - while (!mTransactionQueue.empty()) { - auto& transaction = mTransactionQueue.front(); - const bool pendingTransactions = - mPendingTransactionQueues.find(transaction.applyToken) != - mPendingTransactionQueues.end(); - const auto ready = [&]() REQUIRES(mStateLock) { - if (pendingTransactions) { - ATRACE_NAME("pendingTransactions"); - return TransactionReadiness::NotReady; - } - return transactionIsReadyToBeApplied(transaction, transaction.frameTimelineInfo, - transaction.isAutoTimestamp, - transaction.desiredPresentTime, - transaction.originUid, transaction.states, - bufferLayersReadyToPresent, - transactions.size(), - /*tryApplyUnsignaled*/ false); - }(); - ATRACE_INT("TransactionReadiness", static_cast(ready)); - if (ready != TransactionReadiness::Ready) { - if (ready == TransactionReadiness::NotReadyBarrier) { - transactionsPendingBarrier++; - } - mPendingTransactionQueues[transaction.applyToken].push(std::move(transaction)); - } else { - transaction.traverseStatesWithBuffers([&](const layer_state_t& state) { - const bool frameNumberChanged = state.bufferData->flags.test( - BufferData::BufferDataChange::frameNumberChanged); - if (frameNumberChanged) { - bufferLayersReadyToPresent[state.surface] = state.bufferData->frameNumber; - } else { - // Barrier function only used for BBQ which always includes a frame number. - // This value only used for barrier logic. - bufferLayersReadyToPresent[state.surface] = - std::numeric_limits::max(); - } - }); - transactions.emplace_back(std::move(transaction)); + // ignore the acquire fence if LatchUnsignaledConfig::Always is set. + const bool checkAcquireFence = enableLatchUnsignaledConfig != LatchUnsignaledConfig::Always; + const bool acquireFenceAvailable = s.bufferData && + s.bufferData->flags.test(BufferData::BufferDataChange::fenceChanged) && + s.bufferData->acquireFence; + const bool fenceSignaled = !checkAcquireFence || !acquireFenceAvailable || + s.bufferData->acquireFence->getStatus() != Fence::Status::Unsignaled; + if (!fenceSignaled) { + // check fence status + const bool allowLatchUnsignaled = + shouldLatchUnsignaled(layer, s, transaction.states.size(), + flushState.firstTransaction); + if (allowLatchUnsignaled) { + ATRACE_FORMAT("fence unsignaled try allowLatchUnsignaled %s", + layer->getDebugName()); + ready = TransactionReadiness::NotReadyUnsignaled; + } else { + ready = TransactionReadiness::NotReady; + auto& listener = s.bufferData->releaseBufferListener; + if (listener && + (flushState.queueProcessTime - transaction.postTime) > + std::chrono::nanoseconds(4s).count()) { + mTransactionHandler + .onTransactionQueueStalled(transaction.id, listener, + "Buffer processing hung up due to stuck " + "fence. Indicates GPU hang"); } - mTransactionQueue.pop_front(); - ATRACE_INT("TransactionQueue", mTransactionQueue.size()); - } - - // Transactions with a buffer pending on a barrier may be on a different applyToken - // than the transaction which satisfies our barrier. In fact this is the exact use case - // that the primitive is designed for. This means we may first process - // the barrier dependent transaction, determine it ineligible to complete - // and then satisfy in a later inner iteration of flushPendingTransactionQueues. - // The barrier dependent transaction was eligible to be presented in this frame - // but we would have prevented it without case. To fix this we continually - // loop through flushPendingTransactionQueues until we perform an iteration - // where the number of transactionsPendingBarrier doesn't change. This way - // we can continue to resolve dependency chains of barriers as far as possible. - while (lastTransactionsPendingBarrier != transactionsPendingBarrier) { - lastTransactionsPendingBarrier = transactionsPendingBarrier; - transactionsPendingBarrier = - flushPendingTransactionQueues(transactions, bufferLayersReadyToPresent, - applyTokensWithUnsignaledTransactions, - /*tryApplyUnsignaled*/ false); - } - - // We collected all transactions that could apply without latching unsignaled buffers. - // If we are allowing latch unsignaled of some form, now it's the time to go over the - // transactions that were not applied and try to apply them unsignaled. - if (enableLatchUnsignaledConfig != LatchUnsignaledConfig::Disabled) { - flushUnsignaledPendingTransactionQueues(transactions, bufferLayersReadyToPresent, - applyTokensWithUnsignaledTransactions); + ATRACE_FORMAT("fence unsignaled %s", layer->getDebugName()); + return TraverseBuffersReturnValues::STOP_TRAVERSAL; } } - } - return transactions; + return TraverseBuffersReturnValues::CONTINUE_TRAVERSAL; + }); + return ready; +} + +void SurfaceFlinger::addTransactionReadyFilters() { + mTransactionHandler.addTransactionReadyFilter( + std::bind(&SurfaceFlinger::transactionReadyTimelineCheck, this, std::placeholders::_1)); + mTransactionHandler.addTransactionReadyFilter( + std::bind(&SurfaceFlinger::transactionReadyBufferCheck, this, std::placeholders::_1)); } -// for test only -bool SurfaceFlinger::flushTransactionQueues(int64_t vsyncId) { - std::vector transactions = flushTransactions(); +// For tests only +bool SurfaceFlinger::flushTransactionQueues(VsyncId vsyncId) { + std::vector transactions = mTransactionHandler.flushTransactions(); return applyTransactions(transactions, vsyncId); } bool SurfaceFlinger::applyTransactions(std::vector& transactions, - int64_t vsyncId) { - Mutex::Autolock _l(mStateLock); + VsyncId vsyncId) { + Mutex::Autolock lock(mStateLock); return applyTransactionsLocked(transactions, vsyncId); } bool SurfaceFlinger::applyTransactionsLocked(std::vector& transactions, - int64_t vsyncId) { + VsyncId vsyncId) { bool needsTraversal = false; // Now apply all transactions. for (auto& transaction : transactions) { @@ -3951,50 +4423,40 @@ bool SurfaceFlinger::applyTransactionsLocked(std::vector& tran transaction.displays, transaction.flags, transaction.inputWindowCommands, transaction.desiredPresentTime, transaction.isAutoTimestamp, - transaction.buffer, transaction.postTime, - transaction.permissions, transaction.hasListenerCallbacks, + std::move(transaction.uncacheBufferIds), transaction.postTime, + transaction.hasListenerCallbacks, transaction.listenerCallbacks, transaction.originPid, transaction.originUid, transaction.id); - if (transaction.transactionCommittedSignal) { - mTransactionCommittedSignals.emplace_back( - std::move(transaction.transactionCommittedSignal)); - } - } - - if (mTransactionTracing) { - mTransactionTracing->addCommittedTransactions(transactions, vsyncId); } return needsTraversal; } bool SurfaceFlinger::transactionFlushNeeded() { - Mutex::Autolock _l(mQueueLock); - return !mPendingTransactionQueues.empty() || !mTransactionQueue.empty(); + return mTransactionHandler.hasPendingTransactions(); } -bool SurfaceFlinger::frameIsEarly(nsecs_t expectedPresentTime, int64_t vsyncId) const { - // The amount of time SF can delay a frame if it is considered early based - // on the VsyncModulator::VsyncConfig::appWorkDuration - constexpr static std::chrono::nanoseconds kEarlyLatchMaxThreshold = 100ms; - - const auto currentVsyncPeriod = mScheduler->getDisplayStatInfo(systemTime()).vsyncPeriod; - const auto earlyLatchVsyncThreshold = currentVsyncPeriod / 2; - - const auto prediction = mFrameTimeline->getTokenManager()->getPredictionsForToken(vsyncId); - if (!prediction.has_value()) { +bool SurfaceFlinger::frameIsEarly(TimePoint expectedPresentTime, VsyncId vsyncId) const { + const auto prediction = + mFrameTimeline->getTokenManager()->getPredictionsForToken(vsyncId.value); + if (!prediction) { return false; } - if (std::abs(prediction->presentTime - expectedPresentTime) >= - kEarlyLatchMaxThreshold.count()) { + const auto predictedPresentTime = TimePoint::fromNs(prediction->presentTime); + + if (std::chrono::abs(predictedPresentTime - expectedPresentTime) >= + scheduler::VsyncConfig::kEarlyLatchMaxThreshold) { return false; } - return prediction->presentTime >= expectedPresentTime && - prediction->presentTime - expectedPresentTime >= earlyLatchVsyncThreshold; + const Duration earlyLatchVsyncThreshold = mScheduler->getVsyncSchedule()->period() / 2; + + return predictedPresentTime >= expectedPresentTime && + predictedPresentTime - expectedPresentTime >= earlyLatchVsyncThreshold; } + bool SurfaceFlinger::shouldLatchUnsignaled(const sp& layer, const layer_state_t& state, - size_t numStates, size_t totalTXapplied) const { + size_t numStates, bool firstTransaction) const { if (enableLatchUnsignaledConfig == LatchUnsignaledConfig::Disabled) { ALOGV("%s: false (LatchUnsignaledConfig::Disabled)", __func__); return false; @@ -4013,18 +4475,17 @@ bool SurfaceFlinger::shouldLatchUnsignaled(const sp& layer, const layer_s } if (enableLatchUnsignaledConfig == LatchUnsignaledConfig::AutoSingleLayer) { - if (totalTXapplied > 0) { - ALOGV("%s: false (LatchUnsignaledConfig::AutoSingleLayer; totalTXapplied=%zu)", - __func__, totalTXapplied); + if (!firstTransaction) { + ALOGV("%s: false (LatchUnsignaledConfig::AutoSingleLayer; not first transaction)", + __func__); return false; } // We don't want to latch unsignaled if are in early / client composition // as it leads to jank due to RenderEngine waiting for unsignaled buffer // or window animations being slow. - const auto isDefaultVsyncConfig = mVsyncModulator->isVsyncConfigDefault(); - if (!isDefaultVsyncConfig) { - ALOGV("%s: false (LatchUnsignaledConfig::AutoSingleLayer; !isDefaultVsyncConfig)", + if (mScheduler->vsyncModulator().isVsyncConfigEarly()) { + ALOGV("%s: false (LatchUnsignaledConfig::AutoSingleLayer; isVsyncConfigEarly)", __func__); return false; } @@ -4039,117 +4500,108 @@ bool SurfaceFlinger::shouldLatchUnsignaled(const sp& layer, const layer_s return true; } -auto SurfaceFlinger::transactionIsReadyToBeApplied(TransactionState& transaction, - const FrameTimelineInfo& info, bool isAutoTimestamp, int64_t desiredPresentTime, - uid_t originUid, const Vector& states, - const std::unordered_map< - sp, uint64_t, SpHash>& bufferLayersReadyToPresent, - size_t totalTXapplied, bool tryApplyUnsignaled) const -> TransactionReadiness { - ATRACE_FORMAT("transactionIsReadyToBeApplied vsyncId: %" PRId64, info.vsyncId); - const nsecs_t expectedPresentTime = mExpectedPresentTime.load(); - // Do not present if the desiredPresentTime has not passed unless it is more than one second - // in the future. We ignore timestamps more than 1 second in the future for stability reasons. - if (!isAutoTimestamp && desiredPresentTime >= expectedPresentTime && - desiredPresentTime < expectedPresentTime + s2ns(1)) { - ATRACE_NAME("not current"); - return TransactionReadiness::NotReady; - } +status_t SurfaceFlinger::setTransactionState( + const FrameTimelineInfo& frameTimelineInfo, Vector& states, + const Vector& displays, uint32_t flags, const sp& applyToken, + InputWindowCommands inputWindowCommands, int64_t desiredPresentTime, bool isAutoTimestamp, + const std::vector& uncacheBuffers, bool hasListenerCallbacks, + const std::vector& listenerCallbacks, uint64_t transactionId, + const std::vector& mergedTransactionIds) { + ATRACE_CALL(); - if (!mScheduler->isVsyncValid(expectedPresentTime, originUid)) { - ATRACE_NAME("!isVsyncValid"); - return TransactionReadiness::NotReady; + IPCThreadState* ipc = IPCThreadState::self(); + const int originPid = ipc->getCallingPid(); + const int originUid = ipc->getCallingUid(); + uint32_t permissions = LayerStatePermissions::getTransactionPermissions(originPid, originUid); + for (auto composerState : states) { + composerState.state.sanitize(permissions); } - // If the client didn't specify desiredPresentTime, use the vsyncId to determine the expected - // present time of this transaction. - if (isAutoTimestamp && frameIsEarly(expectedPresentTime, info.vsyncId)) { - ATRACE_NAME("frameIsEarly"); - return TransactionReadiness::NotReady; + for (DisplayState display : displays) { + display.sanitize(permissions); } - bool fenceUnsignaled = false; - auto queueProcessTime = systemTime(); - for (const ComposerState& state : states) { - const layer_state_t& s = state.state; - - sp layer = nullptr; - if (s.surface) { - layer = fromHandle(s.surface).promote(); - } else if (s.hasBufferChanges()) { - ALOGW("Transaction with buffer, but no Layer?"); - continue; - } - if (!layer) { - continue; - } - - if (s.hasBufferChanges() && s.bufferData->hasBarrier && - ((layer->getDrawingState().frameNumber) < s.bufferData->barrierFrameNumber)) { - const bool willApplyBarrierFrame = - (bufferLayersReadyToPresent.find(s.surface) != bufferLayersReadyToPresent.end()) && - (bufferLayersReadyToPresent.at(s.surface) >= s.bufferData->barrierFrameNumber); - if (!willApplyBarrierFrame) { - ATRACE_NAME("NotReadyBarrier"); - return TransactionReadiness::NotReadyBarrier; - } - } - - const bool allowLatchUnsignaled = tryApplyUnsignaled && - shouldLatchUnsignaled(layer, s, states.size(), totalTXapplied); - ATRACE_FORMAT("%s allowLatchUnsignaled=%s", layer->getName().c_str(), - allowLatchUnsignaled ? "true" : "false"); - - const bool acquireFenceChanged = s.bufferData && - s.bufferData->flags.test(BufferData::BufferDataChange::fenceChanged) && - s.bufferData->acquireFence; - fenceUnsignaled = fenceUnsignaled || - (acquireFenceChanged && - s.bufferData->acquireFence->getStatus() == Fence::Status::Unsignaled); - - if (fenceUnsignaled && !allowLatchUnsignaled) { - if (!transaction.sentFenceTimeoutWarning && - queueProcessTime - transaction.queueTime > std::chrono::nanoseconds(4s).count()) { - transaction.sentFenceTimeoutWarning = true; - auto listener = s.bufferData->releaseBufferListener; - if (listener) { - listener->onTransactionQueueStalled(); - } - } - - ATRACE_NAME("fence unsignaled"); - return TransactionReadiness::NotReady; - } + if (!inputWindowCommands.empty() && + (permissions & layer_state_t::Permission::ACCESS_SURFACE_FLINGER) == 0) { + ALOGE("Only privileged callers are allowed to send input commands."); + inputWindowCommands.clear(); + } - if (s.hasBufferChanges()) { - // If backpressure is enabled and we already have a buffer to commit, keep the - // transaction in the queue. - const bool hasPendingBuffer = bufferLayersReadyToPresent.find(s.surface) != - bufferLayersReadyToPresent.end(); - if (layer->backpressureEnabled() && hasPendingBuffer && isAutoTimestamp) { - ATRACE_NAME("hasPendingBuffer"); - return TransactionReadiness::NotReady; - } + if (flags & (eEarlyWakeupStart | eEarlyWakeupEnd)) { + const bool hasPermission = + (permissions & layer_state_t::Permission::ACCESS_SURFACE_FLINGER) || + callingThreadHasPermission(sWakeupSurfaceFlinger); + if (!hasPermission) { + ALOGE("Caller needs permission android.permission.WAKEUP_SURFACE_FLINGER to use " + "eEarlyWakeup[Start|End] flags"); + flags &= ~(eEarlyWakeupStart | eEarlyWakeupEnd); } } - return fenceUnsignaled ? TransactionReadiness::ReadyUnsignaled : TransactionReadiness::Ready; -} -void SurfaceFlinger::queueTransaction(TransactionState& state) { - state.queueTime = systemTime(); + const int64_t postTime = systemTime(); - Mutex::Autolock lock(mQueueLock); + std::vector uncacheBufferIds; + uncacheBufferIds.reserve(uncacheBuffers.size()); + for (const auto& uncacheBuffer : uncacheBuffers) { + sp buffer = ClientCache::getInstance().erase(uncacheBuffer); + if (buffer != nullptr) { + uncacheBufferIds.push_back(buffer->getId()); + } + } + + std::vector resolvedStates; + resolvedStates.reserve(states.size()); + for (auto& state : states) { + resolvedStates.emplace_back(std::move(state)); + auto& resolvedState = resolvedStates.back(); + if (resolvedState.state.hasBufferChanges() && resolvedState.state.hasValidBuffer() && + resolvedState.state.surface) { + sp layer = LayerHandle::getLayer(resolvedState.state.surface); + std::string layerName = (layer) ? + layer->getDebugName() : std::to_string(resolvedState.state.layerId); + resolvedState.externalTexture = + getExternalTextureFromBufferData(*resolvedState.state.bufferData, + layerName.c_str(), transactionId); + mBufferCountTracker.increment(resolvedState.state.surface->localBinder()); + } + resolvedState.layerId = LayerHandle::getLayerId(resolvedState.state.surface); + if (resolvedState.state.what & layer_state_t::eReparent) { + resolvedState.parentId = + getLayerIdFromSurfaceControl(resolvedState.state.parentSurfaceControlForChild); + } + if (resolvedState.state.what & layer_state_t::eRelativeLayerChanged) { + resolvedState.relativeParentId = + getLayerIdFromSurfaceControl(resolvedState.state.relativeLayerSurfaceControl); + } + if (resolvedState.state.what & layer_state_t::eInputInfoChanged) { + wp& touchableRegionCropHandle = + resolvedState.state.windowInfoHandle->editInfo()->touchableRegionCropHandle; + resolvedState.touchCropId = + LayerHandle::getLayerId(touchableRegionCropHandle.promote()); + } + } + + TransactionState state{frameTimelineInfo, + resolvedStates, + displays, + flags, + applyToken, + std::move(inputWindowCommands), + desiredPresentTime, + isAutoTimestamp, + std::move(uncacheBufferIds), + postTime, + hasListenerCallbacks, + listenerCallbacks, + originPid, + originUid, + transactionId, + mergedTransactionIds}; - // Generate a CountDownLatch pending state if this is a synchronous transaction. - if ((state.flags & eSynchronous) || state.inputWindowCommands.syncInputWindows) { - state.transactionCommittedSignal = std::make_shared( - (state.inputWindowCommands.syncInputWindows - ? (CountDownLatch::eSyncInputWindows | CountDownLatch::eSyncTransaction) - : CountDownLatch::eSyncTransaction)); + if (mTransactionTracing) { + mTransactionTracing->addQueuedTransaction(state); } - mTransactionQueue.emplace_back(state); - ATRACE_INT("TransactionQueue", mTransactionQueue.size()); - const auto schedule = [](uint32_t flags) { if (flags & eEarlyWakeupEnd) return TransactionSchedule::EarlyEnd; if (flags & eEarlyWakeupStart) return TransactionSchedule::EarlyStart; @@ -4157,106 +4609,25 @@ void SurfaceFlinger::queueTransaction(TransactionState& state) { }(state.flags); const auto frameHint = state.isFrameActive() ? FrameHint::kActive : FrameHint::kNone; - - setTransactionFlags(eTransactionFlushNeeded, schedule, state.applyToken, frameHint); -} - -void SurfaceFlinger::waitForSynchronousTransaction( - const CountDownLatch& transactionCommittedSignal) { - // applyTransactionState is called on the main SF thread. While a given process may wish - // to wait on synchronous transactions, the main SF thread should apply the transaction and - // set the value to notify this after committed. - if (!transactionCommittedSignal.wait_until( - std::chrono::nanoseconds(mAnimationTransactionTimeout))) { - ALOGE("setTransactionState timed out!"); - } -} - -void SurfaceFlinger::signalSynchronousTransactions(const uint32_t flag) { - for (auto it = mTransactionCommittedSignals.begin(); - it != mTransactionCommittedSignals.end();) { - if ((*it)->countDown(flag)) { - it = mTransactionCommittedSignals.erase(it); - } else { - it++; - } - } -} - -status_t SurfaceFlinger::setTransactionState( - const FrameTimelineInfo& frameTimelineInfo, const Vector& states, - const Vector& displays, uint32_t flags, const sp& applyToken, - const InputWindowCommands& inputWindowCommands, int64_t desiredPresentTime, - bool isAutoTimestamp, const client_cache_t& uncacheBuffer, bool hasListenerCallbacks, - const std::vector& listenerCallbacks, uint64_t transactionId) { - ATRACE_CALL(); - - uint32_t permissions = - callingThreadHasUnscopedSurfaceFlingerAccess() ? - layer_state_t::Permission::ACCESS_SURFACE_FLINGER : 0; - // Avoid checking for rotation permissions if the caller already has ACCESS_SURFACE_FLINGER - // permissions. - if ((permissions & layer_state_t::Permission::ACCESS_SURFACE_FLINGER) || - callingThreadHasRotateSurfaceFlingerAccess()) { - permissions |= layer_state_t::Permission::ROTATE_SURFACE_FLINGER; - } - - if (callingThreadHasInternalSystemWindowAccess()) { - permissions |= layer_state_t::Permission::INTERNAL_SYSTEM_WINDOW; - } - - if (!(permissions & layer_state_t::Permission::ACCESS_SURFACE_FLINGER) && - (flags & (eEarlyWakeupStart | eEarlyWakeupEnd))) { - ALOGE("Only WindowManager is allowed to use eEarlyWakeup[Start|End] flags"); - flags &= ~(eEarlyWakeupStart | eEarlyWakeupEnd); - } - - const int64_t postTime = systemTime(); - - IPCThreadState* ipc = IPCThreadState::self(); - const int originPid = ipc->getCallingPid(); - const int originUid = ipc->getCallingUid(); - TransactionState state{frameTimelineInfo, states, - displays, flags, - applyToken, inputWindowCommands, - desiredPresentTime, isAutoTimestamp, - uncacheBuffer, postTime, - permissions, hasListenerCallbacks, - listenerCallbacks, originPid, - originUid, transactionId}; - - // Check for incoming buffer updates and increment the pending buffer count. - state.traverseStatesWithBuffers([&](const layer_state_t& state) { - mBufferCountTracker.increment(state.surface->localBinder()); - }); - - if (mTransactionTracing) { - mTransactionTracing->addQueuedTransaction(state); - } - queueTransaction(state); - - // Check the pending state to make sure the transaction is synchronous. - if (state.transactionCommittedSignal) { - waitForSynchronousTransaction(*state.transactionCommittedSignal); - } - + mTransactionHandler.queueTransaction(std::move(state)); + setTransactionFlags(eTransactionFlushNeeded, schedule, applyToken, frameHint); return NO_ERROR; } bool SurfaceFlinger::applyTransactionState(const FrameTimelineInfo& frameTimelineInfo, - Vector& states, + std::vector& states, Vector& displays, uint32_t flags, const InputWindowCommands& inputWindowCommands, const int64_t desiredPresentTime, bool isAutoTimestamp, - const client_cache_t& uncacheBuffer, - const int64_t postTime, uint32_t permissions, - bool hasListenerCallbacks, + const std::vector& uncacheBufferIds, + const int64_t postTime, bool hasListenerCallbacks, const std::vector& listenerCallbacks, int originPid, int originUid, uint64_t transactionId) { uint32_t transactionFlags = 0; - for (DisplayState& display : displays) { - display.sanitize(permissions); - transactionFlags |= setDisplayStateLocked(display); + if (!mLayerLifecycleManagerEnabled) { + for (DisplayState& display : displays) { + transactionFlags |= setDisplayStateLocked(display); + } } // start and end registration for listeners w/ no surface so they can get their callback. Note @@ -4267,50 +4638,76 @@ bool SurfaceFlinger::applyTransactionState(const FrameTimelineInfo& frameTimelin } uint32_t clientStateFlags = 0; - for (int i = 0; i < states.size(); i++) { - ComposerState& state = states.editItemAt(i); - clientStateFlags |= setClientStateLocked(frameTimelineInfo, state, desiredPresentTime, - isAutoTimestamp, postTime, permissions); - if ((flags & eAnimation) && state.state.surface) { - if (const auto layer = fromHandle(state.state.surface).promote()) { - using LayerUpdateType = scheduler::LayerHistory::LayerUpdateType; - mScheduler->recordLayerHistory(layer.get(), - isAutoTimestamp ? 0 : desiredPresentTime, - LayerUpdateType::AnimationTX); + for (auto& resolvedState : states) { + if (mLegacyFrontEndEnabled) { + clientStateFlags |= + setClientStateLocked(frameTimelineInfo, resolvedState, desiredPresentTime, + isAutoTimestamp, postTime, transactionId); + + } else /*mLayerLifecycleManagerEnabled*/ { + clientStateFlags |= updateLayerCallbacksAndStats(frameTimelineInfo, resolvedState, + desiredPresentTime, isAutoTimestamp, + postTime, transactionId); + } + if ((flags & eAnimation) && resolvedState.state.surface) { + if (const auto layer = LayerHandle::getLayer(resolvedState.state.surface)) { + const auto layerProps = scheduler::LayerProps{ + .visible = layer->isVisible(), + .bounds = layer->getBounds(), + .transform = layer->getTransform(), + .setFrameRateVote = layer->getFrameRateForLayerTree(), + .frameRateSelectionPriority = layer->getFrameRateSelectionPriority(), + }; + layer->recordLayerHistoryAnimationTx(layerProps); } } } transactionFlags |= clientStateFlags; + transactionFlags |= addInputWindowCommands(inputWindowCommands); - if (permissions & layer_state_t::Permission::ACCESS_SURFACE_FLINGER) { - transactionFlags |= addInputWindowCommands(inputWindowCommands); - } else if (!inputWindowCommands.empty()) { - ALOGE("Only privileged callers are allowed to send input commands."); - } - - if (uncacheBuffer.isValid()) { - ClientCache::getInstance().erase(uncacheBuffer); + for (uint64_t uncacheBufferId : uncacheBufferIds) { + mBufferIdsToUncache.push_back(uncacheBufferId); } // If a synchronous transaction is explicitly requested without any changes, force a transaction // anyway. This can be used as a flush mechanism for previous async transactions. // Empty animation transaction can be used to simulate back-pressure, so also force a // transaction for empty animation transactions. - if (transactionFlags == 0 && - ((flags & eSynchronous) || (flags & eAnimation))) { + if (transactionFlags == 0 && (flags & eAnimation)) { transactionFlags = eTransactionNeeded; } bool needsTraversal = false; if (transactionFlags) { - if (mInterceptor->isEnabled()) { - mInterceptor->saveTransaction(states, mCurrentState.displays, displays, flags, - originPid, originUid, transactionId); + // We are on the main thread, we are about to perform a traversal. Clear the traversal bit + // so we don't have to wake up again next frame to perform an unnecessary traversal. + if (transactionFlags & eTraversalNeeded) { + transactionFlags = transactionFlags & (~eTraversalNeeded); + needsTraversal = true; + } + if (transactionFlags) { + setTransactionFlags(transactionFlags); + } + } + + return needsTraversal; +} + +bool SurfaceFlinger::applyAndCommitDisplayTransactionStates( + std::vector& transactions) { + Mutex::Autolock lock(mStateLock); + bool needsTraversal = false; + uint32_t transactionFlags = 0; + for (auto& transaction : transactions) { + for (DisplayState& display : transaction.displays) { + transactionFlags |= setDisplayStateLocked(display); } + } - // We are on the main thread, we are about to preform a traversal. Clear the traversal bit - // so we don't have to wake up again next frame to preform an unnecessary traversal. + if (transactionFlags) { + // We are on the main thread, we are about to perform a traversal. Clear the traversal bit + // so we don't have to wake up again next frame to perform an unnecessary traversal. if (transactionFlags & eTraversalNeeded) { transactionFlags = transactionFlags & (~eTraversalNeeded); needsTraversal = true; @@ -4318,10 +4715,16 @@ bool SurfaceFlinger::applyTransactionState(const FrameTimelineInfo& frameTimelin if (transactionFlags) { setTransactionFlags(transactionFlags); } + } - if (flags & eAnimation) { - mAnimTransactionPending = true; + mFrontEndDisplayInfosChanged = mTransactionFlags & eDisplayTransactionNeeded; + if (mFrontEndDisplayInfosChanged && !mLegacyFrontEndEnabled) { + processDisplayChangesLocked(); + mFrontEndDisplayInfos.clear(); + for (const auto& [_, display] : mDisplays) { + mFrontEndDisplayInfos.try_emplace(display->getLayerStack(), display->getFrontEndInfo()); } + needsTraversal = true; } return needsTraversal; @@ -4394,11 +4797,10 @@ bool SurfaceFlinger::callingThreadHasUnscopedSurfaceFlingerAccess(bool usePermis } uint32_t SurfaceFlinger::setClientStateLocked(const FrameTimelineInfo& frameTimelineInfo, - ComposerState& composerState, + ResolvedComposerState& composerState, int64_t desiredPresentTime, bool isAutoTimestamp, - int64_t postTime, uint32_t permissions) { + int64_t postTime, uint64_t transactionId) { layer_state_t& s = composerState.state; - s.sanitize(permissions); std::vector filteredListeners; for (auto& listener : s.listeners) { @@ -4422,20 +4824,24 @@ uint32_t SurfaceFlinger::setClientStateLocked(const FrameTimelineInfo& frameTime uint32_t flags = 0; sp layer = nullptr; if (s.surface) { - layer = fromHandle(s.surface).promote(); + layer = LayerHandle::getLayer(s.surface); } else { // The client may provide us a null handle. Treat it as if the layer was removed. ALOGW("Attempt to set client state with a null layer handle"); } if (layer == nullptr) { for (auto& [listener, callbackIds] : s.listeners) { - mTransactionCallbackInvoker.registerUnpresentedCallbackHandle( - new CallbackHandle(listener, callbackIds, s.surface)); + mTransactionCallbackInvoker.addCallbackHandle(sp::make(listener, + callbackIds, + s.surface), + std::vector()); } return 0; } MUTEX_ALIAS(mStateLock, layer->mFlinger->mStateLock); + ui::LayerStack oldLayerStack = layer->getLayerStack(LayerVector::StateSet::Current); + // Only set by BLAST adapter layers if (what & layer_state_t::eProducerDisconnect) { layer->onDisconnect(); @@ -4485,18 +4891,11 @@ uint32_t SurfaceFlinger::setClientStateLocked(const FrameTimelineInfo& frameTime } } } - if (what & layer_state_t::eSizeChanged) { - if (layer->setSize(s.w, s.h)) { - flags |= eTraversalNeeded; - } - } if (what & layer_state_t::eAlphaChanged) { - if (layer->setAlpha(s.alpha)) - flags |= eTraversalNeeded; + if (layer->setAlpha(s.color.a)) flags |= eTraversalNeeded; } if (what & layer_state_t::eColorChanged) { - if (layer->setColor(s.color)) - flags |= eTraversalNeeded; + if (layer->setColor(s.color.rgb)) flags |= eTraversalNeeded; } if (what & layer_state_t::eColorTransformChanged) { if (layer->setColorTransform(s.colorTransform)) { @@ -4504,7 +4903,7 @@ uint32_t SurfaceFlinger::setClientStateLocked(const FrameTimelineInfo& frameTime } } if (what & layer_state_t::eBackgroundColorChanged) { - if (layer->setBackgroundColor(s.color, s.bgColorAlpha, s.bgColorDataspace)) { + if (layer->setBackgroundColor(s.bgColor.rgb, s.bgColor.a, s.bgColorDataspace)) { flags |= eTraversalNeeded; } } @@ -4528,6 +4927,11 @@ 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, @@ -4548,8 +4952,8 @@ uint32_t SurfaceFlinger::setClientStateLocked(const FrameTimelineInfo& frameTime flags |= eTransactionNeeded | eTraversalNeeded | eTransformHintUpdateNeeded; } } - if (what & layer_state_t::eTransformChanged) { - if (layer->setTransform(s.transform)) flags |= eTraversalNeeded; + if (what & layer_state_t::eBufferTransformChanged) { + if (layer->setTransform(s.bufferTransform)) flags |= eTraversalNeeded; } if (what & layer_state_t::eTransformToDisplayInverseChanged) { if (layer->setTransformToDisplayInverse(s.transformToDisplayInverse)) @@ -4561,9 +4965,6 @@ uint32_t SurfaceFlinger::setClientStateLocked(const FrameTimelineInfo& frameTime if (what & layer_state_t::eDataspaceChanged) { if (layer->setDataspace(s.dataspace)) flags |= eTraversalNeeded; } - if (what & layer_state_t::eHdrMetadataChanged) { - if (layer->setHdrMetadata(s.hdrMetadata)) flags |= eTraversalNeeded; - } if (what & layer_state_t::eSurfaceDamageRegionChanged) { if (layer->setSurfaceDamageRegion(s.surfaceDamageRegion)) flags |= eTraversalNeeded; } @@ -4579,16 +4980,20 @@ uint32_t SurfaceFlinger::setClientStateLocked(const FrameTimelineInfo& frameTime } std::optional dequeueBufferTimestamp; if (what & layer_state_t::eMetadataChanged) { - dequeueBufferTimestamp = s.metadata.getInt64(METADATA_DEQUEUE_TIME); + dequeueBufferTimestamp = s.metadata.getInt64(gui::METADATA_DEQUEUE_TIME); - if (const int32_t gameMode = s.metadata.getInt32(METADATA_GAME_MODE, -1); gameMode != -1) { + 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 // child layers. Child layers that are added at a later point will obtain the game mode // info through addChild(). layer->setGameModeForTree(static_cast(gameMode)); } - if (layer->setMetadata(s.metadata)) flags |= eTraversalNeeded; + if (layer->setMetadata(s.metadata)) { + flags |= eTraversalNeeded; + mLayerMetadataSnapshotNeeded = true; + } } if (what & layer_state_t::eColorSpaceAgnosticChanged) { if (layer->setColorSpaceAgnostic(s.colorSpaceAgnostic)) { @@ -4598,6 +5003,14 @@ uint32_t SurfaceFlinger::setClientStateLocked(const FrameTimelineInfo& frameTime if (what & layer_state_t::eShadowRadiusChanged) { if (layer->setShadowRadius(s.shadowRadius)) flags |= eTraversalNeeded; } + if (what & layer_state_t::eDefaultFrameRateCompatibilityChanged) { + const auto compatibility = + Layer::FrameRate::convertCompatibility(s.defaultFrameRateCompatibility); + + if (layer->setDefaultFrameRateCompatibility(compatibility)) { + flags |= eTraversalNeeded; + } + } if (what & layer_state_t::eFrameRateSelectionPriority) { if (layer->setFrameRateSelectionPriority(s.frameRateSelectionPriority)) { flags |= eTraversalNeeded; @@ -4625,6 +5038,19 @@ uint32_t SurfaceFlinger::setClientStateLocked(const FrameTimelineInfo& frameTime if (what & layer_state_t::eDimmingEnabledChanged) { if (layer->setDimmingEnabled(s.dimmingEnabled)) flags |= eTraversalNeeded; } + if (what & layer_state_t::eExtendedRangeBrightnessChanged) { + if (layer->setExtendedRangeBrightness(s.currentHdrSdrRatio, s.desiredHdrSdrRatio)) { + flags |= eTraversalNeeded; + } + } + if (what & layer_state_t::eCachingHintChanged) { + if (layer->setCachingHint(s.cachingHint)) { + flags |= eTraversalNeeded; + } + } + if (what & layer_state_t::eHdrMetadataChanged) { + if (layer->setHdrMetadata(s.hdrMetadata)) flags |= eTraversalNeeded; + } if (what & layer_state_t::eTrustedOverlayChanged) { if (layer->setTrustedOverlay(s.isTrustedOverlay)) { flags |= eTraversalNeeded; @@ -4648,7 +5074,7 @@ uint32_t SurfaceFlinger::setClientStateLocked(const FrameTimelineInfo& frameTime if (what & layer_state_t::eDropInputModeChanged) { if (layer->setDropInputMode(s.dropInputMode)) { flags |= eTraversalNeeded; - mInputInfoChanged = true; + mUpdateInputInfo = true; } } // This has to happen after we reparent children because when we reparent to null we remove @@ -4671,92 +5097,263 @@ uint32_t SurfaceFlinger::setClientStateLocked(const FrameTimelineInfo& frameTime std::vector> callbackHandles; if ((what & layer_state_t::eHasListenerCallbacksChanged) && (!filteredListeners.empty())) { for (auto& [listener, callbackIds] : filteredListeners) { - callbackHandles.emplace_back(new CallbackHandle(listener, callbackIds, s.surface)); + callbackHandles.emplace_back( + sp::make(listener, callbackIds, s.surface)); } } if (what & layer_state_t::eBufferChanged) { - std::shared_ptr buffer = - getExternalTextureFromBufferData(*s.bufferData, layer->getDebugName()); - if (layer->setBuffer(buffer, *s.bufferData, postTime, desiredPresentTime, isAutoTimestamp, - dequeueBufferTimestamp, frameTimelineInfo)) { + if (layer->setBuffer(composerState.externalTexture, *s.bufferData, postTime, + desiredPresentTime, isAutoTimestamp, dequeueBufferTimestamp, + frameTimelineInfo)) { flags |= eTraversalNeeded; } } else if (frameTimelineInfo.vsyncId != FrameTimelineInfo::INVALID_VSYNC_ID) { layer->setFrameTimelineVsyncForBufferlessTransaction(frameTimelineInfo, postTime); } - if (layer->setTransactionCompletedListeners(callbackHandles)) flags |= eTraversalNeeded; - // Do not put anything that updates layer state or modifies flags after - // setTransactionCompletedListener - return flags; -} - -uint32_t SurfaceFlinger::addInputWindowCommands(const InputWindowCommands& inputWindowCommands) { - bool hasChanges = mInputWindowCommands.merge(inputWindowCommands); - return hasChanges ? eTraversalNeeded : 0; -} + if ((what & layer_state_t::eBufferChanged) == 0) { + layer->setDesiredPresentTime(desiredPresentTime, isAutoTimestamp); + } + + if (what & layer_state_t::eTrustedPresentationInfoChanged) { + if (layer->setTrustedPresentationInfo(s.trustedPresentationThresholds, + s.trustedPresentationListener)) { + flags |= eTraversalNeeded; + } + } + + if (what & layer_state_t::eFlushJankData) { + // Do nothing. Processing the transaction completed listeners currently cause the flush. + } + + if (layer->setTransactionCompletedListeners(callbackHandles, + layer->willPresentCurrentTransaction() || + layer->willReleaseBufferOnLatch())) { + flags |= eTraversalNeeded; + } + + // Do not put anything that updates layer state or modifies flags after + // setTransactionCompletedListener + + // if the layer has been parented on to a new display, update its transform hint. + if (((flags & eTransformHintUpdateNeeded) == 0) && + oldLayerStack != layer->getLayerStack(LayerVector::StateSet::Current)) { + flags |= eTransformHintUpdateNeeded; + } + + return flags; +} + +uint32_t SurfaceFlinger::updateLayerCallbacksAndStats(const FrameTimelineInfo& frameTimelineInfo, + ResolvedComposerState& composerState, + int64_t desiredPresentTime, + bool isAutoTimestamp, int64_t postTime, + uint64_t transactionId) { + layer_state_t& s = composerState.state; + + std::vector filteredListeners; + for (auto& listener : s.listeners) { + // Starts a registration but separates the callback ids according to callback type. This + // allows the callback invoker to send on latch callbacks earlier. + // note that startRegistration will not re-register if the listener has + // already be registered for a prior surface control + + ListenerCallbacks onCommitCallbacks = listener.filter(CallbackId::Type::ON_COMMIT); + if (!onCommitCallbacks.callbackIds.empty()) { + filteredListeners.push_back(onCommitCallbacks); + } + + ListenerCallbacks onCompleteCallbacks = listener.filter(CallbackId::Type::ON_COMPLETE); + if (!onCompleteCallbacks.callbackIds.empty()) { + filteredListeners.push_back(onCompleteCallbacks); + } + } + + const uint64_t what = s.what; + uint32_t flags = 0; + sp layer = nullptr; + if (s.surface) { + layer = LayerHandle::getLayer(s.surface); + } else { + // The client may provide us a null handle. Treat it as if the layer was removed. + ALOGW("Attempt to set client state with a null layer handle"); + } + if (layer == nullptr) { + for (auto& [listener, callbackIds] : s.listeners) { + mTransactionCallbackInvoker.addCallbackHandle(sp::make(listener, + callbackIds, + s.surface), + std::vector()); + } + return 0; + } + 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())) { + for (auto& [listener, callbackIds] : filteredListeners) { + callbackHandles.emplace_back( + sp::make(listener, callbackIds, s.surface)); + } + } + // TODO(b/238781169) remove after screenshot refactor, currently screenshots + // requires to read drawing state from binder thread. So we need to fix that + // before removing this. + if (what & layer_state_t::eCropChanged) { + if (layer->setCrop(s.crop)) flags |= eTraversalNeeded; + } + if (what & layer_state_t::eSidebandStreamChanged) { + if (layer->setSidebandStream(s.sidebandStream)) flags |= eTraversalNeeded; + } + if (what & layer_state_t::eBufferChanged) { + std::optional transformHint = std::nullopt; + frontend::LayerSnapshot* snapshot = mLayerSnapshotBuilder.getSnapshot(layer->sequence); + if (snapshot) { + transformHint = snapshot->transformHint; + } + layer->setTransformHint(transformHint); + if (layer->setBuffer(composerState.externalTexture, *s.bufferData, postTime, + desiredPresentTime, isAutoTimestamp, dequeueBufferTimestamp, + frameTimelineInfo)) { + flags |= eTraversalNeeded; + } + mLayersWithQueuedFrames.emplace(layer); + } else if (frameTimelineInfo.vsyncId != FrameTimelineInfo::INVALID_VSYNC_ID) { + layer->setFrameTimelineVsyncForBufferlessTransaction(frameTimelineInfo, postTime); + } + + if ((what & layer_state_t::eBufferChanged) == 0) { + layer->setDesiredPresentTime(desiredPresentTime, isAutoTimestamp); + } + + if (what & layer_state_t::eTrustedPresentationInfoChanged) { + if (layer->setTrustedPresentationInfo(s.trustedPresentationThresholds, + s.trustedPresentationListener)) { + flags |= eTraversalNeeded; + } + } + + const auto& requestedLayerState = mLayerLifecycleManager.getLayerFromId(layer->getSequence()); + bool willPresentCurrentTransaction = requestedLayerState && + (requestedLayerState->hasReadyFrame() || + requestedLayerState->willReleaseBufferOnLatch()); + if (layer->setTransactionCompletedListeners(callbackHandles, willPresentCurrentTransaction)) + flags |= eTraversalNeeded; + + return flags; +} + +uint32_t SurfaceFlinger::addInputWindowCommands(const InputWindowCommands& inputWindowCommands) { + bool hasChanges = mInputWindowCommands.merge(inputWindowCommands); + return hasChanges ? eTraversalNeeded : 0; +} status_t SurfaceFlinger::mirrorLayer(const LayerCreationArgs& args, - const sp& mirrorFromHandle, sp* outHandle, - int32_t* outLayerId) { + const sp& mirrorFromHandle, + gui::CreateSurfaceResult& outResult) { if (!mirrorFromHandle) { return NAME_NOT_FOUND; } sp mirrorLayer; sp mirrorFrom; + LayerCreationArgs mirrorArgs = LayerCreationArgs::fromOtherArgs(args); { Mutex::Autolock _l(mStateLock); - mirrorFrom = fromHandle(mirrorFromHandle).promote(); + mirrorFrom = LayerHandle::getLayer(mirrorFromHandle); if (!mirrorFrom) { return NAME_NOT_FOUND; } - status_t result = createContainerLayer(args, outHandle, &mirrorLayer); + mirrorArgs.flags |= ISurfaceComposerClient::eNoColorFill; + mirrorArgs.mirrorLayerHandle = mirrorFromHandle; + mirrorArgs.addToRoot = false; + status_t result = createEffectLayer(mirrorArgs, &outResult.handle, &mirrorLayer); if (result != NO_ERROR) { return result; } - mirrorLayer->setClonedChild(mirrorFrom->createClone()); + mirrorLayer->setClonedChild(mirrorFrom->createClone(mirrorLayer->getSequence())); } - *outLayerId = mirrorLayer->sequence; - if (mTransactionTracing) { - mTransactionTracing->onMirrorLayerAdded((*outHandle)->localBinder(), mirrorLayer->sequence, - args.name, mirrorFrom->sequence); - } - return addClientLayer(args.client, *outHandle, mirrorLayer /* layer */, nullptr /* parent */, - false /* addToRoot */, nullptr /* outTransformHint */); + outResult.layerId = mirrorLayer->sequence; + outResult.layerName = String16(mirrorLayer->getDebugName()); + return addClientLayer(mirrorArgs, outResult.handle, mirrorLayer /* layer */, + nullptr /* parent */, nullptr /* outTransformHint */); } -status_t SurfaceFlinger::createLayer(LayerCreationArgs& args, sp* outHandle, - const sp& parentHandle, int32_t* outLayerId, - const sp& parentLayer, uint32_t* outTransformHint) { - ALOG_ASSERT(parentLayer == nullptr || parentHandle == nullptr, - "Expected only one of parentLayer or parentHandle to be non-null. " - "Programmer error?"); +status_t SurfaceFlinger::mirrorDisplay(DisplayId displayId, const LayerCreationArgs& args, + gui::CreateSurfaceResult& outResult) { + IPCThreadState* ipc = IPCThreadState::self(); + const int uid = ipc->getCallingUid(); + if (uid != AID_ROOT && uid != AID_GRAPHICS && uid != AID_SYSTEM && uid != AID_SHELL) { + ALOGE("Permission denied when trying to mirror display"); + return PERMISSION_DENIED; + } + + ui::LayerStack layerStack; + sp rootMirrorLayer; + status_t result = 0; + + { + Mutex::Autolock lock(mStateLock); + + const auto display = getDisplayDeviceLocked(displayId); + if (!display) { + return NAME_NOT_FOUND; + } + + layerStack = display->getLayerStack(); + LayerCreationArgs mirrorArgs = LayerCreationArgs::fromOtherArgs(args); + mirrorArgs.flags |= ISurfaceComposerClient::eNoColorFill; + mirrorArgs.addToRoot = true; + mirrorArgs.layerStackToMirror = layerStack; + result = createEffectLayer(mirrorArgs, &outResult.handle, &rootMirrorLayer); + outResult.layerId = rootMirrorLayer->sequence; + outResult.layerName = String16(rootMirrorLayer->getDebugName()); + result |= addClientLayer(mirrorArgs, outResult.handle, rootMirrorLayer /* layer */, + nullptr /* parent */, nullptr /* outTransformHint */); + } + + if (result != NO_ERROR) { + return result; + } + + if (mLegacyFrontEndEnabled) { + std::scoped_lock lock(mMirrorDisplayLock); + mMirrorDisplays.emplace_back(layerStack, outResult.handle, args.client); + } + + setTransactionFlags(eTransactionFlushNeeded); + return NO_ERROR; +} +status_t SurfaceFlinger::createLayer(LayerCreationArgs& args, gui::CreateSurfaceResult& outResult) { status_t result = NO_ERROR; sp layer; switch (args.flags & ISurfaceComposerClient::eFXSurfaceMask) { case ISurfaceComposerClient::eFXSurfaceBufferQueue: - case ISurfaceComposerClient::eFXSurfaceBufferState: { - result = createBufferStateLayer(args, outHandle, &layer); + case ISurfaceComposerClient::eFXSurfaceContainer: + case ISurfaceComposerClient::eFXSurfaceBufferState: + args.flags |= ISurfaceComposerClient::eNoColorFill; + FMT_FALLTHROUGH; + case ISurfaceComposerClient::eFXSurfaceEffect: { + result = createBufferStateLayer(args, &outResult.handle, &layer); std::atomic* pendingBufferCounter = layer->getPendingBufferCounter(); if (pendingBufferCounter) { std::string counterName = layer->getPendingBufferCounterName(); - mBufferCountTracker.add((*outHandle)->localBinder(), counterName, + mBufferCountTracker.add(outResult.handle->localBinder(), counterName, pendingBufferCounter); } } break; - case ISurfaceComposerClient::eFXSurfaceEffect: - result = createEffectLayer(args, outHandle, &layer); - break; - case ISurfaceComposerClient::eFXSurfaceContainer: - result = createContainerLayer(args, outHandle, &layer); - break; default: result = BAD_VALUE; break; @@ -4766,73 +5363,27 @@ status_t SurfaceFlinger::createLayer(LayerCreationArgs& args, sp* outHa return result; } - bool addToRoot = args.addToRoot && callingThreadHasUnscopedSurfaceFlingerAccess(); - wp parent(parentHandle != nullptr ? fromHandle(parentHandle) : parentLayer); - if (parentHandle != nullptr && parent == nullptr) { - ALOGE("Invalid parent handle %p.", parentHandle.get()); - addToRoot = false; - } - if (parentLayer != nullptr) { - addToRoot = false; - } - - int parentId = -1; - // We can safely promote the layer in binder thread because we have a strong reference - // to the layer's handle inside this scope or we were passed in a sp reference to the layer. - sp parentSp = parent.promote(); - if (parentSp != nullptr) { - parentId = parentSp->getSequence(); - } - if (mTransactionTracing) { - mTransactionTracing->onLayerAdded((*outHandle)->localBinder(), layer->sequence, args.name, - args.flags, parentId); + args.addToRoot = args.addToRoot && callingThreadHasUnscopedSurfaceFlingerAccess(); + // We can safely promote the parent layer in binder thread because we have a strong reference + // to the layer's handle inside this scope. + sp parent = LayerHandle::getLayer(args.parentHandle.promote()); + if (args.parentHandle != nullptr && parent == nullptr) { + ALOGE("Invalid parent handle %p", args.parentHandle.promote().get()); + args.addToRoot = false; } - result = addClientLayer(args.client, *outHandle, layer, parent, addToRoot, outTransformHint); + uint32_t outTransformHint; + result = addClientLayer(args, outResult.handle, layer, parent, &outTransformHint); if (result != NO_ERROR) { return result; } - *outLayerId = layer->sequence; + outResult.transformHint = static_cast(outTransformHint); + outResult.layerId = layer->sequence; + outResult.layerName = String16(layer->getDebugName()); return result; } -status_t SurfaceFlinger::createBufferQueueLayer(LayerCreationArgs& args, PixelFormat& format, - sp* handle, - sp* gbp, - sp* outLayer) { - // initialize the surfaces - switch (format) { - case PIXEL_FORMAT_TRANSPARENT: - case PIXEL_FORMAT_TRANSLUCENT: - format = PIXEL_FORMAT_RGBA_8888; - break; - case PIXEL_FORMAT_OPAQUE: - format = PIXEL_FORMAT_RGBX_8888; - break; - } - - sp layer; - args.textureName = getNewTexture(); - { - // Grab the SF state lock during this since it's the only safe way to access - // RenderEngine when creating a BufferLayerConsumer - // TODO: Check if this lock is still needed here - Mutex::Autolock lock(mStateLock); - layer = getFactory().createBufferQueueLayer(args); - } - - status_t err = layer->setDefaultBufferProperties(0, 0, format); - if (err == NO_ERROR) { - *handle = layer->getHandle(); - *gbp = layer->getProducer(); - *outLayer = layer; - } - - ALOGE_IF(err, "createBufferQueueLayer() failed (%s)", strerror(-err)); - return err; -} - status_t SurfaceFlinger::createBufferStateLayer(LayerCreationArgs& args, sp* handle, sp* outLayer) { args.textureName = getNewTexture(); @@ -4848,40 +5399,45 @@ status_t SurfaceFlinger::createEffectLayer(const LayerCreationArgs& args, sp* handle, - sp* outLayer) { - *outLayer = getFactory().createContainerLayer(args); - *handle = (*outLayer)->getHandle(); - return NO_ERROR; -} - void SurfaceFlinger::markLayerPendingRemovalLocked(const sp& layer) { mLayersPendingRemoval.add(layer); mLayersRemoved = true; setTransactionFlags(eTransactionNeeded); } -void SurfaceFlinger::onHandleDestroyed(BBinder* handle, sp& layer) { +void SurfaceFlinger::onHandleDestroyed(BBinder* handle, sp& layer, uint32_t layerId) { + { + std::scoped_lock lock(mCreatedLayersLock); + mDestroyedHandles.emplace_back(layerId); + } + Mutex::Autolock lock(mStateLock); markLayerPendingRemovalLocked(layer); + layer->onHandleDestroyed(); mBufferCountTracker.remove(handle); layer.clear(); - if (mTransactionTracing) { - mTransactionTracing->onHandleRemoved(handle); - } -} -// --------------------------------------------------------------------------- + setTransactionFlags(eTransactionFlushNeeded); +} -void SurfaceFlinger::onInitializeDisplays() { - const auto display = getDefaultDisplayDeviceLocked(); +void SurfaceFlinger::initializeDisplays() { + const auto display = FTL_FAKE_GUARD(mStateLock, getDefaultDisplayDeviceLocked()); if (!display) return; const sp token = display->getDisplayToken().promote(); LOG_ALWAYS_FATAL_IF(token == nullptr); + TransactionState state; + state.inputWindowCommands = mInputWindowCommands; + const nsecs_t now = systemTime(); + state.desiredPresentTime = now; + state.postTime = now; + state.originPid = mPid; + state.originUid = static_cast(getuid()); + const uint64_t transactionId = (static_cast(mPid) << 32) | mUniqueTransactionId++; + state.id = transactionId; + // reset screen orientation and use primary layer stack - Vector state; Vector displays; DisplayState d; d.what = DisplayState::eDisplayProjectionChanged | @@ -4893,30 +5449,21 @@ void SurfaceFlinger::onInitializeDisplays() { d.layerStackSpaceRect.makeInvalid(); d.width = 0; d.height = 0; - displays.add(d); - - nsecs_t now = systemTime(); + state.displays.add(d); - int64_t transactionId = (((int64_t)mPid) << 32) | mUniqueTransactionId++; - // It should be on the main thread, apply it directly. - applyTransactionState(FrameTimelineInfo{}, state, displays, 0, mInputWindowCommands, - /* desiredPresentTime */ now, true, {}, /* postTime */ now, true, false, - {}, mPid, getuid(), transactionId); + std::vector transactions; + transactions.emplace_back(state); - setPowerModeInternal(display, hal::PowerMode::ON); - const nsecs_t vsyncPeriod = display->refreshRateConfigs().getActiveMode()->getVsyncPeriod(); - mAnimFrameTracker.setDisplayRefreshPeriod(vsyncPeriod); - mActiveDisplayTransformHint = display->getTransformHint(); - // Use phase of 0 since phase is not known. - // Use latency of 0, which will snap to the ideal latency. - DisplayStatInfo stats{0 /* vsyncTime */, vsyncPeriod}; - setCompositorTimingSnapped(stats, 0); -} + if (mLegacyFrontEndEnabled) { + applyTransactions(transactions, VsyncId{0}); + } else { + applyAndCommitDisplayTransactionStates(transactions); + } -void SurfaceFlinger::initializeDisplays() { - // Async since we may be called from the main thread. - static_cast( - mScheduler->schedule([this]() FTL_FAKE_GUARD(mStateLock) { onInitializeDisplays(); })); + { + ftl::FakeGuard guard(mStateLock); + setPowerModeInternal(display, hal::PowerMode::ON); + } } void SurfaceFlinger::setPowerModeInternal(const sp& display, hal::PowerMode mode) { @@ -4928,61 +5475,83 @@ void SurfaceFlinger::setPowerModeInternal(const sp& display, hal: const auto displayId = display->getPhysicalId(); ALOGD("Setting power mode %d on display %s", mode, to_string(displayId).c_str()); - std::optional currentMode = display->getPowerMode(); - if (currentMode.has_value() && mode == *currentMode) { + const auto currentModeOpt = display->getPowerMode(); + if (currentModeOpt == mode) { return; } - const auto activeDisplay = getDisplayDeviceLocked(mActiveDisplayToken); - if (activeDisplay != display && display->isInternal() && activeDisplay && - activeDisplay->isPoweredOn()) { - ALOGW("Trying to change power mode on non active display while the active display is ON"); - } + const bool isInternalDisplay = mPhysicalDisplays.get(displayId) + .transform(&PhysicalDisplay::isInternal) + .value_or(false); + + const auto activeDisplay = getDisplayDeviceLocked(mActiveDisplayId); + + ALOGW_IF(display != activeDisplay && isInternalDisplay && activeDisplay && + activeDisplay->isPoweredOn(), + "Trying to change power mode on inactive display without powering off active display"); display->setPowerMode(mode); - if (mInterceptor->isEnabled()) { - mInterceptor->savePowerModeUpdate(display->getSequenceId(), static_cast(mode)); - } - const auto refreshRate = display->refreshRateConfigs().getActiveMode()->getFps(); - if (!currentMode || *currentMode == hal::PowerMode::OFF) { + const auto refreshRate = display->refreshRateSelector().getActiveMode().modePtr->getFps(); + if (!currentModeOpt || *currentModeOpt == hal::PowerMode::OFF) { // Turn on the display - if (display->isInternal() && (!activeDisplay || !activeDisplay->isPoweredOn())) { - onActiveDisplayChangedLocked(display); - } - // Keep uclamp in a separate syscall and set it before changing to RT due to b/190237315. - // We can merge the syscall later. - if (SurfaceFlinger::setSchedAttr(true) != NO_ERROR) { - ALOGW("Couldn't set uclamp.min on display on: %s\n", strerror(errno)); + + // Activate the display (which involves a modeset to the active mode) when the inner or + // outer display of a foldable is powered on. This condition relies on the above + // DisplayDevice::setPowerMode. If `display` and `activeDisplay` are the same display, + // then the `activeDisplay->isPoweredOn()` below is true, such that the display is not + // activated every time it is powered on. + // + // TODO(b/255635821): Remove the concept of active display. + if (isInternalDisplay && (!activeDisplay || !activeDisplay->isPoweredOn())) { + onActiveDisplayChangedLocked(activeDisplay.get(), *display); } - if (SurfaceFlinger::setSchedFifo(true) != NO_ERROR) { - ALOGW("Couldn't set SCHED_FIFO on display on: %s\n", strerror(errno)); + + if (displayId == mActiveDisplayId) { + // TODO(b/281692563): Merge the syscalls. For now, keep uclamp in a separate syscall and + // set it before SCHED_FIFO due to b/190237315. + if (setSchedAttr(true) != NO_ERROR) { + ALOGW("Failed to set uclamp.min after powering on active display: %s", + strerror(errno)); + } + if (setSchedFifo(true) != NO_ERROR) { + ALOGW("Failed to set SCHED_FIFO after powering on active display: %s", + strerror(errno)); + } } + getHwComposer().setPowerMode(displayId, mode); - if (isDisplayActiveLocked(display) && mode != hal::PowerMode::DOZE_SUSPEND) { - setHWCVsyncEnabled(displayId, mHWCVsyncPendingState); - mScheduler->onScreenAcquired(mAppConnectionHandle); - mScheduler->resyncToHardwareVsync(true, refreshRate); + if (displayId == mActiveDisplayId && mode != hal::PowerMode::DOZE_SUSPEND) { + setHWCVsyncEnabled(displayId, + mScheduler->getVsyncSchedule(displayId) + ->getPendingHardwareVsyncState()); + mScheduler->enableSyntheticVsync(false); + mScheduler->resyncToHardwareVsync(displayId, true /* allowToEnable */, refreshRate); } mVisibleRegionsDirty = true; - mHasPoweredOff = true; scheduleComposite(FrameHint::kActive); } else if (mode == hal::PowerMode::OFF) { // Turn off the display - if (SurfaceFlinger::setSchedFifo(false) != NO_ERROR) { - ALOGW("Couldn't set SCHED_OTHER on display off: %s\n", strerror(errno)); - } - if (SurfaceFlinger::setSchedAttr(false) != NO_ERROR) { - ALOGW("Couldn't set uclamp.min on display off: %s\n", strerror(errno)); - } - if (isDisplayActiveLocked(display) && *currentMode != hal::PowerMode::DOZE_SUSPEND) { - mScheduler->disableHardwareVsync(true); - mScheduler->onScreenReleased(mAppConnectionHandle); + + if (displayId == mActiveDisplayId) { + if (setSchedFifo(false) != NO_ERROR) { + ALOGW("Failed to set SCHED_OTHER after powering off active display: %s", + strerror(errno)); + } + if (setSchedAttr(false) != NO_ERROR) { + ALOGW("Failed set uclamp.min after powering off active display: %s", + strerror(errno)); + } + + if (*currentModeOpt != hal::PowerMode::DOZE_SUSPEND) { + mScheduler->disableHardwareVsync(displayId, true); + mScheduler->enableSyntheticVsync(); + } } // Make sure HWVsync is disabled before turning off the display - setHWCVsyncEnabled(displayId, hal::Vsync::DISABLE); + setHWCVsyncEnabled(displayId, false); getHwComposer().setPowerMode(displayId, mode); mVisibleRegionsDirty = true; @@ -4990,15 +5559,18 @@ void SurfaceFlinger::setPowerModeInternal(const sp& display, hal: } else if (mode == hal::PowerMode::DOZE || mode == hal::PowerMode::ON) { // Update display while dozing getHwComposer().setPowerMode(displayId, mode); - if (isDisplayActiveLocked(display) && *currentMode == hal::PowerMode::DOZE_SUSPEND) { - mScheduler->onScreenAcquired(mAppConnectionHandle); - mScheduler->resyncToHardwareVsync(true, refreshRate); + if (displayId == mActiveDisplayId && *currentModeOpt == hal::PowerMode::DOZE_SUSPEND) { + ALOGI("Force repainting for DOZE_SUSPEND -> DOZE or ON."); + mVisibleRegionsDirty = true; + scheduleRepaint(); + mScheduler->enableSyntheticVsync(false); + mScheduler->resyncToHardwareVsync(displayId, true /* allowToEnable */, refreshRate); } } else if (mode == hal::PowerMode::DOZE_SUSPEND) { // Leave display going to doze - if (isDisplayActiveLocked(display)) { - mScheduler->disableHardwareVsync(true); - mScheduler->onScreenReleased(mAppConnectionHandle); + if (displayId == mActiveDisplayId) { + mScheduler->disableHardwareVsync(displayId, true); + mScheduler->enableSyntheticVsync(); } getHwComposer().setPowerMode(displayId, mode); } else { @@ -5006,17 +5578,18 @@ void SurfaceFlinger::setPowerModeInternal(const sp& display, hal: getHwComposer().setPowerMode(displayId, mode); } - if (isDisplayActiveLocked(display)) { + if (displayId == mActiveDisplayId) { mTimeStats->setPowerMode(mode); mRefreshRateStats->setPowerMode(mode); - mScheduler->setDisplayPowerMode(mode); + mScheduler->setDisplayPowerMode(displayId, mode); } ALOGD("Finished setting power mode %d on display %s", mode, to_string(displayId).c_str()); } void SurfaceFlinger::setPowerMode(const sp& displayToken, int mode) { - auto future = mScheduler->schedule([=]() FTL_FAKE_GUARD(mStateLock) { + auto future = mScheduler->schedule([=]() FTL_FAKE_GUARD(mStateLock) FTL_FAKE_GUARD( + kMainThreadContext) { const auto display = getDisplayDeviceLocked(displayToken); if (!display) { ALOGE("Attempt to set power mode %d for invalid display token %p", mode, @@ -5047,17 +5620,18 @@ status_t SurfaceFlinger::doDump(int fd, const DumpArgs& args, bool asProto) { {"--comp-displays"s, dumper(&SurfaceFlinger::dumpCompositionDisplays)}, {"--display-id"s, dumper(&SurfaceFlinger::dumpDisplayIdentificationData)}, {"--displays"s, dumper(&SurfaceFlinger::dumpDisplays)}, - {"--dispsync"s, dumper([this](std::string& s) { mScheduler->dumpVsync(s); })}, {"--edid"s, argsDumper(&SurfaceFlinger::dumpRawDisplayIdentificationData)}, + {"--events"s, dumper(&SurfaceFlinger::dumpEvents)}, + {"--frametimeline"s, argsDumper(&SurfaceFlinger::dumpFrameTimeline)}, + {"--hwclayers"s, dumper(&SurfaceFlinger::dumpHwcLayersMinidumpLocked)}, {"--latency"s, argsDumper(&SurfaceFlinger::dumpStatsLocked)}, {"--latency-clear"s, argsDumper(&SurfaceFlinger::clearStatsLocked)}, {"--list"s, dumper(&SurfaceFlinger::listLayersLocked)}, {"--planner"s, argsDumper(&SurfaceFlinger::dumpPlannerInfo)}, - {"--static-screen"s, dumper(&SurfaceFlinger::dumpStaticScreenStats)}, + {"--scheduler"s, dumper(&SurfaceFlinger::dumpScheduler)}, {"--timestats"s, protoDumper(&SurfaceFlinger::dumpTimeStats)}, - {"--vsync"s, dumper(&SurfaceFlinger::dumpVSync)}, + {"--vsync"s, dumper(&SurfaceFlinger::dumpVsync)}, {"--wide-color"s, dumper(&SurfaceFlinger::dumpWideColorInfo)}, - {"--frametimeline"s, argsDumper(&SurfaceFlinger::dumpFrameTimeline)}, }; const auto flag = args.empty() ? ""s : std::string(String8(args[0])); @@ -5102,7 +5676,8 @@ status_t SurfaceFlinger::doDump(int fd, const DumpArgs& args, bool asProto) { LayersTraceProto* layersTrace = traceFileProto.add_entry(); LayersProto layersProto = dumpProtoFromMainThread(); layersTrace->mutable_layers()->Swap(&layersProto); - dumpDisplayProto(*layersTrace); + auto displayProtos = dumpDisplayProto(); + layersTrace->mutable_displays()->Swap(&displayProtos); if (asProto) { result.append(traceFileProto.SerializeAsString()); @@ -5120,13 +5695,6 @@ status_t SurfaceFlinger::doDump(int fd, const DumpArgs& args, bool asProto) { } status_t SurfaceFlinger::dumpCritical(int fd, const DumpArgs&, bool asProto) { - if (asProto) { - mLayerTracing.writeToFile(); - if (mTransactionTracing) { - mTransactionTracing->writeToFile(); - } - } - return doDump(fd, DumpArgs(), asProto); } @@ -5137,17 +5705,14 @@ void SurfaceFlinger::listLayersLocked(std::string& result) const { void SurfaceFlinger::dumpStatsLocked(const DumpArgs& args, std::string& result) const { StringAppendF(&result, "%" PRId64 "\n", getVsyncPeriodFromHWC()); + if (args.size() < 2) return; - if (args.size() > 1) { - const auto name = String8(args[1]); - mCurrentState.traverseInZOrder([&](Layer* layer) { - if (layer->getName() == name.c_str()) { - layer->dumpFrameStats(result); - } - }); - } else { - mAnimFrameTracker.dumpStats(result); - } + const auto name = String8(args[1]); + mCurrentState.traverseInZOrder([&](Layer* layer) { + if (layer->getName() == name.c_str()) { + layer->dumpFrameStats(result); + } + }); } void SurfaceFlinger::clearStatsLocked(const DumpArgs& args, std::string&) { @@ -5159,8 +5724,6 @@ void SurfaceFlinger::clearStatsLocked(const DumpArgs& args, std::string&) { layer->clearFrameStats(); } }); - - mAnimFrameTracker.clearStats(); } void SurfaceFlinger::dumpTimeStats(const DumpArgs& args, bool asProto, std::string& result) const { @@ -5171,12 +5734,13 @@ void SurfaceFlinger::dumpFrameTimeline(const DumpArgs& args, std::string& result mFrameTimeline->parseArgs(args, result); } -void SurfaceFlinger::logFrameStats() { - mDrawingState.traverse([&](Layer* layer) { - layer->logFrameStats(); - }); +void SurfaceFlinger::logFrameStats(TimePoint now) { + static TimePoint sTimestamp = now; + if (now - sTimestamp < 30min) return; + sTimestamp = now; - mAnimFrameTracker.logAndResetStats(""); + ATRACE_CALL(); + mDrawingState.traverse([&](Layer* layer) { layer->logFrameStats(); }); } void SurfaceFlinger::appendSfConfigString(std::string& result) const { @@ -5192,24 +5756,31 @@ void SurfaceFlinger::appendSfConfigString(std::string& result) const { result.append("]"); } -void SurfaceFlinger::dumpVSync(std::string& result) const { - mScheduler->dump(result); +void SurfaceFlinger::dumpScheduler(std::string& result) const { + utils::Dumper dumper{result}; + + mScheduler->dump(dumper); + + // TODO(b/241285876): Move to DisplayModeController. + dumper.dump("debugDisplayModeSetByBackdoor"sv, mDebugDisplayModeSetByBackdoor); + dumper.eol(); mRefreshRateStats->dump(result); - result.append("\n"); + dumper.eol(); mVsyncConfiguration->dump(result); StringAppendF(&result, - " present offset: %9" PRId64 " ns\t VSYNC period: %9" PRId64 " ns\n\n", + " present offset: %9" PRId64 " ns\t VSYNC period: %9" PRId64 + " ns\n\n", dispSyncPresentTimeOffset, getVsyncPeriodFromHWC()); +} - StringAppendF(&result, "(mode override by backdoor: %s)\n\n", - mDebugDisplayModeSetByBackdoor ? "yes" : "no"); - +void SurfaceFlinger::dumpEvents(std::string& result) const { mScheduler->dump(mAppConnectionHandle, result); +} + +void SurfaceFlinger::dumpVsync(std::string& result) const { mScheduler->dumpVsync(result); - StringAppendF(&result, "mHWCVsyncPendingState=%s mLastHWCVsyncState=%s\n", - to_string(mHWCVsyncPendingState).c_str(), to_string(mLastHWCVsyncState).c_str()); } void SurfaceFlinger::dumpPlannerInfo(const DumpArgs& args, std::string& result) const { @@ -5219,21 +5790,6 @@ void SurfaceFlinger::dumpPlannerInfo(const DumpArgs& args, std::string& result) } } -void SurfaceFlinger::dumpStaticScreenStats(std::string& result) const { - result.append("Static screen stats:\n"); - for (size_t b = 0; b < SurfaceFlingerBE::NUM_BUCKETS - 1; ++b) { - float bucketTimeSec = getBE().mFrameBuckets[b] / 1e9; - float percent = 100.0f * - static_cast(getBE().mFrameBuckets[b]) / getBE().mTotalTime; - StringAppendF(&result, " < %zd frames: %.3f s (%.1f%%)\n", b + 1, bucketTimeSec, percent); - } - float bucketTimeSec = getBE().mFrameBuckets[SurfaceFlingerBE::NUM_BUCKETS - 1] / 1e9; - float percent = 100.0f * - static_cast(getBE().mFrameBuckets[SurfaceFlingerBE::NUM_BUCKETS - 1]) / getBE().mTotalTime; - StringAppendF(&result, " %zd+ frames: %.3f s (%.1f%%)\n", SurfaceFlingerBE::NUM_BUCKETS - 1, - bucketTimeSec, percent); -} - void SurfaceFlinger::dumpCompositionDisplays(std::string& result) const { for (const auto& [token, display] : mDisplays) { display->getCompositionDisplay()->dump(result); @@ -5242,9 +5798,25 @@ void SurfaceFlinger::dumpCompositionDisplays(std::string& result) const { } void SurfaceFlinger::dumpDisplays(std::string& result) const { + utils::Dumper dumper{result}; + + for (const auto& [id, display] : mPhysicalDisplays) { + utils::Dumper::Section section(dumper, ftl::Concat("Display ", id.value).str()); + + display.snapshot().dump(dumper); + + if (const auto device = getDisplayDeviceLocked(id)) { + device->dump(dumper); + } + } + for (const auto& [token, display] : mDisplays) { - display->dump(result); - result += '\n'; + if (display->isVirtual()) { + const auto displayId = display->getId(); + utils::Dumper::Section section(dumper, + ftl::Concat("Virtual Display ", displayId.value).str()); + display->dump(dumper); + } } } @@ -5299,44 +5871,60 @@ void SurfaceFlinger::dumpRawDisplayIdentificationData(const DumpArgs& args, } void SurfaceFlinger::dumpWideColorInfo(std::string& result) const { - StringAppendF(&result, "Device has wide color built-in display: %d\n", hasWideColorDisplay); + StringAppendF(&result, "Device supports wide color: %d\n", mSupportsWideColor); StringAppendF(&result, "Device uses color management: %d\n", useColorManagement); StringAppendF(&result, "DisplayColorSetting: %s\n", decodeDisplayColorSetting(mDisplayColorSetting).c_str()); // TODO: print out if wide-color mode is active or not - for (const auto& [token, display] : mDisplays) { - const auto displayId = PhysicalDisplayId::tryCast(display->getId()); - if (!displayId) { - continue; - } - - StringAppendF(&result, "Display %s color modes:\n", to_string(*displayId).c_str()); - std::vector modes = getHwComposer().getColorModes(*displayId); - for (auto&& mode : modes) { + 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); } - ColorMode currentMode = display->getCompositionDisplay()->getState().colorMode; - StringAppendF(&result, " Current color mode: %s (%d)\n", - decodeColorMode(currentMode).c_str(), currentMode); + 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); + } } result.append("\n"); } LayersProto SurfaceFlinger::dumpDrawingStateProto(uint32_t traceFlags) const { - LayersProto layersProto; - for (const sp& layer : mDrawingState.layersSortedByZ) { - layer->writeToProto(layersProto, traceFlags); + std::unordered_set stackIdsToSkip; + + // Determine if virtual layers display should be skipped + if ((traceFlags & LayerTracing::TRACE_VIRTUAL_DISPLAYS) == 0) { + for (const auto& [_, display] : FTL_FAKE_GUARD(mStateLock, mDisplays)) { + if (display->isVirtual()) { + stackIdsToSkip.insert(display->getLayerStack().id); + } + } + } + + if (mLegacyFrontEndEnabled) { + LayersProto layersProto; + for (const sp& layer : mDrawingState.layersSortedByZ) { + if (stackIdsToSkip.find(layer->getLayerStack().id) != stackIdsToSkip.end()) { + continue; + } + layer->writeToProto(layersProto, traceFlags); + } + return layersProto; } - return layersProto; + return LayerProtoFromSnapshotGenerator(mLayerSnapshotBuilder, mFrontEndDisplayInfos, + mLegacyLayers, traceFlags) + .generate(mLayerHierarchyBuilder.getHierarchy()); } -void SurfaceFlinger::dumpDisplayProto(LayersTraceProto& layersTraceProto) const { +google::protobuf::RepeatedPtrField SurfaceFlinger::dumpDisplayProto() const { + google::protobuf::RepeatedPtrField displays; for (const auto& [_, display] : FTL_FAKE_GUARD(mStateLock, mDisplays)) { - DisplayProto* displayProto = layersTraceProto.add_displays(); + DisplayProto* displayProto = displays.Add(); displayProto->set_id(display->getId().value); displayProto->set_name(display->getDisplayName()); displayProto->set_layer_stack(display->getLayerStack().id); @@ -5349,6 +5937,7 @@ void SurfaceFlinger::dumpDisplayProto(LayersTraceProto& layersTraceProto) const displayProto->mutable_transform()); displayProto->set_is_virtual(display->isVirtual()); } + return displays; } void SurfaceFlinger::dumpHwc(std::string& result) const { @@ -5383,7 +5972,7 @@ void SurfaceFlinger::dumpOffscreenLayers(std::string& result) { std::string result; for (Layer* offscreenLayer : mOffscreenLayers) { offscreenLayer->traverse(LayerVector::StateSet::Drawing, - [&](Layer* layer) { layer->dumpCallingUidPid(result); }); + [&](Layer* layer) { layer->dumpOffscreenDebugInfo(result); }); } return result; }); @@ -5392,6 +5981,23 @@ void SurfaceFlinger::dumpOffscreenLayers(std::string& result) { result.append(future.get()); } +void SurfaceFlinger::dumpHwcLayersMinidumpLocked(std::string& result) const { + for (const auto& [token, display] : mDisplays) { + const auto displayId = HalDisplayId::tryCast(display->getId()); + if (!displayId) { + continue; + } + + StringAppendF(&result, "Display %s (%s) HWC layers:\n", to_string(*displayId).c_str(), + displayId == mActiveDisplayId ? "active" : "inactive"); + Layer::miniDumpHeader(result); + + const DisplayDevice& ref = *display; + mDrawingState.traverseInZOrder([&](Layer* layer) { layer->miniDump(result, ref); }); + result.append("\n"); + } +} + void SurfaceFlinger::dumpAllLocked(const DumpArgs& args, const std::string& compositionLayers, std::string& result) const { const bool colorize = !args.empty() && args[0] == String16("--color"); @@ -5427,10 +6033,9 @@ void SurfaceFlinger::dumpAllLocked(const DumpArgs& args, const std::string& comp colorizer.bold(result); result.append("Scheduler:\n"); colorizer.reset(result); - dumpVSync(result); - result.append("\n"); - - dumpStaticScreenStats(result); + dumpScheduler(result); + dumpEvents(result); + dumpVsync(result); result.append("\n"); StringAppendF(&result, "Total missed frame count: %u\n", mFrameMissedCount.load()); @@ -5475,17 +6080,15 @@ void SurfaceFlinger::dumpAllLocked(const DumpArgs& args, const std::string& comp StringAppendF(&result, " orientation=%s, isPoweredOn=%d\n", toCString(display->getOrientation()), display->isPoweredOn()); } - StringAppendF(&result, - " transaction-flags : %08x\n" - " gpu_to_cpu_unsupported : %d\n", - mTransactionFlags.load(), !mGpuToCpuSupported); + StringAppendF(&result, " transaction-flags : %08x\n", mTransactionFlags.load()); if (const auto display = getDefaultDisplayDeviceLocked()) { std::string fps, xDpi, yDpi; - if (const auto activeMode = display->getActiveMode()) { - fps = to_string(activeMode->getFps()); + if (const auto activeModePtr = + display->refreshRateSelector().getActiveMode().modePtr.get()) { + fps = to_string(activeModePtr->getFps()); - const auto dpi = activeMode->getDpi(); + const auto dpi = activeModePtr->getDpi(); xDpi = base::StringPrintf("%.2f", dpi.x); yDpi = base::StringPrintf("%.2f", dpi.y); } else { @@ -5516,23 +6119,7 @@ void SurfaceFlinger::dumpAllLocked(const DumpArgs& args, const std::string& comp } result.push_back('\n'); - /* - * HWC layer minidump - */ - for (const auto& [token, display] : mDisplays) { - const auto displayId = HalDisplayId::tryCast(display->getId()); - if (!displayId) { - continue; - } - - StringAppendF(&result, "Display %s (%s) HWC layers:\n", to_string(*displayId).c_str(), - (isDisplayActiveLocked(display) ? "active" : "inactive")); - Layer::miniDumpHeader(result); - - const DisplayDevice& ref = *display; - mCurrentState.traverseInZOrder([&](Layer* layer) { layer->miniDump(result, ref); }); - result.append("\n"); - } + dumpHwcLayersMinidumpLocked(result); { DumpArgs plannerArgs; @@ -5564,6 +6151,15 @@ void SurfaceFlinger::dumpAllLocked(const DumpArgs& args, const std::string& comp result.append(mTimeStats->miniDump()); result.append("\n"); + + result.append("Window Infos:\n"); + auto windowInfosDebug = mWindowInfosListenerInvoker->getDebugInfo(); + StringAppendF(&result, " max send vsync id: %" PRId64 "\n", + windowInfosDebug.maxSendDelayVsyncId.value); + StringAppendF(&result, " max send delay (ns): %" PRId64 " ns\n", + windowInfosDebug.maxSendDelayDuration); + StringAppendF(&result, " unsent messages: %zu\n", windowInfosDebug.pendingMessageCount); + result.append("\n"); } mat4 SurfaceFlinger::calculateColorMatrix(float saturation) { @@ -5595,29 +6191,11 @@ status_t SurfaceFlinger::CheckTransactCodeCredentials(uint32_t code) { #pragma clang diagnostic push #pragma clang diagnostic error "-Wswitch-enum" switch (static_cast(code)) { - case ENABLE_VSYNC_INJECTIONS: - case INJECT_VSYNC: - if (!hasMockHwc()) return PERMISSION_DENIED; - [[fallthrough]]; // These methods should at minimum make sure that the client requested // access to SF. - case BOOT_FINISHED: - case CLEAR_ANIMATION_FRAME_STATS: - case GET_ANIMATION_FRAME_STATS: - case OVERRIDE_HDR_TYPES: case GET_HDR_CAPABILITIES: - case SET_DESIRED_DISPLAY_MODE_SPECS: - case GET_DESIRED_DISPLAY_MODE_SPECS: - case SET_ACTIVE_COLOR_MODE: - case SET_BOOT_DISPLAY_MODE: case GET_AUTO_LOW_LATENCY_MODE_SUPPORT: case GET_GAME_CONTENT_TYPE_SUPPORT: - case GET_DISPLAYED_CONTENT_SAMPLING_ATTRIBUTES: - case SET_DISPLAY_CONTENT_SAMPLING_ENABLED: - case GET_DISPLAYED_CONTENT_SAMPLE: - case ADD_TUNNEL_MODE_ENABLED_LISTENER: - case REMOVE_TUNNEL_MODE_ENABLED_LISTENER: - case SET_GLOBAL_SHADOW_SETTINGS: case ACQUIRE_FRAME_RATE_FLEXIBILITY_TOKEN: { // OVERRIDE_HDR_TYPES is used by CTS tests, which acquire the necessary // permission dynamically. Don't use the permission cache for this check. @@ -5630,100 +6208,38 @@ status_t SurfaceFlinger::CheckTransactCodeCredentials(uint32_t code) { } return OK; } - case GET_LAYER_DEBUG_INFO: { - IPCThreadState* ipc = IPCThreadState::self(); - const int pid = ipc->getCallingPid(); - const int uid = ipc->getCallingUid(); - if ((uid != AID_SHELL) && !PermissionCache::checkPermission(sDump, pid, uid)) { - ALOGE("Layer debug info permission denied for pid=%d, uid=%d", pid, uid); - return PERMISSION_DENIED; - } - return OK; - } - // Used by apps to hook Choreographer to SurfaceFlinger. - case CREATE_DISPLAY_EVENT_CONNECTION: // The following calls are currently used by clients that do not // request necessary permissions. However, they do not expose any secret // information, so it is OK to pass them. - case AUTHENTICATE_SURFACE: case GET_ACTIVE_COLOR_MODE: case GET_ACTIVE_DISPLAY_MODE: case GET_DISPLAY_COLOR_MODES: - case GET_DISPLAY_NATIVE_PRIMARIES: - case GET_STATIC_DISPLAY_INFO: - case GET_DYNAMIC_DISPLAY_INFO: case GET_DISPLAY_MODES: - case GET_SUPPORTED_FRAME_TIMESTAMPS: // Calling setTransactionState is safe, because you need to have been // granted a reference to Client* and Handle* to do anything with it. - case SET_TRANSACTION_STATE: - case CREATE_CONNECTION: - case GET_COLOR_MANAGEMENT: - case GET_COMPOSITION_PREFERENCE: - case GET_PROTECTED_CONTENT_SUPPORT: - // setFrameRate() is deliberately available for apps to call without any - // special permissions. - case SET_FRAME_RATE: - case GET_DISPLAY_DECORATION_SUPPORT: - case SET_FRAME_TIMELINE_INFO: - case GET_GPU_CONTEXT_PRIORITY: - case GET_MAX_ACQUIRED_BUFFER_COUNT: { + case SET_TRANSACTION_STATE: { // This is not sensitive information, so should not require permission control. return OK; } - case ADD_FPS_LISTENER: - case REMOVE_FPS_LISTENER: - case ADD_REGION_SAMPLING_LISTENER: - case REMOVE_REGION_SAMPLING_LISTENER: { - // codes that require permission check - IPCThreadState* ipc = IPCThreadState::self(); - const int pid = ipc->getCallingPid(); - const int uid = ipc->getCallingUid(); - if ((uid != AID_GRAPHICS) && - !PermissionCache::checkPermission(sReadFramebuffer, pid, uid)) { - ALOGE("Permission Denial: can't read framebuffer pid=%d, uid=%d", pid, uid); - return PERMISSION_DENIED; - } - return OK; - } - case ADD_TRANSACTION_TRACE_LISTENER: { - IPCThreadState* ipc = IPCThreadState::self(); - const int uid = ipc->getCallingUid(); - if (uid == AID_ROOT || uid == AID_GRAPHICS || uid == AID_SYSTEM || uid == AID_SHELL) { - return OK; - } - return PERMISSION_DENIED; - } - case SET_OVERRIDE_FRAME_RATE: { - const int uid = IPCThreadState::self()->getCallingUid(); - if (uid == AID_ROOT || uid == AID_SYSTEM) { - return OK; - } - return PERMISSION_DENIED; - } - case ON_PULL_ATOM: { - const int uid = IPCThreadState::self()->getCallingUid(); - if (uid == AID_SYSTEM) { - return OK; - } - return PERMISSION_DENIED; - } - case ADD_WINDOW_INFOS_LISTENER: - case REMOVE_WINDOW_INFOS_LISTENER: { - const int uid = IPCThreadState::self()->getCallingUid(); - if (uid == AID_SYSTEM || uid == AID_GRAPHICS) { - return OK; - } - return PERMISSION_DENIED; - } + case BOOT_FINISHED: + // Used by apps to hook Choreographer to SurfaceFlinger. + case CREATE_DISPLAY_EVENT_CONNECTION: + case CREATE_CONNECTION: case CREATE_DISPLAY: case DESTROY_DISPLAY: case GET_PRIMARY_PHYSICAL_DISPLAY_ID: case GET_PHYSICAL_DISPLAY_IDS: case GET_PHYSICAL_DISPLAY_TOKEN: + case AUTHENTICATE_SURFACE: case SET_POWER_MODE: + case GET_SUPPORTED_FRAME_TIMESTAMPS: case GET_DISPLAY_STATE: case GET_DISPLAY_STATS: + case GET_STATIC_DISPLAY_INFO: + case GET_DYNAMIC_DISPLAY_INFO: + case GET_DISPLAY_NATIVE_PRIMARIES: + case SET_ACTIVE_COLOR_MODE: + case SET_BOOT_DISPLAY_MODE: case CLEAR_BOOT_DISPLAY_MODE: case GET_BOOT_DISPLAY_MODE_SUPPORT: case SET_AUTO_LOW_LATENCY_MODE: @@ -5731,12 +6247,43 @@ status_t SurfaceFlinger::CheckTransactCodeCredentials(uint32_t code) { case CAPTURE_LAYERS: case CAPTURE_DISPLAY: case CAPTURE_DISPLAY_BY_ID: + case CLEAR_ANIMATION_FRAME_STATS: + case GET_ANIMATION_FRAME_STATS: + case OVERRIDE_HDR_TYPES: + case ON_PULL_ATOM: + case ENABLE_VSYNC_INJECTIONS: + case INJECT_VSYNC: + case GET_LAYER_DEBUG_INFO: + case GET_COLOR_MANAGEMENT: + case GET_COMPOSITION_PREFERENCE: + case GET_DISPLAYED_CONTENT_SAMPLING_ATTRIBUTES: + case SET_DISPLAY_CONTENT_SAMPLING_ENABLED: + case GET_DISPLAYED_CONTENT_SAMPLE: + case GET_PROTECTED_CONTENT_SUPPORT: case IS_WIDE_COLOR_DISPLAY: + case ADD_REGION_SAMPLING_LISTENER: + case REMOVE_REGION_SAMPLING_LISTENER: + case ADD_FPS_LISTENER: + case REMOVE_FPS_LISTENER: + case ADD_TUNNEL_MODE_ENABLED_LISTENER: + case REMOVE_TUNNEL_MODE_ENABLED_LISTENER: + case ADD_WINDOW_INFOS_LISTENER: + case REMOVE_WINDOW_INFOS_LISTENER: + case SET_DESIRED_DISPLAY_MODE_SPECS: + case GET_DESIRED_DISPLAY_MODE_SPECS: case GET_DISPLAY_BRIGHTNESS_SUPPORT: case SET_DISPLAY_BRIGHTNESS: case ADD_HDR_LAYER_INFO_LISTENER: case REMOVE_HDR_LAYER_INFO_LISTENER: case NOTIFY_POWER_BOOST: + case SET_GLOBAL_SHADOW_SETTINGS: + case GET_DISPLAY_DECORATION_SUPPORT: + case SET_FRAME_RATE: + case SET_OVERRIDE_FRAME_RATE: + case SET_FRAME_TIMELINE_INFO: + case ADD_TRANSACTION_TRACE_LISTENER: + case GET_GPU_CONTEXT_PRIORITY: + case GET_MAX_ACQUIRED_BUFFER_COUNT: LOG_FATAL("Deprecated opcode: %d, migrated to AIDL", code); return PERMISSION_DENIED; } @@ -5818,15 +6365,8 @@ status_t SurfaceFlinger::onTransact(uint32_t code, const Parcel& data, Parcel* r reply->writeInt32(0); reply->writeInt32(mDebugDisableHWC); return NO_ERROR; - case 1013: { - const auto display = getDefaultDisplayDevice(); - if (!display) { - return NAME_NOT_FOUND; - } - - reply->writeInt32(display->getPageFlipCount()); - return NO_ERROR; - } + case 1013: // Unused. + return NAME_NOT_FOUND; case 1014: { Mutex::Autolock _l(mStateLock); // daltonize @@ -5897,17 +6437,8 @@ status_t SurfaceFlinger::onTransact(uint32_t code, const Parcel& data, Parcel* r mScheduler->setDuration(mSfConnectionHandle, std::chrono::nanoseconds(n), 0ns); return NO_ERROR; } - case 1020: { // Layer updates interceptor - n = data.readInt32(); - if (n) { - ALOGV("Interceptor enabled"); - mInterceptor->enable(mDrawingState.layersSortedByZ, mDrawingState.displays); - } - else{ - ALOGV("Interceptor disabled"); - mInterceptor->disable(); - } - return NO_ERROR; + case 1020: { // Unused + return NAME_NOT_FOUND; } case 1021: { // Disable HWC virtual displays const bool enable = data.readInt32() != 0; @@ -5922,12 +6453,11 @@ status_t SurfaceFlinger::onTransact(uint32_t code, const Parcel& data, Parcel* r updateColorMatrixLocked(); return NO_ERROR; } - case 1023: { // Set native mode - int32_t colorMode; - + case 1023: { // Set color mode. mDisplayColorSetting = static_cast(data.readInt32()); - if (data.readInt32(&colorMode) == NO_ERROR) { - mForceColorMode = static_cast(colorMode); + + if (int32_t colorMode; data.readInt32(&colorMode) == NO_ERROR) { + mForceColorMode = static_cast(colorMode); } scheduleRepaint(); return NO_ERROR; @@ -5947,8 +6477,10 @@ status_t SurfaceFlinger::onTransact(uint32_t code, const Parcel& data, Parcel* r int64_t startingTime = (fixedStartingTime) ? fixedStartingTime : systemTime(); mScheduler - ->schedule([&]() FTL_FAKE_GUARD(mStateLock) { - mLayerTracing.notify("start", startingTime); + ->schedule([&]() FTL_FAKE_GUARD(mStateLock) FTL_FAKE_GUARD( + kMainThreadContext) { + addToLayerTracing(true /* visibleRegionDirty */, startingTime, + mLastCommittedVsyncId.value); }) .wait(); } @@ -6056,19 +6588,17 @@ status_t SurfaceFlinger::onTransact(uint32_t code, const Parcel& data, Parcel* r return NO_ERROR; } case 1034: { - auto future = mScheduler->schedule([&] { - switch (n = data.readInt32()) { - case 0: - case 1: - FTL_FAKE_GUARD(mStateLock, - enableRefreshRateOverlay(static_cast(n))); - break; - default: { - reply->writeBool( - FTL_FAKE_GUARD(mStateLock, isRefreshRateOverlayEnabled())); - } - } - }); + auto future = mScheduler->schedule( + [&]() FTL_FAKE_GUARD(mStateLock) FTL_FAKE_GUARD(kMainThreadContext) { + switch (n = data.readInt32()) { + case 0: + case 1: + enableRefreshRateOverlay(static_cast(n)); + break; + default: + reply->writeBool(isRefreshRateOverlayEnabled()); + } + }); future.wait(); return NO_ERROR; @@ -6091,7 +6621,7 @@ status_t SurfaceFlinger::onTransact(uint32_t code, const Parcel& data, Parcel* r }(); mDebugDisplayModeSetByBackdoor = false; - const status_t result = setActiveModeFromBackdoor(display, modeId); + const status_t result = setActiveModeFromBackdoor(display, DisplayModeId{modeId}); mDebugDisplayModeSetByBackdoor = result == NO_ERROR; return result; } @@ -6101,7 +6631,7 @@ status_t SurfaceFlinger::onTransact(uint32_t code, const Parcel& data, Parcel* r case 1036: { if (data.readInt32() > 0) { // turn on return mScheduler - ->schedule([this] { + ->schedule([this]() FTL_FAKE_GUARD(kMainThreadContext) { const auto display = FTL_FAKE_GUARD(mStateLock, getDefaultDisplayDeviceLocked()); @@ -6111,24 +6641,22 @@ status_t SurfaceFlinger::onTransact(uint32_t code, const Parcel& data, Parcel* r // defaultMode. The defaultMode doesn't matter for the override // policy though, since we set allowGroupSwitching to true, so it's // not a problem. - scheduler::RefreshRateConfigs::Policy overridePolicy; - overridePolicy.defaultMode = display->refreshRateConfigs() + scheduler::RefreshRateSelector::OverridePolicy overridePolicy; + overridePolicy.defaultMode = display->refreshRateSelector() .getDisplayManagerPolicy() .defaultMode; overridePolicy.allowGroupSwitching = true; - constexpr bool kOverridePolicy = true; - return setDesiredDisplayModeSpecsInternal(display, overridePolicy, - kOverridePolicy); + return setDesiredDisplayModeSpecsInternal(display, overridePolicy); }) .get(); } else { // turn off return mScheduler - ->schedule([this] { + ->schedule([this]() FTL_FAKE_GUARD(kMainThreadContext) { const auto display = FTL_FAKE_GUARD(mStateLock, getDefaultDisplayDeviceLocked()); - constexpr bool kOverridePolicy = true; - return setDesiredDisplayModeSpecsInternal(display, {}, - kOverridePolicy); + return setDesiredDisplayModeSpecsInternal( + display, + scheduler::RefreshRateSelector::NoOverridePolicy{}); }) .get(); } @@ -6239,7 +6767,7 @@ void SurfaceFlinger::kernelTimerChanged(bool expired) { if (!updateOverlay) return; // Update the overlay on the main thread to avoid race conditions with - // mRefreshRateConfigs->getActiveMode() + // RefreshRateSelector::getActiveMode static_cast(mScheduler->schedule([=] { const auto display = FTL_FAKE_GUARD(mStateLock, getDefaultDisplayDeviceLocked()); if (!display) { @@ -6250,7 +6778,8 @@ void SurfaceFlinger::kernelTimerChanged(bool expired) { const auto desiredActiveMode = display->getDesiredActiveMode(); const std::optional desiredModeId = desiredActiveMode - ? std::make_optional(desiredActiveMode->mode->getId()) + ? std::make_optional(desiredActiveMode->modeOpt->modePtr->getId()) + : std::nullopt; const bool timerExpired = mKernelIdleTimerEnabled && expired; @@ -6303,7 +6832,7 @@ void SurfaceFlinger::updateKernelIdleTimer(std::chrono::milliseconds timeout, } void SurfaceFlinger::toggleKernelIdleTimer() { - using KernelIdleTimerAction = scheduler::RefreshRateConfigs::KernelIdleTimerAction; + using KernelIdleTimerAction = scheduler::RefreshRateSelector::KernelIdleTimerAction; const auto display = getDefaultDisplayDeviceLocked(); if (!display) { @@ -6314,12 +6843,12 @@ void SurfaceFlinger::toggleKernelIdleTimer() { // If the support for kernel idle timer is disabled for the active display, // don't do anything. const std::optional kernelIdleTimerController = - display->refreshRateConfigs().kernelIdleTimerController(); + display->refreshRateSelector().kernelIdleTimerController(); if (!kernelIdleTimerController.has_value()) { return; } - const KernelIdleTimerAction action = display->refreshRateConfigs().getIdleTimerAction(); + const KernelIdleTimerAction action = display->refreshRateSelector().getIdleTimerAction(); switch (action) { case KernelIdleTimerAction::TurnOff: @@ -6335,7 +6864,7 @@ void SurfaceFlinger::toggleKernelIdleTimer() { if (!mKernelIdleTimerEnabled) { ATRACE_INT("KernelIdleTimer", 1); const std::chrono::milliseconds timeout = - display->refreshRateConfigs().getIdleTimerTimeout(); + display->refreshRateSelector().getIdleTimerTimeout(); updateKernelIdleTimer(timeout, kernelIdleTimerController.value(), display->getPhysicalId()); mKernelIdleTimerEnabled = true; @@ -6357,18 +6886,6 @@ private: const int mApi; }; -static Dataspace pickDataspaceFromColorMode(const ColorMode colorMode) { - switch (colorMode) { - case ColorMode::DISPLAY_P3: - case ColorMode::BT2100_PQ: - case ColorMode::BT2100_HLG: - case ColorMode::DISPLAY_BT2020: - return Dataspace::DISPLAY_P3; - default: - return Dataspace::V0_SRGB; - } -} - static bool hasCaptureBlackoutContentPermission() { IPCThreadState* ipc = IPCThreadState::self(); const int pid = ipc->getCallingPid(); @@ -6453,6 +6970,33 @@ status_t SurfaceFlinger::setSchedAttr(bool enabled) { return NO_ERROR; } +namespace { + +ui::Dataspace pickBestDataspace(ui::Dataspace requestedDataspace, const DisplayDevice* display, + bool capturingHdrLayers, bool hintForSeamlessTransition) { + if (requestedDataspace != ui::Dataspace::UNKNOWN || display == nullptr) { + return requestedDataspace; + } + + const auto& state = display->getCompositionDisplay()->getState(); + + const auto dataspaceForColorMode = ui::pickDataspaceFor(state.colorMode); + + // TODO: Enable once HDR screenshots are ready. + if constexpr (/* DISABLES CODE */ (false)) { + // For now since we only support 8-bit screenshots, just use HLG and + // assume that 1.0 >= display max luminance. This isn't quite as future + // proof as PQ is, but is good enough. + // Consider using PQ once we support 16-bit screenshots and we're able + // to consistently supply metadata to image encoders. + return ui::Dataspace::BT2020_HLG; + } + + return dataspaceForColorMode; +} + +} // namespace + status_t SurfaceFlinger::captureDisplay(const DisplayCaptureArgs& args, const sp& captureListener) { ATRACE_CALL(); @@ -6467,7 +7011,7 @@ status_t SurfaceFlinger::captureDisplay(const DisplayCaptureArgs& args, wp displayWeak; ui::LayerStack layerStack; ui::Size reqSize(args.width, args.height); - ui::Dataspace dataspace; + std::unordered_set excludeLayerIds; { Mutex::Autolock lock(mStateLock); sp display = getDisplayDeviceLocked(args.displayToken); @@ -6480,27 +7024,36 @@ status_t SurfaceFlinger::captureDisplay(const DisplayCaptureArgs& args, reqSize = display->getLayerStackSpaceRect().getSize(); } - // The dataspace is depended on the color mode of display, that could use non-native mode - // (ex. displayP3) to enhance the content, but some cases are checking native RGB in bytes, - // and failed if display is not in native mode. This provide a way to force using native - // colors when capture. - dataspace = args.dataspace; - if (dataspace == ui::Dataspace::UNKNOWN) { - const ui::ColorMode colorMode = display->getCompositionDisplay()->getState().colorMode; - dataspace = pickDataspaceFromColorMode(colorMode); + for (const auto& handle : args.excludeHandles) { + uint32_t excludeLayer = LayerHandle::getLayerId(handle); + if (excludeLayer != UNASSIGNED_LAYER_ID) { + excludeLayerIds.emplace(excludeLayer); + } else { + ALOGW("Invalid layer handle passed as excludeLayer to captureDisplay"); + return NAME_NOT_FOUND; + } } } RenderAreaFuture renderAreaFuture = ftl::defer([=] { - return DisplayRenderArea::create(displayWeak, args.sourceCrop, reqSize, dataspace, - args.useIdentityTransform, args.captureSecureLayers); + return DisplayRenderArea::create(displayWeak, args.sourceCrop, reqSize, args.dataspace, + args.useIdentityTransform, args.hintForSeamlessTransition, + args.captureSecureLayers); }); - auto traverseLayers = [this, args, layerStack](const LayerVector::Visitor& visitor) { - traverseLayersInLayerStack(layerStack, args.uid, visitor); - }; + 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); + } - auto future = captureScreenCommon(std::move(renderAreaFuture), traverseLayers, reqSize, + auto future = captureScreenCommon(std::move(renderAreaFuture), getLayerSnapshots, reqSize, args.pixelFormat, args.allowProtected, args.grayscale, captureListener); return fenceStatus(future.get()); @@ -6511,7 +7064,6 @@ status_t SurfaceFlinger::captureDisplay(DisplayId displayId, ui::LayerStack layerStack; wp displayWeak; ui::Size size; - ui::Dataspace dataspace; { Mutex::Autolock lock(mStateLock); @@ -6523,20 +7075,25 @@ status_t SurfaceFlinger::captureDisplay(DisplayId displayId, displayWeak = display; layerStack = display->getLayerStack(); size = display->getLayerStackSpaceRect().getSize(); - - dataspace = - pickDataspaceFromColorMode(display->getCompositionDisplay()->getState().colorMode); } RenderAreaFuture renderAreaFuture = ftl::defer([=] { - return DisplayRenderArea::create(displayWeak, Rect(), size, dataspace, + return DisplayRenderArea::create(displayWeak, Rect(), size, ui::Dataspace::UNKNOWN, false /* useIdentityTransform */, + false /* hintForSeamlessTransition */, false /* captureSecureLayers */); }); - auto traverseLayers = [this, layerStack](const LayerVector::Visitor& visitor) { - traverseLayersInLayerStack(layerStack, CaptureArgs::UNSET_UID, visitor); - }; + 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); + } if (captureListener == nullptr) { ALOGE("capture screen must provide a capture listener callback"); @@ -6546,7 +7103,7 @@ status_t SurfaceFlinger::captureDisplay(DisplayId displayId, constexpr bool kAllowProtected = false; constexpr bool kGrayscale = false; - auto future = captureScreenCommon(std::move(renderAreaFuture), traverseLayers, size, + auto future = captureScreenCommon(std::move(renderAreaFuture), getLayerSnapshots, size, ui::PixelFormat::RGBA_8888, kAllowProtected, kGrayscale, captureListener); return fenceStatus(future.get()); @@ -6564,8 +7121,8 @@ status_t SurfaceFlinger::captureLayers(const LayerCaptureArgs& args, ui::Size reqSize; sp parent; Rect crop(args.sourceCrop); - std::unordered_set, SpHash> excludeLayers; - ui::Dataspace dataspace; + std::unordered_set excludeLayerIds; + ui::Dataspace dataspace = args.dataspace; // Call this before holding mStateLock to avoid any deadlocking. bool canCaptureBlackoutContent = hasCaptureBlackoutContentPermission(); @@ -6573,7 +7130,7 @@ status_t SurfaceFlinger::captureLayers(const LayerCaptureArgs& args, { Mutex::Autolock lock(mStateLock); - parent = fromHandle(args.layerHandle).promote(); + parent = LayerHandle::getLayer(args.layerHandle); if (parent == nullptr) { ALOGE("captureLayers called with an invalid or removed parent"); return NAME_NOT_FOUND; @@ -6604,20 +7161,14 @@ status_t SurfaceFlinger::captureLayers(const LayerCaptureArgs& args, reqSize = ui::Size(crop.width() * args.frameScaleX, crop.height() * args.frameScaleY); for (const auto& handle : args.excludeHandles) { - sp excludeLayer = fromHandle(handle).promote(); - if (excludeLayer != nullptr) { - excludeLayers.emplace(excludeLayer); + uint32_t excludeLayer = LayerHandle::getLayerId(handle); + if (excludeLayer != UNASSIGNED_LAYER_ID) { + excludeLayerIds.emplace(excludeLayer); } else { ALOGW("Invalid layer handle passed as excludeLayer to captureLayers"); return NAME_NOT_FOUND; } } - - // The dataspace is depended on the color mode of display, that could use non-native mode - // (ex. displayP3) to enhance the content, but some cases are checking native RGB in bytes, - // and failed if display is not in native mode. This provide a way to force using native - // colors when capture. - dataspace = args.dataspace; } // mStateLock // really small crop or frameScale @@ -6626,49 +7177,78 @@ status_t SurfaceFlinger::captureLayers(const LayerCaptureArgs& args, return BAD_VALUE; } - Rect layerStackSpaceRect(0, 0, reqSize.width, reqSize.height); bool childrenOnly = args.childrenOnly; RenderAreaFuture renderAreaFuture = ftl::defer([=]() -> std::unique_ptr { + ui::Transform layerTransform; + Rect layerBufferSize; + if (mLayerLifecycleManagerEnabled) { + frontend::LayerSnapshot* snapshot = + mLayerSnapshotBuilder.getSnapshot(parent->getSequence()); + if (!snapshot) { + ALOGW("Couldn't find layer snapshot for %d", parent->getSequence()); + } else { + layerTransform = snapshot->localTransform; + layerBufferSize = snapshot->bufferSize; + } + } else { + layerTransform = parent->getTransform(); + layerBufferSize = parent->getBufferSize(parent->getDrawingState()); + } + return std::make_unique(*this, parent, crop, reqSize, dataspace, - childrenOnly, layerStackSpaceRect, - args.captureSecureLayers); + childrenOnly, args.captureSecureLayers, + layerTransform, layerBufferSize, + args.hintForSeamlessTransition); }); + GetLayerSnapshotsFunction getLayerSnapshots; + if (mLayerLifecycleManagerEnabled) { + std::optional parentCrop = std::nullopt; + if (args.childrenOnly) { + parentCrop = crop.isEmpty() ? FloatRect(0, 0, reqSize.width, reqSize.height) + : crop.toFloatRect(); + } - auto traverseLayers = [parent, args, excludeLayers](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; - } - - sp p = layer; - while (p != nullptr) { - if (excludeLayers.count(p) != 0) { + 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; } - p = p->getParent(); - } - visitor(layer); - }); - }; + auto p = sp::fromExisting(layer); + while (p != nullptr) { + if (excludeLayerIds.count(p->sequence) != 0) { + return; + } + p = p->getParent(); + } + + visitor(layer); + }); + }; + getLayerSnapshots = RenderArea::fromTraverseLayersLambda(traverseLayers); + } if (captureListener == nullptr) { ALOGE("capture screen must provide a capture listener callback"); return BAD_VALUE; } - auto future = captureScreenCommon(std::move(renderAreaFuture), traverseLayers, reqSize, + auto future = captureScreenCommon(std::move(renderAreaFuture), getLayerSnapshots, reqSize, args.pixelFormat, args.allowProtected, args.grayscale, captureListener); return fenceStatus(future.get()); } ftl::SharedFuture SurfaceFlinger::captureScreenCommon( - RenderAreaFuture renderAreaFuture, TraverseLayersFunction traverseLayers, + RenderAreaFuture renderAreaFuture, GetLayerSnapshotsFunction getLayerSnapshots, ui::Size bufferSize, ui::PixelFormat reqPixelFormat, bool allowProtected, bool grayscale, const sp& captureListener) { ATRACE_CALL(); @@ -6687,15 +7267,18 @@ ftl::SharedFuture SurfaceFlinger::captureScreenCommon( const bool supportsProtected = getRenderEngine().supportsProtectedContent(); bool hasProtectedLayer = false; if (allowProtected && supportsProtected) { - auto future = mScheduler->schedule([=]() { - bool protectedLayerFound = false; - traverseLayers([&](Layer* layer) { - protectedLayerFound = - protectedLayerFound || (layer->isVisible() && layer->isProtected()); - }); - return protectedLayerFound; - }); - hasProtectedLayer = future.get(); + hasProtectedLayer = mScheduler + ->schedule([=]() { + bool protectedLayerFound = false; + auto layers = getLayerSnapshots(); + for (auto& [_, layerFe] : layers) { + protectedLayerFound |= + (layerFe->mSnapshot->isVisible && + layerFe->mSnapshot->hasProtectedContent); + } + return protectedLayerFound; + }) + .get(); } const uint32_t usage = GRALLOC_USAGE_HW_COMPOSER | GRALLOC_USAGE_HW_RENDER | @@ -6720,56 +7303,52 @@ ftl::SharedFuture SurfaceFlinger::captureScreenCommon( renderengine::impl::ExternalTexture>(buffer, getRenderEngine(), renderengine::impl::ExternalTexture::Usage:: WRITEABLE); - return captureScreenCommon(std::move(renderAreaFuture), traverseLayers, texture, + return captureScreenCommon(std::move(renderAreaFuture), getLayerSnapshots, texture, false /* regionSampling */, grayscale, captureListener); } ftl::SharedFuture SurfaceFlinger::captureScreenCommon( - RenderAreaFuture renderAreaFuture, TraverseLayersFunction traverseLayers, + RenderAreaFuture renderAreaFuture, GetLayerSnapshotsFunction getLayerSnapshots, const std::shared_ptr& buffer, bool regionSampling, bool grayscale, const sp& captureListener) { ATRACE_CALL(); bool canCaptureBlackoutContent = hasCaptureBlackoutContentPermission(); - auto future = mScheduler->schedule([=, renderAreaFuture = std::move(renderAreaFuture)]() mutable - -> ftl::SharedFuture { - ScreenCaptureResults captureResults; - std::unique_ptr renderArea = renderAreaFuture.get(); - if (!renderArea) { - ALOGW("Skipping screen capture because of invalid render area."); - if (captureListener) { - captureResults.result = NO_MEMORY; - captureListener->onScreenCaptureCompleted(captureResults); - } - return ftl::yield(base::unexpected(NO_ERROR)).share(); - } + auto future = mScheduler->schedule( + [=, 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(); + } - ftl::SharedFuture renderFuture; - renderArea->render([&] { - renderFuture = - renderScreenImpl(*renderArea, traverseLayers, buffer, canCaptureBlackoutContent, - regionSampling, grayscale, captureResults); - }); + ftl::SharedFuture renderFuture; + renderArea->render([&]() FTL_FAKE_GUARD(kMainThreadContext) { + renderFuture = renderScreenImpl(renderArea, getLayerSnapshots, buffer, + canCaptureBlackoutContent, regionSampling, + grayscale, captureResults); + }); - if (captureListener) { - // TODO: The future returned by std::async blocks the main thread. Return a chain of - // futures to the Binder thread instead. - (void)std::async([=]() mutable { - ATRACE_NAME("captureListener is nonnull!"); - auto fenceResult = renderFuture.get(); - // TODO(b/232535621): Change ScreenCaptureResults to store a FenceResult. - captureResults.result = fenceStatus(fenceResult); - captureResults.fence = std::move(fenceResult).value_or(Fence::NO_FENCE); - captureListener->onScreenCaptureCompleted(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; }); - } - return renderFuture; - }); - - if (captureListener) { - return ftl::yield(base::unexpected(NO_ERROR)).share(); - } // Flatten nested futures. auto chain = ftl::Future(std::move(future)).then([](ftl::SharedFuture future) { @@ -6780,18 +7359,22 @@ ftl::SharedFuture SurfaceFlinger::captureScreenCommon( } ftl::SharedFuture SurfaceFlinger::renderScreenImpl( - const RenderArea& renderArea, TraverseLayersFunction traverseLayers, + std::shared_ptr renderArea, GetLayerSnapshotsFunction getLayerSnapshots, const std::shared_ptr& buffer, bool canCaptureBlackoutContent, bool regionSampling, bool grayscale, ScreenCaptureResults& captureResults) { ATRACE_CALL(); - traverseLayers([&](Layer* layer) { - captureResults.capturedSecureLayers = - captureResults.capturedSecureLayers || (layer->isVisible() && layer->isSecure()); - }); - - const bool useProtected = buffer->getUsage() & GRALLOC_USAGE_PROTECTED; + auto layers = getLayerSnapshots(); + for (auto& [_, layerFE] : layers) { + frontend::LayerSnapshot* snapshot = layerFE->mSnapshot.get(); + captureResults.capturedSecureLayers |= (snapshot->isVisible && snapshot->isSecure); + captureResults.capturedHdrLayers |= isHdrLayer(*snapshot); + layerFE->mSnapshot->geomLayerTransform = + renderArea->getTransform() * layerFE->mSnapshot->geomLayerTransform; + layerFE->mSnapshot->geomInverseLayerTransform = + layerFE->mSnapshot->geomLayerTransform.inverse(); + } // We allow the system server to take screenshots of secure layers for // use in situations like the Screen-rotation animation and place @@ -6801,146 +7384,153 @@ ftl::SharedFuture SurfaceFlinger::renderScreenImpl( return ftl::yield(base::unexpected(PERMISSION_DENIED)).share(); } - captureResults.buffer = buffer->getBuffer(); - auto dataspace = renderArea.getReqDataSpace(); - auto parent = renderArea.getParentLayer(); + auto capturedBuffer = buffer; + + auto requestedDataspace = renderArea->getReqDataSpace(); + auto parent = renderArea->getParentLayer(); auto renderIntent = RenderIntent::TONE_MAP_COLORIMETRIC; auto sdrWhitePointNits = DisplayDevice::sDefaultMaxLumiance; auto displayBrightnessNits = DisplayDevice::sDefaultMaxLumiance; - if ((dataspace == ui::Dataspace::UNKNOWN) && (parent != nullptr)) { + captureResults.capturedDataspace = requestedDataspace; + + { Mutex::Autolock lock(mStateLock); - auto display = findDisplay([layerStack = parent->getLayerStack()](const auto& display) { - return display.getLayerStack() == layerStack; - }); - if (!display) { - // If the layer is not on a display, use the dataspace for the default display. - display = getDefaultDisplayDeviceLocked(); - } - - const ui::ColorMode colorMode = display->getCompositionDisplay()->getState().colorMode; - dataspace = pickDataspaceFromColorMode(colorMode); - renderIntent = display->getCompositionDisplay()->getState().renderIntent; - sdrWhitePointNits = display->getCompositionDisplay()->getState().sdrWhitePointNits; - displayBrightnessNits = display->getCompositionDisplay()->getState().displayBrightnessNits; - } - captureResults.capturedDataspace = dataspace; - - const auto reqWidth = renderArea.getReqWidth(); - const auto reqHeight = renderArea.getReqHeight(); - const auto sourceCrop = renderArea.getSourceCrop(); - const auto transform = renderArea.getTransform(); - const auto rotation = renderArea.getRotationFlags(); - const auto& layerStackSpaceRect = renderArea.getLayerStackSpaceRect(); - - renderengine::DisplaySettings clientCompositionDisplay; - std::vector clientCompositionLayers; - - // assume that bounds are never offset, and that they are the same as the - // buffer bounds. - clientCompositionDisplay.physicalDisplay = Rect(reqWidth, reqHeight); - clientCompositionDisplay.clip = sourceCrop; - clientCompositionDisplay.orientation = rotation; - - clientCompositionDisplay.outputDataspace = dataspace; - clientCompositionDisplay.currentLuminanceNits = displayBrightnessNits; - clientCompositionDisplay.maxLuminance = DisplayDevice::sDefaultMaxLumiance; - clientCompositionDisplay.renderIntent = - static_cast(renderIntent); - - const float colorSaturation = grayscale ? 0 : 1; - clientCompositionDisplay.colorTransform = calculateColorMatrix(colorSaturation); - - const float alpha = RenderArea::getCaptureFillValue(renderArea.getCaptureFill()); - - compositionengine::LayerFE::LayerSettings fillLayer; - fillLayer.source.buffer.buffer = nullptr; - fillLayer.source.solidColor = half3(0.0, 0.0, 0.0); - fillLayer.geometry.boundaries = - FloatRect(sourceCrop.left, sourceCrop.top, sourceCrop.right, sourceCrop.bottom); - fillLayer.alpha = half(alpha); - clientCompositionLayers.push_back(fillLayer); - - const auto display = renderArea.getDisplayDevice(); - std::vector renderedLayers; - bool disableBlurs = false; - traverseLayers([&](Layer* layer) { - disableBlurs |= layer->getDrawingState().sidebandStream != nullptr; - - Region clip(renderArea.getBounds()); - compositionengine::LayerFE::ClientCompositionTargetSettings targetSettings{ - clip, - layer->needsFilteringForScreenshots(display.get(), transform) || - renderArea.needsFiltering(), - renderArea.isSecure(), - useProtected, - layerStackSpaceRect, - clientCompositionDisplay.outputDataspace, - true, /* realContentIsVisible */ - false, /* clearContent */ - disableBlurs ? compositionengine::LayerFE::ClientCompositionTargetSettings:: - BlurSetting::Disabled - : compositionengine::LayerFE::ClientCompositionTargetSettings:: - BlurSetting::Enabled, - isHdrLayer(layer) ? displayBrightnessNits : sdrWhitePointNits, + const DisplayDevice* display = nullptr; + if (parent) { + display = findDisplay([layerStack = parent->getLayerStack()](const auto& display) { + return display.getLayerStack() == layerStack; + }).get(); + } + + if (display == nullptr) { + display = renderArea->getDisplayDevice().get(); + } + + if (display == nullptr) { + display = getDefaultDisplayDeviceLocked().get(); + } + + if (display != nullptr) { + const auto& state = display->getCompositionDisplay()->getState(); + captureResults.capturedDataspace = + pickBestDataspace(requestedDataspace, display, captureResults.capturedHdrLayers, + renderArea->getHintForSeamlessTransition()); + sdrWhitePointNits = state.sdrWhitePointNits; + displayBrightnessNits = state.displayBrightnessNits; + if (sdrWhitePointNits > 1.0f) { + // 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); + } - }; - std::vector results = - layer->prepareClientCompositionList(targetSettings); - if (results.size() > 0) { - for (auto& settings : results) { - settings.geometry.positionTransform = - transform.asMatrix4() * settings.geometry.positionTransform; - // There's no need to process blurs when we're executing region sampling, - // we're just trying to understand what we're drawing, and doing so without - // blurs is already a pretty good approximation. - if (regionSampling) { - settings.backgroundBlurRadius = 0; - } - captureResults.capturedHdrLayers |= isHdrLayer(layer); + if (requestedDataspace == ui::Dataspace::UNKNOWN) { + renderIntent = state.renderIntent; } + } + } + + captureResults.buffer = capturedBuffer->getBuffer(); + + ui::LayerStack layerStack{ui::DEFAULT_LAYER_STACK}; + if (!layers.empty()) { + const sp& layerFE = layers.back().second; + layerStack = layerFE->getCompositionState()->outputFilter.layerStack; + } - clientCompositionLayers.insert(clientCompositionLayers.end(), - std::make_move_iterator(results.begin()), - std::make_move_iterator(results.end())); - renderedLayers.push_back(layer); + auto copyLayerFEs = [&layers]() { + std::vector> layerFEs; + layerFEs.reserve(layers.size()); + for (const auto& [_, layerFE] : layers) { + layerFEs.push_back(layerFE); } + return layerFEs; + }; - }); + auto present = [this, buffer = capturedBuffer, dataspace = captureResults.capturedDataspace, + sdrWhitePointNits, displayBrightnessNits, grayscale, layerFEs = copyLayerFEs(), + layerStack, regionSampling, renderArea = std::move(renderArea), + renderIntent]() -> FenceResult { + std::unique_ptr compositionEngine = + mFactory.createCompositionEngine(); + compositionEngine->setRenderEngine(mRenderEngine.get()); + + compositionengine::Output::ColorProfile colorProfile{.dataspace = dataspace, + .renderIntent = renderIntent}; + + float targetBrightness = 1.0f; + 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. + if (maxBrightnessNits < 1000.f) { + targetBrightness = 1000.f / maxBrightnessNits; + } + } - std::vector clientRenderEngineLayers; - clientRenderEngineLayers.reserve(clientCompositionLayers.size()); - std::transform(clientCompositionLayers.begin(), clientCompositionLayers.end(), - std::back_inserter(clientRenderEngineLayers), - [](compositionengine::LayerFE::LayerSettings& settings) - -> renderengine::LayerSettings { return settings; }); + std::shared_ptr output = createScreenCaptureOutput( + ScreenCaptureOutputArgs{.compositionEngine = *compositionEngine, + .colorProfile = colorProfile, + .renderArea = *renderArea, + .layerStack = layerStack, + .buffer = std::move(buffer), + .sdrWhitePointNits = sdrWhitePointNits, + .displayBrightnessNits = displayBrightnessNits, + .targetBrightness = targetBrightness, + .regionSampling = regionSampling}); + + const float colorSaturation = grayscale ? 0 : 1; + compositionengine::CompositionRefreshArgs refreshArgs{ + .outputs = {output}, + .layers = std::move(layerFEs), + .updatingOutputGeometryThisFrame = true, + .updatingGeometryThisFrame = true, + .colorTransformMatrix = calculateColorMatrix(colorSaturation), + }; + compositionEngine->present(refreshArgs); - // Use an empty fence for the buffer fence, since we just created the buffer so - // there is no need for synchronization with the GPU. - base::unique_fd bufferFence; - getRenderEngine().useProtectedContext(useProtected); + return output->getRenderSurface()->getClientTargetAcquireFence(); + }; - constexpr bool kUseFramebufferCache = false; - auto chain = - ftl::Future(getRenderEngine().drawLayers(clientCompositionDisplay, - clientRenderEngineLayers, buffer, - kUseFramebufferCache, std::move(bufferFence))) - .then(&toFenceResult); + // If RenderEngine is threaded, we can safely call CompositionEngine::present off the main + // thread as the RenderEngine::drawLayers call will run on RenderEngine's thread. Otherwise, + // we need RenderEngine to run on the main thread so we call CompositionEngine::present + // immediately. + // + // TODO(b/196334700) Once we use RenderEngineThreaded everywhere we can always defer the call + // to CompositionEngine::present. + const bool renderEngineIsThreaded = [&]() { + using Type = renderengine::RenderEngine::RenderEngineType; + const auto type = mRenderEngine->getRenderEngineType(); + return type == Type::THREADED || type == Type::SKIA_GL_THREADED; + }(); + auto presentFuture = renderEngineIsThreaded ? ftl::defer(std::move(present)).share() + : ftl::yield(present()).share(); - const auto future = chain.share(); - for (auto* layer : renderedLayers) { - layer->onLayerDisplayed(future); + for (auto& [layer, layerFE] : layers) { + layer->onLayerDisplayed(ftl::Future(presentFuture) + .then([layerFE = std::move(layerFE)](FenceResult) { + return layerFE->stealCompositionResult() + .releaseFences.back() + .first.get(); + }) + .share(), + ui::INVALID_LAYER_STACK); } - // Always switch back to unprotected context. - getRenderEngine().useProtectedContext(false); - - return future; + return presentFuture; } -void SurfaceFlinger::windowInfosReported() { - Mutex::Autolock _l(mStateLock); - signalSynchronousTransactions(CountDownLatch::eSyncInputWindows); +void SurfaceFlinger::traverseLegacyLayers(const LayerVector::Visitor& visitor) const { + if (mLayerLifecycleManagerEnabled) { + for (auto& layer : mLegacyLayers) { + visitor(layer.second.get()); + } + } else { + mDrawingState.traverse(visitor); + } } // --------------------------------------------------------------------------- @@ -6958,6 +7548,7 @@ void SurfaceFlinger::State::traverseInReverseZOrder(const LayerVector::Visitor& } void SurfaceFlinger::traverseLayersInLayerStack(ui::LayerStack layerStack, const int32_t uid, + std::unordered_set excludeLayerIds, const LayerVector::Visitor& visitor) { // We loop through the first level of layers without traversing, // as we need to determine which layers belong to the requested display. @@ -6976,14 +7567,44 @@ void SurfaceFlinger::traverseLayersInLayerStack(ui::LayerStack layerStack, const if (uid != CaptureArgs::UNSET_UID && layer->getOwnerUid() != uid) { return; } + + if (!excludeLayerIds.empty()) { + auto p = sp::fromExisting(layer); + while (p != nullptr) { + if (excludeLayerIds.count(p->sequence) != 0) { + return; + } + p = p->getParent(); + } + } + visitor(layer); }); } } +ftl::Optional SurfaceFlinger::getPreferredDisplayMode( + PhysicalDisplayId displayId, DisplayModeId defaultModeId) const { + if (const auto schedulerMode = mScheduler->getPreferredDisplayMode(); + schedulerMode.modePtr->getPhysicalDisplayId() == displayId) { + return schedulerMode; + } + + return mPhysicalDisplays.get(displayId) + .transform(&PhysicalDisplay::snapshotRef) + .and_then([&](const display::DisplaySnapshot& snapshot) { + return snapshot.displayModes().get(defaultModeId); + }) + .transform([](const DisplayModePtr& modePtr) { + return scheduler::FrameRateMode{modePtr->getFps(), ftl::as_non_null(modePtr)}; + }); +} + status_t SurfaceFlinger::setDesiredDisplayModeSpecsInternal( const sp& display, - const std::optional& policy, bool overridePolicy) { + const scheduler::RefreshRateSelector::PolicyVariant& policy) { + const auto displayId = display->getPhysicalId(); + Mutex::Autolock lock(mStateLock); if (mDebugDisplayModeSetByBackdoor) { @@ -6991,74 +7612,100 @@ status_t SurfaceFlinger::setDesiredDisplayModeSpecsInternal( return NO_ERROR; } - const status_t setPolicyResult = display->setRefreshRatePolicy(policy, overridePolicy); - if (setPolicyResult < 0) { - return BAD_VALUE; - } - if (setPolicyResult == scheduler::RefreshRateConfigs::CURRENT_POLICY_UNCHANGED) { - return NO_ERROR; + auto& selector = display->refreshRateSelector(); + using SetPolicyResult = scheduler::RefreshRateSelector::SetPolicyResult; + + switch (selector.setPolicy(policy)) { + case SetPolicyResult::Invalid: + return BAD_VALUE; + case SetPolicyResult::Unchanged: + return NO_ERROR; + case SetPolicyResult::Changed: + break; } - if (display->isInternal() && !isDisplayActiveLocked(display)) { + const bool isInternalDisplay = mPhysicalDisplays.get(displayId) + .transform(&PhysicalDisplay::isInternal) + .value_or(false); + + if (isInternalDisplay && displayId != mActiveDisplayId) { // The policy will be be applied when the display becomes active. - ALOGV("%s(%s): Inactive display", __func__, to_string(display->getId()).c_str()); + ALOGV("%s(%s): Inactive display", __func__, to_string(displayId).c_str()); return NO_ERROR; } - return applyRefreshRateConfigsPolicy(display); + return applyRefreshRateSelectorPolicy(displayId, selector); } -status_t SurfaceFlinger::applyRefreshRateConfigsPolicy(const sp& display, - bool force) { - const scheduler::RefreshRateConfigs::Policy currentPolicy = - display->refreshRateConfigs().getCurrentPolicy(); +status_t SurfaceFlinger::applyRefreshRateSelectorPolicy( + PhysicalDisplayId displayId, const scheduler::RefreshRateSelector& selector, bool force) { + const scheduler::RefreshRateSelector::Policy currentPolicy = selector.getCurrentPolicy(); ALOGV("Setting desired display mode specs: %s", currentPolicy.toString().c_str()); // TODO(b/140204874): Leave the event in until we do proper testing with all apps that might // be depending in this callback. - const auto activeMode = display->getActiveMode(); - if (isDisplayActiveLocked(display)) { + if (const auto activeMode = selector.getActiveMode(); displayId == mActiveDisplayId) { mScheduler->onPrimaryDisplayModeChanged(mAppConnectionHandle, activeMode); toggleKernelIdleTimer(); } else { mScheduler->onNonPrimaryDisplayModeChanged(mAppConnectionHandle, activeMode); } - const DisplayModePtr preferredDisplayMode = [&] { - const auto schedulerMode = mScheduler->getPreferredDisplayMode(); - if (schedulerMode && schedulerMode->getPhysicalDisplayId() == display->getPhysicalId()) { - return schedulerMode; - } + auto preferredModeOpt = getPreferredDisplayMode(displayId, currentPolicy.defaultMode); + if (!preferredModeOpt) { + ALOGE("%s: Preferred mode is unknown", __func__); + return NAME_NOT_FOUND; + } - return display->getMode(currentPolicy.defaultMode); - }(); + auto preferredMode = std::move(*preferredModeOpt); + const auto preferredModeId = preferredMode.modePtr->getId(); - ALOGV("trying to switch to Scheduler preferred mode %d (%s)", - preferredDisplayMode->getId().value(), to_string(preferredDisplayMode->getFps()).c_str()); + ALOGV("Switching to Scheduler preferred mode %d (%s)", preferredModeId.value(), + to_string(preferredMode.fps).c_str()); - if (display->refreshRateConfigs().isModeAllowed(preferredDisplayMode->getId())) { - ALOGV("switching to Scheduler preferred display mode %d", - preferredDisplayMode->getId().value()); - setDesiredActiveMode({preferredDisplayMode, DisplayModeEvent::Changed}, force); - } else { - LOG_ALWAYS_FATAL("Desired display mode not allowed: %d", - preferredDisplayMode->getId().value()); + if (!selector.isModeAllowed(preferredMode)) { + ALOGE("%s: Preferred mode %d is disallowed", __func__, preferredModeId.value()); + return INVALID_OPERATION; } + setDesiredActiveMode({std::move(preferredMode), .emitEvent = true}, force); return NO_ERROR; } -status_t SurfaceFlinger::setDesiredDisplayModeSpecs( - const sp& displayToken, ui::DisplayModeId defaultMode, bool allowGroupSwitching, - float primaryRefreshRateMin, float primaryRefreshRateMax, float appRequestRefreshRateMin, - float appRequestRefreshRateMax) { - ATRACE_CALL(); +namespace { +FpsRange translate(const gui::DisplayModeSpecs::RefreshRateRanges::RefreshRateRange& aidlRange) { + return FpsRange{Fps::fromValue(aidlRange.min), Fps::fromValue(aidlRange.max)}; +} - if (!displayToken) { - return BAD_VALUE; +FpsRanges translate(const gui::DisplayModeSpecs::RefreshRateRanges& aidlRanges) { + return FpsRanges{translate(aidlRanges.physical), translate(aidlRanges.render)}; +} + +gui::DisplayModeSpecs::RefreshRateRanges::RefreshRateRange translate(const FpsRange& range) { + gui::DisplayModeSpecs::RefreshRateRanges::RefreshRateRange aidlRange; + aidlRange.min = range.min.getValue(); + aidlRange.max = range.max.getValue(); + return aidlRange; +} + +gui::DisplayModeSpecs::RefreshRateRanges translate(const FpsRanges& ranges) { + gui::DisplayModeSpecs::RefreshRateRanges aidlRanges; + aidlRanges.physical = translate(ranges.physical); + aidlRanges.render = translate(ranges.render); + return aidlRanges; +} + +} // namespace + +status_t SurfaceFlinger::setDesiredDisplayModeSpecs(const sp& displayToken, + const gui::DisplayModeSpecs& specs) { + ATRACE_CALL(); + + if (!displayToken) { + return BAD_VALUE; } - auto future = mScheduler->schedule([=]() -> status_t { + auto future = mScheduler->schedule([=]() FTL_FAKE_GUARD(kMainThreadContext) -> status_t { const auto display = FTL_FAKE_GUARD(mStateLock, getDisplayDeviceLocked(displayToken)); if (!display) { ALOGE("Attempt to set desired display modes for invalid display token %p", @@ -7068,16 +7715,11 @@ status_t SurfaceFlinger::setDesiredDisplayModeSpecs( ALOGW("Attempt to set desired display modes for virtual display"); return INVALID_OPERATION; } else { - using Policy = scheduler::RefreshRateConfigs::Policy; - const Policy policy{DisplayModeId(defaultMode), - allowGroupSwitching, - {Fps::fromValue(primaryRefreshRateMin), - Fps::fromValue(primaryRefreshRateMax)}, - {Fps::fromValue(appRequestRefreshRateMin), - Fps::fromValue(appRequestRefreshRateMax)}}; - constexpr bool kOverridePolicy = false; + using Policy = scheduler::RefreshRateSelector::DisplayManagerPolicy; + const Policy policy{DisplayModeId(specs.defaultMode), translate(specs.primaryRanges), + translate(specs.appRequestRanges), specs.allowGroupSwitching}; - return setDesiredDisplayModeSpecsInternal(display, policy, kOverridePolicy); + return setDesiredDisplayModeSpecsInternal(display, policy); } }); @@ -7085,16 +7727,10 @@ status_t SurfaceFlinger::setDesiredDisplayModeSpecs( } status_t SurfaceFlinger::getDesiredDisplayModeSpecs(const sp& displayToken, - ui::DisplayModeId* outDefaultMode, - bool* outAllowGroupSwitching, - float* outPrimaryRefreshRateMin, - float* outPrimaryRefreshRateMax, - float* outAppRequestRefreshRateMin, - float* outAppRequestRefreshRateMax) { + gui::DisplayModeSpecs* outSpecs) { ATRACE_CALL(); - if (!displayToken || !outDefaultMode || !outPrimaryRefreshRateMin || - !outPrimaryRefreshRateMax || !outAppRequestRefreshRateMin || !outAppRequestRefreshRateMax) { + if (!displayToken || !outSpecs) { return BAD_VALUE; } @@ -7108,21 +7744,15 @@ status_t SurfaceFlinger::getDesiredDisplayModeSpecs(const sp& displayTo return INVALID_OPERATION; } - scheduler::RefreshRateConfigs::Policy policy = - display->refreshRateConfigs().getDisplayManagerPolicy(); - *outDefaultMode = policy.defaultMode.value(); - *outAllowGroupSwitching = policy.allowGroupSwitching; - *outPrimaryRefreshRateMin = policy.primaryRange.min.getValue(); - *outPrimaryRefreshRateMax = policy.primaryRange.max.getValue(); - *outAppRequestRefreshRateMin = policy.appRequestRange.min.getValue(); - *outAppRequestRefreshRateMax = policy.appRequestRange.max.getValue(); + scheduler::RefreshRateSelector::Policy policy = + display->refreshRateSelector().getDisplayManagerPolicy(); + outSpecs->defaultMode = policy.defaultMode.value(); + outSpecs->allowGroupSwitching = policy.allowGroupSwitching; + outSpecs->primaryRanges = translate(policy.primaryRanges); + outSpecs->appRequestRanges = translate(policy.appRequestRanges); return NO_ERROR; } -wp SurfaceFlinger::fromHandle(const sp& handle) const { - return Layer::fromHandle(handle); -} - void SurfaceFlinger::onLayerFirstRef(Layer* layer) { mNumLayers++; if (!layer->isRemovedFromCurrentState()) { @@ -7185,45 +7815,12 @@ const std::unordered_map& SurfaceFlinger::getGenericLayer // on the work to remove the table in that bug rather than adding more to // it. static const std::unordered_map genericLayerMetadataKeyMap{ - {"org.chromium.arc.V1_0.TaskId", METADATA_TASK_ID}, - {"org.chromium.arc.V1_0.CursorInfo", METADATA_MOUSE_CURSOR}, + {"org.chromium.arc.V1_0.TaskId", gui::METADATA_TASK_ID}, + {"org.chromium.arc.V1_0.CursorInfo", gui::METADATA_MOUSE_CURSOR}, }; return genericLayerMetadataKeyMap; } -status_t SurfaceFlinger::setFrameRate(const sp& surface, float frameRate, - int8_t compatibility, int8_t changeFrameRateStrategy) { - if (!ValidateFrameRate(frameRate, compatibility, changeFrameRateStrategy, - "SurfaceFlinger::setFrameRate")) { - return BAD_VALUE; - } - - static_cast(mScheduler->schedule([=] { - Mutex::Autolock lock(mStateLock); - if (authenticateSurfaceTextureLocked(surface)) { - sp layer = (static_cast(surface.get()))->getLayer(); - if (layer == nullptr) { - ALOGE("Attempt to set frame rate on a layer that no longer exists"); - return BAD_VALUE; - } - const auto strategy = - Layer::FrameRate::convertChangeFrameRateStrategy(changeFrameRateStrategy); - if (layer->setFrameRate( - Layer::FrameRate(Fps::fromValue(frameRate), - Layer::FrameRate::convertCompatibility(compatibility), - strategy))) { - setTransactionFlags(eTraversalNeeded); - } - } else { - ALOGE("Attempt to set frame rate on an unrecognized IGraphicBufferProducer"); - return BAD_VALUE; - } - return NO_ERROR; - })); - - return NO_ERROR; -} - status_t SurfaceFlinger::setOverrideFrameRate(uid_t uid, float frameRate) { PhysicalDisplayId displayId = [&]() { Mutex::Autolock lock(mStateLock); @@ -7235,44 +7832,29 @@ status_t SurfaceFlinger::setOverrideFrameRate(uid_t uid, float frameRate) { return NO_ERROR; } -status_t SurfaceFlinger::setFrameTimelineInfo(const sp& surface, - const FrameTimelineInfo& frameTimelineInfo) { - Mutex::Autolock lock(mStateLock); - if (!authenticateSurfaceTextureLocked(surface)) { - ALOGE("Attempt to set frame timeline info on an unrecognized IGraphicBufferProducer"); - return BAD_VALUE; - } - - sp layer = (static_cast(surface.get()))->getLayer(); - if (layer == nullptr) { - ALOGE("Attempt to set frame timeline info on a layer that no longer exists"); - return BAD_VALUE; - } - - layer->setFrameTimelineInfoForBuffer(frameTimelineInfo); - return NO_ERROR; -} - void SurfaceFlinger::enableRefreshRateOverlay(bool enable) { - for (const auto& [ignored, display] : mDisplays) { - if (display->isInternal()) { - display->enableRefreshRateOverlay(enable, mRefreshRateOverlaySpinner); - } - } -} + bool setByHwc = getHwComposer().hasCapability(Capability::REFRESH_RATE_CHANGED_CALLBACK_DEBUG); + for (const auto& [id, display] : mPhysicalDisplays) { + if (display.snapshot().connectionType() == ui::DisplayConnectionType::Internal) { + if (setByHwc) { + const auto status = + getHwComposer().setRefreshRateChangedCallbackDebugEnabled(id, enable); + if (status != NO_ERROR) { + ALOGE("Error updating the refresh rate changed callback debug enabled"); + return; + } + } -status_t SurfaceFlinger::addTransactionTraceListener( - const sp& listener) { - if (!listener) { - return BAD_VALUE; + if (const auto device = getDisplayDeviceLocked(id)) { + device->enableRefreshRateOverlay(enable, setByHwc, mRefreshRateOverlaySpinner, + mRefreshRateOverlayRenderRate, + mRefreshRateOverlayShowInMiddle); + } + } } - - mInterceptor->addTransactionTraceListener(listener); - - return NO_ERROR; } -int SurfaceFlinger::getGPUContextPriority() { +int SurfaceFlinger::getGpuContextPriority() { return getRenderEngine().getContextPriority(); } @@ -7290,7 +7872,7 @@ status_t SurfaceFlinger::getMaxAcquiredBufferCount(int* buffers) const { if (!getHwComposer().isHeadless()) { if (const auto display = getDefaultDisplayDevice()) { - maxRefreshRate = display->refreshRateConfigs().getSupportedRefreshRateRange().max; + maxRefreshRate = display->refreshRateSelector().getSupportedRefreshRateRange().max; } } @@ -7305,7 +7887,7 @@ uint32_t SurfaceFlinger::getMaxAcquiredBufferCountForCurrentRefreshRate(uid_t ui refreshRate = *frameRateOverride; } else if (!getHwComposer().isHeadless()) { if (const auto display = FTL_FAKE_GUARD(mStateLock, getDefaultDisplayDeviceLocked())) { - refreshRate = display->refreshRateConfigs().getActiveMode()->getFps(); + refreshRate = display->refreshRateSelector().getActiveMode().fps; } } @@ -7318,7 +7900,7 @@ int SurfaceFlinger::getMaxAcquiredBufferCountForRefreshRate(Fps refreshRate) con return calculateMaxAcquiredBufferCount(refreshRate, presentLatency); } -void SurfaceFlinger::handleLayerCreatedLocked(const LayerCreatedState& state) { +void SurfaceFlinger::handleLayerCreatedLocked(const LayerCreatedState& state, VsyncId vsyncId) { sp layer = state.layer.promote(); if (!layer) { ALOGD("Layer was destroyed soon after creation %p", state.layer.unsafe_get()); @@ -7348,9 +7930,19 @@ void SurfaceFlinger::handleLayerCreatedLocked(const LayerCreatedState& state) { parent->addChild(layer); } - layer->updateTransformHint(mActiveDisplayTransformHint); + ui::LayerStack layerStack = layer->getLayerStack(LayerVector::StateSet::Current); + sp hintDisplay; + // Find the display that includes the layer. + for (const auto& [token, display] : mDisplays) { + if (display->getLayerStack() == layerStack) { + hintDisplay = display; + break; + } + } - mInterceptor->saveSurfaceCreation(layer); + if (hintDisplay) { + layer->updateTransformHint(hintDisplay->getTransformHint()); + } } void SurfaceFlinger::sample() { @@ -7361,49 +7953,48 @@ void SurfaceFlinger::sample() { mRegionSamplingThread->onCompositionComplete(mScheduler->getScheduledFrameTime()); } -void SurfaceFlinger::onActiveDisplaySizeChanged(const sp& activeDisplay) { - mScheduler->onActiveDisplayAreaChanged(activeDisplay->getWidth() * activeDisplay->getHeight()); - getRenderEngine().onActiveDisplaySizeChanged(activeDisplay->getSize()); +void SurfaceFlinger::onActiveDisplaySizeChanged(const DisplayDevice& activeDisplay) { + mScheduler->onActiveDisplayAreaChanged(activeDisplay.getWidth() * activeDisplay.getHeight()); + getRenderEngine().onActiveDisplaySizeChanged(activeDisplay.getSize()); } -void SurfaceFlinger::onActiveDisplayChangedLocked(const sp& activeDisplay) { +void SurfaceFlinger::onActiveDisplayChangedLocked(const DisplayDevice* inactiveDisplayPtr, + const DisplayDevice& activeDisplay) { ATRACE_CALL(); - // During boot, SF powers on the primary display, which is the first display to be active. In - // that case, there is no need to force setDesiredActiveMode, because DM is about to send its - // policy via setDesiredDisplayModeSpecs. + // For the first display activated during boot, there is no need to force setDesiredActiveMode, + // because DM is about to send its policy via setDesiredDisplayModeSpecs. bool forceApplyPolicy = false; - if (const auto display = getDisplayDeviceLocked(mActiveDisplayToken)) { - display->getCompositionDisplay()->setLayerCachingTexturePoolEnabled(false); + if (inactiveDisplayPtr) { + inactiveDisplayPtr->getCompositionDisplay()->setLayerCachingTexturePoolEnabled(false); forceApplyPolicy = true; } - if (!activeDisplay) { - ALOGE("%s: activeDisplay is null", __func__); - return; - } + mActiveDisplayId = activeDisplay.getPhysicalId(); + activeDisplay.getCompositionDisplay()->setLayerCachingTexturePoolEnabled(true); - ALOGI("Active display is %s", to_string(activeDisplay->getPhysicalId()).c_str()); + resetPhaseConfiguration(activeDisplay.getActiveMode().fps); - mActiveDisplayToken = activeDisplay->getDisplayToken(); - activeDisplay->getCompositionDisplay()->setLayerCachingTexturePoolEnabled(true); - updateInternalDisplayVsyncLocked(activeDisplay); mScheduler->setModeChangePending(false); - mScheduler->setRefreshRateConfigs(activeDisplay->holdRefreshRateConfigs()); + mScheduler->setPacesetterDisplay(mActiveDisplayId); + onActiveDisplaySizeChanged(activeDisplay); - mActiveDisplayTransformHint = activeDisplay->getTransformHint(); + mActiveDisplayTransformHint = activeDisplay.getTransformHint(); + sActiveDisplayRotationFlags = ui::Transform::toRotationFlags(activeDisplay.getOrientation()); - // The policy of the new active/leader display may have changed while it was inactive. In that - // case, its preferred mode has not been propagated to HWC (via setDesiredActiveMode). 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. - applyRefreshRateConfigsPolicy(activeDisplay, forceApplyPolicy); + // 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 setDesiredActiveMode). 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); } -status_t SurfaceFlinger::addWindowInfosListener( - const sp& windowInfosListener) const { - mWindowInfosListenerInvoker->addWindowInfosListener(windowInfosListener); +status_t SurfaceFlinger::addWindowInfosListener(const sp& windowInfosListener, + gui::WindowInfosListenerInfo* outInfo) { + mWindowInfosListenerInvoker->addWindowInfosListener(windowInfosListener, outInfo); + setTransactionFlags(eInputInfoUpdateNeeded); return NO_ERROR; } @@ -7414,76 +8005,424 @@ status_t SurfaceFlinger::removeWindowInfosListener( } std::shared_ptr SurfaceFlinger::getExternalTextureFromBufferData( - const BufferData& bufferData, const char* layerName) const { - bool cacheIdChanged = bufferData.flags.test(BufferData::BufferDataChange::cachedBufferChanged); - bool bufferSizeExceedsLimit = false; - std::shared_ptr buffer = nullptr; - if (cacheIdChanged && bufferData.buffer != nullptr) { - bufferSizeExceedsLimit = exceedsMaxRenderTargetSize(bufferData.buffer->getWidth(), - bufferData.buffer->getHeight()); - if (!bufferSizeExceedsLimit) { - ClientCache::getInstance().add(bufferData.cachedBuffer, bufferData.buffer); - buffer = ClientCache::getInstance().get(bufferData.cachedBuffer); - } - } else if (cacheIdChanged) { - buffer = ClientCache::getInstance().get(bufferData.cachedBuffer); - } else if (bufferData.buffer != nullptr) { - bufferSizeExceedsLimit = exceedsMaxRenderTargetSize(bufferData.buffer->getWidth(), - bufferData.buffer->getHeight()); - if (!bufferSizeExceedsLimit) { - buffer = std::make_shared< - renderengine::impl::ExternalTexture>(bufferData.buffer, getRenderEngine(), - renderengine::impl::ExternalTexture:: - Usage::READABLE); - } - } - ALOGE_IF(bufferSizeExceedsLimit, - "Attempted to create an ExternalTexture for layer %s that exceeds render target size " - "limit.", - layerName); - return buffer; -} - -bool SurfaceFlinger::commitCreatedLayers() { - std::vector createdLayers; + BufferData& bufferData, const char* layerName, uint64_t transactionId) { + if (bufferData.buffer && + exceedsMaxRenderTargetSize(bufferData.buffer->getWidth(), bufferData.buffer->getHeight())) { + std::string errorMessage = + base::StringPrintf("Attempted to create an ExternalTexture with size (%u, %u) for " + "layer %s that exceeds render target size limit of %u.", + bufferData.buffer->getWidth(), bufferData.buffer->getHeight(), + layerName, static_cast(mMaxRenderTargetSize)); + ALOGD("%s", errorMessage.c_str()); + if (bufferData.releaseBufferListener) { + bufferData.releaseBufferListener->onTransactionQueueStalled( + String8(errorMessage.c_str())); + } + return nullptr; + } + + bool cachedBufferChanged = + bufferData.flags.test(BufferData::BufferDataChange::cachedBufferChanged); + if (cachedBufferChanged && bufferData.buffer) { + auto result = ClientCache::getInstance().add(bufferData.cachedBuffer, bufferData.buffer); + if (result.ok()) { + return result.value(); + } + + if (result.error() == ClientCache::AddError::CacheFull) { + ALOGE("Attempted to create an ExternalTexture for layer %s but CacheFull", layerName); + + if (bufferData.releaseBufferListener) { + bufferData.releaseBufferListener->onTransactionQueueStalled( + String8("Buffer processing hung due to full buffer cache")); + } + } + + return nullptr; + } + + if (cachedBufferChanged) { + return ClientCache::getInstance().get(bufferData.cachedBuffer); + } + + if (bufferData.buffer) { + return std::make_shared< + renderengine::impl::ExternalTexture>(bufferData.buffer, getRenderEngine(), + renderengine::impl::ExternalTexture::Usage:: + READABLE); + } + + return nullptr; +} + +bool SurfaceFlinger::commitMirrorDisplays(VsyncId vsyncId) { + std::vector mirrorDisplays; { - std::scoped_lock lock(mCreatedLayersLock); - createdLayers = std::move(mCreatedLayers); - mCreatedLayers.clear(); - if (createdLayers.size() == 0) { + std::scoped_lock lock(mMirrorDisplayLock); + mirrorDisplays = std::move(mMirrorDisplays); + mMirrorDisplays.clear(); + if (mirrorDisplays.size() == 0) { return false; } } + sp unused; + for (const auto& mirrorDisplay : mirrorDisplays) { + // Set mirror layer's default layer stack to -1 so it doesn't end up rendered on a display + // accidentally. + sp rootMirrorLayer = LayerHandle::getLayer(mirrorDisplay.rootHandle); + rootMirrorLayer->setLayerStack(ui::LayerStack::fromValue(-1)); + for (const auto& layer : mDrawingState.layersSortedByZ) { + if (layer->getLayerStack() != mirrorDisplay.layerStack || + layer->isInternalDisplayOverlay()) { + continue; + } + + LayerCreationArgs mirrorArgs(this, mirrorDisplay.client, "MirrorLayerParent", + ISurfaceComposerClient::eNoColorFill, + gui::LayerMetadata()); + sp childMirror; + { + Mutex::Autolock lock(mStateLock); + createEffectLayer(mirrorArgs, &unused, &childMirror); + MUTEX_ALIAS(mStateLock, childMirror->mFlinger->mStateLock); + childMirror->setClonedChild(layer->createClone(childMirror->getSequence())); + childMirror->reparent(mirrorDisplay.rootHandle); + } + // lock on mStateLock needs to be released before binder handle gets destroyed + unused.clear(); + } + } + return true; +} + +bool SurfaceFlinger::commitCreatedLayers(VsyncId vsyncId, + std::vector& createdLayers) { + if (createdLayers.size() == 0) { + return false; + } + Mutex::Autolock _l(mStateLock); for (const auto& createdLayer : createdLayers) { - handleLayerCreatedLocked(createdLayer); + handleLayerCreatedLocked(createdLayer, vsyncId); } - createdLayers.clear(); mLayersAdded = true; - return true; + return mLayersAdded; +} + +void SurfaceFlinger::updateLayerMetadataSnapshot() { + LayerMetadata parentMetadata; + for (const auto& layer : mDrawingState.layersSortedByZ) { + layer->updateMetadataSnapshot(parentMetadata); + } + + std::unordered_set visited; + mDrawingState.traverse([&visited](Layer* layer) { + if (visited.find(layer) != visited.end()) { + return; + } + + // If the layer isRelativeOf, then either it's relative metadata will be set + // recursively when updateRelativeMetadataSnapshot is called on its relative parent or + // it's relative parent has been deleted. Clear the layer's relativeLayerMetadata to ensure + // that layers with deleted relative parents don't hold stale relativeLayerMetadata. + if (layer->getDrawingState().isRelativeOf) { + layer->editLayerSnapshot()->relativeLayerMetadata = {}; + return; + } + + layer->updateRelativeMetadataSnapshot({}, visited); + }); +} + +void SurfaceFlinger::moveSnapshotsFromCompositionArgs( + compositionengine::CompositionRefreshArgs& refreshArgs, + std::vector>& layers) { + if (mLayerLifecycleManagerEnabled) { + std::vector>& snapshots = + mLayerSnapshotBuilder.getSnapshots(); + for (auto [_, layerFE] : layers) { + auto i = layerFE->mSnapshot->globalZ; + snapshots[i] = std::move(layerFE->mSnapshot); + } + } + if (mLegacyFrontEndEnabled && !mLayerLifecycleManagerEnabled) { + for (auto [layer, layerFE] : layers) { + layer->updateLayerSnapshot(std::move(layerFE->mSnapshot)); + } + } +} + +std::vector> SurfaceFlinger::moveSnapshotsToCompositionArgs( + compositionengine::CompositionRefreshArgs& refreshArgs, bool cursorOnly, int64_t vsyncId) { + std::vector> layers; + if (mLayerLifecycleManagerEnabled) { + nsecs_t currentTime = systemTime(); + mLayerSnapshotBuilder.forEachVisibleSnapshot( + [&](std::unique_ptr& snapshot) { + if (cursorOnly && + snapshot->compositionType != + aidl::android::hardware::graphics::composer3::Composition::CURSOR) { + return; + } + + if (!snapshot->hasSomethingToDraw()) { + return; + } + + auto it = mLegacyLayers.find(snapshot->sequence); + LOG_ALWAYS_FATAL_IF(it == mLegacyLayers.end(), + "Couldnt find layer object for %s", + snapshot->getDebugString().c_str()); + auto& legacyLayer = it->second; + sp layerFE = legacyLayer->getCompositionEngineLayerFE(snapshot->path); + snapshot->fps = getLayerFramerate(currentTime, snapshot->sequence); + layerFE->mSnapshot = std::move(snapshot); + refreshArgs.layers.push_back(layerFE); + layers.emplace_back(legacyLayer.get(), layerFE.get()); + }); + } + if (mLegacyFrontEndEnabled && !mLayerLifecycleManagerEnabled) { + auto moveSnapshots = [&layers, &refreshArgs, cursorOnly](Layer* layer) { + if (const auto& layerFE = layer->getCompositionEngineLayerFE()) { + if (cursorOnly && + layer->getLayerSnapshot()->compositionType != + aidl::android::hardware::graphics::composer3::Composition::CURSOR) + return; + layer->updateSnapshot(refreshArgs.updatingGeometryThisFrame); + layerFE->mSnapshot = layer->stealLayerSnapshot(); + refreshArgs.layers.push_back(layerFE); + layers.emplace_back(layer, layerFE.get()); + } + }; + + if (cursorOnly || !mVisibleRegionsDirty) { + // for hot path avoid traversals by walking though the previous composition list + for (sp layer : mPreviouslyComposedLayers) { + moveSnapshots(layer.get()); + } + } else { + mPreviouslyComposedLayers.clear(); + mDrawingState.traverseInZOrder( + [&moveSnapshots](Layer* layer) { moveSnapshots(layer); }); + mPreviouslyComposedLayers.reserve(layers.size()); + for (auto [layer, _] : layers) { + mPreviouslyComposedLayers.push_back(sp::fromExisting(layer)); + } + } + } + + return layers; +} + +std::function>>()> +SurfaceFlinger::getLayerSnapshotsForScreenshots( + std::optional layerStack, uint32_t uid, + std::function + snapshotFilterFn) { + return [&, layerStack, uid]() { + std::vector>> layers; + bool stopTraversal = false; + mLayerSnapshotBuilder.forEachVisibleSnapshot( + [&](std::unique_ptr& snapshot) { + if (stopTraversal) { + return; + } + if (layerStack && snapshot->outputFilter.layerStack != *layerStack) { + return; + } + if (uid != CaptureArgs::UNSET_UID && snapshot->uid != uid) { + return; + } + if (!snapshot->hasSomethingToDraw()) { + return; + } + if (snapshotFilterFn && !snapshotFilterFn(*snapshot, stopTraversal)) { + return; + } + + auto it = mLegacyLayers.find(snapshot->sequence); + LOG_ALWAYS_FATAL_IF(it == mLegacyLayers.end(), + "Couldnt find layer object for %s", + snapshot->getDebugString().c_str()); + Layer* legacyLayer = (it == mLegacyLayers.end()) ? nullptr : it->second.get(); + sp layerFE = getFactory().createLayerFE(snapshot->name); + layerFE->mSnapshot = std::make_unique(*snapshot); + layers.emplace_back(legacyLayer, std::move(layerFE)); + }); + + return layers; + }; +} + +std::function>>()> +SurfaceFlinger::getLayerSnapshotsForScreenshots(std::optional layerStack, + uint32_t uid, + std::unordered_set excludeLayerIds) { + return [&, layerStack, uid, excludeLayerIds = std::move(excludeLayerIds)]() { + if (excludeLayerIds.empty()) { + auto getLayerSnapshotsFn = + getLayerSnapshotsForScreenshots(layerStack, uid, /*snapshotFilterFn=*/nullptr); + std::vector>> layers = getLayerSnapshotsFn(); + return layers; + } + + frontend::LayerSnapshotBuilder::Args + args{.root = mLayerHierarchyBuilder.getHierarchy(), + .layerLifecycleManager = mLayerLifecycleManager, + .forceUpdate = frontend::LayerSnapshotBuilder::ForceUpdateFlags::HIERARCHY, + .displays = mFrontEndDisplayInfos, + .displayChanges = true, + .globalShadowSettings = mDrawingState.globalShadowSettings, + .supportsBlur = mSupportsBlur, + .forceFullDamage = mForceFullDamage, + .excludeLayerIds = std::move(excludeLayerIds), + .supportedLayerGenericMetadata = + getHwComposer().getSupportedLayerGenericMetadata(), + .genericLayerMetadataKeyMap = getGenericLayerMetadataKeyMap()}; + mLayerSnapshotBuilder.update(args); + + auto getLayerSnapshotsFn = + getLayerSnapshotsForScreenshots(layerStack, uid, /*snapshotFilterFn=*/nullptr); + std::vector>> layers = getLayerSnapshotsFn(); + + args.excludeLayerIds.clear(); + mLayerSnapshotBuilder.update(args); + + return layers; + }; +} + +std::function>>()> +SurfaceFlinger::getLayerSnapshotsForScreenshots(uint32_t rootLayerId, uint32_t uid, + std::unordered_set excludeLayerIds, + bool childrenOnly, + const std::optional& parentCrop) { + return [&, rootLayerId, uid, excludeLayerIds = std::move(excludeLayerIds), childrenOnly, + parentCrop]() { + auto root = mLayerHierarchyBuilder.getPartialHierarchy(rootLayerId, childrenOnly); + frontend::LayerSnapshotBuilder::Args + args{.root = root, + .layerLifecycleManager = mLayerLifecycleManager, + .forceUpdate = frontend::LayerSnapshotBuilder::ForceUpdateFlags::HIERARCHY, + .displays = mFrontEndDisplayInfos, + .displayChanges = true, + .globalShadowSettings = mDrawingState.globalShadowSettings, + .supportsBlur = mSupportsBlur, + .forceFullDamage = mForceFullDamage, + .parentCrop = parentCrop, + .excludeLayerIds = std::move(excludeLayerIds), + .supportedLayerGenericMetadata = + getHwComposer().getSupportedLayerGenericMetadata(), + .genericLayerMetadataKeyMap = getGenericLayerMetadataKeyMap()}; + mLayerSnapshotBuilder.update(args); + + auto getLayerSnapshotsFn = + getLayerSnapshotsForScreenshots({}, uid, /*snapshotFilterFn=*/nullptr); + std::vector>> layers = getLayerSnapshotsFn(); + args.root = mLayerHierarchyBuilder.getHierarchy(); + args.parentCrop.reset(); + args.excludeLayerIds.clear(); + mLayerSnapshotBuilder.update(args); + return layers; + }; +} + +frontend::Update SurfaceFlinger::flushLifecycleUpdates() { + frontend::Update update; + ATRACE_NAME("TransactionHandler:flushTransactions"); + // Locking: + // 1. to prevent onHandleDestroyed from being called while the state lock is held, + // we must keep a copy of the transactions (specifically the composer + // states) around outside the scope of the lock. + // 2. Transactions and created layers do not share a lock. To prevent applying + // transactions with layers still in the createdLayer queue, flush the transactions + // before committing the created layers. + update.transactions = mTransactionHandler.flushTransactions(); + { + // TODO(b/238781169) lockless queue this and keep order. + std::scoped_lock lock(mCreatedLayersLock); + update.layerCreatedStates = std::move(mCreatedLayers); + mCreatedLayers.clear(); + update.newLayers = std::move(mNewLayers); + mNewLayers.clear(); + update.layerCreationArgs = std::move(mNewLayerArgs); + mNewLayerArgs.clear(); + update.destroyedHandles = std::move(mDestroyedHandles); + mDestroyedHandles.clear(); + } + return update; +} + +void SurfaceFlinger::addToLayerTracing(bool visibleRegionDirty, int64_t time, int64_t vsyncId) { + const uint32_t tracingFlags = mLayerTracing.getFlags(); + LayersProto layers(dumpDrawingStateProto(tracingFlags)); + if (tracingFlags & LayerTracing::TRACE_EXTRA) { + dumpOffscreenLayersProto(layers); + } + std::string hwcDump; + if (tracingFlags & LayerTracing::TRACE_HWC) { + dumpHwc(hwcDump); + } + auto displays = dumpDisplayProto(); + mLayerTracing.notify(visibleRegionDirty, time, vsyncId, &layers, std::move(hwcDump), &displays); } // gui::ISurfaceComposer +binder::Status SurfaceComposerAIDL::bootFinished() { + status_t status = checkAccessPermission(); + if (status != OK) { + return binderStatusFromStatusT(status); + } + mFlinger->bootFinished(); + return binder::Status::ok(); +} + +binder::Status SurfaceComposerAIDL::createDisplayEventConnection( + VsyncSource vsyncSource, EventRegistration eventRegistration, + const sp& layerHandle, sp* outConnection) { + sp conn = + mFlinger->createDisplayEventConnection(vsyncSource, eventRegistration, layerHandle); + if (conn == nullptr) { + *outConnection = nullptr; + return binderStatusFromStatusT(BAD_VALUE); + } else { + *outConnection = conn; + return binder::Status::ok(); + } +} + +binder::Status SurfaceComposerAIDL::createConnection(sp* outClient) { + const sp client = sp::make(mFlinger); + if (client->initCheck() == NO_ERROR) { + *outClient = client; + return binder::Status::ok(); + } else { + *outClient = nullptr; + return binderStatusFromStatusT(BAD_VALUE); + } +} + binder::Status SurfaceComposerAIDL::createDisplay(const std::string& displayName, bool secure, + float requestedRefreshRate, sp* outDisplay) { status_t status = checkAccessPermission(); - if (status == OK) { - String8 displayName8 = String8::format("%s", displayName.c_str()); - *outDisplay = mFlinger->createDisplay(displayName8, secure); - return binder::Status::ok(); + if (status != OK) { + return binderStatusFromStatusT(status); } - return binder::Status::fromStatusT(status); + String8 displayName8 = String8::format("%s", displayName.c_str()); + *outDisplay = mFlinger->createDisplay(displayName8, secure, requestedRefreshRate); + return binder::Status::ok(); } binder::Status SurfaceComposerAIDL::destroyDisplay(const sp& display) { status_t status = checkAccessPermission(); - if (status == OK) { - mFlinger->destroyDisplay(display); - return binder::Status::ok(); + if (status != OK) { + return binderStatusFromStatusT(status); } - return binder::Status::fromStatusT(status); + mFlinger->destroyDisplay(display); + return binder::Status::ok(); } binder::Status SurfaceComposerAIDL::getPhysicalDisplayIds(std::vector* outDisplayIds) { @@ -7497,22 +8436,12 @@ binder::Status SurfaceComposerAIDL::getPhysicalDisplayIds(std::vector* return binder::Status::ok(); } -binder::Status SurfaceComposerAIDL::getPrimaryPhysicalDisplayId(int64_t* outDisplayId) { +binder::Status SurfaceComposerAIDL::getPhysicalDisplayToken(int64_t displayId, + sp* outDisplay) { status_t status = checkAccessPermission(); if (status != OK) { - return binder::Status::fromStatusT(status); + return binderStatusFromStatusT(status); } - - PhysicalDisplayId id; - status = mFlinger->getPrimaryPhysicalDisplayId(&id); - if (status == NO_ERROR) { - *outDisplayId = id.value; - } - return binder::Status::fromStatusT(status); -} - -binder::Status SurfaceComposerAIDL::getPhysicalDisplayToken(int64_t displayId, - sp* outDisplay) { const auto id = DisplayId::fromValue(static_cast(displayId)); *outDisplay = mFlinger->getPhysicalDisplayToken(*id); return binder::Status::ok(); @@ -7520,12 +8449,25 @@ binder::Status SurfaceComposerAIDL::getPhysicalDisplayToken(int64_t displayId, binder::Status SurfaceComposerAIDL::setPowerMode(const sp& display, int mode) { status_t status = checkAccessPermission(); - if (status != OK) return binder::Status::fromStatusT(status); - + if (status != OK) { + return binderStatusFromStatusT(status); + } mFlinger->setPowerMode(display, mode); return binder::Status::ok(); } +binder::Status SurfaceComposerAIDL::getSupportedFrameTimestamps( + std::vector* outSupported) { + status_t status; + if (!outSupported) { + status = UNEXPECTED_NULL; + } else { + outSupported->clear(); + status = mFlinger->getSupportedFrameTimestamps(outSupported); + } + return binderStatusFromStatusT(status); +} + binder::Status SurfaceComposerAIDL::getDisplayStats(const sp& display, gui::DisplayStatInfo* outStatInfo) { DisplayStatInfo statInfo; @@ -7534,7 +8476,7 @@ binder::Status SurfaceComposerAIDL::getDisplayStats(const sp& display, outStatInfo->vsyncTime = static_cast(statInfo.vsyncTime); outStatInfo->vsyncPeriod = static_cast(statInfo.vsyncPeriod); } - return binder::Status::fromStatusT(status); + return binderStatusFromStatusT(status); } binder::Status SurfaceComposerAIDL::getDisplayState(const sp& display, @@ -7547,37 +8489,229 @@ binder::Status SurfaceComposerAIDL::getDisplayState(const sp& display, outState->layerStackSpaceRect.width = state.layerStackSpaceRect.width; outState->layerStackSpaceRect.height = state.layerStackSpaceRect.height; } - return binder::Status::fromStatusT(status); + return binderStatusFromStatusT(status); +} + +binder::Status SurfaceComposerAIDL::getStaticDisplayInfo(int64_t displayId, + gui::StaticDisplayInfo* outInfo) { + using Tag = gui::DeviceProductInfo::ManufactureOrModelDate::Tag; + ui::StaticDisplayInfo info; + + status_t status = mFlinger->getStaticDisplayInfo(displayId, &info); + if (status == NO_ERROR) { + // convert ui::StaticDisplayInfo to gui::StaticDisplayInfo + outInfo->connectionType = static_cast(info.connectionType); + outInfo->density = info.density; + outInfo->secure = info.secure; + outInfo->installOrientation = static_cast(info.installOrientation); + + gui::DeviceProductInfo dinfo; + std::optional dpi = info.deviceProductInfo; + dinfo.name = std::move(dpi->name); + dinfo.manufacturerPnpId = + std::vector(dpi->manufacturerPnpId.begin(), dpi->manufacturerPnpId.end()); + dinfo.productId = dpi->productId; + dinfo.relativeAddress = + std::vector(dpi->relativeAddress.begin(), dpi->relativeAddress.end()); + if (const auto* model = + std::get_if(&dpi->manufactureOrModelDate)) { + gui::DeviceProductInfo::ModelYear modelYear; + modelYear.year = model->year; + dinfo.manufactureOrModelDate.set(modelYear); + } else if (const auto* manufacture = std::get_if( + &dpi->manufactureOrModelDate)) { + gui::DeviceProductInfo::ManufactureYear date; + date.modelYear.year = manufacture->year; + dinfo.manufactureOrModelDate.set(date); + } else if (const auto* manufacture = std::get_if( + &dpi->manufactureOrModelDate)) { + gui::DeviceProductInfo::ManufactureWeekAndYear date; + date.manufactureYear.modelYear.year = manufacture->year; + date.week = manufacture->week; + dinfo.manufactureOrModelDate.set(date); + } + + outInfo->deviceProductInfo = dinfo; + } + return binderStatusFromStatusT(status); +} + +void SurfaceComposerAIDL::getDynamicDisplayInfoInternal(ui::DynamicDisplayInfo& info, + gui::DynamicDisplayInfo*& outInfo) { + // convert ui::DynamicDisplayInfo to gui::DynamicDisplayInfo + outInfo->supportedDisplayModes.clear(); + outInfo->supportedDisplayModes.reserve(info.supportedDisplayModes.size()); + for (const auto& mode : info.supportedDisplayModes) { + gui::DisplayMode outMode; + outMode.id = mode.id; + outMode.resolution.width = mode.resolution.width; + outMode.resolution.height = mode.resolution.height; + outMode.xDpi = mode.xDpi; + outMode.yDpi = mode.yDpi; + outMode.refreshRate = mode.refreshRate; + outMode.appVsyncOffset = mode.appVsyncOffset; + outMode.sfVsyncOffset = mode.sfVsyncOffset; + outMode.presentationDeadline = mode.presentationDeadline; + outMode.group = mode.group; + std::transform(mode.supportedHdrTypes.begin(), mode.supportedHdrTypes.end(), + std::back_inserter(outMode.supportedHdrTypes), + [](const ui::Hdr& value) { return static_cast(value); }); + outInfo->supportedDisplayModes.push_back(outMode); + } + + outInfo->activeDisplayModeId = info.activeDisplayModeId; + outInfo->renderFrameRate = info.renderFrameRate; + + outInfo->supportedColorModes.clear(); + outInfo->supportedColorModes.reserve(info.supportedColorModes.size()); + for (const auto& cmode : info.supportedColorModes) { + outInfo->supportedColorModes.push_back(static_cast(cmode)); + } + + outInfo->activeColorMode = static_cast(info.activeColorMode); + + gui::HdrCapabilities& hdrCapabilities = outInfo->hdrCapabilities; + hdrCapabilities.supportedHdrTypes.clear(); + hdrCapabilities.supportedHdrTypes.reserve(info.hdrCapabilities.getSupportedHdrTypes().size()); + for (const auto& hdr : info.hdrCapabilities.getSupportedHdrTypes()) { + hdrCapabilities.supportedHdrTypes.push_back(static_cast(hdr)); + } + hdrCapabilities.maxLuminance = info.hdrCapabilities.getDesiredMaxLuminance(); + hdrCapabilities.maxAverageLuminance = info.hdrCapabilities.getDesiredMaxAverageLuminance(); + hdrCapabilities.minLuminance = info.hdrCapabilities.getDesiredMinLuminance(); + + outInfo->autoLowLatencyModeSupported = info.autoLowLatencyModeSupported; + outInfo->gameContentTypeSupported = info.gameContentTypeSupported; + outInfo->preferredBootDisplayMode = info.preferredBootDisplayMode; +} + +binder::Status SurfaceComposerAIDL::getDynamicDisplayInfoFromToken( + const sp& display, gui::DynamicDisplayInfo* outInfo) { + ui::DynamicDisplayInfo info; + status_t status = mFlinger->getDynamicDisplayInfoFromToken(display, &info); + if (status == NO_ERROR) { + getDynamicDisplayInfoInternal(info, outInfo); + } + return binderStatusFromStatusT(status); +} + +binder::Status SurfaceComposerAIDL::getDynamicDisplayInfoFromId(int64_t displayId, + gui::DynamicDisplayInfo* outInfo) { + ui::DynamicDisplayInfo info; + status_t status = mFlinger->getDynamicDisplayInfoFromId(displayId, &info); + if (status == NO_ERROR) { + getDynamicDisplayInfoInternal(info, outInfo); + } + return binderStatusFromStatusT(status); +} + +binder::Status SurfaceComposerAIDL::getDisplayNativePrimaries(const sp& display, + gui::DisplayPrimaries* outPrimaries) { + ui::DisplayPrimaries primaries; + status_t status = mFlinger->getDisplayNativePrimaries(display, primaries); + if (status == NO_ERROR) { + outPrimaries->red.X = primaries.red.X; + outPrimaries->red.Y = primaries.red.Y; + outPrimaries->red.Z = primaries.red.Z; + + outPrimaries->green.X = primaries.green.X; + outPrimaries->green.Y = primaries.green.Y; + outPrimaries->green.Z = primaries.green.Z; + + outPrimaries->blue.X = primaries.blue.X; + outPrimaries->blue.Y = primaries.blue.Y; + outPrimaries->blue.Z = primaries.blue.Z; + + outPrimaries->white.X = primaries.white.X; + outPrimaries->white.Y = primaries.white.Y; + outPrimaries->white.Z = primaries.white.Z; + } + return binderStatusFromStatusT(status); +} + +binder::Status SurfaceComposerAIDL::setActiveColorMode(const sp& display, int colorMode) { + status_t status = checkAccessPermission(); + if (status == OK) { + status = mFlinger->setActiveColorMode(display, static_cast(colorMode)); + } + return binderStatusFromStatusT(status); +} + +binder::Status SurfaceComposerAIDL::setBootDisplayMode(const sp& display, + int displayModeId) { + status_t status = checkAccessPermission(); + if (status == OK) { + status = mFlinger->setBootDisplayMode(display, DisplayModeId{displayModeId}); + } + return binderStatusFromStatusT(status); } binder::Status SurfaceComposerAIDL::clearBootDisplayMode(const sp& display) { status_t status = checkAccessPermission(); - if (status != OK) return binder::Status::fromStatusT(status); + if (status == OK) { + status = mFlinger->clearBootDisplayMode(display); + } + return binderStatusFromStatusT(status); +} - status = mFlinger->clearBootDisplayMode(display); - return binder::Status::fromStatusT(status); +binder::Status SurfaceComposerAIDL::getOverlaySupport(gui::OverlayProperties* outProperties) { + status_t status = checkAccessPermission(); + if (status == OK) { + status = mFlinger->getOverlaySupport(outProperties); + } + return binderStatusFromStatusT(status); } binder::Status SurfaceComposerAIDL::getBootDisplayModeSupport(bool* outMode) { status_t status = checkAccessPermission(); - if (status != OK) return binder::Status::fromStatusT(status); + if (status == OK) { + status = mFlinger->getBootDisplayModeSupport(outMode); + } + return binderStatusFromStatusT(status); +} - status = mFlinger->getBootDisplayModeSupport(outMode); - return binder::Status::fromStatusT(status); +binder::Status SurfaceComposerAIDL::getHdrConversionCapabilities( + std::vector* hdrConversionCapabilities) { + status_t status = checkAccessPermission(); + if (status == OK) { + status = mFlinger->getHdrConversionCapabilities(hdrConversionCapabilities); + } + return binderStatusFromStatusT(status); } -binder::Status SurfaceComposerAIDL::setAutoLowLatencyMode(const sp& display, bool on) { +binder::Status SurfaceComposerAIDL::setHdrConversionStrategy( + const gui::HdrConversionStrategy& hdrConversionStrategy, + int32_t* outPreferredHdrOutputType) { status_t status = checkAccessPermission(); - if (status != OK) return binder::Status::fromStatusT(status); + if (status == OK) { + status = mFlinger->setHdrConversionStrategy(hdrConversionStrategy, + outPreferredHdrOutputType); + } + return binderStatusFromStatusT(status); +} + +binder::Status SurfaceComposerAIDL::getHdrOutputConversionSupport(bool* outMode) { + status_t status = checkAccessPermission(); + if (status == OK) { + status = mFlinger->getHdrOutputConversionSupport(outMode); + } + return binderStatusFromStatusT(status); +} +binder::Status SurfaceComposerAIDL::setAutoLowLatencyMode(const sp& display, bool on) { + status_t status = checkAccessPermission(); + if (status != OK) { + return binderStatusFromStatusT(status); + } mFlinger->setAutoLowLatencyMode(display, on); return binder::Status::ok(); } binder::Status SurfaceComposerAIDL::setGameContentType(const sp& display, bool on) { status_t status = checkAccessPermission(); - if (status != OK) return binder::Status::fromStatusT(status); - + if (status != OK) { + return binderStatusFromStatusT(status); + } mFlinger->setGameContentType(display, on); return binder::Status::ok(); } @@ -7585,7 +8719,7 @@ binder::Status SurfaceComposerAIDL::setGameContentType(const sp& displa binder::Status SurfaceComposerAIDL::captureDisplay( const DisplayCaptureArgs& args, const sp& captureListener) { status_t status = mFlinger->captureDisplay(args, captureListener); - return binder::Status::fromStatusT(status); + return binderStatusFromStatusT(status); } binder::Status SurfaceComposerAIDL::captureDisplayById( @@ -7599,60 +8733,376 @@ binder::Status SurfaceComposerAIDL::captureDisplayById( } else { status = PERMISSION_DENIED; } - return binder::Status::fromStatusT(status); + return binderStatusFromStatusT(status); } binder::Status SurfaceComposerAIDL::captureLayers( const LayerCaptureArgs& args, const sp& captureListener) { status_t status = mFlinger->captureLayers(args, captureListener); - return binder::Status::fromStatusT(status); + return binderStatusFromStatusT(status); +} + +binder::Status SurfaceComposerAIDL::overrideHdrTypes(const sp& display, + const std::vector& hdrTypes) { + // overrideHdrTypes is used by CTS tests, which acquire the necessary + // permission dynamically. Don't use the permission cache for this check. + status_t status = checkAccessPermission(false); + if (status != OK) { + return binderStatusFromStatusT(status); + } + + std::vector hdrTypesVector; + for (int32_t i : hdrTypes) { + hdrTypesVector.push_back(static_cast(i)); + } + status = mFlinger->overrideHdrTypes(display, hdrTypesVector); + return binderStatusFromStatusT(status); +} + +binder::Status SurfaceComposerAIDL::onPullAtom(int32_t atomId, gui::PullAtomData* outPullData) { + status_t status; + const int uid = IPCThreadState::self()->getCallingUid(); + if (uid != AID_SYSTEM) { + status = PERMISSION_DENIED; + } else { + status = mFlinger->onPullAtom(atomId, &outPullData->data, &outPullData->success); + } + return binderStatusFromStatusT(status); +} + +binder::Status SurfaceComposerAIDL::getLayerDebugInfo(std::vector* outLayers) { + if (!outLayers) { + return binderStatusFromStatusT(UNEXPECTED_NULL); + } + + IPCThreadState* ipc = IPCThreadState::self(); + const int pid = ipc->getCallingPid(); + const int uid = ipc->getCallingUid(); + if ((uid != AID_SHELL) && !PermissionCache::checkPermission(sDump, pid, uid)) { + ALOGE("Layer debug info permission denied for pid=%d, uid=%d", pid, uid); + return binderStatusFromStatusT(PERMISSION_DENIED); + } + status_t status = mFlinger->getLayerDebugInfo(outLayers); + return binderStatusFromStatusT(status); +} + +binder::Status SurfaceComposerAIDL::getColorManagement(bool* outGetColorManagement) { + status_t status = mFlinger->getColorManagement(outGetColorManagement); + return binderStatusFromStatusT(status); +} + +binder::Status SurfaceComposerAIDL::getCompositionPreference(gui::CompositionPreference* outPref) { + ui::Dataspace dataspace; + ui::PixelFormat pixelFormat; + ui::Dataspace wideColorGamutDataspace; + ui::PixelFormat wideColorGamutPixelFormat; + status_t status = + mFlinger->getCompositionPreference(&dataspace, &pixelFormat, &wideColorGamutDataspace, + &wideColorGamutPixelFormat); + if (status == NO_ERROR) { + outPref->defaultDataspace = static_cast(dataspace); + outPref->defaultPixelFormat = static_cast(pixelFormat); + outPref->wideColorGamutDataspace = static_cast(wideColorGamutDataspace); + outPref->wideColorGamutPixelFormat = static_cast(wideColorGamutPixelFormat); + } + return binderStatusFromStatusT(status); +} + +binder::Status SurfaceComposerAIDL::getDisplayedContentSamplingAttributes( + const sp& display, gui::ContentSamplingAttributes* outAttrs) { + status_t status = checkAccessPermission(); + if (status != OK) { + return binderStatusFromStatusT(status); + } + + ui::PixelFormat format; + ui::Dataspace dataspace; + uint8_t componentMask; + status = mFlinger->getDisplayedContentSamplingAttributes(display, &format, &dataspace, + &componentMask); + if (status == NO_ERROR) { + outAttrs->format = static_cast(format); + outAttrs->dataspace = static_cast(dataspace); + outAttrs->componentMask = static_cast(componentMask); + } + return binderStatusFromStatusT(status); +} + +binder::Status SurfaceComposerAIDL::setDisplayContentSamplingEnabled(const sp& display, + bool enable, + int8_t componentMask, + int64_t maxFrames) { + status_t status = checkAccessPermission(); + if (status == OK) { + status = mFlinger->setDisplayContentSamplingEnabled(display, enable, + static_cast(componentMask), + static_cast(maxFrames)); + } + return binderStatusFromStatusT(status); +} + +binder::Status SurfaceComposerAIDL::getDisplayedContentSample(const sp& display, + int64_t maxFrames, int64_t timestamp, + gui::DisplayedFrameStats* outStats) { + if (!outStats) { + return binderStatusFromStatusT(BAD_VALUE); + } + + status_t status = checkAccessPermission(); + if (status != OK) { + return binderStatusFromStatusT(status); + } + + DisplayedFrameStats stats; + status = mFlinger->getDisplayedContentSample(display, static_cast(maxFrames), + static_cast(timestamp), &stats); + if (status == NO_ERROR) { + // convert from ui::DisplayedFrameStats to gui::DisplayedFrameStats + outStats->numFrames = static_cast(stats.numFrames); + outStats->component_0_sample.reserve(stats.component_0_sample.size()); + for (const auto& s : stats.component_0_sample) { + outStats->component_0_sample.push_back(static_cast(s)); + } + outStats->component_1_sample.reserve(stats.component_1_sample.size()); + for (const auto& s : stats.component_1_sample) { + outStats->component_1_sample.push_back(static_cast(s)); + } + outStats->component_2_sample.reserve(stats.component_2_sample.size()); + for (const auto& s : stats.component_2_sample) { + outStats->component_2_sample.push_back(static_cast(s)); + } + outStats->component_3_sample.reserve(stats.component_3_sample.size()); + for (const auto& s : stats.component_3_sample) { + outStats->component_3_sample.push_back(static_cast(s)); + } + } + return binderStatusFromStatusT(status); +} + +binder::Status SurfaceComposerAIDL::getProtectedContentSupport(bool* outSupported) { + status_t status = mFlinger->getProtectedContentSupport(outSupported); + return binderStatusFromStatusT(status); } binder::Status SurfaceComposerAIDL::isWideColorDisplay(const sp& token, bool* outIsWideColorDisplay) { status_t status = mFlinger->isWideColorDisplay(token, outIsWideColorDisplay); - return binder::Status::fromStatusT(status); + return binderStatusFromStatusT(status); +} + +binder::Status SurfaceComposerAIDL::addRegionSamplingListener( + const gui::ARect& samplingArea, const sp& stopLayerHandle, + const sp& listener) { + status_t status = checkReadFrameBufferPermission(); + if (status != OK) { + return binderStatusFromStatusT(status); + } + android::Rect rect; + rect.left = samplingArea.left; + rect.top = samplingArea.top; + rect.right = samplingArea.right; + rect.bottom = samplingArea.bottom; + status = mFlinger->addRegionSamplingListener(rect, stopLayerHandle, listener); + return binderStatusFromStatusT(status); +} + +binder::Status SurfaceComposerAIDL::removeRegionSamplingListener( + const sp& listener) { + status_t status = checkReadFrameBufferPermission(); + if (status == OK) { + status = mFlinger->removeRegionSamplingListener(listener); + } + return binderStatusFromStatusT(status); +} + +binder::Status SurfaceComposerAIDL::addFpsListener(int32_t taskId, + const sp& listener) { + status_t status = checkReadFrameBufferPermission(); + if (status == OK) { + status = mFlinger->addFpsListener(taskId, listener); + } + return binderStatusFromStatusT(status); +} + +binder::Status SurfaceComposerAIDL::removeFpsListener(const sp& listener) { + status_t status = checkReadFrameBufferPermission(); + if (status == OK) { + status = mFlinger->removeFpsListener(listener); + } + return binderStatusFromStatusT(status); +} + +binder::Status SurfaceComposerAIDL::addTunnelModeEnabledListener( + const sp& listener) { + status_t status = checkAccessPermission(); + if (status == OK) { + status = mFlinger->addTunnelModeEnabledListener(listener); + } + return binderStatusFromStatusT(status); +} + +binder::Status SurfaceComposerAIDL::removeTunnelModeEnabledListener( + const sp& listener) { + status_t status = checkAccessPermission(); + if (status == OK) { + status = mFlinger->removeTunnelModeEnabledListener(listener); + } + return binderStatusFromStatusT(status); +} + +binder::Status SurfaceComposerAIDL::setDesiredDisplayModeSpecs(const sp& displayToken, + const gui::DisplayModeSpecs& specs) { + status_t status = checkAccessPermission(); + if (status == OK) { + status = mFlinger->setDesiredDisplayModeSpecs(displayToken, specs); + } + return binderStatusFromStatusT(status); +} + +binder::Status SurfaceComposerAIDL::getDesiredDisplayModeSpecs(const sp& displayToken, + gui::DisplayModeSpecs* outSpecs) { + if (!outSpecs) { + return binderStatusFromStatusT(BAD_VALUE); + } + + status_t status = checkAccessPermission(); + if (status != OK) { + return binderStatusFromStatusT(status); + } + + status = mFlinger->getDesiredDisplayModeSpecs(displayToken, outSpecs); + return binderStatusFromStatusT(status); } binder::Status SurfaceComposerAIDL::getDisplayBrightnessSupport(const sp& displayToken, bool* outSupport) { status_t status = mFlinger->getDisplayBrightnessSupport(displayToken, outSupport); - return binder::Status::fromStatusT(status); + return binderStatusFromStatusT(status); } binder::Status SurfaceComposerAIDL::setDisplayBrightness(const sp& displayToken, const gui::DisplayBrightness& brightness) { status_t status = checkControlDisplayBrightnessPermission(); - if (status != OK) return binder::Status::fromStatusT(status); - - status = mFlinger->setDisplayBrightness(displayToken, brightness); - return binder::Status::fromStatusT(status); + if (status == OK) { + status = mFlinger->setDisplayBrightness(displayToken, brightness); + } + return binderStatusFromStatusT(status); } binder::Status SurfaceComposerAIDL::addHdrLayerInfoListener( const sp& displayToken, const sp& listener) { status_t status = checkControlDisplayBrightnessPermission(); - if (status != OK) return binder::Status::fromStatusT(status); - - status = mFlinger->addHdrLayerInfoListener(displayToken, listener); - return binder::Status::fromStatusT(status); + if (status == OK) { + status = mFlinger->addHdrLayerInfoListener(displayToken, listener); + } + return binderStatusFromStatusT(status); } binder::Status SurfaceComposerAIDL::removeHdrLayerInfoListener( const sp& displayToken, const sp& listener) { status_t status = checkControlDisplayBrightnessPermission(); - if (status != OK) return binder::Status::fromStatusT(status); - - status = mFlinger->removeHdrLayerInfoListener(displayToken, listener); - return binder::Status::fromStatusT(status); + if (status == OK) { + status = mFlinger->removeHdrLayerInfoListener(displayToken, listener); + } + return binderStatusFromStatusT(status); } binder::Status SurfaceComposerAIDL::notifyPowerBoost(int boostId) { status_t status = checkAccessPermission(); - if (status != OK) return binder::Status::fromStatusT(status); + if (status == OK) { + status = mFlinger->notifyPowerBoost(boostId); + } + return binderStatusFromStatusT(status); +} + +binder::Status SurfaceComposerAIDL::setGlobalShadowSettings(const gui::Color& ambientColor, + const gui::Color& spotColor, + float lightPosY, float lightPosZ, + float lightRadius) { + status_t status = checkAccessPermission(); + if (status != OK) { + return binderStatusFromStatusT(status); + } + + half4 ambientColorHalf = {ambientColor.r, ambientColor.g, ambientColor.b, ambientColor.a}; + half4 spotColorHalf = {spotColor.r, spotColor.g, spotColor.b, spotColor.a}; + status = mFlinger->setGlobalShadowSettings(ambientColorHalf, spotColorHalf, lightPosY, + lightPosZ, lightRadius); + return binderStatusFromStatusT(status); +} + +binder::Status SurfaceComposerAIDL::getDisplayDecorationSupport( + const sp& displayToken, std::optional* outSupport) { + std::optional support; + status_t status = mFlinger->getDisplayDecorationSupport(displayToken, &support); + if (status != NO_ERROR) { + ALOGE("getDisplayDecorationSupport failed with error %d", status); + return binderStatusFromStatusT(status); + } + + if (!support || !support.has_value()) { + outSupport->reset(); + } else { + outSupport->emplace(); + outSupport->value().format = static_cast(support->format); + outSupport->value().alphaInterpretation = + static_cast(support->alphaInterpretation); + } + + return binder::Status::ok(); +} + +binder::Status SurfaceComposerAIDL::setOverrideFrameRate(int32_t uid, float frameRate) { + status_t status; + const int c_uid = IPCThreadState::self()->getCallingUid(); + if (c_uid == AID_ROOT || c_uid == AID_SYSTEM) { + status = mFlinger->setOverrideFrameRate(uid, frameRate); + } else { + ALOGE("setOverrideFrameRate() permission denied for uid: %d", c_uid); + status = PERMISSION_DENIED; + } + return binderStatusFromStatusT(status); +} + +binder::Status SurfaceComposerAIDL::getGpuContextPriority(int32_t* outPriority) { + *outPriority = mFlinger->getGpuContextPriority(); + return binder::Status::ok(); +} + +binder::Status SurfaceComposerAIDL::getMaxAcquiredBufferCount(int32_t* buffers) { + status_t status = mFlinger->getMaxAcquiredBufferCount(buffers); + return binderStatusFromStatusT(status); +} - status = mFlinger->notifyPowerBoost(boostId); - return binder::Status::fromStatusT(status); +binder::Status SurfaceComposerAIDL::addWindowInfosListener( + const sp& windowInfosListener, + gui::WindowInfosListenerInfo* outInfo) { + status_t status; + const int pid = IPCThreadState::self()->getCallingPid(); + const int uid = IPCThreadState::self()->getCallingUid(); + // TODO(b/270566761) update permissions check so that only system_server and shell can add + // WindowInfosListeners + if (uid == AID_SYSTEM || uid == AID_GRAPHICS || + checkPermission(sAccessSurfaceFlinger, pid, uid)) { + status = mFlinger->addWindowInfosListener(windowInfosListener, outInfo); + } else { + status = PERMISSION_DENIED; + } + return binderStatusFromStatusT(status); +} + +binder::Status SurfaceComposerAIDL::removeWindowInfosListener( + const sp& windowInfosListener) { + status_t status; + const int pid = IPCThreadState::self()->getCallingPid(); + const int uid = IPCThreadState::self()->getCallingUid(); + if (uid == AID_SYSTEM || uid == AID_GRAPHICS || + checkPermission(sAccessSurfaceFlinger, pid, uid)) { + status = mFlinger->removeWindowInfosListener(windowInfosListener); + } else { + status = PERMISSION_DENIED; + } + return binderStatusFromStatusT(status); } status_t SurfaceComposerAIDL::checkAccessPermission(bool usePermissionCache) { @@ -7677,6 +9127,30 @@ status_t SurfaceComposerAIDL::checkControlDisplayBrightnessPermission() { return OK; } +status_t SurfaceComposerAIDL::checkReadFrameBufferPermission() { + IPCThreadState* ipc = IPCThreadState::self(); + const int pid = ipc->getCallingPid(); + const int uid = ipc->getCallingUid(); + if ((uid != AID_GRAPHICS) && !PermissionCache::checkPermission(sReadFramebuffer, pid, uid)) { + ALOGE("Permission Denial: can't read framebuffer pid=%d, uid=%d", pid, uid); + return PERMISSION_DENIED; + } + return OK; +} + +void SurfaceFlinger::forceFutureUpdate(int delayInMs) { + static_cast(mScheduler->scheduleDelayed([&]() { scheduleRepaint(); }, ms2ns(delayInMs))); +} + +const DisplayDevice* SurfaceFlinger::getDisplayFromLayerStack(ui::LayerStack layerStack) { + for (const auto& [_, display] : mDisplays) { + if (display->getLayerStack() == layerStack) { + return display.get(); + } + } + return nullptr; +} + } // namespace android #if defined(__gl_h_) diff --git a/services/surfaceflinger/SurfaceFlinger.h b/services/surfaceflinger/SurfaceFlinger.h index d9add5c88cda0a42577b27bf8329af22da38c90d..d4700a4e259ec0e542c271e3198b9b47d9f804c3 100644 --- a/services/surfaceflinger/SurfaceFlinger.h +++ b/services/surfaceflinger/SurfaceFlinger.h @@ -25,15 +25,18 @@ #include #include #include +#include #include #include +#include #include -#include +#include #include +#include #include #include -#include #include +#include #include #include #include @@ -50,26 +53,36 @@ #include #include -#include #include #include - -#include "ClientCache.h" +#include +#include +#include +#include +#include +#include + +#include "Display/DisplayMap.h" +#include "Display/PhysicalDisplay.h" #include "DisplayDevice.h" #include "DisplayHardware/HWC2.h" #include "DisplayHardware/PowerAdvisor.h" #include "DisplayIdGenerator.h" #include "Effects/Daltonizer.h" #include "FlagManager.h" -#include "FrameTracker.h" +#include "FrontEnd/DisplayInfo.h" +#include "FrontEnd/LayerCreationArgs.h" +#include "FrontEnd/LayerLifecycleManager.h" +#include "FrontEnd/LayerSnapshot.h" +#include "FrontEnd/LayerSnapshotBuilder.h" +#include "FrontEnd/TransactionHandler.h" #include "LayerVector.h" -#include "Scheduler/RefreshRateConfigs.h" +#include "Scheduler/ISchedulerCallback.h" +#include "Scheduler/RefreshRateSelector.h" #include "Scheduler/RefreshRateStats.h" #include "Scheduler/Scheduler.h" -#include "Scheduler/VsyncModulator.h" #include "SurfaceFlingerFactory.h" #include "ThreadContext.h" -#include "TracedOrdinal.h" #include "Tracing/LayerTracing.h" #include "Tracing/TransactionTracing.h" #include "TransactionCallbackInvoker.h" @@ -90,14 +103,16 @@ #include #include #include +#include #include +#include +#include "Client.h" using namespace android::surfaceflinger; namespace android { -class Client; class EventThread; class FlagManager; class FpsReporter; @@ -115,6 +130,8 @@ class FrameTracer; class ScreenCapturer; class WindowInfosListenerInvoker; +using ::aidl::android::hardware::graphics::composer3::RefreshRateChangedDebugData; +using frontend::TransactionHandler; using gui::CaptureArgs; using gui::DisplayCaptureArgs; using gui::IRegionSamplingListener; @@ -146,7 +163,8 @@ enum { eDisplayTransactionNeeded = 0x04, eTransformHintUpdateNeeded = 0x08, eTransactionFlushNeeded = 0x10, - eTransactionMask = 0x1f, + eInputInfoUpdateNeeded = 0x20, + eTransactionMask = 0x3f, }; // Latch Unsignaled buffer behaviours @@ -157,33 +175,20 @@ enum class LatchUnsignaledConfig { // Latch unsignaled is permitted when a single layer is updated in a frame, // and the update includes just a buffer update (i.e. no sync transactions // or geometry changes). + // Latch unsignaled is also only permitted when a single transaction is ready + // to be applied. If we pass an unsignaled fence to HWC, HWC might miss presenting + // the frame if the fence does not fire in time. If we apply another transaction, + // we may penalize the other transaction unfairly. AutoSingleLayer, // All buffers are latched unsignaled. This behaviour is discouraged as it // can break sync transactions, stall the display and cause undesired side effects. + // This is equivalent to ignoring the acquire fence when applying transactions. Always, }; using DisplayColorSetting = compositionengine::OutputColorSetting; -struct SurfaceFlingerBE { - // protected by mCompositorTimingLock; - mutable std::mutex mCompositorTimingLock; - CompositorTiming mCompositorTiming; - - // Only accessed from the main thread. - struct CompositePresentTime { - nsecs_t composite = -1; - std::shared_ptr display = FenceTime::NO_FENCE; - }; - std::queue mCompositePresentTimes; - - static const size_t NUM_BUCKETS = 8; // < 1-7, 7+ - nsecs_t mFrameBuckets[NUM_BUCKETS] = {}; - nsecs_t mTotalTime = 0; - std::atomic mLastSwapTime = 0; -}; - class SurfaceFlinger : public BnSurfaceComposer, public PriorityDumper, private IBinder::DeathRecipient, @@ -204,29 +209,6 @@ public: static char const* getServiceName() ANDROID_API { return "SurfaceFlinger"; } - // This is the phase offset in nanoseconds of the software vsync event - // relative to the vsync event reported by HWComposer. The software vsync - // event is when SurfaceFlinger and Choreographer-based applications run each - // frame. - // - // This phase offset allows adjustment of the minimum latency from application - // wake-up time (by Choreographer) to the time at which the resulting window - // image is displayed. This value may be either positive (after the HW vsync) - // or negative (before the HW vsync). Setting it to 0 will result in a lower - // latency bound of two vsync periods because the app and SurfaceFlinger - // will run just after the HW vsync. Setting it to a positive number will - // result in the minimum latency being: - // - // (2 * VSYNC_PERIOD - (vsyncPhaseOffsetNs % VSYNC_PERIOD)) - // - // Note that reducing this latency makes it more likely for the applications - // to not have their window content image ready in time. When this happens - // the latency will end up being an additional vsync period, and animations - // will hiccup. Therefore, this latency should be tuned somewhat - // conservatively (or at least with awareness of the trade-off being made). - static int64_t vsyncPhaseOffsetNs; - static int64_t sfVsyncPhaseOffsetNs; - // If fences from sync Framework are supported. static bool hasSyncFramework; @@ -249,10 +231,6 @@ public: static uint32_t maxGraphicsWidth; static uint32_t maxGraphicsHeight; - // Indicate if a device has wide color gamut display. This is typically - // found on devices with wide color gamut (e.g. Display-P3) display. - static bool hasWideColorDisplay; - // Indicate if device wants color management on its display. static const constexpr bool useColorManagement = true; @@ -280,9 +258,6 @@ public: // starts SurfaceFlinger main loop in the current thread void run() ANDROID_API; - SurfaceFlingerBE& getBE() { return mBE; } - const SurfaceFlingerBE& getBE() const { return mBE; } - // Indicates frame activity, i.e. whether commit and/or composite is taking place. enum class FrameHint { kNone, kActive }; @@ -309,9 +284,6 @@ public: renderengine::RenderEngine& getRenderEngine() const; - bool authenticateSurfaceTextureLocked( - const sp& bufferProducer) const; - void onLayerFirstRef(Layer*); void onLayerDestroyed(Layer*); void onLayerUpdate(); @@ -319,23 +291,20 @@ public: void removeHierarchyFromOffscreenLayers(Layer* layer); void removeFromOffscreenLayers(Layer* layer); - // TODO: Remove atomic if move dtor to main thread CL lands - std::atomic mNumClones; + // Called when all clients have released all their references to + // this layer. The layer may still be kept alive by its parents but + // the client can no longer modify this layer directly. + void onHandleDestroyed(BBinder* handle, sp& layer, uint32_t layerId); + + std::vector mLayerMirrorRoots; TransactionCallbackInvoker& getTransactionCallbackInvoker() { return mTransactionCallbackInvoker; } - // Converts from a binder handle to a Layer - // Returns nullptr if the handle does not point to an existing layer. - // Otherwise, returns a weak reference so that callers off the main-thread - // won't accidentally hold onto the last strong reference. - wp fromHandle(const sp& handle) const; - // If set, disables reusing client composition buffers. This can be set by // debug.sf.disable_client_composition_cache bool mDisableClientCompositionCache = false; - void windowInfosReported(); // Disables expensive rendering for all displays // This is scheduled on the main thread @@ -353,6 +322,23 @@ public: // on this behavior to increase contrast for some media sources. bool mTreat170mAsSrgb = false; + // Allows to ignore physical orientation provided through hwc API in favour of + // 'ro.surface_flinger.primary_display_orientation'. + // TODO(b/246793311): Clean up a temporary property + bool mIgnoreHwcPhysicalDisplayOrientation = false; + + void forceFutureUpdate(int delayInMs); + const DisplayDevice* getDisplayFromLayerStack(ui::LayerStack) + REQUIRES(mStateLock, kMainThreadContext); + + // TODO (b/259407931): Remove. + // TODO (b/281857977): This should be annotated with REQUIRES(kMainThreadContext), but this + // would require thread safety annotations throughout the frontend (in particular Layer and + // LayerFE). + static ui::Transform::RotationFlags getActiveDisplayRotationFlags() { + return sActiveDisplayRotationFlags; + } + protected: // We're reference counted, never destroy SurfaceFlinger directly virtual ~SurfaceFlinger(); @@ -361,7 +347,7 @@ protected: REQUIRES(mStateLock); virtual std::shared_ptr getExternalTextureFromBufferData( - const BufferData& bufferData, const char* layerName) const; + BufferData& bufferData, const char* layerName, uint64_t transactionId); // Returns true if any display matches a `bool(const DisplayDevice&)` predicate. template @@ -375,34 +361,28 @@ protected: private: friend class BufferLayer; - friend class BufferQueueLayer; - friend class BufferStateLayer; friend class Client; friend class FpsReporter; friend class TunnelModeEnabledReporter; friend class Layer; - friend class MonitoredProducer; friend class RefreshRateOverlay; friend class RegionSamplingThread; friend class LayerRenderArea; friend class LayerTracing; + friend class SurfaceComposerAIDL; + friend class DisplayRenderArea; // For unit tests friend class TestableSurfaceFlinger; friend class TransactionApplicationTest; friend class TunnelModeEnabledReporterTest; - using VsyncModulator = scheduler::VsyncModulator; using TransactionSchedule = scheduler::TransactionSchedule; - using TraverseLayersFunction = std::function; + using GetLayerSnapshotsFunction = std::function>>()>; using RenderAreaFuture = ftl::Future>; using DumpArgs = Vector; using Dumper = std::function; - // This value is specified in number of frames. Log frame stats at most - // every half hour. - enum { LOG_FRAME_STATS_PERIOD = 30*60*60 }; - class State { public: explicit State(LayerVector::StateSet set) : stateSet(set), layersSortedByZ(set) {} @@ -422,7 +402,20 @@ private: const LayerVector::StateSet stateSet = LayerVector::StateSet::Invalid; LayerVector layersSortedByZ; - DefaultKeyedVector< wp, DisplayDeviceState> displays; + + // TODO(b/241285876): Replace deprecated DefaultKeyedVector with ftl::SmallMap. + DefaultKeyedVector, DisplayDeviceState> displays; + + std::optional getDisplayIndex(PhysicalDisplayId displayId) const { + for (size_t i = 0; i < displays.size(); i++) { + const auto& state = displays.valueAt(i); + if (state.physical && state.physical->id == displayId) { + return i; + } + } + + return {}; + } bool colorMatrixChanged = true; mat4 colorMatrix; @@ -471,21 +464,12 @@ private: mCounterByLayerHandle GUARDED_BY(mLock); }; - using ActiveModeInfo = DisplayDevice::ActiveModeInfo; - using KernelIdleTimerController = - ::android::scheduler::RefreshRateConfigs::KernelIdleTimerController; - enum class BootStage { BOOTLOADER, BOOTANIMATION, FINISHED, }; - struct HotplugEvent { - hal::HWDisplayId hwcDisplayId; - hal::Connection connection = hal::Connection::INVALID; - }; - template >* = nullptr> static Dumper dumper(F&& dump) { using namespace std::placeholders; @@ -510,19 +494,11 @@ private: return std::bind(dump, this, _1, _2, _3); } - template - void modulateVsync(Handler handler, Args... args) { - if (const auto config = (*mVsyncModulator.*handler)(args...)) { - const auto vsyncPeriod = mScheduler->getVsyncPeriodFromRefreshRateConfigs(); - setVsyncConfig(*config, vsyncPeriod); - } - } - - static const int MAX_TRACING_MEMORY = 100 * 1024 * 1024; // 100MB // Maximum allowed number of display frames that can be set through backdoor static const int MAX_ALLOWED_DISPLAY_FRAMES = 2048; + static const size_t MAX_LAYERS = 4096; + // Implements IBinder. 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); } @@ -530,32 +506,29 @@ private: EXCLUDES(mStateLock); // Implements ISurfaceComposer - sp createConnection() override; - sp createDisplay(const String8& displayName, bool secure); + sp createDisplay(const String8& displayName, bool secure, + float requestedRefreshRate = 0.0f); void destroyDisplay(const sp& displayToken); std::vector getPhysicalDisplayIds() const EXCLUDES(mStateLock) { Mutex::Autolock lock(mStateLock); return getPhysicalDisplayIdsLocked(); } - status_t getPrimaryPhysicalDisplayId(PhysicalDisplayId*) const EXCLUDES(mStateLock); sp getPhysicalDisplayToken(PhysicalDisplayId displayId) const; - status_t setTransactionState(const FrameTimelineInfo& frameTimelineInfo, - const Vector& state, - const Vector& displays, uint32_t flags, - const sp& applyToken, - const InputWindowCommands& inputWindowCommands, - int64_t desiredPresentTime, bool isAutoTimestamp, - const client_cache_t& uncacheBuffer, bool hasListenerCallbacks, - const std::vector& listenerCallbacks, - uint64_t transactionId) override; - void bootFinished() override; - bool authenticateSurfaceTexture( - const sp& bufferProducer) const override; - status_t getSupportedFrameTimestamps(std::vector* outSupported) const override; + status_t setTransactionState( + const FrameTimelineInfo& frameTimelineInfo, Vector& state, + const Vector& displays, uint32_t flags, const sp& applyToken, + InputWindowCommands inputWindowCommands, int64_t desiredPresentTime, + bool isAutoTimestamp, const std::vector& uncacheBuffers, + bool hasListenerCallbacks, const std::vector& listenerCallbacks, + uint64_t transactionId, const std::vector& mergedTransactionIds) override; + void bootFinished(); + virtual status_t getSupportedFrameTimestamps(std::vector* outSupported) const; sp createDisplayEventConnection( - ISurfaceComposer::VsyncSource vsyncSource = eVsyncSourceApp, - ISurfaceComposer::EventRegistrationFlags eventRegistration = {}) override; + gui::ISurfaceComposer::VsyncSource vsyncSource = + gui::ISurfaceComposer::VsyncSource::eVsyncSourceApp, + EventRegistrationFlags eventRegistration = {}, + const sp& layerHandle = nullptr); status_t captureDisplay(const DisplayCaptureArgs&, const sp&); status_t captureDisplay(DisplayId, const sp&); @@ -564,63 +537,55 @@ private: status_t getDisplayStats(const sp& displayToken, DisplayStatInfo* stats); status_t getDisplayState(const sp& displayToken, ui::DisplayState*) EXCLUDES(mStateLock); - status_t getStaticDisplayInfo(const sp& displayToken, ui::StaticDisplayInfo*) - EXCLUDES(mStateLock) override; - status_t getDynamicDisplayInfo(const sp& displayToken, ui::DynamicDisplayInfo*) - EXCLUDES(mStateLock) override; - status_t getDisplayNativePrimaries(const sp& displayToken, - ui::DisplayPrimaries&) override; - status_t setActiveColorMode(const sp& displayToken, ui::ColorMode colorMode) override; + status_t getStaticDisplayInfo(int64_t displayId, ui::StaticDisplayInfo*) EXCLUDES(mStateLock); + status_t getDynamicDisplayInfoFromId(int64_t displayId, ui::DynamicDisplayInfo*) + EXCLUDES(mStateLock); + status_t getDynamicDisplayInfoFromToken(const sp& displayToken, + ui::DynamicDisplayInfo*) EXCLUDES(mStateLock); + void getDynamicDisplayInfoInternal(ui::DynamicDisplayInfo*&, const sp&, + const display::DisplaySnapshot&); + status_t getDisplayNativePrimaries(const sp& displayToken, ui::DisplayPrimaries&); + status_t setActiveColorMode(const sp& displayToken, ui::ColorMode colorMode); status_t getBootDisplayModeSupport(bool* outSupport) const; - status_t setBootDisplayMode(const sp& displayToken, ui::DisplayModeId id) override; + status_t setBootDisplayMode(const sp&, DisplayModeId); + status_t getOverlaySupport(gui::OverlayProperties* outProperties) const; status_t clearBootDisplayMode(const sp& displayToken); + status_t getHdrConversionCapabilities( + std::vector* hdrConversionCapaabilities) const; + status_t setHdrConversionStrategy(const gui::HdrConversionStrategy& hdrConversionStrategy, + int32_t*); + status_t getHdrOutputConversionSupport(bool* outSupport) const; void setAutoLowLatencyMode(const sp& displayToken, bool on); void setGameContentType(const sp& displayToken, bool on); void setPowerMode(const sp& displayToken, int mode); - status_t clearAnimationFrameStats() override; - status_t getAnimationFrameStats(FrameStats* outStats) const override; status_t overrideHdrTypes(const sp& displayToken, - const std::vector& hdrTypes) override; - status_t onPullAtom(const int32_t atomId, std::string* pulledData, bool* success) override; - status_t enableVSyncInjections(bool enable) override; - status_t injectVSync(nsecs_t when) override; - status_t getLayerDebugInfo(std::vector* outLayers) override; - status_t getColorManagement(bool* outGetColorManagement) const override; + const std::vector& hdrTypes); + status_t onPullAtom(const int32_t atomId, std::vector* pulledData, bool* success); + status_t getLayerDebugInfo(std::vector* outLayers); + status_t getColorManagement(bool* outGetColorManagement) const; status_t getCompositionPreference(ui::Dataspace* outDataspace, ui::PixelFormat* outPixelFormat, ui::Dataspace* outWideColorGamutDataspace, - ui::PixelFormat* outWideColorGamutPixelFormat) const override; + ui::PixelFormat* outWideColorGamutPixelFormat) const; status_t getDisplayedContentSamplingAttributes(const sp& displayToken, ui::PixelFormat* outFormat, ui::Dataspace* outDataspace, - uint8_t* outComponentMask) const override; + uint8_t* outComponentMask) const; status_t setDisplayContentSamplingEnabled(const sp& displayToken, bool enable, - uint8_t componentMask, uint64_t maxFrames) override; + uint8_t componentMask, uint64_t maxFrames); status_t getDisplayedContentSample(const sp& displayToken, uint64_t maxFrames, - uint64_t timestamp, - DisplayedFrameStats* outStats) const override; - status_t getProtectedContentSupport(bool* outSupported) const override; + uint64_t timestamp, DisplayedFrameStats* outStats) const; + status_t getProtectedContentSupport(bool* outSupported) const; status_t isWideColorDisplay(const sp& displayToken, bool* outIsWideColorDisplay) const; status_t addRegionSamplingListener(const Rect& samplingArea, const sp& stopLayerHandle, - const sp& listener) override; - status_t removeRegionSamplingListener(const sp& listener) override; - status_t addFpsListener(int32_t taskId, const sp& listener) override; - status_t removeFpsListener(const sp& listener) override; - status_t addTunnelModeEnabledListener( - const sp& listener) override; - status_t removeTunnelModeEnabledListener( - const sp& listener) override; + const sp& listener); + status_t removeRegionSamplingListener(const sp& listener); + status_t addFpsListener(int32_t taskId, const sp& listener); + status_t removeFpsListener(const sp& listener); + status_t addTunnelModeEnabledListener(const sp& listener); + status_t removeTunnelModeEnabledListener(const sp& listener); status_t setDesiredDisplayModeSpecs(const sp& displayToken, - ui::DisplayModeId displayModeId, bool allowGroupSwitching, - float primaryRefreshRateMin, float primaryRefreshRateMax, - float appRequestRefreshRateMin, - float appRequestRefreshRateMax) override; - status_t getDesiredDisplayModeSpecs(const sp& displayToken, - ui::DisplayModeId* outDefaultMode, - bool* outAllowGroupSwitching, - float* outPrimaryRefreshRateMin, - float* outPrimaryRefreshRateMax, - float* outAppRequestRefreshRateMin, - float* outAppRequestRefreshRateMax) override; + const gui::DisplayModeSpecs&); + status_t getDesiredDisplayModeSpecs(const sp& displayToken, gui::DisplayModeSpecs*); status_t getDisplayBrightnessSupport(const sp& displayToken, bool* outSupport) const; status_t setDisplayBrightness(const sp& displayToken, const gui::DisplayBrightness& brightness); @@ -630,36 +595,33 @@ private: const sp& listener); status_t notifyPowerBoost(int32_t boostId); status_t setGlobalShadowSettings(const half4& ambientColor, const half4& spotColor, - float lightPosY, float lightPosZ, float lightRadius) override; + float lightPosY, float lightPosZ, float lightRadius); status_t getDisplayDecorationSupport( const sp& displayToken, std::optional* - outSupport) const override; + outSupport) const; status_t setFrameRate(const sp& surface, float frameRate, - int8_t compatibility, int8_t changeFrameRateStrategy) override; + int8_t compatibility, int8_t changeFrameRateStrategy); status_t setFrameTimelineInfo(const sp& surface, - const FrameTimelineInfo& frameTimelineInfo) override; - - status_t setOverrideFrameRate(uid_t uid, float frameRate) override; + const gui::FrameTimelineInfo& frameTimelineInfo); - status_t addTransactionTraceListener( - const sp& listener) override; + status_t setOverrideFrameRate(uid_t uid, float frameRate); - int getGPUContextPriority() override; + int getGpuContextPriority(); - status_t getMaxAcquiredBufferCount(int* buffers) const override; + status_t getMaxAcquiredBufferCount(int* buffers) const; - status_t addWindowInfosListener( - const sp& windowInfosListener) const override; + status_t addWindowInfosListener(const sp& windowInfosListener, + gui::WindowInfosListenerInfo* outResult); status_t removeWindowInfosListener( - const sp& windowInfosListener) const override; + const sp& windowInfosListener) const; // Implements IBinder::DeathRecipient. void binderDied(const wp& who) override; // HWC2::ComposerCallback overrides: - void onComposerHalVsync(hal::HWDisplayId, int64_t timestamp, + void onComposerHalVsync(hal::HWDisplayId, nsecs_t timestamp, std::optional) override; void onComposerHalHotplug(hal::HWDisplayId, hal::Connection) override; void onComposerHalRefresh(hal::HWDisplayId) override; @@ -667,34 +629,28 @@ private: const hal::VsyncPeriodChangeTimeline&) override; void onComposerHalSeamlessPossible(hal::HWDisplayId) override; void onComposerHalVsyncIdle(hal::HWDisplayId) override; + void onRefreshRateChangedDebug(const RefreshRateChangedDebugData&) override; // ICompositor overrides: - - // Commits transactions for layers and displays. Returns whether any state has been invalidated, - // i.e. whether a frame should be composited for each display. - bool commit(nsecs_t frameTime, int64_t vsyncId, nsecs_t expectedVsyncTime) override; - - // Composites a frame for each display. CompositionEngine performs GPU and/or HAL composition - // via RenderEngine and the Composer HAL, respectively. - void composite(nsecs_t frameTime, int64_t vsyncId) override; - - // Samples the composited frame via RegionSamplingThread. + void configure() override; + bool commit(TimePoint frameTime, VsyncId, TimePoint expectedVsyncTime) override; + void composite(TimePoint frameTime, VsyncId) override; void sample() override; - /* - * ISchedulerCallback - */ + // ISchedulerCallback overrides: // Toggles hardware VSYNC by calling into HWC. - void setVsyncEnabled(bool) override; - // Sets the desired display mode if allowed by policy. - void requestDisplayMode(DisplayModePtr, DisplayModeEvent) override; - // Called when kernel idle timer has expired. Used to update the refresh rate overlay. + // TODO(b/241286146): Rename for self-explanatory API. + void setVsyncEnabled(PhysicalDisplayId, bool) override; + void requestDisplayModes(std::vector) override; void kernelTimerChanged(bool expired) override; - // Called when the frame rate override list changed to trigger an event. void triggerOnFrameRateOverridesChanged() override; + // Toggles the kernel idle timer on or off depending the policy decisions around refresh rates. void toggleKernelIdleTimer() REQUIRES(mStateLock); + + using KernelIdleTimerController = scheduler::RefreshRateSelector::KernelIdleTimerController; + // 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> @@ -708,93 +664,115 @@ private: bool mKernelIdleTimerEnabled = false; // Show spinner with refresh rate overlay bool mRefreshRateOverlaySpinner = false; + // Show render rate with refresh rate overlay + bool mRefreshRateOverlayRenderRate = false; + // Show render rate overlay offseted to the middle of the screen (e.g. for circular displays) + bool mRefreshRateOverlayShowInMiddle = false; - // Called on the main thread in response to initializeDisplays() - void onInitializeDisplays() REQUIRES(mStateLock); - // Sets the desired active mode bit. It obtains the lock, and sets mDesiredActiveMode. - void setDesiredActiveMode(const ActiveModeInfo& info, bool force = false) REQUIRES(mStateLock); - status_t setActiveModeFromBackdoor(const sp& displayToken, int id); + void setDesiredActiveMode(display::DisplayModeRequest&&, bool force = false) + REQUIRES(mStateLock); + + status_t setActiveModeFromBackdoor(const sp&, DisplayModeId); // Sets the active mode and a new refresh rate in SF. - void updateInternalStateWithChangedMode() REQUIRES(mStateLock); + void updateInternalStateWithChangedMode() REQUIRES(mStateLock, kMainThreadContext); // Calls to setActiveMode on the main thread if there is a pending mode change // that needs to be applied. - void setActiveModeInHwcIfNeeded() REQUIRES(mStateLock); + void setActiveModeInHwcIfNeeded() REQUIRES(mStateLock, kMainThreadContext); void clearDesiredActiveModeState(const sp&) REQUIRES(mStateLock); // Called when active mode is no longer is progress void desiredActiveModeChangeDone(const sp&) REQUIRES(mStateLock); // Called on the main thread in response to setPowerMode() void setPowerModeInternal(const sp& display, hal::PowerMode mode) - REQUIRES(mStateLock); + REQUIRES(mStateLock, kMainThreadContext); - // Returns true if the display has a visible HDR layer in its layer stack. - bool hasVisibleHdrLayer(const sp& display) REQUIRES(mStateLock); + // Returns the preferred mode for PhysicalDisplayId if the Scheduler has selected one for that + // display. Falls back to the display's defaultModeId otherwise. + ftl::Optional getPreferredDisplayMode( + PhysicalDisplayId, DisplayModeId defaultModeId) const REQUIRES(mStateLock); - // Sets the desired display mode specs. status_t setDesiredDisplayModeSpecsInternal( - const sp& display, - const std::optional& policy, bool overridePolicy) - EXCLUDES(mStateLock); - - status_t applyRefreshRateConfigsPolicy(const sp&, bool force = false) - REQUIRES(mStateLock); - - void commitTransactions() EXCLUDES(mStateLock); - void commitTransactionsLocked(uint32_t transactionFlags) REQUIRES(mStateLock); + const sp&, const scheduler::RefreshRateSelector::PolicyVariant&) + EXCLUDES(mStateLock) REQUIRES(kMainThreadContext); + + // TODO(b/241285191): Look up RefreshRateSelector on Scheduler to remove redundant parameter. + status_t applyRefreshRateSelectorPolicy(PhysicalDisplayId, + const scheduler::RefreshRateSelector&, + bool force = false) + REQUIRES(mStateLock, kMainThreadContext); + + void commitTransactions() EXCLUDES(mStateLock) REQUIRES(kMainThreadContext); + void commitTransactionsLocked(uint32_t transactionFlags) + REQUIRES(mStateLock, kMainThreadContext); void doCommitTransactions() REQUIRES(mStateLock); // Returns whether a new buffer has been latched. bool latchBuffers(); void updateLayerGeometry(); - - void updateInputFlinger(); + void updateLayerMetadataSnapshot(); + std::vector> moveSnapshotsToCompositionArgs( + compositionengine::CompositionRefreshArgs& refreshArgs, bool cursorOnly, + int64_t vsyncId); + void moveSnapshotsFromCompositionArgs(compositionengine::CompositionRefreshArgs& refreshArgs, + std::vector>& layers); + bool updateLayerSnapshotsLegacy(VsyncId vsyncId, frontend::Update& update, + bool transactionsFlushed, bool& out) + REQUIRES(kMainThreadContext); + bool updateLayerSnapshots(VsyncId vsyncId, frontend::Update& update, bool transactionsFlushed, + bool& out) REQUIRES(kMainThreadContext); + void updateLayerHistory(const frontend::LayerSnapshot& snapshot); + frontend::Update flushLifecycleUpdates() REQUIRES(kMainThreadContext); + + void updateInputFlinger(VsyncId vsyncId, TimePoint frameTime); void persistDisplayBrightness(bool needsComposite) REQUIRES(kMainThreadContext); void buildWindowInfos(std::vector& outWindowInfos, std::vector& outDisplayInfos); void commitInputWindowCommands() REQUIRES(mStateLock); void updateCursorAsync(); - void initScheduler(const sp& display) REQUIRES(mStateLock); - void updatePhaseConfiguration(const Fps&) REQUIRES(mStateLock); - void setVsyncConfig(const VsyncModulator::VsyncConfig&, nsecs_t vsyncPeriod); + void initScheduler(const sp&) REQUIRES(kMainThreadContext, mStateLock); + void resetPhaseConfiguration(Fps) REQUIRES(mStateLock, kMainThreadContext); + void updatePhaseConfiguration(Fps) REQUIRES(mStateLock); /* * Transactions */ - bool applyTransactionState(const FrameTimelineInfo& info, Vector& state, + bool applyTransactionState(const FrameTimelineInfo& info, + std::vector& state, Vector& displays, uint32_t flags, const InputWindowCommands& inputWindowCommands, const int64_t desiredPresentTime, bool isAutoTimestamp, - const client_cache_t& uncacheBuffer, const int64_t postTime, - uint32_t permissions, bool hasListenerCallbacks, + const std::vector& uncacheBufferIds, + const int64_t postTime, bool hasListenerCallbacks, const std::vector& listenerCallbacks, int originPid, int originUid, uint64_t transactionId) REQUIRES(mStateLock); - // flush pending transaction that was presented after desiredPresentTime. - bool flushTransactionQueues(int64_t vsyncId); + // Flush pending transactions that were presented after desiredPresentTime. + // For test only + bool flushTransactionQueues(VsyncId) REQUIRES(kMainThreadContext); - std::vector flushTransactions(); + bool applyTransactions(std::vector&, VsyncId) REQUIRES(kMainThreadContext); + bool applyAndCommitDisplayTransactionStates(std::vector& transactions) + REQUIRES(kMainThreadContext); // Returns true if there is at least one transaction that needs to be flushed bool transactionFlushNeeded(); - - int flushPendingTransactionQueues( - std::vector& transactions, - std::unordered_map, uint64_t, SpHash>& bufferLayersReadyToPresent, - std::unordered_set, SpHash>& applyTokensWithUnsignaledTransactions, - bool tryApplyUnsignaled) REQUIRES(mStateLock, mQueueLock); - - int flushUnsignaledPendingTransactionQueues( - std::vector& transactions, - std::unordered_map, uint64_t, SpHash>& bufferLayersReadyToPresent, - std::unordered_set, SpHash>& applyTokensWithUnsignaledTransactions) - REQUIRES(mStateLock, mQueueLock); - - uint32_t setClientStateLocked(const FrameTimelineInfo&, ComposerState&, + void addTransactionReadyFilters(); + TransactionHandler::TransactionReadiness transactionReadyTimelineCheck( + const TransactionHandler::TransactionFlushState& flushState) + REQUIRES(kMainThreadContext); + TransactionHandler::TransactionReadiness transactionReadyBufferCheck( + const TransactionHandler::TransactionFlushState& flushState) + REQUIRES(kMainThreadContext); + + uint32_t setClientStateLocked(const FrameTimelineInfo&, ResolvedComposerState&, int64_t desiredPresentTime, bool isAutoTimestamp, - int64_t postTime, uint32_t permissions) REQUIRES(mStateLock); - + int64_t postTime, uint64_t transactionId) REQUIRES(mStateLock); + uint32_t updateLayerCallbacksAndStats(const FrameTimelineInfo&, ResolvedComposerState&, + int64_t desiredPresentTime, bool isAutoTimestamp, + int64_t postTime, uint64_t transactionId) + REQUIRES(mStateLock); uint32_t getTransactionFlags() const; // Sets the masked bits, and schedules a commit if needed. @@ -807,41 +785,20 @@ private: void commitOffscreenLayers(); - enum class TransactionReadiness { - NotReady, - NotReadyBarrier, - Ready, - ReadyUnsignaled, - }; - TransactionReadiness transactionIsReadyToBeApplied(TransactionState& state, - const FrameTimelineInfo& info, bool isAutoTimestamp, int64_t desiredPresentTime, - uid_t originUid, const Vector& states, - const std::unordered_map< - sp, uint64_t, SpHash>& bufferLayersReadyToPresent, - size_t totalTXapplied, bool tryApplyUnsignaled) const REQUIRES(mStateLock); static LatchUnsignaledConfig getLatchUnsignaledConfig(); bool shouldLatchUnsignaled(const sp& layer, const layer_state_t&, size_t numStates, - size_t totalTXapplied) const; - bool stopTransactionProcessing(const std::unordered_set, SpHash>& - applyTokensWithUnsignaledTransactions) const; - bool applyTransactions(std::vector& transactions, int64_t vsyncId); - bool applyTransactionsLocked(std::vector& transactions, int64_t vsyncId) + bool firstTransaction) const; + bool applyTransactionsLocked(std::vector& transactions, VsyncId) REQUIRES(mStateLock); uint32_t setDisplayStateLocked(const DisplayState& s) REQUIRES(mStateLock); uint32_t addInputWindowCommands(const InputWindowCommands& inputWindowCommands) REQUIRES(mStateLock); - bool frameIsEarly(nsecs_t expectedPresentTime, int64_t vsyncId) const; + bool frameIsEarly(TimePoint expectedPresentTime, VsyncId) const; + /* * Layer management */ - status_t createLayer(LayerCreationArgs& args, sp* outHandle, - const sp& parentHandle, int32_t* outLayerId, - const sp& parentLayer = nullptr, - uint32_t* outTransformHint = nullptr); - - status_t createBufferQueueLayer(LayerCreationArgs& args, PixelFormat& format, - sp* outHandle, sp* outGbp, - sp* outLayer); + status_t createLayer(LayerCreationArgs& args, gui::CreateSurfaceResult& outResult); status_t createBufferStateLayer(LayerCreationArgs& args, sp* outHandle, sp* outLayer); @@ -849,21 +806,17 @@ private: status_t createEffectLayer(const LayerCreationArgs& args, sp* outHandle, sp* outLayer); - status_t createContainerLayer(const LayerCreationArgs& args, sp* outHandle, - sp* outLayer); - status_t mirrorLayer(const LayerCreationArgs& args, const sp& mirrorFromHandle, - sp* outHandle, int32_t* outLayerId); + gui::CreateSurfaceResult& outResult); + + status_t mirrorDisplay(DisplayId displayId, const LayerCreationArgs& args, + gui::CreateSurfaceResult& outResult); - // called when all clients have released all their references to - // this layer meaning it is entirely safe to destroy all - // resources associated to this layer. - void onHandleDestroyed(BBinder* handle, sp& layer); void markLayerPendingRemovalLocked(const sp& layer) REQUIRES(mStateLock); // add a layer to SurfaceFlinger - status_t addClientLayer(const sp& client, const sp& handle, - const sp& lbc, const wp& parentLayer, bool addToRoot, + status_t addClientLayer(LayerCreationArgs& args, const sp& handle, + const sp& layer, const wp& parentLayer, uint32_t* outTransformHint); // Traverse through all the layers and compute and cache its bounds. @@ -872,22 +825,25 @@ private: // Boot animation, on/off animations and screen capture void startBootAnim(); - ftl::SharedFuture captureScreenCommon(RenderAreaFuture, TraverseLayersFunction, + ftl::SharedFuture captureScreenCommon(RenderAreaFuture, GetLayerSnapshotsFunction, ui::Size bufferSize, ui::PixelFormat, bool allowProtected, bool grayscale, const sp&); ftl::SharedFuture captureScreenCommon( - RenderAreaFuture, TraverseLayersFunction, + RenderAreaFuture, GetLayerSnapshotsFunction, const std::shared_ptr&, bool regionSampling, bool grayscale, const sp&); ftl::SharedFuture renderScreenImpl( - const RenderArea&, TraverseLayersFunction, + std::shared_ptr, GetLayerSnapshotsFunction, const std::shared_ptr&, bool canCaptureBlackoutContent, - bool regionSampling, bool grayscale, ScreenCaptureResults&) EXCLUDES(mStateLock); + bool regionSampling, bool grayscale, ScreenCaptureResults&) 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, const LayerVector::Visitor&); + void traverseLayersInLayerStack(ui::LayerStack, const int32_t uid, + std::unordered_set excludeLayerIds, + const LayerVector::Visitor&); void readPersistentProperties(); @@ -896,8 +852,9 @@ private: /* * Display and layer stack management */ - // called when starting, or restarting after system_server death - void initializeDisplays(); + + // Called during boot, and restart after system_server death. + void initializeDisplays() REQUIRES(kMainThreadContext); sp getDisplayDeviceLocked(const wp& displayToken) const REQUIRES(mStateLock) { @@ -905,8 +862,9 @@ private: } sp getDisplayDeviceLocked(const wp& displayToken) REQUIRES(mStateLock) { - const sp nullDisplay; - return mDisplays.get(displayToken).value_or(std::cref(nullDisplay)); + return mDisplays.get(displayToken) + .or_else(ftl::static_ref>([] { return nullptr; })) + .value(); } sp getDisplayDeviceLocked(PhysicalDisplayId id) const @@ -933,12 +891,12 @@ private: } sp getDefaultDisplayDeviceLocked() REQUIRES(mStateLock) { - if (const auto display = getDisplayDeviceLocked(mActiveDisplayToken)) { + if (const auto display = getDisplayDeviceLocked(mActiveDisplayId)) { return display; } // The active display is outdated, so fall back to the primary display. - mActiveDisplayToken.clear(); - return getDisplayDeviceLocked(getPrimaryDisplayTokenLocked()); + mActiveDisplayId = getPrimaryDisplayIdLocked(); + return getDisplayDeviceLocked(mActiveDisplayId); } sp getDefaultDisplayDevice() const EXCLUDES(mStateLock) { @@ -946,6 +904,21 @@ private: return getDefaultDisplayDeviceLocked(); } + using DisplayDeviceAndSnapshot = + std::pair, display::PhysicalDisplay::SnapshotRef>; + + // Combinator for ftl::Optional::and_then. + auto getDisplayDeviceAndSnapshot() REQUIRES(mStateLock) { + return [this](const display::PhysicalDisplay& display) REQUIRES( + mStateLock) -> ftl::Optional { + if (auto device = getDisplayDeviceLocked(display.snapshot().displayId())) { + return std::make_pair(std::move(device), display.snapshotRef()); + } + + return {}; + }; + } + // Returns the first display that matches a `bool(const DisplayDevice&)` predicate. template sp findDisplay(Predicate p) const REQUIRES(mStateLock) { @@ -959,10 +932,15 @@ private: // mark a region of a layer stack dirty. this updates the dirty // region of all screens presenting this layer stack. - void invalidateLayerStack(const sp& layer, const Region& dirty); + void invalidateLayerStack(const ui::LayerFilter& layerFilter, const Region& dirty); - bool isDisplayActiveLocked(const sp& display) const REQUIRES(mStateLock) { - return display->getDisplayToken() == mActiveDisplayToken; + ui::LayerFilter makeLayerFilterForDisplay(DisplayId displayId, ui::LayerStack layerStack) + REQUIRES(mStateLock) { + return {layerStack, + PhysicalDisplayId::tryCast(displayId) + .and_then(display::getPhysicalDisplay(mPhysicalDisplays)) + .transform(&display::PhysicalDisplay::isInternal) + .value_or(false)}; } /* @@ -979,14 +957,7 @@ private: /* * Compositing */ - void postComposition(); - void getCompositorTiming(CompositorTiming* compositorTiming); - void updateCompositorTiming(const DisplayStatInfo& stats, nsecs_t compositeTime, - std::shared_ptr& presentFenceTime); - void setCompositorTimingSnapped(const DisplayStatInfo& stats, - nsecs_t compositeToPresentLatency); - - void postFrame() REQUIRES(kMainThreadContext); + void postComposition(nsecs_t callTime) REQUIRES(kMainThreadContext); /* * Display management @@ -994,18 +965,31 @@ private: std::pair loadDisplayModes(PhysicalDisplayId) const REQUIRES(mStateLock); + // TODO(b/241285876): Move to DisplayConfigurator. + // + // Returns whether displays have been added/changed/removed, i.e. whether ICompositor should + // commit display transactions. + bool configureLocked() REQUIRES(mStateLock) REQUIRES(kMainThreadContext) + EXCLUDES(mHotplugMutex); + + // Returns a string describing the hotplug, or nullptr if it was rejected. + const char* processHotplug(PhysicalDisplayId, hal::HWDisplayId, bool connected, + DisplayIdentificationInfo&&) REQUIRES(mStateLock) + REQUIRES(kMainThreadContext); + sp setupNewDisplayDeviceInternal( const wp& displayToken, std::shared_ptr compositionDisplay, const DisplayDeviceState& state, const sp& displaySurface, const sp& producer) REQUIRES(mStateLock); - void processDisplayChangesLocked() REQUIRES(mStateLock); - void processDisplayRemoved(const wp& displayToken) REQUIRES(mStateLock); + void processDisplayChangesLocked() REQUIRES(mStateLock, kMainThreadContext); + void processDisplayRemoved(const wp& displayToken) + REQUIRES(mStateLock, kMainThreadContext); void processDisplayChanged(const wp& displayToken, const DisplayDeviceState& currentState, - const DisplayDeviceState& drawingState) REQUIRES(mStateLock); - void processDisplayHotplugEventsLocked() REQUIRES(mStateLock); + const DisplayDeviceState& drawingState) + REQUIRES(mStateLock, kMainThreadContext); void dispatchDisplayHotplugEvent(PhysicalDisplayId displayId, bool connected); @@ -1014,55 +998,38 @@ private: */ nsecs_t getVsyncPeriodFromHWC() const REQUIRES(mStateLock); - void setHWCVsyncEnabled(PhysicalDisplayId id, hal::Vsync enabled) { - mLastHWCVsyncState = enabled; - getHwComposer().setVsyncEnabled(id, enabled); + void setHWCVsyncEnabled(PhysicalDisplayId id, bool enabled) { + hal::Vsync halState = enabled ? hal::Vsync::ENABLE : hal::Vsync::DISABLE; + getHwComposer().setVsyncEnabled(id, halState); } - struct FenceWithFenceTime { - sp fence = Fence::NO_FENCE; - std::shared_ptr fenceTime = FenceTime::NO_FENCE; - }; + using FenceTimePtr = std::shared_ptr; - // Gets the fence for the previous frame. - // Must be called on the main thread. - FenceWithFenceTime previousFrameFence(); + bool wouldPresentEarly(TimePoint frameTime, Period) const REQUIRES(kMainThreadContext); - // Whether the previous frame has not yet been presented to the display. - // If graceTimeMs is positive, this method waits for at most the provided - // grace period before reporting if the frame missed. - // Must be called on the main thread. - bool previousFramePending(int graceTimeMs = 0); + const FenceTimePtr& getPreviousPresentFence(TimePoint frameTime, Period) const + REQUIRES(kMainThreadContext); - // Returns the previous time that the frame was presented. If the frame has - // not been presented yet, then returns Fence::SIGNAL_TIME_PENDING. If there - // is no pending frame, then returns Fence::SIGNAL_TIME_INVALID. - // Must be called on the main thread. - nsecs_t previousFramePresentTime(); + // Blocks the thread waiting for up to graceTimeMs in case the fence is about to signal. + static bool isFencePending(const FenceTimePtr&, int graceTimeMs); // Calculates the expected present time for this frame. For negative offsets, performs a // correction using the predicted vsync for the next frame instead. - - nsecs_t calculateExpectedPresentTime(DisplayStatInfo) const; + TimePoint calculateExpectedPresentTime(TimePoint frameTime) const; /* * Display identification */ - sp getPhysicalDisplayTokenLocked(PhysicalDisplayId displayId) const + sp getPhysicalDisplayTokenLocked(PhysicalDisplayId displayId) const REQUIRES(mStateLock) { - const sp nullToken; - return mPhysicalDisplayTokens.get(displayId).value_or(std::cref(nullToken)); + return mPhysicalDisplays.get(displayId) + .transform([](const display::PhysicalDisplay& display) { return display.token(); }) + .or_else([] { return std::optional>(nullptr); }) + .value(); } std::optional getPhysicalDisplayIdLocked( - const sp& displayToken) const REQUIRES(mStateLock) { - for (const auto& [id, token] : mPhysicalDisplayTokens) { - if (token == displayToken) { - return id; - } - } - return {}; - } + const sp&) const REQUIRES(mStateLock); // Returns the first display connected at boot. // @@ -1085,15 +1052,18 @@ private: VirtualDisplayId acquireVirtualDisplay(ui::Size, ui::PixelFormat) REQUIRES(mStateLock); void releaseVirtualDisplay(VirtualDisplayId); - void onActiveDisplayChangedLocked(const sp& activeDisplay) REQUIRES(mStateLock); + void onActiveDisplayChangedLocked(const DisplayDevice* inactiveDisplayPtr, + const DisplayDevice& activeDisplay) + REQUIRES(mStateLock, kMainThreadContext); - void onActiveDisplaySizeChanged(const sp& activeDisplay); + void onActiveDisplaySizeChanged(const DisplayDevice&); /* * Debugging & dumpsys */ void dumpAllLocked(const DumpArgs& args, const std::string& compositionLayers, std::string& result) const REQUIRES(mStateLock); + void dumpHwcLayersMinidumpLocked(std::string& result) const REQUIRES(mStateLock); void appendSfConfigString(std::string& result) const; void listLayersLocked(std::string& result) const; @@ -1101,10 +1071,11 @@ private: void clearStatsLocked(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() REQUIRES(kMainThreadContext); + void logFrameStats(TimePoint now) REQUIRES(kMainThreadContext); - void dumpVSync(std::string& result) const REQUIRES(mStateLock); - void dumpStaticScreenStats(std::string& result) const; + void dumpScheduler(std::string& result) const REQUIRES(mStateLock); + void dumpEvents(std::string& result) const REQUIRES(mStateLock); + void dumpVsync(std::string& result) const REQUIRES(mStateLock); void dumpCompositionDisplays(std::string& result) const REQUIRES(mStateLock); void dumpDisplays(std::string& result) const REQUIRES(mStateLock); @@ -1115,7 +1086,9 @@ private: LayersProto dumpDrawingStateProto(uint32_t traceFlags) const; void dumpOffscreenLayersProto(LayersProto& layersProto, uint32_t traceFlags = LayerTracing::TRACE_ALL) const; - void dumpDisplayProto(LayersTraceProto& layersTraceProto) const; + google::protobuf::RepeatedPtrField dumpDisplayProto() const; + void addToLayerTracing(bool visibleRegionDirty, int64_t time, int64_t vsyncId) + REQUIRES(kMainThreadContext); // Dumps state from HW Composer void dumpHwc(std::string& result) const; @@ -1141,52 +1114,52 @@ private: status_t CheckTransactCodeCredentials(uint32_t code); // Add transaction to the Transaction Queue - void queueTransaction(TransactionState& state) EXCLUDES(mQueueLock); - void waitForSynchronousTransaction(const CountDownLatch& transactionCommittedSignal); - void signalSynchronousTransactions(const uint32_t flag); /* * Generic Layer Metadata */ const std::unordered_map& getGenericLayerMetadataKeyMap() const; - /* - * Misc - */ - std::vector getDisplayColorModes(const DisplayDevice&) REQUIRES(mStateLock); - static int calculateMaxAcquiredBufferCount(Fps refreshRate, std::chrono::nanoseconds presentLatency); int getMaxAcquiredBufferCountForRefreshRate(Fps refreshRate) const; - void updateInternalDisplayVsyncLocked(const sp& activeDisplay) - REQUIRES(mStateLock); - - bool isHdrLayer(Layer* layer) const; + bool isHdrLayer(const frontend::LayerSnapshot& snapshot) const; ui::Rotation getPhysicalDisplayOrientation(DisplayId, bool isPrimary) const REQUIRES(mStateLock); + void traverseLegacyLayers(const LayerVector::Visitor& visitor) const; sp mStartPropertySetThread; surfaceflinger::Factory& mFactory; pid_t mPid; std::future mRenderEnginePrimeCacheFuture; - // access must be protected by mStateLock + // mStateLock has conventions related to the current thread, because only + // the main thread should modify variables protected by mStateLock. + // - read access from a non-main thread must lock mStateLock, since the main + // thread may modify these variables. + // - write access from a non-main thread is not permitted. + // - read access from the main thread can use an ftl::FakeGuard, since other + // threads must not modify these variables. + // - write access from the main thread must lock mStateLock, since another + // thread may be reading these variables. mutable Mutex mStateLock; State mCurrentState{LayerVector::StateSet::Current}; std::atomic mTransactionFlags = 0; - std::vector> mTransactionCommittedSignals; - bool mAnimTransactionPending = false; std::atomic mUniqueTransactionId = 1; SortedVector> mLayersPendingRemoval; + // Buffers that have been discarded by clients and need to be evicted from per-layer caches so + // the graphics memory can be immediately freed. + std::vector mBufferIdsToUncache; + // global color transform states Daltonizer mDaltonizer; float mGlobalSaturationFactor = 1.0f; mat4 mClientColorMatrix; - size_t mMaxGraphicBufferProducerListSize = ISurfaceComposer::MAX_LAYERS; + size_t mMaxGraphicBufferProducerListSize = MAX_LAYERS; // If there are more GraphicBufferProducers tracked by SurfaceFlinger than // this threshold, then begin logging. size_t mGraphicBufferProducerListSizeLogThreshold = @@ -1201,7 +1174,6 @@ private: // constant members (no synchronization needed for access) const nsecs_t mBootTime = systemTime(); - bool mGpuToCpuSupported = false; bool mIsUserBuild = true; // Can only accessed from the main thread, these members @@ -1209,9 +1181,8 @@ private: State mDrawingState{LayerVector::StateSet::Drawing}; bool mVisibleRegionsDirty = false; - // VisibleRegions dirty is already cleared by postComp, but we need to track it to prevent - // extra work in the HDR layer info listener. - bool mVisibleRegionsWereDirtyThisFrame = false; + bool mHdrLayerInfoChanged = false; + // Used to ensure we omit a callback when HDR layer info listener is newly added but the // scene hasn't changed bool mAddingHDRLayerInfoListener = false; @@ -1220,44 +1191,48 @@ private: // Set during transaction application stage to track if the input info or children // for a layer has changed. // TODO: Also move visibleRegions over to a boolean system. - bool mInputInfoChanged = false; + bool mUpdateInputInfo = false; bool mSomeChildrenChanged; - bool mSomeDataspaceChanged = false; bool mForceTransactionDisplayChange = false; - bool mAnimCompositionPending = false; + // Set if LayerMetadata has changed since the last LayerMetadata snapshot. + bool mLayerMetadataSnapshotNeeded = false; + // TODO(b/238781169) validate these on composition // Tracks layers that have pending frames which are candidates for being // latched. std::unordered_set, SpHash> mLayersWithQueuedFrames; + std::unordered_set, SpHash> mLayersWithBuffersRemoved; // Tracks layers that need to update a display's dirty region. std::vector> mLayersPendingRefresh; - // size should be longest sf-duration / shortest vsync period and round up - std::array mPreviousPresentFences; // currently consider 166hz. - // True if in the previous frame at least one layer was composed via the GPU. - bool mHadClientComposition = false; - // True if in the previous frame at least one layer was composed via HW Composer. - // Note that it is possible for a frame to be composed via both client and device - // composition, for example in the case of overlays. - bool mHadDeviceComposition = false; - // True if in the previous frame, the client composition was skipped by reusing the buffer - // used in a previous composition. This can happed if the client composition requests - // did not change. - bool mReusedClientComposition = false; + // Sorted list of layers that were composed during previous frame. This is used to + // avoid an expensive traversal of the layer hierarchy when there are no + // visible region changes. Because this is a list of strong pointers, this will + // extend the life of the layer but this list is only updated in the main thread. + std::vector> mPreviouslyComposedLayers; BootStage mBootStage = BootStage::BOOTLOADER; - std::vector mPendingHotplugEvents GUARDED_BY(mStateLock); + struct HotplugEvent { + hal::HWDisplayId hwcDisplayId; + hal::Connection connection = hal::Connection::INVALID; + }; + + std::mutex mHotplugMutex; + std::vector mPendingHotplugEvents GUARDED_BY(mHotplugMutex); // Displays are composited in `mDisplays` order. Internal displays are inserted at boot and // never removed, so take precedence over external and virtual displays. // - // The static capacities were chosen to exceed a typical number of physical/virtual displays. - // // May be read from any thread, but must only be written from the main thread. - ftl::SmallMap, const sp, 5> mDisplays GUARDED_BY(mStateLock); - ftl::SmallMap, 3> mPhysicalDisplayTokens - GUARDED_BY(mStateLock); + display::DisplayMap, const sp> mDisplays GUARDED_BY(mStateLock); + + 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); struct { DisplayIdGenerator gpu; @@ -1271,10 +1246,9 @@ private: std::atomic_bool mForceFullDamage = false; bool mLayerCachingEnabled = false; - bool mPropagateBackpressureClientComposition = false; - sp mInterceptor; + bool mBackpressureGpuComposition = false; - LayerTracing mLayerTracing{*this}; + LayerTracing mLayerTracing; bool mLayerTracingEnabled = false; std::optional mTransactionTracing; @@ -1284,19 +1258,16 @@ private: const std::unique_ptr mFrameTracer; const std::unique_ptr mFrameTimeline; + VsyncId mLastCommittedVsyncId; + // If blurs should be enabled on this device. bool mSupportsBlur = false; - // If blurs are considered expensive and should require high GPU frequency. - bool mBlursAreExpensive = false; std::atomic mFrameMissedCount = 0; std::atomic mHwcFrameMissedCount = 0; std::atomic mGpuFrameMissedCount = 0; TransactionCallbackInvoker mTransactionCallbackInvoker; - // Thread-safe. - FrameTracker mAnimFrameTracker; - // We maintain a pool of pre-generated texture names to hand out to avoid // layer creation needing to run on the main thread (which it would // otherwise need to do to access RenderEngine). @@ -1304,18 +1275,6 @@ private: uint32_t mTexturePoolSize = 0; std::vector mTexturePool; - mutable Mutex mQueueLock; - Condition mTransactionQueueCV; - std::unordered_map, std::queue, IListenerHash> - mPendingTransactionQueues GUARDED_BY(mQueueLock); - std::deque mTransactionQueue GUARDED_BY(mQueueLock); - /* - * Feature prototyping - */ - - // Static screen stats - bool mHasPoweredOff = false; - std::atomic mNumLayers = 0; // to linkToDeath @@ -1338,21 +1297,28 @@ private: // This property can be used to force SurfaceFlinger to always pick a certain color mode. ui::ColorMode mForceColorMode = ui::ColorMode::NATIVE; + // Whether to enable wide color gamut (e.g. Display P3) for internal displays that support it. + // If false, wide color modes are filtered out for all internal displays. + bool mSupportsWideColor = false; + ui::Dataspace mDefaultCompositionDataspace; ui::Dataspace mWideColorGamutCompositionDataspace; ui::Dataspace mColorSpaceAgnosticDataspace; float mDimmingRatio = -1.f; - SurfaceFlingerBE mBE; + std::unique_ptr mRenderEngine; + std::atomic mNumTrustedPresentationListeners = 0; + std::unique_ptr mCompositionEngine; + + CompositionCoverageFlags mCompositionCoverage; + // mMaxRenderTargetSize is only set once in init() so it doesn't need to be protected by // any mutex. size_t mMaxRenderTargetSize{1}; const std::string mHwcServiceName; - bool hasMockHwc() const { return mHwcServiceName == "mock"; } - /* * Scheduler */ @@ -1363,15 +1329,17 @@ private: // Stores phase offsets configured per refresh rate. std::unique_ptr mVsyncConfiguration; - // Optional to defer construction until PhaseConfiguration is created. - sp mVsyncModulator; - std::unique_ptr mRefreshRateStats; + scheduler::PresentLatencyTracker mPresentLatencyTracker GUARDED_BY(kMainThreadContext); + + struct FenceWithFenceTime { + sp fence = Fence::NO_FENCE; + FenceTimePtr fenceTime = FenceTime::NO_FENCE; + }; + std::array mPreviousPresentFences; - std::atomic mExpectedPresentTime = 0; - nsecs_t mScheduledPresentTime = 0; - hal::Vsync mHWCVsyncPendingState = hal::Vsync::DISABLE; - hal::Vsync mLastHWCVsyncState = hal::Vsync::DISABLE; + TimePoint mScheduledPresentTime GUARDED_BY(kMainThreadContext); + TimePoint mExpectedPresentTime GUARDED_BY(kMainThreadContext); // below flags are set by main thread only bool mSetActiveModePending = false; @@ -1391,7 +1359,7 @@ private: std::unique_ptr mPowerAdvisor; - void enableRefreshRateOverlay(bool enable) REQUIRES(mStateLock); + void enableRefreshRateOverlay(bool enable) REQUIRES(mStateLock, kMainThreadContext); // Flag used to set override desired display mode from backdoor bool mDebugDisplayModeSetByBackdoor = false; @@ -1406,34 +1374,48 @@ private: std::unordered_map> mHdrLayerInfoListeners GUARDED_BY(mStateLock); + mutable std::mutex mCreatedLayersLock; - struct LayerCreatedState { - LayerCreatedState(const wp& layer, const wp parent, bool addToRoot) - : layer(layer), initialParent(parent), addToRoot(addToRoot) {} - wp layer; - // Indicates the initial parent of the created layer, only used for creating layer in - // SurfaceFlinger. If nullptr, it may add the created layer into the current root layers. - wp initialParent; - // Indicates whether the layer getting created should be added at root if there's no parent - // and has permission ACCESS_SURFACE_FLINGER. If set to false and no parent, the layer will - // be added offscreen. - bool addToRoot; - }; // A temporay pool that store the created layers and will be added to current state in main // thread. std::vector mCreatedLayers GUARDED_BY(mCreatedLayersLock); - bool commitCreatedLayers(); - void handleLayerCreatedLocked(const LayerCreatedState& state) REQUIRES(mStateLock); + bool commitCreatedLayers(VsyncId, std::vector& createdLayers); + void handleLayerCreatedLocked(const LayerCreatedState&, VsyncId) REQUIRES(mStateLock); + + mutable std::mutex mMirrorDisplayLock; + struct MirrorDisplayState { + MirrorDisplayState(ui::LayerStack layerStack, sp& rootHandle, + const sp& client) + : layerStack(layerStack), rootHandle(rootHandle), client(client) {} + + ui::LayerStack layerStack; + sp rootHandle; + const sp client; + }; + std::vector mMirrorDisplays GUARDED_BY(mMirrorDisplayLock); + bool commitMirrorDisplays(VsyncId); std::atomic mActiveDisplayTransformHint; + // Must only be accessed on the main thread. + // TODO (b/259407931): Remove. + static ui::Transform::RotationFlags sActiveDisplayRotationFlags; + bool isRefreshRateOverlayEnabled() const REQUIRES(mStateLock) { return hasDisplay( [](const auto& display) { return display.isRefreshRateOverlayEnabled(); }); } - - wp mActiveDisplayToken GUARDED_BY(mStateLock); + std::function>>()> getLayerSnapshotsForScreenshots( + std::optional layerStack, uint32_t uid, + std::function + snapshotFilterFn); + std::function>>()> getLayerSnapshotsForScreenshots( + std::optional layerStack, uint32_t uid, + std::unordered_set excludeLayerIds); + std::function>>()> getLayerSnapshotsForScreenshots( + uint32_t rootLayerId, uint32_t uid, std::unordered_set excludeLayerIds, + bool childrenOnly, const std::optional& optionalParentCrop); const sp mWindowInfosListenerInvoker; @@ -1446,38 +1428,67 @@ private: bool mPowerHintSessionEnabled; - struct { - bool late = false; - bool early = false; - } mPowerHintSessionMode; + bool mLayerLifecycleManagerEnabled = false; + bool mLegacyFrontEndEnabled = true; - nsecs_t mAnimationTransactionTimeout = s2ns(5); + frontend::LayerLifecycleManager mLayerLifecycleManager; + frontend::LayerHierarchyBuilder mLayerHierarchyBuilder{{}}; + frontend::LayerSnapshotBuilder mLayerSnapshotBuilder; - friend class SurfaceComposerAIDL; + std::vector mDestroyedHandles; + std::vector> mNewLayers; + std::vector mNewLayerArgs; + // These classes do not store any client state but help with managing transaction callbacks + // and stats. + std::unordered_map> mLegacyLayers; - // Layers visible during the last commit. This set should only be used for testing set equality - // and membership. The pointers should not be dereferenced as it's possible the set contains - // pointers to freed layers. - std::unordered_set mVisibleLayers; + TransactionHandler mTransactionHandler; + display::DisplayMap mFrontEndDisplayInfos; + bool mFrontEndDisplayInfosChanged = false; + + // WindowInfo ids visible during the last commit. + std::unordered_set mVisibleWindowIds; }; class SurfaceComposerAIDL : public gui::BnSurfaceComposer { public: - SurfaceComposerAIDL(sp sf) { mFlinger = sf; } - + SurfaceComposerAIDL(sp sf) : mFlinger(std::move(sf)) {} + + binder::Status bootFinished() override; + binder::Status createDisplayEventConnection( + VsyncSource vsyncSource, EventRegistration eventRegistration, + const sp& layerHandle, + sp* outConnection) override; + binder::Status createConnection(sp* outClient) override; binder::Status createDisplay(const std::string& displayName, bool secure, - sp* outDisplay) override; + float requestedRefreshRate, sp* outDisplay) override; binder::Status destroyDisplay(const sp& display) override; binder::Status getPhysicalDisplayIds(std::vector* outDisplayIds) override; - binder::Status getPrimaryPhysicalDisplayId(int64_t* outDisplayId) override; binder::Status getPhysicalDisplayToken(int64_t displayId, sp* outDisplay) override; binder::Status setPowerMode(const sp& display, int mode) override; + binder::Status getSupportedFrameTimestamps(std::vector* outSupported) override; binder::Status getDisplayStats(const sp& display, gui::DisplayStatInfo* outStatInfo) override; binder::Status getDisplayState(const sp& display, gui::DisplayState* outState) override; + binder::Status getStaticDisplayInfo(int64_t displayId, + gui::StaticDisplayInfo* outInfo) override; + binder::Status getDynamicDisplayInfoFromId(int64_t displayId, + gui::DynamicDisplayInfo* outInfo) override; + binder::Status getDynamicDisplayInfoFromToken(const sp& display, + gui::DynamicDisplayInfo* outInfo) override; + binder::Status getDisplayNativePrimaries(const sp& display, + gui::DisplayPrimaries* outPrimaries) override; + binder::Status setActiveColorMode(const sp& display, int colorMode) override; + binder::Status setBootDisplayMode(const sp& display, int displayModeId) override; binder::Status clearBootDisplayMode(const sp& display) override; binder::Status getBootDisplayModeSupport(bool* outMode) override; + binder::Status getOverlaySupport(gui::OverlayProperties* outProperties) override; + binder::Status getHdrConversionCapabilities( + std::vector*) override; + binder::Status setHdrConversionStrategy(const gui::HdrConversionStrategy& hdrConversionStrategy, + int32_t*) override; + binder::Status getHdrOutputConversionSupport(bool* outSupport) override; binder::Status setAutoLowLatencyMode(const sp& display, bool on) override; binder::Status setGameContentType(const sp& display, bool on) override; binder::Status captureDisplay(const DisplayCaptureArgs&, @@ -1485,8 +1496,47 @@ public: binder::Status captureDisplayById(int64_t, const sp&) override; binder::Status captureLayers(const LayerCaptureArgs&, const sp&) override; + + // TODO(b/239076119): Remove deprecated AIDL. + [[deprecated]] binder::Status clearAnimationFrameStats() override { + return binder::Status::ok(); + } + [[deprecated]] binder::Status getAnimationFrameStats(gui::FrameStats*) override { + return binder::Status::ok(); + } + + binder::Status overrideHdrTypes(const sp& display, + const std::vector& hdrTypes) override; + binder::Status onPullAtom(int32_t atomId, gui::PullAtomData* outPullData) override; + binder::Status getLayerDebugInfo(std::vector* outLayers) override; + binder::Status getColorManagement(bool* outGetColorManagement) override; + binder::Status getCompositionPreference(gui::CompositionPreference* outPref) override; + binder::Status getDisplayedContentSamplingAttributes( + const sp& display, gui::ContentSamplingAttributes* outAttrs) override; + binder::Status setDisplayContentSamplingEnabled(const sp& display, bool enable, + int8_t componentMask, + int64_t maxFrames) override; + binder::Status getDisplayedContentSample(const sp& display, int64_t maxFrames, + int64_t timestamp, + gui::DisplayedFrameStats* outStats) override; + binder::Status getProtectedContentSupport(bool* outSupporte) override; binder::Status isWideColorDisplay(const sp& token, bool* outIsWideColorDisplay) override; + binder::Status addRegionSamplingListener( + const gui::ARect& samplingArea, const sp& stopLayerHandle, + const sp& listener) override; + binder::Status removeRegionSamplingListener( + const sp& listener) override; + binder::Status addFpsListener(int32_t taskId, const sp& listener) override; + binder::Status removeFpsListener(const sp& listener) override; + binder::Status addTunnelModeEnabledListener( + const sp& listener) override; + binder::Status removeTunnelModeEnabledListener( + const sp& listener) override; + binder::Status setDesiredDisplayModeSpecs(const sp& displayToken, + const gui::DisplayModeSpecs&) override; + binder::Status getDesiredDisplayModeSpecs(const sp& displayToken, + gui::DisplayModeSpecs* outSpecs) override; binder::Status getDisplayBrightnessSupport(const sp& displayToken, bool* outSupport) override; binder::Status setDisplayBrightness(const sp& displayToken, @@ -1496,12 +1546,29 @@ public: binder::Status removeHdrLayerInfoListener( const sp& displayToken, const sp& listener) override; + binder::Status notifyPowerBoost(int boostId) override; + binder::Status setGlobalShadowSettings(const gui::Color& ambientColor, + const gui::Color& spotColor, float lightPosY, + float lightPosZ, float lightRadius) override; + binder::Status getDisplayDecorationSupport( + const sp& displayToken, + std::optional* outSupport) override; + binder::Status setOverrideFrameRate(int32_t uid, float frameRate) override; + binder::Status getGpuContextPriority(int32_t* outPriority) override; + binder::Status getMaxAcquiredBufferCount(int32_t* buffers) override; + binder::Status addWindowInfosListener(const sp& windowInfosListener, + gui::WindowInfosListenerInfo* outInfo) override; + binder::Status removeWindowInfosListener( + const sp& windowInfosListener) override; private: static const constexpr bool kUsePermissionCache = true; status_t checkAccessPermission(bool usePermissionCache = kUsePermissionCache); status_t checkControlDisplayBrightnessPermission(); + status_t checkReadFrameBufferPermission(); + static void getDynamicDisplayInfoInternal(ui::DynamicDisplayInfo& info, + gui::DynamicDisplayInfo*& outInfo); private: sp mFlinger; diff --git a/services/surfaceflinger/SurfaceFlingerDefaultFactory.cpp b/services/surfaceflinger/SurfaceFlingerDefaultFactory.cpp index b81b445daddb846070ae13b28ac1209f5a585156..7e6894d3f7fbc9d7b75be450fb26e4001cb651d8 100644 --- a/services/surfaceflinger/SurfaceFlingerDefaultFactory.cpp +++ b/services/surfaceflinger/SurfaceFlingerDefaultFactory.cpp @@ -22,22 +22,16 @@ #include #include -#include "BufferLayerConsumer.h" -#include "BufferQueueLayer.h" -#include "BufferStateLayer.h" -#include "ContainerLayer.h" #include "DisplayDevice.h" -#include "EffectLayer.h" #include "FrameTracer/FrameTracer.h" #include "Layer.h" -#include "MonitoredProducer.h" #include "NativeWindowSurface.h" #include "StartPropertySetThread.h" #include "SurfaceFlingerDefaultFactory.h" #include "SurfaceFlingerProperties.h" -#include "SurfaceInterceptor.h" #include "DisplayHardware/ComposerHal.h" +#include "FrameTimeline/FrameTimeline.h" #include "Scheduler/Scheduler.h" #include "Scheduler/VsyncConfiguration.h" #include "Scheduler/VsyncController.h" @@ -59,23 +53,19 @@ std::unique_ptr DefaultFactory::createVsyncConfig } } -sp DefaultFactory::createSurfaceInterceptor() { - return new android::impl::SurfaceInterceptor(); -} - sp DefaultFactory::createStartPropertySetThread( bool timestampPropertyValue) { - return new StartPropertySetThread(timestampPropertyValue); + return sp::make(timestampPropertyValue); } sp DefaultFactory::createDisplayDevice(DisplayDeviceCreationArgs& creationArgs) { - return new DisplayDevice(creationArgs); + return sp::make(creationArgs); } sp DefaultFactory::createGraphicBuffer(uint32_t width, uint32_t height, PixelFormat format, uint32_t layerCount, uint64_t usage, std::string requestorName) { - return new GraphicBuffer(width, height, format, layerCount, usage, requestorName); + return sp::make(width, height, format, layerCount, usage, requestorName); } void DefaultFactory::createBufferQueue(sp* outProducer, @@ -84,18 +74,6 @@ void DefaultFactory::createBufferQueue(sp* outProducer, BufferQueue::createBufferQueue(outProducer, outConsumer, consumerIsSurfaceFlinger); } -sp DefaultFactory::createMonitoredProducer( - const sp& producer, const sp& flinger, - const wp& layer) { - return new MonitoredProducer(producer, flinger, layer); -} - -sp DefaultFactory::createBufferLayerConsumer( - const sp& consumer, renderengine::RenderEngine& renderEngine, - uint32_t textureName, Layer* layer) { - return new BufferLayerConsumer(consumer, renderEngine, textureName, layer); -} - std::unique_ptr DefaultFactory::createNativeWindowSurface( const sp& producer) { return surfaceflinger::impl::createNativeWindowSurface(producer); @@ -105,20 +83,16 @@ std::unique_ptr DefaultFactory::createComp return compositionengine::impl::createCompositionEngine(); } -sp DefaultFactory::createContainerLayer(const LayerCreationArgs& args) { - return new ContainerLayer(args); -} - -sp DefaultFactory::createBufferQueueLayer(const LayerCreationArgs& args) { - return new BufferQueueLayer(args); +sp DefaultFactory::createBufferStateLayer(const LayerCreationArgs& args) { + return sp::make(args); } -sp DefaultFactory::createBufferStateLayer(const LayerCreationArgs& args) { - return new BufferStateLayer(args); +sp DefaultFactory::createEffectLayer(const LayerCreationArgs& args) { + return sp::make(args); } -sp DefaultFactory::createEffectLayer(const LayerCreationArgs& args) { - return new EffectLayer(args); +sp DefaultFactory::createLayerFE(const std::string& layerName) { + return sp::make(layerName); } std::unique_ptr DefaultFactory::createFrameTracer() { diff --git a/services/surfaceflinger/SurfaceFlingerDefaultFactory.h b/services/surfaceflinger/SurfaceFlingerDefaultFactory.h index 501629d4da26855372e0fd28ebb2034b30a73f0b..2c6de0e113c59a29ec062450e7b4d61162bd3d81 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 createSurfaceInterceptor() override; sp createStartPropertySetThread(bool timestampPropertyValue) override; sp createDisplayDevice(DisplayDeviceCreationArgs&) override; sp createGraphicBuffer(uint32_t width, uint32_t height, PixelFormat format, @@ -38,19 +37,12 @@ public: void createBufferQueue(sp* outProducer, sp* outConsumer, bool consumerIsSurfaceFlinger) override; - sp createMonitoredProducer(const sp&, - const sp&, - const wp&) override; - sp createBufferLayerConsumer(const sp&, - renderengine::RenderEngine&, uint32_t tex, - Layer*) override; std::unique_ptr createNativeWindowSurface( const sp&) override; std::unique_ptr createCompositionEngine() override; - sp createBufferQueueLayer(const LayerCreationArgs& args) override; - sp createBufferStateLayer(const LayerCreationArgs& args) override; - sp createEffectLayer(const LayerCreationArgs& args) override; - sp createContainerLayer(const LayerCreationArgs& args) override; + sp createBufferStateLayer(const LayerCreationArgs& args) override; + sp createEffectLayer(const LayerCreationArgs& args) override; + sp createLayerFE(const std::string& layerName) override; std::unique_ptr createFrameTracer() override; std::unique_ptr createFrameTimeline( std::shared_ptr timeStats, pid_t surfaceFlingerPid) override; diff --git a/services/surfaceflinger/SurfaceFlingerFactory.cpp b/services/surfaceflinger/SurfaceFlingerFactory.cpp index 3997b04f5fdd6faa399271cc59e45169aa1f3de7..7bd6cf69f366f2c2fd5ecc44abf16c52be79a75b 100644 --- a/services/surfaceflinger/SurfaceFlingerFactory.cpp +++ b/services/surfaceflinger/SurfaceFlingerFactory.cpp @@ -26,7 +26,7 @@ namespace android::surfaceflinger { sp createSurfaceFlinger() { static DefaultFactory factory; - return new SurfaceFlinger(factory); + return sp::make(factory); } } // namespace android::surfaceflinger diff --git a/services/surfaceflinger/SurfaceFlingerFactory.h b/services/surfaceflinger/SurfaceFlingerFactory.h index 6153e8e35490d44de9b597c9b296155b4ee2ee01..f310c4ac53c131e28be76b3dffb2ba8d9a444e36 100644 --- a/services/surfaceflinger/SurfaceFlingerFactory.h +++ b/services/surfaceflinger/SurfaceFlingerFactory.h @@ -30,25 +30,20 @@ namespace android { typedef int32_t PixelFormat; -class BufferQueueLayer; class BufferLayerConsumer; -class BufferStateLayer; -class ContainerLayer; class DisplayDevice; -class EffectLayer; class FrameTracer; class GraphicBuffer; class HWComposer; class IGraphicBufferConsumer; class IGraphicBufferProducer; class Layer; +class LayerFE; class StartPropertySetThread; class SurfaceFlinger; -class SurfaceInterceptor; class TimeStats; struct DisplayDeviceCreationArgs; -struct LayerCreationArgs; namespace compositionengine { class CompositionEngine; @@ -57,7 +52,6 @@ class CompositionEngine; namespace scheduler { class VsyncConfiguration; class VsyncController; -class RefreshRateConfigs; } // namespace scheduler namespace frametimeline { @@ -66,6 +60,7 @@ class FrameTimeline; namespace surfaceflinger { +struct LayerCreationArgs; class NativeWindowSurface; // The interface that SurfaceFlinger uses to create all of the implementations @@ -75,7 +70,6 @@ public: virtual std::unique_ptr createHWComposer(const std::string& serviceName) = 0; virtual std::unique_ptr createVsyncConfiguration( Fps currentRefreshRate) = 0; - virtual sp createSurfaceInterceptor() = 0; virtual sp createStartPropertySetThread( bool timestampPropertyValue) = 0; @@ -86,22 +80,15 @@ public: virtual void createBufferQueue(sp* outProducer, sp* outConsumer, bool consumerIsSurfaceFlinger) = 0; - virtual sp createMonitoredProducer(const sp&, - const sp&, - const wp&) = 0; - virtual sp createBufferLayerConsumer(const sp&, - renderengine::RenderEngine&, - uint32_t tex, Layer*) = 0; virtual std::unique_ptr createNativeWindowSurface( const sp&) = 0; virtual std::unique_ptr createCompositionEngine() = 0; - virtual sp createBufferQueueLayer(const LayerCreationArgs& args) = 0; - virtual sp createBufferStateLayer(const LayerCreationArgs& args) = 0; - virtual sp createEffectLayer(const LayerCreationArgs& args) = 0; - virtual sp createContainerLayer(const LayerCreationArgs& args) = 0; + virtual sp createBufferStateLayer(const LayerCreationArgs& args) = 0; + virtual sp createEffectLayer(const LayerCreationArgs& args) = 0; + virtual sp createLayerFE(const std::string& layerName) = 0; virtual std::unique_ptr createFrameTracer() = 0; virtual std::unique_ptr createFrameTimeline( std::shared_ptr timeStats, pid_t surfaceFlingerPid) = 0; diff --git a/services/surfaceflinger/SurfaceFlingerProperties.cpp b/services/surfaceflinger/SurfaceFlingerProperties.cpp index 20fa0917307f0f9d8fa39de12e6cca0ec729638c..96c8b54005bd9b8c048d61ce2022fb6735559e7a 100644 --- a/services/surfaceflinger/SurfaceFlingerProperties.cpp +++ b/services/surfaceflinger/SurfaceFlingerProperties.cpp @@ -375,5 +375,9 @@ bool ignore_hdr_camera_layers(bool defaultValue) { return SurfaceFlingerProperties::ignore_hdr_camera_layers().value_or(defaultValue); } +bool clear_slots_with_set_layer_buffer(bool defaultValue) { + return SurfaceFlingerProperties::clear_slots_with_set_layer_buffer().value_or(defaultValue); +} + } // namespace sysprop } // namespace android diff --git a/services/surfaceflinger/SurfaceFlingerProperties.h b/services/surfaceflinger/SurfaceFlingerProperties.h index 080feee686fb2f3e30a3cb80a51d664505bfc5a4..951f8f8cb3073f8f49b9b6de73cb1ddf2ecb54e7 100644 --- a/services/surfaceflinger/SurfaceFlingerProperties.h +++ b/services/surfaceflinger/SurfaceFlingerProperties.h @@ -102,6 +102,8 @@ bool enable_sdr_dimming(bool defaultValue); bool ignore_hdr_camera_layers(bool defaultValue); +bool clear_slots_with_set_layer_buffer(bool defaultValue); + } // namespace sysprop } // namespace android #endif // SURFACEFLINGERPROPERTIES_H_ diff --git a/services/surfaceflinger/SurfaceInterceptor.cpp b/services/surfaceflinger/SurfaceInterceptor.cpp deleted file mode 100644 index bace6cc6a6501b768cec57eae57bd83d10c8bfe7..0000000000000000000000000000000000000000 --- a/services/surfaceflinger/SurfaceInterceptor.cpp +++ /dev/null @@ -1,719 +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. - */ - -// TODO(b/129481165): remove the #pragma below and fix conversion issues -#pragma clang diagnostic push -#pragma clang diagnostic ignored "-Wconversion" -#undef LOG_TAG -#define LOG_TAG "SurfaceInterceptor" -#define ATRACE_TAG ATRACE_TAG_GRAPHICS - -#include "Layer.h" -#include "SurfaceFlinger.h" -#include "SurfaceInterceptor.h" - -#include - -#include -#include -#include - -namespace android { - -// ---------------------------------------------------------------------------- -// TODO(marissaw): add new layer state values to SurfaceInterceptor - -SurfaceInterceptor::~SurfaceInterceptor() = default; - -namespace impl { - -void SurfaceInterceptor::addTransactionTraceListener( - const sp& listener) { - sp asBinder = IInterface::asBinder(listener); - - std::scoped_lock lock(mListenersMutex); - - asBinder->linkToDeath(this); - - listener->onToggled(mEnabled); // notifies of current state - - mTraceToggledListeners.emplace(asBinder, listener); -} - -void SurfaceInterceptor::binderDied(const wp& who) { - std::scoped_lock lock(mListenersMutex); - mTraceToggledListeners.erase(who); -} - -void SurfaceInterceptor::enable(const SortedVector>& layers, - const DefaultKeyedVector< wp, DisplayDeviceState>& displays) -{ - if (mEnabled) { - return; - } - ATRACE_CALL(); - { - std::scoped_lock lock(mListenersMutex); - for (const auto& [_, listener] : mTraceToggledListeners) { - listener->onToggled(true); - } - } - mEnabled = true; - std::scoped_lock protoGuard(mTraceMutex); - saveExistingDisplaysLocked(displays); - saveExistingSurfacesLocked(layers); -} - -void SurfaceInterceptor::disable() { - if (!mEnabled) { - return; - } - ATRACE_CALL(); - { - std::scoped_lock lock(mListenersMutex); - for (const auto& [_, listener] : mTraceToggledListeners) { - listener->onToggled(false); - } - } - mEnabled = false; - std::scoped_lock protoGuard(mTraceMutex); - status_t err(writeProtoFileLocked()); - ALOGE_IF(err == PERMISSION_DENIED, "Could not save the proto file! Permission denied"); - ALOGE_IF(err == NOT_ENOUGH_DATA, "Could not save the proto file! There are missing fields"); - mTrace.Clear(); -} - -bool SurfaceInterceptor::isEnabled() { - return mEnabled; -} - -void SurfaceInterceptor::saveExistingDisplaysLocked( - const DefaultKeyedVector< wp, DisplayDeviceState>& displays) -{ - // Caveat: The initial snapshot does not capture the power mode of the existing displays - ATRACE_CALL(); - for (size_t i = 0 ; i < displays.size() ; i++) { - addDisplayCreationLocked(createTraceIncrementLocked(), displays[i]); - addInitialDisplayStateLocked(createTraceIncrementLocked(), displays[i]); - } -} - -void SurfaceInterceptor::saveExistingSurfacesLocked(const SortedVector>& layers) { - ATRACE_CALL(); - for (const auto& l : layers) { - l->traverseInZOrder(LayerVector::StateSet::Drawing, [this](Layer* layer) { - addSurfaceCreationLocked(createTraceIncrementLocked(), layer); - addInitialSurfaceStateLocked(createTraceIncrementLocked(), layer); - }); - } -} - -void SurfaceInterceptor::addInitialSurfaceStateLocked(Increment* increment, - const sp& layer) -{ - Transaction* transaction(increment->mutable_transaction()); - const uint32_t layerFlags = layer->getTransactionFlags(); - transaction->set_synchronous(layerFlags & BnSurfaceComposer::eSynchronous); - transaction->set_animation(layerFlags & BnSurfaceComposer::eAnimation); - - const int32_t layerId(getLayerId(layer)); - addPositionLocked(transaction, layerId, layer->mDrawingState.transform.tx(), - layer->mDrawingState.transform.ty()); - addDepthLocked(transaction, layerId, layer->mDrawingState.z); - addAlphaLocked(transaction, layerId, layer->mDrawingState.color.a); - addTransparentRegionLocked(transaction, layerId, - layer->mDrawingState.activeTransparentRegion_legacy); - addLayerStackLocked(transaction, layerId, layer->mDrawingState.layerStack); - addCropLocked(transaction, layerId, layer->mDrawingState.crop); - addCornerRadiusLocked(transaction, layerId, layer->mDrawingState.cornerRadius); - addBackgroundBlurRadiusLocked(transaction, layerId, layer->mDrawingState.backgroundBlurRadius); - addBlurRegionsLocked(transaction, layerId, layer->mDrawingState.blurRegions); - addFlagsLocked(transaction, layerId, layer->mDrawingState.flags, - layer_state_t::eLayerHidden | layer_state_t::eLayerOpaque | - layer_state_t::eLayerSecure); - addReparentLocked(transaction, layerId, getLayerIdFromWeakRef(layer->mDrawingParent)); - addRelativeParentLocked(transaction, layerId, - getLayerIdFromWeakRef(layer->mDrawingState.zOrderRelativeOf), - layer->mDrawingState.z); - addShadowRadiusLocked(transaction, layerId, layer->mDrawingState.shadowRadius); - addTrustedOverlayLocked(transaction, layerId, layer->mDrawingState.isTrustedOverlay); -} - -void SurfaceInterceptor::addInitialDisplayStateLocked(Increment* increment, - const DisplayDeviceState& display) -{ - Transaction* transaction(increment->mutable_transaction()); - transaction->set_synchronous(false); - transaction->set_animation(false); - - addDisplaySurfaceLocked(transaction, display.sequenceId, display.surface); - addDisplayLayerStackLocked(transaction, display.sequenceId, display.layerStack); - addDisplayFlagsLocked(transaction, display.sequenceId, display.flags); - addDisplaySizeLocked(transaction, display.sequenceId, display.width, display.height); - addDisplayProjectionLocked(transaction, display.sequenceId, toRotationInt(display.orientation), - display.layerStackSpaceRect, display.orientedDisplaySpaceRect); -} - -status_t SurfaceInterceptor::writeProtoFileLocked() { - ATRACE_CALL(); - std::string output; - - if (!mTrace.IsInitialized()) { - return NOT_ENOUGH_DATA; - } - if (!mTrace.SerializeToString(&output)) { - return PERMISSION_DENIED; - } - if (!android::base::WriteStringToFile(output, mOutputFileName, true)) { - return PERMISSION_DENIED; - } - - return NO_ERROR; -} - -const sp SurfaceInterceptor::getLayer(const wp& weakHandle) const { - sp handle = weakHandle.promote(); - return Layer::fromHandle(handle).promote(); -} - -int32_t SurfaceInterceptor::getLayerId(const sp& layer) const { - return layer->sequence; -} - -int32_t SurfaceInterceptor::getLayerIdFromWeakRef(const wp& layer) const { - if (layer == nullptr) { - return -1; - } - auto strongLayer = layer.promote(); - return strongLayer == nullptr ? -1 : getLayerId(strongLayer); -} - -int32_t SurfaceInterceptor::getLayerIdFromHandle(const sp& handle) const { - if (handle == nullptr) { - return -1; - } - const sp layer = Layer::fromHandle(handle).promote(); - return layer == nullptr ? -1 : getLayerId(layer); -} - -Increment* SurfaceInterceptor::createTraceIncrementLocked() { - Increment* increment(mTrace.add_increment()); - increment->set_time_stamp(elapsedRealtimeNano()); - return increment; -} - -SurfaceChange* SurfaceInterceptor::createSurfaceChangeLocked(Transaction* transaction, - int32_t layerId) -{ - SurfaceChange* change(transaction->add_surface_change()); - change->set_id(layerId); - return change; -} - -DisplayChange* SurfaceInterceptor::createDisplayChangeLocked(Transaction* transaction, - int32_t sequenceId) -{ - DisplayChange* dispChange(transaction->add_display_change()); - dispChange->set_id(sequenceId); - return dispChange; -} - -void SurfaceInterceptor::setProtoRectLocked(Rectangle* protoRect, const Rect& rect) { - protoRect->set_left(rect.left); - protoRect->set_top(rect.top); - protoRect->set_right(rect.right); - protoRect->set_bottom(rect.bottom); -} - -void SurfaceInterceptor::setTransactionOriginLocked(Transaction* transaction, int32_t pid, - int32_t uid) { - Origin* origin(transaction->mutable_origin()); - origin->set_pid(pid); - origin->set_uid(uid); -} - -void SurfaceInterceptor::addPositionLocked(Transaction* transaction, int32_t layerId, - float x, float y) -{ - SurfaceChange* change(createSurfaceChangeLocked(transaction, layerId)); - PositionChange* posChange(change->mutable_position()); - posChange->set_x(x); - posChange->set_y(y); -} - -void SurfaceInterceptor::addDepthLocked(Transaction* transaction, int32_t layerId, - uint32_t z) -{ - SurfaceChange* change(createSurfaceChangeLocked(transaction, layerId)); - LayerChange* depthChange(change->mutable_layer()); - depthChange->set_layer(z); -} - -void SurfaceInterceptor::addSizeLocked(Transaction* transaction, int32_t layerId, uint32_t w, - uint32_t h) -{ - SurfaceChange* change(createSurfaceChangeLocked(transaction, layerId)); - SizeChange* sizeChange(change->mutable_size()); - sizeChange->set_w(w); - sizeChange->set_h(h); -} - -void SurfaceInterceptor::addAlphaLocked(Transaction* transaction, int32_t layerId, - float alpha) -{ - SurfaceChange* change(createSurfaceChangeLocked(transaction, layerId)); - AlphaChange* alphaChange(change->mutable_alpha()); - alphaChange->set_alpha(alpha); -} - -void SurfaceInterceptor::addMatrixLocked(Transaction* transaction, int32_t layerId, - const layer_state_t::matrix22_t& matrix) -{ - SurfaceChange* change(createSurfaceChangeLocked(transaction, layerId)); - MatrixChange* matrixChange(change->mutable_matrix()); - matrixChange->set_dsdx(matrix.dsdx); - matrixChange->set_dtdx(matrix.dtdx); - matrixChange->set_dsdy(matrix.dsdy); - matrixChange->set_dtdy(matrix.dtdy); -} - -void SurfaceInterceptor::addTransparentRegionLocked(Transaction* transaction, - int32_t layerId, const Region& transRegion) -{ - SurfaceChange* change(createSurfaceChangeLocked(transaction, layerId)); - TransparentRegionHintChange* transparentChange(change->mutable_transparent_region_hint()); - - for (const auto& rect : transRegion) { - Rectangle* protoRect(transparentChange->add_region()); - setProtoRectLocked(protoRect, rect); - } -} - -void SurfaceInterceptor::addFlagsLocked(Transaction* transaction, int32_t layerId, uint8_t flags, - uint8_t mask) { - // There can be multiple flags changed - if (mask & layer_state_t::eLayerHidden) { - SurfaceChange* change(createSurfaceChangeLocked(transaction, layerId)); - HiddenFlagChange* flagChange(change->mutable_hidden_flag()); - flagChange->set_hidden_flag(flags & layer_state_t::eLayerHidden); - } - if (mask & layer_state_t::eLayerOpaque) { - SurfaceChange* change(createSurfaceChangeLocked(transaction, layerId)); - OpaqueFlagChange* flagChange(change->mutable_opaque_flag()); - flagChange->set_opaque_flag(flags & layer_state_t::eLayerOpaque); - } - if (mask & layer_state_t::eLayerSecure) { - SurfaceChange* change(createSurfaceChangeLocked(transaction, layerId)); - SecureFlagChange* flagChange(change->mutable_secure_flag()); - flagChange->set_secure_flag(flags & layer_state_t::eLayerSecure); - } -} - -void SurfaceInterceptor::addLayerStackLocked(Transaction* transaction, int32_t layerId, - ui::LayerStack layerStack) { - SurfaceChange* change(createSurfaceChangeLocked(transaction, layerId)); - LayerStackChange* layerStackChange(change->mutable_layer_stack()); - layerStackChange->set_layer_stack(layerStack.id); -} - -void SurfaceInterceptor::addCropLocked(Transaction* transaction, int32_t layerId, - const Rect& rect) -{ - SurfaceChange* change(createSurfaceChangeLocked(transaction, layerId)); - CropChange* cropChange(change->mutable_crop()); - Rectangle* protoRect(cropChange->mutable_rectangle()); - setProtoRectLocked(protoRect, rect); -} - -void SurfaceInterceptor::addCornerRadiusLocked(Transaction* transaction, int32_t layerId, - float cornerRadius) -{ - SurfaceChange* change(createSurfaceChangeLocked(transaction, layerId)); - CornerRadiusChange* cornerRadiusChange(change->mutable_corner_radius()); - cornerRadiusChange->set_corner_radius(cornerRadius); -} - -void SurfaceInterceptor::addBackgroundBlurRadiusLocked(Transaction* transaction, int32_t layerId, - int32_t backgroundBlurRadius) { - SurfaceChange* change(createSurfaceChangeLocked(transaction, layerId)); - BackgroundBlurRadiusChange* blurRadiusChange(change->mutable_background_blur_radius()); - blurRadiusChange->set_background_blur_radius(backgroundBlurRadius); -} - -void SurfaceInterceptor::addBlurRegionsLocked(Transaction* transaction, int32_t layerId, - const std::vector& blurRegions) { - SurfaceChange* change(createSurfaceChangeLocked(transaction, layerId)); - BlurRegionsChange* blurRegionsChange(change->mutable_blur_regions()); - for (const auto blurRegion : blurRegions) { - const auto blurRegionChange = blurRegionsChange->add_blur_regions(); - blurRegionChange->set_blur_radius(blurRegion.blurRadius); - blurRegionChange->set_corner_radius_tl(blurRegion.cornerRadiusTL); - blurRegionChange->set_corner_radius_tr(blurRegion.cornerRadiusTR); - blurRegionChange->set_corner_radius_bl(blurRegion.cornerRadiusBL); - blurRegionChange->set_corner_radius_br(blurRegion.cornerRadiusBR); - blurRegionChange->set_alpha(blurRegion.alpha); - blurRegionChange->set_left(blurRegion.left); - blurRegionChange->set_top(blurRegion.top); - blurRegionChange->set_right(blurRegion.right); - blurRegionChange->set_bottom(blurRegion.bottom); - } -} - -void SurfaceInterceptor::addReparentLocked(Transaction* transaction, int32_t layerId, - int32_t parentId) { - SurfaceChange* change(createSurfaceChangeLocked(transaction, layerId)); - ReparentChange* overrideChange(change->mutable_reparent()); - overrideChange->set_parent_id(parentId); -} - -void SurfaceInterceptor::addRelativeParentLocked(Transaction* transaction, int32_t layerId, - int32_t parentId, int z) { - SurfaceChange* change(createSurfaceChangeLocked(transaction, layerId)); - RelativeParentChange* overrideChange(change->mutable_relative_parent()); - overrideChange->set_relative_parent_id(parentId); - overrideChange->set_z(z); -} - -void SurfaceInterceptor::addShadowRadiusLocked(Transaction* transaction, int32_t layerId, - float shadowRadius) { - SurfaceChange* change(createSurfaceChangeLocked(transaction, layerId)); - ShadowRadiusChange* overrideChange(change->mutable_shadow_radius()); - overrideChange->set_radius(shadowRadius); -} - -void SurfaceInterceptor::addTrustedOverlayLocked(Transaction* transaction, int32_t layerId, - bool isTrustedOverlay) { - SurfaceChange* change(createSurfaceChangeLocked(transaction, layerId)); - TrustedOverlayChange* overrideChange(change->mutable_trusted_overlay()); - overrideChange->set_is_trusted_overlay(isTrustedOverlay); -} - -void SurfaceInterceptor::addSurfaceChangesLocked(Transaction* transaction, - const layer_state_t& state) -{ - const sp layer(getLayer(state.surface)); - if (layer == nullptr) { - ALOGE("An existing layer could not be retrieved with the surface " - "from the layer_state_t surface in the update transaction"); - return; - } - - const int32_t layerId(getLayerId(layer)); - - if (state.what & layer_state_t::ePositionChanged) { - addPositionLocked(transaction, layerId, state.x, state.y); - } - if (state.what & layer_state_t::eLayerChanged) { - addDepthLocked(transaction, layerId, state.z); - } - if (state.what & layer_state_t::eSizeChanged) { - addSizeLocked(transaction, layerId, state.w, state.h); - } - if (state.what & layer_state_t::eAlphaChanged) { - addAlphaLocked(transaction, layerId, state.alpha); - } - if (state.what & layer_state_t::eMatrixChanged) { - addMatrixLocked(transaction, layerId, state.matrix); - } - if (state.what & layer_state_t::eTransparentRegionChanged) { - addTransparentRegionLocked(transaction, layerId, state.transparentRegion); - } - if (state.what & layer_state_t::eFlagsChanged) { - addFlagsLocked(transaction, layerId, state.flags, state.mask); - } - if (state.what & layer_state_t::eLayerStackChanged) { - addLayerStackLocked(transaction, layerId, state.layerStack); - } - if (state.what & layer_state_t::eCropChanged) { - addCropLocked(transaction, layerId, state.crop); - } - if (state.what & layer_state_t::eCornerRadiusChanged) { - addCornerRadiusLocked(transaction, layerId, state.cornerRadius); - } - if (state.what & layer_state_t::eBackgroundBlurRadiusChanged) { - addBackgroundBlurRadiusLocked(transaction, layerId, state.backgroundBlurRadius); - } - if (state.what & layer_state_t::eBlurRegionsChanged) { - addBlurRegionsLocked(transaction, layerId, state.blurRegions); - } - if (state.what & layer_state_t::eReparent) { - auto parentHandle = (state.parentSurfaceControlForChild) - ? state.parentSurfaceControlForChild->getHandle() - : nullptr; - addReparentLocked(transaction, layerId, getLayerIdFromHandle(parentHandle)); - } - if (state.what & layer_state_t::eRelativeLayerChanged) { - addRelativeParentLocked(transaction, layerId, - getLayerIdFromHandle( - state.relativeLayerSurfaceControl->getHandle()), - state.z); - } - if (state.what & layer_state_t::eShadowRadiusChanged) { - addShadowRadiusLocked(transaction, layerId, state.shadowRadius); - } - if (state.what & layer_state_t::eTrustedOverlayChanged) { - addTrustedOverlayLocked(transaction, layerId, state.isTrustedOverlay); - } - if (state.what & layer_state_t::eStretchChanged) { - ALOGW("SurfaceInterceptor not implemented for eStretchChanged"); - } -} - -void SurfaceInterceptor::addDisplayChangesLocked(Transaction* transaction, - const DisplayState& state, int32_t sequenceId) -{ - if (state.what & DisplayState::eSurfaceChanged) { - addDisplaySurfaceLocked(transaction, sequenceId, state.surface); - } - if (state.what & DisplayState::eLayerStackChanged) { - addDisplayLayerStackLocked(transaction, sequenceId, state.layerStack); - } - if (state.what & DisplayState::eFlagsChanged) { - addDisplayFlagsLocked(transaction, sequenceId, state.flags); - } - if (state.what & DisplayState::eDisplaySizeChanged) { - addDisplaySizeLocked(transaction, sequenceId, state.width, state.height); - } - if (state.what & DisplayState::eDisplayProjectionChanged) { - addDisplayProjectionLocked(transaction, sequenceId, toRotationInt(state.orientation), - state.layerStackSpaceRect, state.orientedDisplaySpaceRect); - } -} - -void SurfaceInterceptor::addTransactionLocked( - Increment* increment, const Vector& stateUpdates, - const DefaultKeyedVector, DisplayDeviceState>& displays, - const Vector& changedDisplays, uint32_t transactionFlags, int originPid, - int originUid, uint64_t transactionId) { - Transaction* transaction(increment->mutable_transaction()); - transaction->set_synchronous(transactionFlags & BnSurfaceComposer::eSynchronous); - transaction->set_animation(transactionFlags & BnSurfaceComposer::eAnimation); - setTransactionOriginLocked(transaction, originPid, originUid); - transaction->set_id(transactionId); - for (const auto& compState: stateUpdates) { - addSurfaceChangesLocked(transaction, compState.state); - } - for (const auto& disp: changedDisplays) { - ssize_t dpyIdx = displays.indexOfKey(disp.token); - if (dpyIdx >= 0) { - const DisplayDeviceState& dispState(displays.valueAt(dpyIdx)); - addDisplayChangesLocked(transaction, disp, dispState.sequenceId); - } - } -} - -void SurfaceInterceptor::addSurfaceCreationLocked(Increment* increment, - const sp& layer) -{ - SurfaceCreation* creation(increment->mutable_surface_creation()); - creation->set_id(getLayerId(layer)); - creation->set_name(layer->getName()); - creation->set_w(layer->mDrawingState.active_legacy.w); - creation->set_h(layer->mDrawingState.active_legacy.h); -} - -void SurfaceInterceptor::addSurfaceDeletionLocked(Increment* increment, - const sp& layer) -{ - SurfaceDeletion* deletion(increment->mutable_surface_deletion()); - deletion->set_id(getLayerId(layer)); -} - -void SurfaceInterceptor::addBufferUpdateLocked(Increment* increment, int32_t layerId, - uint32_t width, uint32_t height, uint64_t frameNumber) -{ - BufferUpdate* update(increment->mutable_buffer_update()); - update->set_id(layerId); - update->set_w(width); - update->set_h(height); - update->set_frame_number(frameNumber); -} - -void SurfaceInterceptor::addVSyncUpdateLocked(Increment* increment, nsecs_t timestamp) { - VSyncEvent* event(increment->mutable_vsync_event()); - event->set_when(timestamp); -} - -void SurfaceInterceptor::addDisplaySurfaceLocked(Transaction* transaction, int32_t sequenceId, - const sp& surface) -{ - if (surface == nullptr) { - return; - } - uint64_t bufferQueueId = 0; - status_t err(surface->getUniqueId(&bufferQueueId)); - if (err == NO_ERROR) { - DisplayChange* dispChange(createDisplayChangeLocked(transaction, sequenceId)); - DispSurfaceChange* surfaceChange(dispChange->mutable_surface()); - surfaceChange->set_buffer_queue_id(bufferQueueId); - surfaceChange->set_buffer_queue_name(surface->getConsumerName().c_str()); - } - else { - ALOGE("invalid graphic buffer producer received while tracing a display change (%s)", - strerror(-err)); - } -} - -void SurfaceInterceptor::addDisplayLayerStackLocked(Transaction* transaction, int32_t sequenceId, - ui::LayerStack layerStack) { - DisplayChange* dispChange(createDisplayChangeLocked(transaction, sequenceId)); - LayerStackChange* layerStackChange(dispChange->mutable_layer_stack()); - layerStackChange->set_layer_stack(layerStack.id); -} - -void SurfaceInterceptor::addDisplayFlagsLocked(Transaction* transaction, int32_t sequenceId, - uint32_t flags) { - DisplayChange* dispChange(createDisplayChangeLocked(transaction, sequenceId)); - DisplayFlagsChange* flagsChange(dispChange->mutable_flags()); - flagsChange->set_flags(flags); -} - -void SurfaceInterceptor::addDisplaySizeLocked(Transaction* transaction, int32_t sequenceId, - uint32_t w, uint32_t h) -{ - DisplayChange* dispChange(createDisplayChangeLocked(transaction, sequenceId)); - SizeChange* sizeChange(dispChange->mutable_size()); - sizeChange->set_w(w); - sizeChange->set_h(h); -} - -void SurfaceInterceptor::addDisplayProjectionLocked(Transaction* transaction, - int32_t sequenceId, int32_t orientation, const Rect& viewport, const Rect& frame) -{ - DisplayChange* dispChange(createDisplayChangeLocked(transaction, sequenceId)); - ProjectionChange* projectionChange(dispChange->mutable_projection()); - projectionChange->set_orientation(orientation); - Rectangle* viewportRect(projectionChange->mutable_viewport()); - setProtoRectLocked(viewportRect, viewport); - Rectangle* frameRect(projectionChange->mutable_frame()); - setProtoRectLocked(frameRect, frame); -} - -void SurfaceInterceptor::addDisplayCreationLocked(Increment* increment, - const DisplayDeviceState& info) -{ - DisplayCreation* creation(increment->mutable_display_creation()); - creation->set_id(info.sequenceId); - creation->set_name(info.displayName); - creation->set_is_secure(info.isSecure); - if (info.physical) { - creation->set_display_id(info.physical->id.value); - } -} - -void SurfaceInterceptor::addDisplayDeletionLocked(Increment* increment, int32_t sequenceId) { - DisplayDeletion* deletion(increment->mutable_display_deletion()); - deletion->set_id(sequenceId); -} - -void SurfaceInterceptor::addPowerModeUpdateLocked(Increment* increment, int32_t sequenceId, - int32_t mode) -{ - PowerModeUpdate* powerModeUpdate(increment->mutable_power_mode_update()); - powerModeUpdate->set_id(sequenceId); - powerModeUpdate->set_mode(mode); -} - -void SurfaceInterceptor::saveTransaction( - const Vector& stateUpdates, - const DefaultKeyedVector, DisplayDeviceState>& displays, - const Vector& changedDisplays, uint32_t flags, int originPid, int originUid, - uint64_t transactionId) { - if (!mEnabled || (stateUpdates.size() <= 0 && changedDisplays.size() <= 0)) { - return; - } - ATRACE_CALL(); - std::lock_guard protoGuard(mTraceMutex); - addTransactionLocked(createTraceIncrementLocked(), stateUpdates, displays, changedDisplays, - flags, originPid, originUid, transactionId); -} - -void SurfaceInterceptor::saveSurfaceCreation(const sp& layer) { - if (!mEnabled || layer == nullptr) { - return; - } - ATRACE_CALL(); - std::lock_guard protoGuard(mTraceMutex); - addSurfaceCreationLocked(createTraceIncrementLocked(), layer); -} - -void SurfaceInterceptor::saveSurfaceDeletion(const sp& layer) { - if (!mEnabled || layer == nullptr) { - return; - } - ATRACE_CALL(); - std::lock_guard protoGuard(mTraceMutex); - addSurfaceDeletionLocked(createTraceIncrementLocked(), layer); -} - -/** - * Here we pass the layer by ID instead of by sp<> since this is called without - * holding the state-lock from a Binder thread. If we required the caller - * to pass 'this' by sp<> the temporary sp<> constructed could end up - * being the last reference and we might accidentally destroy the Layer - * from this binder thread. - */ -void SurfaceInterceptor::saveBufferUpdate(int32_t layerId, uint32_t width, - uint32_t height, uint64_t frameNumber) -{ - if (!mEnabled) { - return; - } - ATRACE_CALL(); - std::lock_guard protoGuard(mTraceMutex); - addBufferUpdateLocked(createTraceIncrementLocked(), layerId, width, height, frameNumber); -} - -void SurfaceInterceptor::saveVSyncEvent(nsecs_t timestamp) { - if (!mEnabled) { - return; - } - std::lock_guard protoGuard(mTraceMutex); - addVSyncUpdateLocked(createTraceIncrementLocked(), timestamp); -} - -void SurfaceInterceptor::saveDisplayCreation(const DisplayDeviceState& info) { - if (!mEnabled) { - return; - } - ATRACE_CALL(); - std::lock_guard protoGuard(mTraceMutex); - addDisplayCreationLocked(createTraceIncrementLocked(), info); -} - -void SurfaceInterceptor::saveDisplayDeletion(int32_t sequenceId) { - if (!mEnabled) { - return; - } - ATRACE_CALL(); - std::lock_guard protoGuard(mTraceMutex); - addDisplayDeletionLocked(createTraceIncrementLocked(), sequenceId); -} - -void SurfaceInterceptor::savePowerModeUpdate(int32_t sequenceId, int32_t mode) { - if (!mEnabled) { - return; - } - ATRACE_CALL(); - std::lock_guard protoGuard(mTraceMutex); - addPowerModeUpdateLocked(createTraceIncrementLocked(), sequenceId, mode); -} - -} // namespace impl -} // namespace android - -// TODO(b/129481165): remove the #pragma below and fix conversion issues -#pragma clang diagnostic pop // ignored "-Wconversion" diff --git a/services/surfaceflinger/SurfaceInterceptor.h b/services/surfaceflinger/SurfaceInterceptor.h deleted file mode 100644 index 970c3e5c27c9b5f59f4ff229e961282658906e88..0000000000000000000000000000000000000000 --- a/services/surfaceflinger/SurfaceInterceptor.h +++ /dev/null @@ -1,211 +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. - */ - -#ifndef ANDROID_SURFACEINTERCEPTOR_H -#define ANDROID_SURFACEINTERCEPTOR_H - -#include - -#include - -#include - -#include - -#include -#include -#include -#include - -#include "DisplayDevice.h" - -namespace android { - -class BufferItem; -class Layer; -class SurfaceFlinger; -struct ComposerState; -struct DisplayDeviceState; -struct DisplayState; -struct layer_state_t; -using Transaction = surfaceflinger::Transaction; -using Trace = surfaceflinger::Trace; -using Rectangle = surfaceflinger::Rectangle; -using SurfaceChange = surfaceflinger::SurfaceChange; -using Increment = surfaceflinger::Increment; -using DisplayChange = surfaceflinger::DisplayChange; - -constexpr auto DEFAULT_FILENAME = "/data/misc/wmtrace/transaction_trace.winscope"; - -class SurfaceInterceptor : public IBinder::DeathRecipient { -public: - virtual ~SurfaceInterceptor(); - - // Both vectors are used to capture the current state of SF as the initial snapshot in the trace - virtual void enable(const SortedVector>& layers, - const DefaultKeyedVector, DisplayDeviceState>& displays) = 0; - virtual void disable() = 0; - virtual bool isEnabled() = 0; - - virtual void addTransactionTraceListener( - const sp& listener) = 0; - virtual void binderDied(const wp& who) = 0; - - // Intercept display and surface transactions - virtual void saveTransaction( - const Vector& stateUpdates, - const DefaultKeyedVector, DisplayDeviceState>& displays, - const Vector& changedDisplays, uint32_t flags, int originPid, - int originUid, uint64_t transactionId) = 0; - - // Intercept surface data - virtual void saveSurfaceCreation(const sp& layer) = 0; - virtual void saveSurfaceDeletion(const sp& layer) = 0; - virtual void saveBufferUpdate(int32_t layerId, uint32_t width, uint32_t height, - uint64_t frameNumber) = 0; - - // Intercept display data - virtual void saveDisplayCreation(const DisplayDeviceState& info) = 0; - virtual void saveDisplayDeletion(int32_t sequenceId) = 0; - virtual void savePowerModeUpdate(int32_t sequenceId, int32_t mode) = 0; - virtual void saveVSyncEvent(nsecs_t timestamp) = 0; -}; - -namespace impl { - -/* - * SurfaceInterceptor intercepts and stores incoming streams of window - * properties on SurfaceFlinger. - */ -class SurfaceInterceptor final : public android::SurfaceInterceptor { -public: - SurfaceInterceptor() = default; - ~SurfaceInterceptor() override = default; - - // Both vectors are used to capture the current state of SF as the initial snapshot in the trace - void enable(const SortedVector>& layers, - const DefaultKeyedVector, DisplayDeviceState>& displays) override; - void disable() override; - bool isEnabled() override; - - void addTransactionTraceListener(const sp& listener) override; - void binderDied(const wp& who) override; - - // Intercept display and surface transactions - void saveTransaction(const Vector& stateUpdates, - const DefaultKeyedVector, DisplayDeviceState>& displays, - const Vector& changedDisplays, uint32_t flags, int originPid, - int originUid, uint64_t transactionId) override; - - // Intercept surface data - void saveSurfaceCreation(const sp& layer) override; - void saveSurfaceDeletion(const sp& layer) override; - void saveBufferUpdate(int32_t layerId, uint32_t width, uint32_t height, - uint64_t frameNumber) override; - - // Intercept display data - void saveDisplayCreation(const DisplayDeviceState& info) override; - void saveDisplayDeletion(int32_t sequenceId) override; - void savePowerModeUpdate(int32_t sequenceId, int32_t mode) override; - void saveVSyncEvent(nsecs_t timestamp) override; - -private: - // The creation increments of Surfaces and Displays do not contain enough information to capture - // the initial state of each object, so a transaction with all of the missing properties is - // performed at the initial snapshot for each display and surface. - void saveExistingDisplaysLocked( - const DefaultKeyedVector< wp, DisplayDeviceState>& displays); - void saveExistingSurfacesLocked(const SortedVector>& layers); - void addInitialSurfaceStateLocked(Increment* increment, const sp& layer); - void addInitialDisplayStateLocked(Increment* increment, const DisplayDeviceState& display); - - status_t writeProtoFileLocked(); - const sp getLayer(const wp& weakHandle) const; - int32_t getLayerId(const sp& layer) const; - int32_t getLayerIdFromWeakRef(const wp& layer) const; - int32_t getLayerIdFromHandle(const sp& weakHandle) const; - - Increment* createTraceIncrementLocked(); - void addSurfaceCreationLocked(Increment* increment, const sp& layer); - void addSurfaceDeletionLocked(Increment* increment, const sp& layer); - void addBufferUpdateLocked(Increment* increment, int32_t layerId, uint32_t width, - uint32_t height, uint64_t frameNumber); - void addVSyncUpdateLocked(Increment* increment, nsecs_t timestamp); - void addDisplayCreationLocked(Increment* increment, const DisplayDeviceState& info); - void addDisplayDeletionLocked(Increment* increment, int32_t sequenceId); - void addPowerModeUpdateLocked(Increment* increment, int32_t sequenceId, int32_t mode); - - // Add surface transactions to the trace - SurfaceChange* createSurfaceChangeLocked(Transaction* transaction, int32_t layerId); - void setProtoRectLocked(Rectangle* protoRect, const Rect& rect); - void addPositionLocked(Transaction* transaction, int32_t layerId, float x, float y); - void addDepthLocked(Transaction* transaction, int32_t layerId, uint32_t z); - void addSizeLocked(Transaction* transaction, int32_t layerId, uint32_t w, uint32_t h); - void addAlphaLocked(Transaction* transaction, int32_t layerId, float alpha); - void addMatrixLocked(Transaction* transaction, int32_t layerId, - const layer_state_t::matrix22_t& matrix); - void addTransparentRegionLocked(Transaction* transaction, int32_t layerId, - const Region& transRegion); - void addFlagsLocked(Transaction* transaction, int32_t layerId, uint8_t flags, uint8_t mask); - void addLayerStackLocked(Transaction* transaction, int32_t layerId, ui::LayerStack); - void addCropLocked(Transaction* transaction, int32_t layerId, const Rect& rect); - void addCornerRadiusLocked(Transaction* transaction, int32_t layerId, float cornerRadius); - void addBackgroundBlurRadiusLocked(Transaction* transaction, int32_t layerId, - int32_t backgroundBlurRadius); - void addBlurRegionsLocked(Transaction* transaction, int32_t layerId, - const std::vector& effectRegions); - void addSurfaceChangesLocked(Transaction* transaction, const layer_state_t& state); - void addTransactionLocked(Increment* increment, const Vector& stateUpdates, - const DefaultKeyedVector, DisplayDeviceState>& displays, - const Vector& changedDisplays, - uint32_t transactionFlags, int originPid, int originUid, - uint64_t transactionId); - void addReparentLocked(Transaction* transaction, int32_t layerId, int32_t parentId); - void addRelativeParentLocked(Transaction* transaction, int32_t layerId, int32_t parentId, - int z); - void addShadowRadiusLocked(Transaction* transaction, int32_t layerId, float shadowRadius); - void addTrustedOverlayLocked(Transaction* transaction, int32_t layerId, bool isTrustedOverlay); - - // Add display transactions to the trace - DisplayChange* createDisplayChangeLocked(Transaction* transaction, int32_t sequenceId); - void addDisplaySurfaceLocked(Transaction* transaction, int32_t sequenceId, - const sp& surface); - void addDisplayLayerStackLocked(Transaction* transaction, int32_t sequenceId, ui::LayerStack); - void addDisplayFlagsLocked(Transaction* transaction, int32_t sequenceId, uint32_t flags); - void addDisplaySizeLocked(Transaction* transaction, int32_t sequenceId, uint32_t w, - uint32_t h); - void addDisplayProjectionLocked(Transaction* transaction, int32_t sequenceId, - int32_t orientation, const Rect& viewport, const Rect& frame); - void addDisplayChangesLocked(Transaction* transaction, - const DisplayState& state, int32_t sequenceId); - - // Add transaction origin to trace - void setTransactionOriginLocked(Transaction* transaction, int32_t pid, int32_t uid); - - bool mEnabled {false}; - std::string mOutputFileName {DEFAULT_FILENAME}; - std::mutex mTraceMutex {}; - Trace mTrace {}; - std::mutex mListenersMutex; - std::map, sp> mTraceToggledListeners - GUARDED_BY(mListenersMutex); -}; - -} // namespace impl - -} // namespace android - -#endif // ANDROID_SURFACEINTERCEPTOR_H diff --git a/services/surfaceflinger/TEST_MAPPING b/services/surfaceflinger/TEST_MAPPING index 57752b7a399d5e2835a99f4029bad8f70263a354..155a27531b25fb484239dd15b068aad0838858e0 100644 --- a/services/surfaceflinger/TEST_MAPPING +++ b/services/surfaceflinger/TEST_MAPPING @@ -6,6 +6,14 @@ { "name": "libcompositionengine_test" }, + { + "name": "libgui_test", + "options": [ + { + "native-test-flag": "--gtest_filter=\"InputSurfacesTest*:MultiDisplayTests*\"" + } + ] + }, { "name": "libscheduler_test" } diff --git a/services/surfaceflinger/TimeStats/TimeStats.cpp b/services/surfaceflinger/TimeStats/TimeStats.cpp index e5a9dd47c3c75dd3c13ecc45d5bedd098fba9a53..630cef1fd4f26e136a639097d5f4cbdaea8d0475 100644 --- a/services/surfaceflinger/TimeStats/TimeStats.cpp +++ b/services/surfaceflinger/TimeStats/TimeStats.cpp @@ -27,6 +27,7 @@ #include #include +#include #include #include "TimeStats.h" @@ -68,6 +69,8 @@ SurfaceflingerStatsLayerInfo_GameMode gameModeToProto(GameMode gameMode) { return SurfaceflingerStatsLayerInfo::GAME_MODE_PERFORMANCE; case GameMode::Battery: return SurfaceflingerStatsLayerInfo::GAME_MODE_BATTERY; + case GameMode::Custom: + return SurfaceflingerStatsLayerInfo::GAME_MODE_CUSTOM; default: return SurfaceflingerStatsLayerInfo::GAME_MODE_UNSPECIFIED; } @@ -88,7 +91,7 @@ SurfaceflingerStatsLayerInfo_SetFrameRateVote frameRateVoteToProto( } } // namespace -bool TimeStats::populateGlobalAtom(std::string* pulledData) { +bool TimeStats::populateGlobalAtom(std::vector* pulledData) { std::lock_guard lock(mMutex); if (mTimeStats.statsStartLegacy == 0) { @@ -136,10 +139,11 @@ bool TimeStats::populateGlobalAtom(std::string* pulledData) { // Always clear data. clearGlobalLocked(); - return atomList.SerializeToString(pulledData); + pulledData->resize(atomList.ByteSizeLong()); + return atomList.SerializeToArray(pulledData->data(), atomList.ByteSizeLong()); } -bool TimeStats::populateLayerAtom(std::string* pulledData) { +bool TimeStats::populateLayerAtom(std::vector* pulledData) { std::lock_guard lock(mMutex); std::vector dumpStats; @@ -177,6 +181,12 @@ bool TimeStats::populateLayerAtom(std::string* pulledData) { *atom->mutable_present_to_present() = histogramToProto(present2PresentHist->second.hist, mMaxPulledHistogramBuckets); } + const auto& present2PresentDeltaHist = layer->deltas.find("present2presentDelta"); + if (present2PresentDeltaHist != layer->deltas.cend()) { + *atom->mutable_present_to_present_delta() = + histogramToProto(present2PresentDeltaHist->second.hist, + mMaxPulledHistogramBuckets); + } const auto& post2presentHist = layer->deltas.find("post2present"); if (post2presentHist != layer->deltas.cend()) { *atom->mutable_post_to_present() = @@ -227,7 +237,8 @@ bool TimeStats::populateLayerAtom(std::string* pulledData) { // Always clear data. clearLayersLocked(); - return atomList.SerializeToString(pulledData); + pulledData->resize(atomList.ByteSizeLong()); + return atomList.SerializeToArray(pulledData->data(), atomList.ByteSizeLong()); } TimeStats::TimeStats() : TimeStats(std::nullopt, std::nullopt) {} @@ -243,7 +254,7 @@ TimeStats::TimeStats(std::optional maxPulledLayers, } } -bool TimeStats::onPullAtom(const int atomId, std::string* pulledData) { +bool TimeStats::onPullAtom(const int atomId, std::vector* pulledData) { bool success = false; if (atomId == 10062) { // SURFACEFLINGER_STATS_GLOBAL_INFO success = populateGlobalAtom(pulledData); @@ -448,6 +459,7 @@ void TimeStats::flushAvailableRecordsToStatsLocked(int32_t layerId, Fps displayR LayerRecord& layerRecord = mTimeStatsTracker[layerId]; TimeRecord& prevTimeRecord = layerRecord.prevTimeRecord; + std::optional& prevPresentToPresentMs = layerRecord.prevPresentToPresentMs; std::deque& timeRecords = layerRecord.timeRecords; const int32_t refreshRateBucket = clampToNearestBucket(displayRefreshRate, REFRESH_RATE_BUCKET_WIDTH); @@ -525,6 +537,12 @@ void TimeStats::flushAvailableRecordsToStatsLocked(int32_t layerId, Fps displayR ALOGV("[%d]-[%" PRIu64 "]-present2present[%d]", layerId, timeRecords[0].frameTime.frameNumber, presentToPresentMs); timeStatsLayer.deltas["present2present"].insert(presentToPresentMs); + if (prevPresentToPresentMs) { + const int32_t presentToPresentDeltaMs = + std::abs(presentToPresentMs - *prevPresentToPresentMs); + timeStatsLayer.deltas["present2presentDelta"].insert(presentToPresentDeltaMs); + } + prevPresentToPresentMs = presentToPresentMs; } prevTimeRecord = timeRecords[0]; timeRecords.pop_front(); diff --git a/services/surfaceflinger/TimeStats/TimeStats.h b/services/surfaceflinger/TimeStats/TimeStats.h index 7a159b8eb7ec9f511285b61720bdf433a3fbbd6c..5f586577ac2cbd1aa727e54df8f0dda2a79ad2c2 100644 --- a/services/surfaceflinger/TimeStats/TimeStats.h +++ b/services/surfaceflinger/TimeStats/TimeStats.h @@ -34,6 +34,8 @@ #include +using android::gui::GameMode; +using android::gui::LayerMetadata; using namespace android::surfaceflinger; namespace android { @@ -45,7 +47,7 @@ public: virtual ~TimeStats() = default; // Process a pull request from statsd. - virtual bool onPullAtom(const int atomId, std::string* pulledData) = 0; + virtual bool onPullAtom(const int atomId, std::vector* pulledData) = 0; virtual void parseArgs(bool asProto, const Vector& args, std::string& result) = 0; virtual bool isEnabled() = 0; @@ -217,6 +219,7 @@ class TimeStats : public android::TimeStats { uint32_t lateAcquireFrames = 0; uint32_t badDesiredPresentFrames = 0; TimeRecord prevTimeRecord; + std::optional prevPresentToPresentMs; std::deque timeRecords; }; @@ -242,7 +245,7 @@ public: TimeStats(std::optional maxPulledLayers, std::optional maxPulledHistogramBuckets); - bool onPullAtom(const int atomId, std::string* pulledData) override; + bool onPullAtom(const int atomId, std::vector* pulledData) override; void parseArgs(bool asProto, const Vector& args, std::string& result) override; bool isEnabled() override; std::string miniDump() override; @@ -290,8 +293,8 @@ public: static const size_t MAX_NUM_TIME_RECORDS = 64; private: - bool populateGlobalAtom(std::string* pulledData); - bool populateLayerAtom(std::string* pulledData); + bool populateGlobalAtom(std::vector* pulledData); + bool populateLayerAtom(std::vector* pulledData); bool recordReadyLocked(int32_t layerId, TimeRecord* timeRecord); void flushAvailableRecordsToStatsLocked(int32_t layerId, Fps displayRefreshRate, std::optional renderRate, SetFrameRateVote, diff --git a/services/surfaceflinger/TimeStats/timestatsatomsproto/timestats_atoms.proto b/services/surfaceflinger/TimeStats/timestatsatomsproto/timestats_atoms.proto index e45757ddfd6d1374a8baba90ccee60e8d0114380..8615947db538dbc5a00d7a1988493a5e781e3e1b 100644 --- a/services/surfaceflinger/TimeStats/timestatsatomsproto/timestats_atoms.proto +++ b/services/surfaceflinger/TimeStats/timestatsatomsproto/timestats_atoms.proto @@ -173,6 +173,7 @@ message SurfaceflingerStatsLayerInfo { GAME_MODE_STANDARD = 2; GAME_MODE_PERFORMANCE = 3; GAME_MODE_BATTERY = 4; + GAME_MODE_CUSTOM = 5; } // Game mode that the layer was running at. Used to track user engagement @@ -288,7 +289,11 @@ message SurfaceflingerStatsLayerInfo { // Introduced in Android 12. optional FrameTimingHistogram app_deadline_misses = 25; - // Next ID: 27 + // Variability histogram of present_to_present timings. + // Introduced in Android 14. + optional FrameTimingHistogram present_to_present_delta = 27; + + // Next ID: 28 } /** diff --git a/services/surfaceflinger/TimeStats/timestatsproto/include/timestatsproto/TimeStatsHelper.h b/services/surfaceflinger/TimeStats/timestatsproto/include/timestatsproto/TimeStatsHelper.h index 237ae8d7616cae0dd83f76e7f22ceaeb5527acb1..60aa810e8b85f98d528356af39a1e0a2d0d92b78 100644 --- a/services/surfaceflinger/TimeStats/timestatsproto/include/timestatsproto/TimeStatsHelper.h +++ b/services/surfaceflinger/TimeStats/timestatsproto/include/timestatsproto/TimeStatsHelper.h @@ -24,6 +24,9 @@ #include #include +using android::gui::GameMode; +using android::gui::LayerMetadata; + namespace android { namespace surfaceflinger { diff --git a/services/surfaceflinger/TracedOrdinal.h b/services/surfaceflinger/TracedOrdinal.h index 558b3be2737a10ae628913f7824d7ae4f56c036d..1adc3a531d193d878c8bf7fa751bb1730a02030e 100644 --- a/services/surfaceflinger/TracedOrdinal.h +++ b/services/surfaceflinger/TracedOrdinal.h @@ -24,16 +24,24 @@ #include #include -namespace std { +namespace android { + +namespace { template bool signbit(std::chrono::duration v) { - return signbit(std::chrono::duration_cast(v).count()); + return std::signbit(std::chrono::duration_cast(v).count()); } -} // namespace std -namespace android { +template ::value>::type* = nullptr> +bool signbit(Enum e) { + return std::signbit(static_cast::type>(e)); +} + +template ::value>::type* = nullptr> +bool signbit(T t) { + return std::signbit(t); +} -namespace { template int64_t to_int64(T v) { return int64_t(v); @@ -49,14 +57,12 @@ template class TracedOrdinal { public: static_assert(std::is_same() || (std::is_signed() && std::is_integral()) || - std::is_same(), + std::is_same() || std::is_enum(), "Type is not supported. Please test it with systrace before adding " "it to the list."); TracedOrdinal(std::string name, T initialValue) - : mName(std::move(name)), - mHasGoneNegative(std::signbit(initialValue)), - mData(initialValue) { + : mName(std::move(name)), mHasGoneNegative(signbit(initialValue)), mData(initialValue) { trace(); } @@ -66,7 +72,7 @@ public: TracedOrdinal& operator=(T other) { mData = other; - mHasGoneNegative = mHasGoneNegative || std::signbit(mData); + mHasGoneNegative = mHasGoneNegative || signbit(mData); trace(); return *this; } @@ -81,7 +87,7 @@ private: mNameNegative = mName + "Negative"; } - if (!std::signbit(mData)) { + if (!signbit(mData)) { ATRACE_INT64(mName.c_str(), to_int64(mData)); if (mHasGoneNegative) { ATRACE_INT64(mNameNegative.c_str(), 0); diff --git a/services/surfaceflinger/Tracing/LayerTracing.cpp b/services/surfaceflinger/Tracing/LayerTracing.cpp index 49554c7dd8b805059f694577b6d055ffe5dbe641..ecdeabe52802dd57f1aef7b9be2828cd5d699572 100644 --- a/services/surfaceflinger/Tracing/LayerTracing.cpp +++ b/services/surfaceflinger/Tracing/LayerTracing.cpp @@ -18,6 +18,8 @@ #define LOG_TAG "LayerTracing" #define ATRACE_TAG ATRACE_TAG_GRAPHICS +#include + #include #include #include @@ -29,9 +31,8 @@ namespace android { -LayerTracing::LayerTracing(SurfaceFlinger& flinger) : mFlinger(flinger) { - mBuffer = std::make_unique>(); -} +LayerTracing::LayerTracing() + : mBuffer(std::make_unique>()) {} LayerTracing::~LayerTracing() = default; @@ -45,30 +46,39 @@ bool LayerTracing::enable() { return true; } -bool LayerTracing::disable(std::string filename) { +bool LayerTracing::disable(std::string filename, bool writeToFile) { std::scoped_lock lock(mTraceLock); if (!mEnabled) { return false; } mEnabled = false; - LayersTraceFileProto fileProto = createTraceFileProto(); - mBuffer->writeToFile(fileProto, filename); + if (writeToFile) { + LayersTraceFileProto fileProto = createTraceFileProto(); + mBuffer->writeToFile(fileProto, filename); + } mBuffer->reset(); return true; } +void LayerTracing::appendToStream(std::ofstream& out) { + std::scoped_lock lock(mTraceLock); + LayersTraceFileProto fileProto = createTraceFileProto(); + mBuffer->appendToStream(fileProto, out); + mBuffer->reset(); +} + bool LayerTracing::isEnabled() const { std::scoped_lock lock(mTraceLock); return mEnabled; } -status_t LayerTracing::writeToFile() { +status_t LayerTracing::writeToFile(std::string filename) { std::scoped_lock lock(mTraceLock); if (!mEnabled) { return STATUS_OK; } LayersTraceFileProto fileProto = createTraceFileProto(); - return mBuffer->writeToFile(fileProto, FILE_NAME); + return mBuffer->writeToFile(fileProto, filename); } void LayerTracing::setTraceFlags(uint32_t flags) { @@ -84,11 +94,17 @@ void LayerTracing::setBufferSize(size_t bufferSizeInBytes) { bool LayerTracing::flagIsSet(uint32_t flags) const { return (mFlags & flags) == flags; } +uint32_t LayerTracing::getFlags() const { + return mFlags; +} -LayersTraceFileProto LayerTracing::createTraceFileProto() const { +LayersTraceFileProto LayerTracing::createTraceFileProto() { LayersTraceFileProto fileProto; fileProto.set_magic_number(uint64_t(LayersTraceFileProto_MagicNumber_MAGIC_NUMBER_H) << 32 | LayersTraceFileProto_MagicNumber_MAGIC_NUMBER_L); + auto timeOffsetNs = static_cast(systemTime(SYSTEM_TIME_REALTIME) - + systemTime(SYSTEM_TIME_MONOTONIC)); + fileProto.set_real_to_elapsed_time_offset_nanos(timeOffsetNs); return fileProto; } @@ -98,7 +114,9 @@ void LayerTracing::dump(std::string& result) const { mBuffer->dump(result); } -void LayerTracing::notify(bool visibleRegionDirty, int64_t time) { +void LayerTracing::notify(bool visibleRegionDirty, int64_t time, int64_t vsyncId, + LayersProto* layers, std::string hwcDump, + google::protobuf::RepeatedPtrField* displays) { std::scoped_lock lock(mTraceLock); if (!mEnabled) { return; @@ -113,22 +131,16 @@ void LayerTracing::notify(bool visibleRegionDirty, int64_t time) { entry.set_elapsed_realtime_nanos(time); const char* where = visibleRegionDirty ? "visibleRegionsDirty" : "bufferLatched"; entry.set_where(where); - LayersProto layers(mFlinger.dumpDrawingStateProto(mFlags)); - - if (flagIsSet(LayerTracing::TRACE_EXTRA)) { - mFlinger.dumpOffscreenLayersProto(layers); - } - entry.mutable_layers()->Swap(&layers); + entry.mutable_layers()->Swap(layers); if (flagIsSet(LayerTracing::TRACE_HWC)) { - std::string hwcDump; - mFlinger.dumpHwc(hwcDump); entry.set_hwc_blob(hwcDump); } if (!flagIsSet(LayerTracing::TRACE_COMPOSITION)) { entry.set_excludes_composition_state(true); } - mFlinger.dumpDisplayProto(entry); + entry.mutable_displays()->Swap(displays); + entry.set_vsync_id(vsyncId); mBuffer->emplace(std::move(entry)); } diff --git a/services/surfaceflinger/Tracing/LayerTracing.h b/services/surfaceflinger/Tracing/LayerTracing.h index 88a19ecdf1e2296cbd4d57aed6581ea50a5218c9..40b0fbee1892654a82e1f71d840e71710c1feb00 100644 --- a/services/surfaceflinger/Tracing/LayerTracing.h +++ b/services/surfaceflinger/Tracing/LayerTracing.h @@ -40,14 +40,16 @@ class SurfaceFlinger; */ class LayerTracing { public: - LayerTracing(SurfaceFlinger& flinger); + LayerTracing(); ~LayerTracing(); bool enable(); - bool disable(std::string filename = FILE_NAME); + bool disable(std::string filename = FILE_NAME, bool writeToFile = true); + void appendToStream(std::ofstream& out); bool isEnabled() const; - status_t writeToFile(); - LayersTraceFileProto createTraceFileProto() const; - void notify(bool visibleRegionDirty, int64_t time); + status_t writeToFile(std::string filename = FILE_NAME); + static LayersTraceFileProto createTraceFileProto(); + void notify(bool visibleRegionDirty, int64_t time, int64_t vsyncId, LayersProto* layers, + std::string hwcDump, google::protobuf::RepeatedPtrField* displays); enum : uint32_t { TRACE_INPUT = 1 << 1, @@ -55,17 +57,17 @@ public: TRACE_EXTRA = 1 << 3, TRACE_HWC = 1 << 4, TRACE_BUFFERS = 1 << 5, + TRACE_VIRTUAL_DISPLAYS = 1 << 6, TRACE_ALL = TRACE_INPUT | TRACE_COMPOSITION | TRACE_EXTRA, }; void setTraceFlags(uint32_t flags); bool flagIsSet(uint32_t flags) const; + uint32_t getFlags() const; void setBufferSize(size_t bufferSizeInBytes); void dump(std::string&) const; private: static constexpr auto FILE_NAME = "/data/misc/wmtrace/layers_trace.winscope"; - - SurfaceFlinger& mFlinger; uint32_t mFlags = TRACE_INPUT; mutable std::mutex mTraceLock; bool mEnabled GUARDED_BY(mTraceLock) = false; diff --git a/services/surfaceflinger/Tracing/RingBuffer.h b/services/surfaceflinger/Tracing/RingBuffer.h index 7e38c5584066416698df53a1f856561d2a9647ff..b41c65b10ed4bf07f06aae3b4c5a85a71e8a8446 100644 --- a/services/surfaceflinger/Tracing/RingBuffer.h +++ b/services/surfaceflinger/Tracing/RingBuffer.h @@ -24,6 +24,7 @@ #include #include #include +#include #include namespace android { @@ -73,6 +74,19 @@ public: return NO_ERROR; } + status_t appendToStream(FileProto& fileProto, std::ofstream& out) { + ATRACE_CALL(); + writeToProto(fileProto); + std::string output; + if (!fileProto.SerializeToString(&output)) { + ALOGE("Could not serialize proto."); + return UNKNOWN_ERROR; + } + + out << output; + return NO_ERROR; + } + std::vector emplace(std::string&& serializedProto) { std::vector replacedEntries; size_t protoSize = static_cast(serializedProto.size()); diff --git a/services/surfaceflinger/Tracing/TransactionProtoParser.cpp b/services/surfaceflinger/Tracing/TransactionProtoParser.cpp index a73eccf0e5b71bee5ce4393a680386d812e17357..06941809ba78d0f49aed64e8d75a7c21a1e32228 100644 --- a/services/surfaceflinger/Tracing/TransactionProtoParser.cpp +++ b/services/surfaceflinger/Tracing/TransactionProtoParser.cpp @@ -15,13 +15,42 @@ */ #include +#include #include +#include "FrontEnd/LayerCreationArgs.h" #include "LayerProtoHelper.h" #include "TransactionProtoParser.h" +#include "TransactionState.h" +#include "gui/LayerState.h" namespace android::surfaceflinger { +class FakeExternalTexture : public renderengine::ExternalTexture { + const sp mEmptyBuffer = nullptr; + uint32_t mWidth; + uint32_t mHeight; + uint64_t mId; + PixelFormat mPixelFormat; + uint64_t mUsage; + +public: + FakeExternalTexture(uint32_t width, uint32_t height, uint64_t id, PixelFormat pixelFormat, + uint64_t usage) + : mWidth(width), mHeight(height), mId(id), mPixelFormat(pixelFormat), mUsage(usage) {} + const sp& getBuffer() const { return mEmptyBuffer; } + bool hasSameBuffer(const renderengine::ExternalTexture& other) const override { + return getId() == other.getId(); + } + uint32_t getWidth() const override { return mWidth; } + uint32_t getHeight() const override { return mHeight; } + uint64_t getId() const override { return mId; } + PixelFormat getPixelFormat() const override { return mPixelFormat; } + uint64_t getUsage() const override { return mUsage; } + void remapBuffer() override {} + ~FakeExternalTexture() = default; +}; + proto::TransactionState TransactionProtoParser::toProto(const TransactionState& t) { proto::TransactionState proto; proto.set_pid(t.originPid); @@ -33,51 +62,40 @@ proto::TransactionState TransactionProtoParser::toProto(const TransactionState& proto.mutable_layer_changes()->Reserve(static_cast(t.states.size())); for (auto& layerState : t.states) { - proto.mutable_layer_changes()->Add(std::move(toProto(layerState.state))); + proto.mutable_layer_changes()->Add(std::move(toProto(layerState))); } proto.mutable_display_changes()->Reserve(static_cast(t.displays.size())); for (auto& displayState : t.displays) { proto.mutable_display_changes()->Add(std::move(toProto(displayState))); } + + proto.mutable_merged_transaction_ids()->Reserve( + static_cast(t.mergedTransactionIds.size())); + for (auto& mergedTransactionId : t.mergedTransactionIds) { + proto.mutable_merged_transaction_ids()->Add(mergedTransactionId); + } + return proto; } proto::TransactionState TransactionProtoParser::toProto( - const std::map& states) { + const std::map& states) { proto::TransactionState proto; proto.mutable_layer_changes()->Reserve(static_cast(states.size())); for (auto& [layerId, state] : states) { proto::LayerState layerProto = toProto(state); - if (layerProto.has_buffer_data()) { - proto::LayerState_BufferData* bufferProto = layerProto.mutable_buffer_data(); - bufferProto->set_buffer_id(state.bufferId); - bufferProto->set_width(state.bufferWidth); - bufferProto->set_height(state.bufferHeight); - bufferProto->set_pixel_format( - static_cast(state.pixelFormat)); - bufferProto->set_usage(state.bufferUsage); - } layerProto.set_has_sideband_stream(state.hasSidebandStream); - layerProto.set_layer_id(state.layerId); - layerProto.set_parent_id(state.parentId); - layerProto.set_relative_parent_id(state.relativeParentId); - if (layerProto.has_window_info_handle()) { - layerProto.mutable_window_info_handle()->set_crop_layer_id(state.inputCropId); - } proto.mutable_layer_changes()->Add(std::move(layerProto)); } return proto; } -proto::LayerState TransactionProtoParser::toProto(const layer_state_t& layer) { +proto::LayerState TransactionProtoParser::toProto( + const ResolvedComposerState& resolvedComposerState) { proto::LayerState proto; - if (layer.surface) { - proto.set_layer_id(mMapper->getLayerId(layer.surface)); - } else { - proto.set_layer_id(layer.layerId); - } - + auto& layer = resolvedComposerState.state; + proto.set_layer_id(resolvedComposerState.layerId); proto.set_what(layer.what); if (layer.what & layer_state_t::ePositionChanged) { @@ -87,10 +105,7 @@ proto::LayerState TransactionProtoParser::toProto(const layer_state_t& layer) { if (layer.what & layer_state_t::eLayerChanged) { proto.set_z(layer.z); } - if (layer.what & layer_state_t::eSizeChanged) { - proto.set_w(layer.w); - proto.set_h(layer.h); - } + if (layer.what & layer_state_t::eLayerStackChanged) { proto.set_layer_stack(layer.layerStack.id); } @@ -113,7 +128,7 @@ proto::LayerState TransactionProtoParser::toProto(const layer_state_t& layer) { } if (layer.what & layer_state_t::eAlphaChanged) { - proto.set_alpha(layer.alpha); + proto.set_alpha(layer.color.a); } if (layer.what & layer_state_t::eColorChanged) { @@ -125,8 +140,8 @@ proto::LayerState TransactionProtoParser::toProto(const layer_state_t& layer) { if (layer.what & layer_state_t::eTransparentRegionChanged) { LayerProtoHelper::writeToProto(layer.transparentRegion, proto.mutable_transparent_region()); } - if (layer.what & layer_state_t::eTransformChanged) { - proto.set_transform(layer.transform); + if (layer.what & layer_state_t::eBufferTransformChanged) { + proto.set_transform(layer.bufferTransform); } if (layer.what & layer_state_t::eTransformToDisplayInverseChanged) { proto.set_transform_to_display_inverse(layer.transformToDisplayInverse); @@ -136,27 +151,13 @@ proto::LayerState TransactionProtoParser::toProto(const layer_state_t& layer) { } if (layer.what & layer_state_t::eBufferChanged) { proto::LayerState_BufferData* bufferProto = proto.mutable_buffer_data(); - if (layer.bufferData->hasBuffer()) { - bufferProto->set_buffer_id(layer.bufferData->getId()); - bufferProto->set_width(layer.bufferData->getWidth()); - bufferProto->set_height(layer.bufferData->getHeight()); + if (resolvedComposerState.externalTexture) { + bufferProto->set_buffer_id(resolvedComposerState.externalTexture->getId()); + bufferProto->set_width(resolvedComposerState.externalTexture->getWidth()); + bufferProto->set_height(resolvedComposerState.externalTexture->getHeight()); bufferProto->set_pixel_format(static_cast( - layer.bufferData->getPixelFormat())); - bufferProto->set_usage(layer.bufferData->getUsage()); - } else { - uint64_t bufferId; - uint32_t width; - uint32_t height; - int32_t pixelFormat; - uint64_t usage; - mMapper->getGraphicBufferPropertiesFromCache(layer.bufferData->cachedBuffer, &bufferId, - &width, &height, &pixelFormat, &usage); - bufferProto->set_buffer_id(bufferId); - bufferProto->set_width(width); - bufferProto->set_height(height); - bufferProto->set_pixel_format( - static_cast(pixelFormat)); - bufferProto->set_usage(usage); + resolvedComposerState.externalTexture->getPixelFormat())); + bufferProto->set_usage(resolvedComposerState.externalTexture->getUsage()); } bufferProto->set_frame_number(layer.bufferData->frameNumber); bufferProto->set_flags(layer.bufferData->flags.get()); @@ -180,16 +181,10 @@ proto::LayerState TransactionProtoParser::toProto(const layer_state_t& layer) { } if (layer.what & layer_state_t::eReparent) { - int64_t layerId = layer.parentSurfaceControlForChild - ? mMapper->getLayerId(layer.parentSurfaceControlForChild->getHandle()) - : -1; - proto.set_parent_id(layerId); + proto.set_parent_id(resolvedComposerState.parentId); } if (layer.what & layer_state_t::eRelativeLayerChanged) { - int64_t layerId = layer.relativeLayerSurfaceControl - ? mMapper->getLayerId(layer.relativeLayerSurfaceControl->getHandle()) - : -1; - proto.set_relative_parent_id(layerId); + proto.set_relative_parent_id(resolvedComposerState.relativeParentId); proto.set_z(layer.z); } @@ -208,7 +203,7 @@ proto::LayerState TransactionProtoParser::toProto(const layer_state_t& layer) { windowInfoProto->set_has_wallpaper(inputInfo->inputConfig.test( gui::WindowInfo::InputConfig::DUPLICATE_TOUCH_TO_WALLPAPER)); windowInfoProto->set_global_scale_factor(inputInfo->globalScaleFactor); - proto::LayerState_Transform* transformProto = windowInfoProto->mutable_transform(); + proto::Transform* transformProto = windowInfoProto->mutable_transform(); transformProto->set_dsdx(inputInfo->transform.dsdx()); transformProto->set_dtdx(inputInfo->transform.dtdx()); transformProto->set_dtdy(inputInfo->transform.dtdy()); @@ -217,17 +212,16 @@ proto::LayerState TransactionProtoParser::toProto(const layer_state_t& layer) { transformProto->set_ty(inputInfo->transform.ty()); windowInfoProto->set_replace_touchable_region_with_crop( inputInfo->replaceTouchableRegionWithCrop); - windowInfoProto->set_crop_layer_id( - mMapper->getLayerId(inputInfo->touchableRegionCropHandle.promote())); + windowInfoProto->set_crop_layer_id(resolvedComposerState.touchCropId); } } if (layer.what & layer_state_t::eBackgroundColorChanged) { - proto.set_bg_color_alpha(layer.bgColorAlpha); + proto.set_bg_color_alpha(layer.bgColor.a); proto.set_bg_color_dataspace(static_cast(layer.bgColorDataspace)); proto::LayerState_Color3* colorProto = proto.mutable_color(); - colorProto->set_r(layer.color.r); - colorProto->set_g(layer.color.g); - colorProto->set_b(layer.color.b); + colorProto->set_r(layer.bgColor.r); + colorProto->set_g(layer.bgColor.g); + colorProto->set_b(layer.bgColor.b); } if (layer.what & layer_state_t::eColorSpaceAgnosticChanged) { proto.set_color_space_agnostic(layer.colorSpaceAgnostic); @@ -290,13 +284,15 @@ proto::DisplayState TransactionProtoParser::toProto(const DisplayState& display) return proto; } -proto::LayerCreationArgs TransactionProtoParser::toProto(const TracingLayerCreationArgs& args) { +proto::LayerCreationArgs TransactionProtoParser::toProto(const LayerCreationArgs& args) { proto::LayerCreationArgs proto; - proto.set_layer_id(args.layerId); + proto.set_layer_id(args.sequence); proto.set_name(args.name); proto.set_flags(args.flags); proto.set_parent_id(args.parentId); - proto.set_mirror_from_id(args.mirrorFromId); + proto.set_mirror_from_id(args.layerIdToMirror); + proto.set_add_to_root(args.addToRoot); + proto.set_layer_stack_to_mirror(args.layerStackToMirror.id); return proto; } @@ -312,10 +308,10 @@ TransactionState TransactionProtoParser::fromProto(const proto::TransactionState int32_t layerCount = proto.layer_changes_size(); t.states.reserve(static_cast(layerCount)); for (int i = 0; i < layerCount; i++) { - ComposerState s; + ResolvedComposerState s; s.state.what = 0; - fromProto(proto.layer_changes(i), s.state); - t.states.add(s); + fromProto(proto.layer_changes(i), s); + t.states.emplace_back(s); } int32_t displayCount = proto.display_changes_size(); @@ -327,46 +323,47 @@ TransactionState TransactionProtoParser::fromProto(const proto::TransactionState } void TransactionProtoParser::fromProto(const proto::LayerCreationArgs& proto, - TracingLayerCreationArgs& outArgs) { - outArgs.layerId = proto.layer_id(); + LayerCreationArgs& outArgs) { + outArgs.sequence = proto.layer_id(); + outArgs.name = proto.name(); outArgs.flags = proto.flags(); outArgs.parentId = proto.parent_id(); - outArgs.mirrorFromId = proto.mirror_from_id(); + outArgs.layerIdToMirror = proto.mirror_from_id(); + outArgs.addToRoot = proto.add_to_root(); + outArgs.layerStackToMirror.id = proto.layer_stack_to_mirror(); } void TransactionProtoParser::mergeFromProto(const proto::LayerState& proto, TracingLayerState& outState) { - layer_state_t state; - fromProto(proto, state); - outState.merge(state); + ResolvedComposerState resolvedComposerState; + fromProto(proto, resolvedComposerState); + layer_state_t& state = resolvedComposerState.state; + outState.state.merge(state); + outState.layerId = resolvedComposerState.layerId; if (state.what & layer_state_t::eReparent) { - outState.parentId = static_cast(proto.parent_id()); + outState.parentId = resolvedComposerState.parentId; } if (state.what & layer_state_t::eRelativeLayerChanged) { - outState.relativeParentId = static_cast(proto.relative_parent_id()); + outState.relativeParentId = resolvedComposerState.relativeParentId; } if (state.what & layer_state_t::eInputInfoChanged) { - outState.inputCropId = static_cast(proto.window_info_handle().crop_layer_id()); + outState.touchCropId = resolvedComposerState.touchCropId; } if (state.what & layer_state_t::eBufferChanged) { - const proto::LayerState_BufferData& bufferProto = proto.buffer_data(); - outState.bufferId = bufferProto.buffer_id(); - outState.bufferWidth = bufferProto.width(); - outState.bufferHeight = bufferProto.height(); - outState.pixelFormat = bufferProto.pixel_format(); - outState.bufferUsage = bufferProto.usage(); + outState.externalTexture = resolvedComposerState.externalTexture; } if (state.what & layer_state_t::eSidebandStreamChanged) { outState.hasSidebandStream = proto.has_sideband_stream(); } } -void TransactionProtoParser::fromProto(const proto::LayerState& proto, layer_state_t& layer) { - layer.layerId = (int32_t)proto.layer_id(); +void TransactionProtoParser::fromProto(const proto::LayerState& proto, + ResolvedComposerState& resolvedComposerState) { + auto& layer = resolvedComposerState.state; + resolvedComposerState.layerId = proto.layer_id(); layer.what |= proto.what(); - layer.surface = mMapper->getLayerHandle(layer.layerId); if (proto.what() & layer_state_t::ePositionChanged) { layer.x = proto.x(); @@ -375,10 +372,6 @@ void TransactionProtoParser::fromProto(const proto::LayerState& proto, layer_sta if (proto.what() & layer_state_t::eLayerChanged) { layer.z = proto.z(); } - if (proto.what() & layer_state_t::eSizeChanged) { - layer.w = proto.w(); - layer.h = proto.h(); - } if (proto.what() & layer_state_t::eLayerStackChanged) { layer.layerStack.id = proto.layer_stack(); } @@ -401,7 +394,7 @@ void TransactionProtoParser::fromProto(const proto::LayerState& proto, layer_sta } if (proto.what() & layer_state_t::eAlphaChanged) { - layer.alpha = proto.alpha(); + layer.color.a = proto.alpha(); } if (proto.what() & layer_state_t::eColorChanged) { @@ -413,8 +406,8 @@ void TransactionProtoParser::fromProto(const proto::LayerState& proto, layer_sta if (proto.what() & layer_state_t::eTransparentRegionChanged) { LayerProtoHelper::readFromProto(proto.transparent_region(), layer.transparentRegion); } - if (proto.what() & layer_state_t::eTransformChanged) { - layer.transform = proto.transform(); + if (proto.what() & layer_state_t::eBufferTransformChanged) { + layer.bufferTransform = proto.transform(); } if (proto.what() & layer_state_t::eTransformToDisplayInverseChanged) { layer.transformToDisplayInverse = proto.transform_to_display_inverse(); @@ -425,9 +418,15 @@ void TransactionProtoParser::fromProto(const proto::LayerState& proto, layer_sta if (proto.what() & layer_state_t::eBufferChanged) { const proto::LayerState_BufferData& bufferProto = proto.buffer_data(); layer.bufferData = - std::move(mMapper->getGraphicData(bufferProto.buffer_id(), bufferProto.width(), - bufferProto.height(), bufferProto.pixel_format(), - bufferProto.usage())); + std::make_shared(bufferProto.buffer_id(), bufferProto.width(), + bufferProto.height(), bufferProto.pixel_format(), + bufferProto.usage()); + resolvedComposerState.externalTexture = + std::make_shared(layer.bufferData->getWidth(), + layer.bufferData->getHeight(), + layer.bufferData->getId(), + layer.bufferData->getPixelFormat(), + layer.bufferData->getUsage()); layer.bufferData->frameNumber = bufferProto.frame_number(); layer.bufferData->flags = ftl::Flags(bufferProto.flags()); layer.bufferData->cachedBuffer.id = bufferProto.cached_buffer_id(); @@ -451,26 +450,10 @@ void TransactionProtoParser::fromProto(const proto::LayerState& proto, layer_sta } if (proto.what() & layer_state_t::eReparent) { - int64_t layerId = proto.parent_id(); - if (layerId == -1) { - layer.parentSurfaceControlForChild = nullptr; - } else { - layer.parentSurfaceControlForChild = - new SurfaceControl(SurfaceComposerClient::getDefault(), - mMapper->getLayerHandle(static_cast(layerId)), - nullptr, static_cast(layerId)); - } + resolvedComposerState.parentId = proto.parent_id(); } if (proto.what() & layer_state_t::eRelativeLayerChanged) { - int64_t layerId = proto.relative_parent_id(); - if (layerId == -1) { - layer.relativeLayerSurfaceControl = nullptr; - } else { - layer.relativeLayerSurfaceControl = - new SurfaceControl(SurfaceComposerClient::getDefault(), - mMapper->getLayerHandle(static_cast(layerId)), - nullptr, static_cast(layerId)); - } + resolvedComposerState.relativeParentId = proto.relative_parent_id(); layer.z = proto.z(); } @@ -490,24 +473,23 @@ void TransactionProtoParser::fromProto(const proto::LayerState& proto, layer_sta inputInfo.setInputConfig(gui::WindowInfo::InputConfig::DUPLICATE_TOUCH_TO_WALLPAPER, windowInfoProto.has_wallpaper()); inputInfo.globalScaleFactor = windowInfoProto.global_scale_factor(); - const proto::LayerState_Transform& transformProto = windowInfoProto.transform(); + const proto::Transform& transformProto = windowInfoProto.transform(); inputInfo.transform.set(transformProto.dsdx(), transformProto.dtdx(), transformProto.dtdy(), transformProto.dsdy()); inputInfo.transform.set(transformProto.tx(), transformProto.ty()); inputInfo.replaceTouchableRegionWithCrop = windowInfoProto.replace_touchable_region_with_crop(); - int64_t layerId = windowInfoProto.crop_layer_id(); - inputInfo.touchableRegionCropHandle = - mMapper->getLayerHandle(static_cast(layerId)); + resolvedComposerState.touchCropId = windowInfoProto.crop_layer_id(); + layer.windowInfoHandle = sp::make(inputInfo); } if (proto.what() & layer_state_t::eBackgroundColorChanged) { - layer.bgColorAlpha = proto.bg_color_alpha(); + layer.bgColor.a = proto.bg_color_alpha(); layer.bgColorDataspace = static_cast(proto.bg_color_dataspace()); const proto::LayerState_Color3& colorProto = proto.color(); - layer.color.r = colorProto.r(); - layer.color.g = colorProto.g(); - layer.color.b = colorProto.b(); + layer.bgColor.r = colorProto.r(); + layer.bgColor.g = colorProto.g(); + layer.bgColor.b = colorProto.b(); } if (proto.what() & layer_state_t::eColorSpaceAgnosticChanged) { layer.colorSpaceAgnostic = proto.color_space_agnostic(); @@ -569,4 +551,62 @@ DisplayState TransactionProtoParser::fromProto(const proto::DisplayState& proto) return display; } +void asProto(proto::Transform* proto, const ui::Transform& transform) { + proto->set_dsdx(transform.dsdx()); + proto->set_dtdx(transform.dtdx()); + proto->set_dtdy(transform.dtdy()); + proto->set_dsdy(transform.dsdy()); + proto->set_tx(transform.tx()); + proto->set_ty(transform.ty()); +} + +proto::DisplayInfo TransactionProtoParser::toProto(const frontend::DisplayInfo& displayInfo, + uint32_t layerStack) { + proto::DisplayInfo proto; + proto.set_layer_stack(layerStack); + proto.set_display_id(displayInfo.info.displayId); + proto.set_logical_width(displayInfo.info.logicalWidth); + proto.set_logical_height(displayInfo.info.logicalHeight); + asProto(proto.mutable_transform_inverse(), displayInfo.info.transform); + asProto(proto.mutable_transform(), displayInfo.transform); + proto.set_receives_input(displayInfo.receivesInput); + proto.set_is_secure(displayInfo.isSecure); + proto.set_is_primary(displayInfo.isPrimary); + proto.set_is_virtual(displayInfo.isVirtual); + proto.set_rotation_flags((int)displayInfo.rotationFlags); + proto.set_transform_hint((int)displayInfo.transformHint); + return proto; +} + +void fromProto2(ui::Transform& outTransform, const proto::Transform& proto) { + outTransform.set(proto.dsdx(), proto.dtdx(), proto.dtdy(), proto.dsdy()); + outTransform.set(proto.tx(), proto.ty()); +} + +frontend::DisplayInfo TransactionProtoParser::fromProto(const proto::DisplayInfo& proto) { + frontend::DisplayInfo displayInfo; + displayInfo.info.displayId = proto.display_id(); + displayInfo.info.logicalWidth = proto.logical_width(); + displayInfo.info.logicalHeight = proto.logical_height(); + fromProto2(displayInfo.info.transform, proto.transform_inverse()); + fromProto2(displayInfo.transform, proto.transform()); + displayInfo.receivesInput = proto.receives_input(); + displayInfo.isSecure = proto.is_secure(); + displayInfo.isPrimary = proto.is_primary(); + displayInfo.isVirtual = proto.is_virtual(); + displayInfo.rotationFlags = (ui::Transform::RotationFlags)proto.rotation_flags(); + displayInfo.transformHint = (ui::Transform::RotationFlags)proto.transform_hint(); + return displayInfo; +} + +void TransactionProtoParser::fromProto( + const google::protobuf::RepeatedPtrField& proto, + display::DisplayMap& outDisplayInfos) { + outDisplayInfos.clear(); + for (const proto::DisplayInfo& displayInfo : proto) { + outDisplayInfos.emplace_or_replace(ui::LayerStack::fromValue(displayInfo.layer_stack()), + fromProto(displayInfo)); + } +} + } // namespace android::surfaceflinger diff --git a/services/surfaceflinger/Tracing/TransactionProtoParser.h b/services/surfaceflinger/Tracing/TransactionProtoParser.h index 872a901b2175dffd396823d847e314d6d8d679a9..d6c98e11203ffbc8d33f148d8cd8be033873e5ba 100644 --- a/services/surfaceflinger/Tracing/TransactionProtoParser.h +++ b/services/surfaceflinger/Tracing/TransactionProtoParser.h @@ -15,61 +15,20 @@ */ #pragma once +#include #include #include +#include "Display/DisplayMap.h" +#include "FrontEnd/DisplayInfo.h" +#include "FrontEnd/LayerCreationArgs.h" #include "TransactionState.h" namespace android::surfaceflinger { -struct TracingLayerCreationArgs { - int32_t layerId; - std::string name; - uint32_t flags = 0; - int32_t parentId = -1; - int32_t mirrorFromId = -1; -}; - -struct TracingLayerState : layer_state_t { - uint64_t bufferId; - uint32_t bufferHeight; - uint32_t bufferWidth; - int32_t pixelFormat; - uint64_t bufferUsage; +struct TracingLayerState : ResolvedComposerState { bool hasSidebandStream; - int32_t parentId; - int32_t relativeParentId; - int32_t inputCropId; - TracingLayerCreationArgs args; -}; - -// Class which exposes buffer properties from BufferData without holding on to the actual buffer -// handle. -class BufferDataStub : public BufferData { -public: - BufferDataStub(uint64_t bufferId, uint32_t width, uint32_t height, int32_t pixelFormat, - uint64_t outUsage) - : mBufferId(bufferId), - mWidth(width), - mHeight(height), - mPixelFormat(pixelFormat), - mOutUsage(outUsage) {} - bool hasBuffer() const override { return mBufferId != 0; } - bool hasSameBuffer(const BufferData& other) const override { - return getId() == other.getId() && frameNumber == other.frameNumber; - } - uint32_t getWidth() const override { return mWidth; } - uint32_t getHeight() const override { return mHeight; } - uint64_t getId() const override { return mBufferId; } - PixelFormat getPixelFormat() const override { return mPixelFormat; } - uint64_t getUsage() const override { return mOutUsage; } - -private: - uint64_t mBufferId; - uint32_t mWidth; - uint32_t mHeight; - int32_t mPixelFormat; - uint64_t mOutUsage; + LayerCreationArgs args; }; class TransactionProtoParser { @@ -79,40 +38,31 @@ public: class FlingerDataMapper { public: virtual ~FlingerDataMapper() = default; - virtual sp getLayerHandle(int32_t /* layerId */) const { return nullptr; } - virtual int64_t getLayerId(const sp& /* layerHandle */) const { return -1; } - virtual int64_t getLayerId(BBinder* /* layerHandle */) const { return -1; } virtual sp getDisplayHandle(int32_t /* displayId */) const { return nullptr; } virtual int32_t getDisplayId(const sp& /* displayHandle */) const { return -1; } - virtual std::shared_ptr getGraphicData(uint64_t bufferId, uint32_t width, - uint32_t height, int32_t pixelFormat, - uint64_t usage) const { - return std::make_shared(bufferId, width, height, pixelFormat, usage); - } - virtual void getGraphicBufferPropertiesFromCache(client_cache_t /* cachedBuffer */, - uint64_t* /* outBufferId */, - uint32_t* /* outWidth */, - uint32_t* /* outHeight */, - int32_t* /* outPixelFormat */, - uint64_t* /* outUsage */) const {} }; TransactionProtoParser(std::unique_ptr provider) : mMapper(std::move(provider)) {} proto::TransactionState toProto(const TransactionState&); - proto::TransactionState toProto(const std::map&); - proto::LayerCreationArgs toProto(const TracingLayerCreationArgs& args); + proto::TransactionState toProto(const std::map&); + proto::LayerCreationArgs toProto(const LayerCreationArgs& args); + proto::LayerState toProto(const ResolvedComposerState&); + static proto::DisplayInfo toProto(const frontend::DisplayInfo&, uint32_t layerStack); TransactionState fromProto(const proto::TransactionState&); void mergeFromProto(const proto::LayerState&, TracingLayerState& outState); - void fromProto(const proto::LayerCreationArgs&, TracingLayerCreationArgs& outArgs); + void fromProto(const proto::LayerCreationArgs&, LayerCreationArgs& outArgs); std::unique_ptr mMapper; + static frontend::DisplayInfo fromProto(const proto::DisplayInfo&); + static void fromProto( + const google::protobuf::RepeatedPtrField&, + display::DisplayMap& outDisplayInfos); private: - proto::LayerState toProto(const layer_state_t&); proto::DisplayState toProto(const DisplayState&); - void fromProto(const proto::LayerState&, layer_state_t& out); + void fromProto(const proto::LayerState&, ResolvedComposerState& out); DisplayState fromProto(const proto::DisplayState&); }; diff --git a/services/surfaceflinger/Tracing/TransactionTracing.cpp b/services/surfaceflinger/Tracing/TransactionTracing.cpp index 6381758c7b1e8a7b5d51d19a8c30076abc7f2e80..87a633fc9d9c41ffc0017c831ce8349e6fca4d64 100644 --- a/services/surfaceflinger/Tracing/TransactionTracing.cpp +++ b/services/surfaceflinger/Tracing/TransactionTracing.cpp @@ -23,75 +23,14 @@ #include #include -#include "ClientCache.h" +#include "Client.h" +#include "FrontEnd/LayerCreationArgs.h" #include "TransactionTracing.h" -#include "renderengine/ExternalTexture.h" namespace android { -// Keeps the binder address as the layer id so we can avoid holding the tracing lock in the -// binder thread. -class FlatDataMapper : public TransactionProtoParser::FlingerDataMapper { -public: - virtual int64_t getLayerId(const sp& layerHandle) const { - if (layerHandle == nullptr) { - return -1; - } - - return reinterpret_cast(layerHandle->localBinder()); - } - - void getGraphicBufferPropertiesFromCache(client_cache_t cachedBuffer, uint64_t* outBufferId, - uint32_t* outWidth, uint32_t* outHeight, - int32_t* outPixelFormat, - uint64_t* outUsage) const override { - std::shared_ptr buffer = - ClientCache::getInstance().get(cachedBuffer); - if (!buffer || !buffer->getBuffer()) { - *outBufferId = 0; - *outWidth = 0; - *outHeight = 0; - *outPixelFormat = 0; - *outUsage = 0; - return; - } - - *outBufferId = buffer->getId(); - *outWidth = buffer->getWidth(); - *outHeight = buffer->getHeight(); - *outPixelFormat = buffer->getPixelFormat(); - *outUsage = buffer->getUsage(); - return; - } -}; - -class FlingerDataMapper : public FlatDataMapper { - std::unordered_map& mLayerHandles; - -public: - FlingerDataMapper(std::unordered_map& layerHandles) - : mLayerHandles(layerHandles) {} - - int64_t getLayerId(const sp& layerHandle) const override { - if (layerHandle == nullptr) { - return -1; - } - return getLayerId(layerHandle->localBinder()); - } - - int64_t getLayerId(BBinder* localBinder) const { - auto it = mLayerHandles.find(localBinder); - if (it == mLayerHandles.end()) { - ALOGW("Could not find layer handle %p", localBinder); - return -1; - } - return it->second; - } -}; - TransactionTracing::TransactionTracing() - : mProtoParser(std::make_unique(mLayerHandles)), - mLockfreeProtoParser(std::make_unique()) { + : mProtoParser(std::make_unique()) { std::scoped_lock lock(mTraceLock); mBuffer.setSize(mBufferSizeInBytes); @@ -134,68 +73,80 @@ proto::TransactionTraceFile TransactionTracing::createTraceFileProto() const { proto::TransactionTraceFile proto; proto.set_magic_number(uint64_t(proto::TransactionTraceFile_MagicNumber_MAGIC_NUMBER_H) << 32 | proto::TransactionTraceFile_MagicNumber_MAGIC_NUMBER_L); + auto timeOffsetNs = static_cast(systemTime(SYSTEM_TIME_REALTIME) - + systemTime(SYSTEM_TIME_MONOTONIC)); + proto.set_real_to_elapsed_time_offset_nanos(timeOffsetNs); + proto.set_version(TRACING_VERSION); return proto; } void TransactionTracing::dump(std::string& result) const { std::scoped_lock lock(mTraceLock); - base::StringAppendF(&result, - " queued transactions=%zu created layers=%zu handles=%zu states=%zu\n", - mQueuedTransactions.size(), mCreatedLayers.size(), mLayerHandles.size(), - mStartingStates.size()); + base::StringAppendF(&result, " queued transactions=%zu created layers=%zu states=%zu\n", + mQueuedTransactions.size(), mCreatedLayers.size(), mStartingStates.size()); mBuffer.dump(result); } void TransactionTracing::addQueuedTransaction(const TransactionState& transaction) { - proto::TransactionState* state = - new proto::TransactionState(mLockfreeProtoParser.toProto(transaction)); + proto::TransactionState* state = new proto::TransactionState(mProtoParser.toProto(transaction)); mTransactionQueue.push(state); } -void TransactionTracing::addCommittedTransactions(std::vector& transactions, - int64_t vsyncId) { - CommittedTransactions committedTransactions; - committedTransactions.vsyncId = vsyncId; - committedTransactions.timestamp = systemTime(); - committedTransactions.transactionIds.reserve(transactions.size()); - for (const auto& transaction : transactions) { - committedTransactions.transactionIds.emplace_back(transaction.id); +void TransactionTracing::addCommittedTransactions( + int64_t vsyncId, nsecs_t commitTime, frontend::Update& newUpdate, + const display::DisplayMap& displayInfos, + bool displayInfoChanged) { + CommittedUpdates update; + update.vsyncId = vsyncId; + update.timestamp = commitTime; + update.transactionIds.reserve(newUpdate.transactions.size()); + for (const auto& transaction : newUpdate.transactions) { + update.transactionIds.emplace_back(transaction.id); } - - mPendingTransactions.emplace_back(committedTransactions); + update.displayInfoChanged = displayInfoChanged; + if (displayInfoChanged) { + update.displayInfos = displayInfos; + } + update.createdLayers = std::move(newUpdate.layerCreationArgs); + newUpdate.layerCreationArgs.clear(); + update.destroyedLayerHandles.reserve(newUpdate.destroyedHandles.size()); + for (uint32_t handle : newUpdate.destroyedHandles) { + update.destroyedLayerHandles.push_back(handle); + } + mPendingUpdates.emplace_back(update); tryPushToTracingThread(); } void TransactionTracing::loop() { while (true) { - std::vector committedTransactions; - std::vector removedLayers; + std::vector committedUpdates; + std::vector destroyedLayers; { std::unique_lock lock(mMainThreadLock); base::ScopedLockAssertion assumeLocked(mMainThreadLock); mTransactionsAvailableCv.wait(lock, [&]() REQUIRES(mMainThreadLock) { - return mDone || !mCommittedTransactions.empty(); + return mDone || !mUpdates.empty(); }); if (mDone) { - mCommittedTransactions.clear(); - mRemovedLayers.clear(); + mUpdates.clear(); + mDestroyedLayers.clear(); break; } - removedLayers = std::move(mRemovedLayers); - mRemovedLayers.clear(); - committedTransactions = std::move(mCommittedTransactions); - mCommittedTransactions.clear(); + destroyedLayers = std::move(mDestroyedLayers); + mDestroyedLayers.clear(); + committedUpdates = std::move(mUpdates); + mUpdates.clear(); } // unlock mMainThreadLock - if (!committedTransactions.empty() || !removedLayers.empty()) { - addEntry(committedTransactions, removedLayers); + if (!committedUpdates.empty() || !destroyedLayers.empty()) { + addEntry(committedUpdates, destroyedLayers); } } } -void TransactionTracing::addEntry(const std::vector& committedTransactions, - const std::vector& removedLayers) { +void TransactionTracing::addEntry(const std::vector& committedUpdates, + const std::vector& destroyedLayers) { ATRACE_CALL(); std::scoped_lock lock(mTraceLock); std::vector removedEntries; @@ -203,50 +154,27 @@ void TransactionTracing::addEntry(const std::vector& comm while (auto incomingTransaction = mTransactionQueue.pop()) { auto transaction = *incomingTransaction; - int32_t layerCount = transaction.layer_changes_size(); - for (int i = 0; i < layerCount; i++) { - auto layer = transaction.mutable_layer_changes(i); - layer->set_layer_id( - mProtoParser.mMapper->getLayerId(reinterpret_cast(layer->layer_id()))); - if ((layer->what() & layer_state_t::eReparent) && layer->parent_id() != -1) { - layer->set_parent_id( - mProtoParser.mMapper->getLayerId(reinterpret_cast( - layer->parent_id()))); - } - - if ((layer->what() & layer_state_t::eRelativeLayerChanged) && - layer->relative_parent_id() != -1) { - layer->set_relative_parent_id( - mProtoParser.mMapper->getLayerId(reinterpret_cast( - layer->relative_parent_id()))); - } - - if (layer->has_window_info_handle() && - layer->window_info_handle().crop_layer_id() != -1) { - auto input = layer->mutable_window_info_handle(); - input->set_crop_layer_id( - mProtoParser.mMapper->getLayerId(reinterpret_cast( - input->crop_layer_id()))); - } - } mQueuedTransactions[incomingTransaction->transaction_id()] = transaction; delete incomingTransaction; } - for (const CommittedTransactions& entry : committedTransactions) { - entryProto.set_elapsed_realtime_nanos(entry.timestamp); - entryProto.set_vsync_id(entry.vsyncId); - entryProto.mutable_added_layers()->Reserve(static_cast(mCreatedLayers.size())); - for (auto& newLayer : mCreatedLayers) { - entryProto.mutable_added_layers()->Add(std::move(newLayer)); + for (const CommittedUpdates& update : committedUpdates) { + entryProto.set_elapsed_realtime_nanos(update.timestamp); + entryProto.set_vsync_id(update.vsyncId); + entryProto.mutable_added_layers()->Reserve( + static_cast(update.createdLayers.size())); + + for (const auto& args : update.createdLayers) { + entryProto.mutable_added_layers()->Add(std::move(mProtoParser.toProto(args))); } - entryProto.mutable_removed_layers()->Reserve(static_cast(removedLayers.size())); - for (auto& removedLayer : removedLayers) { - entryProto.mutable_removed_layers()->Add(removedLayer); + + entryProto.mutable_destroyed_layers()->Reserve( + static_cast(destroyedLayers.size())); + for (auto& destroyedLayer : destroyedLayers) { + entryProto.mutable_destroyed_layers()->Add(destroyedLayer); } - mCreatedLayers.clear(); entryProto.mutable_transactions()->Reserve( - static_cast(entry.transactionIds.size())); - for (const uint64_t& id : entry.transactionIds) { + static_cast(update.transactionIds.size())); + for (const uint64_t& id : update.transactionIds) { auto it = mQueuedTransactions.find(id); if (it != mQueuedTransactions.end()) { entryProto.mutable_transactions()->Add(std::move(it->second)); @@ -256,6 +184,22 @@ void TransactionTracing::addEntry(const std::vector& comm } } + entryProto.mutable_destroyed_layer_handles()->Reserve( + static_cast(update.destroyedLayerHandles.size())); + for (auto layerId : update.destroyedLayerHandles) { + entryProto.mutable_destroyed_layer_handles()->Add(layerId); + } + + entryProto.set_displays_changed(update.displayInfoChanged); + if (update.displayInfoChanged) { + entryProto.mutable_displays()->Reserve( + static_cast(update.displayInfos.size())); + for (auto& [layerStack, displayInfo] : update.displayInfos) { + entryProto.mutable_displays()->Add( + std::move(mProtoParser.toProto(displayInfo, layerStack.id))); + } + } + std::string serializedProto; entryProto.SerializeToString(&serializedProto); entryProto.Clear(); @@ -263,13 +207,6 @@ void TransactionTracing::addEntry(const std::vector& comm removedEntries.reserve(removedEntries.size() + entries.size()); removedEntries.insert(removedEntries.end(), std::make_move_iterator(entries.begin()), std::make_move_iterator(entries.end())); - - entryProto.mutable_removed_layer_handles()->Reserve( - static_cast(mRemovedLayerHandles.size())); - for (auto& handle : mRemovedLayerHandles) { - entryProto.mutable_removed_layer_handles()->Add(handle); - } - mRemovedLayerHandles.clear(); } proto::TransactionTraceEntry removedEntryProto; @@ -282,7 +219,7 @@ void TransactionTracing::addEntry(const std::vector& comm } void TransactionTracing::flush(int64_t vsyncId) { - while (!mPendingTransactions.empty() || !mPendingRemovedLayers.empty()) { + while (!mPendingUpdates.empty() || !mPendingDestroyedLayers.empty()) { tryPushToTracingThread(); } std::unique_lock lock(mTraceLock); @@ -296,56 +233,21 @@ void TransactionTracing::flush(int64_t vsyncId) { }); } -void TransactionTracing::onLayerAdded(BBinder* layerHandle, int layerId, const std::string& name, - uint32_t flags, int parentId) { - std::scoped_lock lock(mTraceLock); - TracingLayerCreationArgs args{layerId, name, flags, parentId, -1 /* mirrorFromId */}; - if (mLayerHandles.find(layerHandle) != mLayerHandles.end()) { - ALOGW("Duplicate handles found. %p", layerHandle); - } - mLayerHandles[layerHandle] = layerId; - mCreatedLayers.push_back(mProtoParser.toProto(args)); -} - -void TransactionTracing::onMirrorLayerAdded(BBinder* layerHandle, int layerId, - const std::string& name, int mirrorFromId) { - std::scoped_lock lock(mTraceLock); - TracingLayerCreationArgs args{layerId, name, 0 /* flags */, -1 /* parentId */, mirrorFromId}; - if (mLayerHandles.find(layerHandle) != mLayerHandles.end()) { - ALOGW("Duplicate handles found. %p", layerHandle); - } - mLayerHandles[layerHandle] = layerId; - mCreatedLayers.emplace_back(mProtoParser.toProto(args)); -} - void TransactionTracing::onLayerRemoved(int32_t layerId) { - mPendingRemovedLayers.emplace_back(layerId); + mPendingDestroyedLayers.emplace_back(layerId); tryPushToTracingThread(); } -void TransactionTracing::onHandleRemoved(BBinder* layerHandle) { - std::scoped_lock lock(mTraceLock); - auto it = mLayerHandles.find(layerHandle); - if (it == mLayerHandles.end()) { - ALOGW("handle not found. %p", layerHandle); - return; - } - - mRemovedLayerHandles.push_back(it->second); - mLayerHandles.erase(it); -} - void TransactionTracing::tryPushToTracingThread() { // Try to acquire the lock from main thread. if (mMainThreadLock.try_lock()) { // We got the lock! Collect any pending transactions and continue. - mCommittedTransactions.insert(mCommittedTransactions.end(), - std::make_move_iterator(mPendingTransactions.begin()), - std::make_move_iterator(mPendingTransactions.end())); - mPendingTransactions.clear(); - mRemovedLayers.insert(mRemovedLayers.end(), mPendingRemovedLayers.begin(), - mPendingRemovedLayers.end()); - mPendingRemovedLayers.clear(); + mUpdates.insert(mUpdates.end(), std::make_move_iterator(mPendingUpdates.begin()), + std::make_move_iterator(mPendingUpdates.end())); + mPendingUpdates.clear(); + mDestroyedLayers.insert(mDestroyedLayers.end(), mPendingDestroyedLayers.begin(), + mPendingDestroyedLayers.end()); + mPendingDestroyedLayers.clear(); mTransactionsAvailableCv.notify_one(); mMainThreadLock.unlock(); } else { @@ -367,19 +269,29 @@ void TransactionTracing::updateStartingStateLocked( // Merge layer states to starting transaction state. for (const proto::TransactionState& transaction : removedEntry.transactions()) { for (const proto::LayerState& layerState : transaction.layer_changes()) { - auto it = mStartingStates.find((int32_t)layerState.layer_id()); + auto it = mStartingStates.find(layerState.layer_id()); if (it == mStartingStates.end()) { - ALOGW("Could not find layer id %d", (int32_t)layerState.layer_id()); + // TODO(b/238781169) make this log fatal when we switch over to using new fe + ALOGW("Could not find layer id %d", layerState.layer_id()); continue; } mProtoParser.mergeFromProto(layerState, it->second); } } + for (const uint32_t destroyedLayerHandleId : removedEntry.destroyed_layer_handles()) { + mRemovedLayerHandlesAtStart.insert(destroyedLayerHandleId); + } + // Clean up stale starting states since the layer has been removed and the buffer does not // contain any references to the layer. - for (const int32_t removedLayerId : removedEntry.removed_layers()) { - mStartingStates.erase(removedLayerId); + for (const uint32_t destroyedLayerId : removedEntry.destroyed_layers()) { + mStartingStates.erase(destroyedLayerId); + mRemovedLayerHandlesAtStart.erase(destroyedLayerId); + } + + if (removedEntry.displays_changed()) { + mProtoParser.fromProto(removedEntry.displays(), mStartingDisplayInfos); } } @@ -401,6 +313,17 @@ void TransactionTracing::addStartingStateToProtoLocked(proto::TransactionTraceFi transactionProto.set_vsync_id(0); transactionProto.set_post_time(mStartingTimestamp); entryProto->mutable_transactions()->Add(std::move(transactionProto)); + + entryProto->mutable_destroyed_layer_handles()->Reserve( + static_cast(mRemovedLayerHandlesAtStart.size())); + for (const uint32_t destroyedLayerHandleId : mRemovedLayerHandlesAtStart) { + entryProto->mutable_destroyed_layer_handles()->Add(destroyedLayerHandleId); + } + + entryProto->mutable_displays()->Reserve(static_cast(mStartingDisplayInfos.size())); + for (auto& [layerStack, displayInfo] : mStartingDisplayInfos) { + entryProto->mutable_displays()->Add(mProtoParser.toProto(displayInfo, layerStack.id)); + } } proto::TransactionTraceFile TransactionTracing::writeToProto() { diff --git a/services/surfaceflinger/Tracing/TransactionTracing.h b/services/surfaceflinger/Tracing/TransactionTracing.h index 4c291f960fcbcb1271e157f694f9ffaff14fa5b3..f27e7a9663046f900c17c90e196bab6565ac03b0 100644 --- a/services/surfaceflinger/Tracing/TransactionTracing.h +++ b/services/surfaceflinger/Tracing/TransactionTracing.h @@ -25,8 +25,12 @@ #include #include -#include "RingBuffer.h" +#include "Display/DisplayMap.h" +#include "FrontEnd/DisplayInfo.h" +#include "FrontEnd/LayerCreationArgs.h" +#include "FrontEnd/Update.h" #include "LocklessStack.h" +#include "RingBuffer.h" #include "TransactionProtoParser.h" using namespace android::surfaceflinger; @@ -55,21 +59,22 @@ public: ~TransactionTracing(); void addQueuedTransaction(const TransactionState&); - void addCommittedTransactions(std::vector& transactions, int64_t vsyncId); + void addCommittedTransactions( + int64_t vsyncId, nsecs_t commitTime, frontend::Update& update, + const display::DisplayMap& displayInfos, + bool displayInfoChanged); status_t writeToFile(std::string filename = FILE_NAME); void setBufferSize(size_t bufferSizeInBytes); - void onLayerAdded(BBinder* layerHandle, int layerId, const std::string& name, uint32_t flags, - int parentId); - void onMirrorLayerAdded(BBinder* layerHandle, int layerId, const std::string& name, - int mirrorFromId); void onLayerRemoved(int layerId); - void onHandleRemoved(BBinder* layerHandle); void dump(std::string&) const; static constexpr auto CONTINUOUS_TRACING_BUFFER_SIZE = 512 * 1024; static constexpr auto ACTIVE_TRACING_BUFFER_SIZE = 100 * 1024 * 1024; + // version 1 - switching to support new frontend + static constexpr auto TRACING_VERSION = 1; private: friend class TransactionTracingTest; + friend class SurfaceFlinger; static constexpr auto FILE_NAME = "/data/misc/wmtrace/transactions_trace.winscope"; @@ -81,15 +86,13 @@ private: GUARDED_BY(mTraceLock); LocklessStack mTransactionQueue; nsecs_t mStartingTimestamp GUARDED_BY(mTraceLock); - std::vector mCreatedLayers GUARDED_BY(mTraceLock); - std::unordered_map mLayerHandles + std::unordered_map mCreatedLayers GUARDED_BY(mTraceLock); + std::map mStartingStates GUARDED_BY(mTraceLock); + display::DisplayMap mStartingDisplayInfos GUARDED_BY(mTraceLock); - std::vector mRemovedLayerHandles GUARDED_BY(mTraceLock); - std::map mStartingStates GUARDED_BY(mTraceLock); - TransactionProtoParser mProtoParser GUARDED_BY(mTraceLock); - // Parses the transaction to proto without holding any tracing locks so we can generate proto - // in the binder thread without any contention. - TransactionProtoParser mLockfreeProtoParser; + + std::set mRemovedLayerHandlesAtStart GUARDED_BY(mTraceLock); + TransactionProtoParser mProtoParser; // We do not want main thread to block so main thread will try to acquire mMainThreadLock, // otherwise will push data to temporary container. @@ -98,26 +101,29 @@ private: bool mDone GUARDED_BY(mMainThreadLock) = false; std::condition_variable mTransactionsAvailableCv; std::condition_variable mTransactionsAddedToBufferCv; - struct CommittedTransactions { + struct CommittedUpdates { std::vector transactionIds; + std::vector createdLayers; + std::vector destroyedLayerHandles; + bool displayInfoChanged; + display::DisplayMap displayInfos; int64_t vsyncId; int64_t timestamp; }; - std::vector mCommittedTransactions GUARDED_BY(mMainThreadLock); - std::vector mPendingTransactions; // only accessed by main thread + std::vector mUpdates GUARDED_BY(mMainThreadLock); + std::vector mPendingUpdates; // only accessed by main thread - std::vector mRemovedLayers GUARDED_BY(mMainThreadLock); - std::vector mPendingRemovedLayers; // only accessed by main thread + std::vector mDestroyedLayers GUARDED_BY(mMainThreadLock); + std::vector mPendingDestroyedLayers; // only accessed by main thread proto::TransactionTraceFile createTraceFileProto() const; void loop(); - void addEntry(const std::vector& committedTransactions, - const std::vector& removedLayers) EXCLUDES(mTraceLock); + void addEntry(const std::vector& committedTransactions, + const std::vector& removedLayers) EXCLUDES(mTraceLock); int32_t getLayerIdLocked(const sp& layerHandle) REQUIRES(mTraceLock); void tryPushToTracingThread() EXCLUDES(mMainThreadLock); void addStartingStateToProtoLocked(proto::TransactionTraceFile& proto) REQUIRES(mTraceLock); void updateStartingStateLocked(const proto::TransactionTraceEntry& entry) REQUIRES(mTraceLock); - // TEST // Wait until all the committed transactions for the specified vsync id are added to the buffer. void flush(int64_t vsyncId) EXCLUDES(mMainThreadLock); diff --git a/services/surfaceflinger/Tracing/tools/Android.bp b/services/surfaceflinger/Tracing/tools/Android.bp index e8fe734a8fdbed879c8437f1d5a433a48b0acad1..b6435a8a1321f8a401226db32d6fda3f1111487b 100644 --- a/services/surfaceflinger/Tracing/tools/Android.bp +++ b/services/surfaceflinger/Tracing/tools/Android.bp @@ -25,8 +25,8 @@ cc_binary { name: "layertracegenerator", defaults: [ "libsurfaceflinger_mocks_defaults", + "librenderengine_deps", "surfaceflinger_defaults", - "skia_renderengine_deps", ], srcs: [ ":libsurfaceflinger_sources", diff --git a/services/surfaceflinger/Tracing/tools/LayerTraceGenerator.cpp b/services/surfaceflinger/Tracing/tools/LayerTraceGenerator.cpp index cf44effe50862168efd986065b563fb284bdfca8..55004c5e70c9eae80b3a4c095c7a390f4f0c5ed1 100644 --- a/services/surfaceflinger/Tracing/tools/LayerTraceGenerator.cpp +++ b/services/surfaceflinger/Tracing/tools/LayerTraceGenerator.cpp @@ -16,268 +16,156 @@ #undef LOG_TAG #define LOG_TAG "LayerTraceGenerator" +//#define LOG_NDEBUG 0 -#include #include -#include -#include -#include #include #include -#include #include -#include -#include #include +#include +#include +#include #include +#include +#include "FrontEnd/LayerCreationArgs.h" +#include "FrontEnd/RequestedLayerState.h" +#include "LayerProtoHelper.h" +#include "Tracing/LayerTracing.h" +#include "TransactionState.h" +#include "cutils/properties.h" #include "LayerTraceGenerator.h" namespace android { - -class Factory final : public surfaceflinger::Factory { -public: - ~Factory() = default; - - std::unique_ptr createHWComposer(const std::string&) override { return nullptr; } - - std::unique_ptr createVsyncConfiguration( - Fps /*currentRefreshRate*/) override { - return std::make_unique(); - } - - sp createSurfaceInterceptor() override { - return new android::impl::SurfaceInterceptor(); - } - - sp createStartPropertySetThread( - bool /* timestampPropertyValue */) override { - return nullptr; - } - - sp createDisplayDevice(DisplayDeviceCreationArgs& /* creationArgs */) override { - return nullptr; - } - - sp createGraphicBuffer(uint32_t /* width */, uint32_t /* height */, - PixelFormat /* format */, uint32_t /* layerCount */, - uint64_t /* usage */, - std::string /* requestorName */) override { - return nullptr; - } - - void createBufferQueue(sp* /* outProducer */, - sp* /* outConsumer */, - bool /* consumerIsSurfaceFlinger */) override {} - - sp createMonitoredProducer( - const sp& /* producer */, - const sp& /* flinger */, const wp& /* layer */) override { - return nullptr; - } - - sp createBufferLayerConsumer( - const sp& /* consumer */, - renderengine::RenderEngine& /* renderEngine */, uint32_t /* textureName */, - Layer* /* layer */) override { - return nullptr; - } - - std::unique_ptr createNativeWindowSurface( - const sp& /* producer */) override { - return nullptr; - } - - std::unique_ptr createCompositionEngine() override { - return compositionengine::impl::createCompositionEngine(); - } - - sp createContainerLayer(const LayerCreationArgs& args) { - return sp::make(args); - } - - sp createBufferStateLayer(const LayerCreationArgs& args) { - return new BufferStateLayer(args); - } - - sp createEffectLayer(const LayerCreationArgs& args) { - return new EffectLayer(args); - } - - sp createBufferQueueLayer(const LayerCreationArgs&) override { - return nullptr; - } - - std::unique_ptr createFrameTracer() override { - return std::make_unique>(); - } - - std::unique_ptr createFrameTimeline( - std::shared_ptr timeStats, pid_t surfaceFlingerPid = 0) override { - return std::make_unique>(timeStats, - surfaceFlingerPid); - } -}; - -class MockSurfaceFlinger : public SurfaceFlinger { -public: - MockSurfaceFlinger(Factory& factory) - : SurfaceFlinger(factory, SurfaceFlinger::SkipInitialization) {} - std::shared_ptr getExternalTextureFromBufferData( - const BufferData& bufferData, const char* /* layerName */) const override { - return std::make_shared(bufferData.getWidth(), - bufferData.getHeight(), - bufferData.getId(), - bufferData - .getPixelFormat(), - bufferData.getUsage()); - }; - - // b/220017192 migrate from transact codes to ISurfaceComposer apis - void setLayerTracingFlags(int32_t flags) { - Parcel data; - Parcel reply; - data.writeInterfaceToken(String16("android.ui.ISurfaceComposer")); - data.writeInt32(flags); - transact(1033, data, &reply, 0 /* flags */); - } - - void startLayerTracing(int64_t traceStartTime) { - Parcel data; - Parcel reply; - data.writeInterfaceToken(String16("android.ui.ISurfaceComposer")); - data.writeInt32(1); - data.writeInt64(traceStartTime); - transact(1025, data, &reply, 0 /* flags */); - } - - void stopLayerTracing(const char* tracePath) { - Parcel data; - Parcel reply; - data.writeInterfaceToken(String16("android.ui.ISurfaceComposer")); - data.writeInt32(2); - data.writeCString(tracePath); - transact(1025, data, &reply, 0 /* flags */); - } -}; - -class TraceGenFlingerDataMapper : public TransactionProtoParser::FlingerDataMapper { -public: - std::unordered_map /* handle */> mLayerHandles; - sp getLayerHandle(int32_t layerId) const override { - if (layerId == -1) { - ALOGE("Error: Called with layer=%d", layerId); - return nullptr; - } - auto it = mLayerHandles.find(layerId); - if (it == mLayerHandles.end()) { - ALOGE("Error: Could not find handle for layer=%d", layerId); - return nullptr; - } - return it->second; - } -}; +using namespace ftl::flag_operators; bool LayerTraceGenerator::generate(const proto::TransactionTraceFile& traceFile, const char* outputLayersTracePath) { if (traceFile.entry_size() == 0) { + ALOGD("Trace file is empty"); return false; } - Factory mFactory; - sp flinger = new MockSurfaceFlinger(mFactory); - TestableSurfaceFlinger mFlinger(flinger); - mFlinger.setupRenderEngine( - std::make_unique>()); - mock::VsyncController* mVsyncController = new testing::NiceMock(); - mock::VSyncTracker* mVSyncTracker = new testing::NiceMock(); - mock::EventThread* mEventThread = new testing::NiceMock(); - mock::EventThread* mSFEventThread = new testing::NiceMock(); - mFlinger.setupScheduler(std::unique_ptr(mVsyncController), - std::unique_ptr(mVSyncTracker), - std::unique_ptr(mEventThread), - std::unique_ptr(mSFEventThread), - TestableSurfaceFlinger::SchedulerCallbackImpl::kNoOp, - TestableSurfaceFlinger::kOneDisplayMode, true /* useNiceMock */); + TransactionProtoParser parser(std::make_unique()); - Hwc2::mock::Composer* mComposer = new testing::NiceMock(); - mFlinger.setupComposer(std::unique_ptr(mComposer)); - mFlinger.mutableMaxRenderTargetSize() = 16384; + // frontend + frontend::LayerLifecycleManager lifecycleManager; + frontend::LayerHierarchyBuilder hierarchyBuilder{{}}; + frontend::LayerSnapshotBuilder snapshotBuilder; + display::DisplayMap displayInfos; - flinger->setLayerTracingFlags(LayerTracing::TRACE_INPUT | LayerTracing::TRACE_BUFFERS); - flinger->startLayerTracing(traceFile.entry(0).elapsed_realtime_nanos()); - std::unique_ptr mapper = - std::make_unique(); - TraceGenFlingerDataMapper* dataMapper = mapper.get(); - TransactionProtoParser parser(std::move(mapper)); + renderengine::ShadowSettings globalShadowSettings{.ambientColor = {1, 1, 1, 1}}; + char value[PROPERTY_VALUE_MAX]; + property_get("ro.surface_flinger.supports_background_blur", value, "0"); + bool supportsBlur = atoi(value); + + LayerTracing layerTracing; + layerTracing.setTraceFlags(LayerTracing::TRACE_INPUT | LayerTracing::TRACE_BUFFERS); + // 10MB buffer size (large enough to hold a single entry) + layerTracing.setBufferSize(10 * 1024 * 1024); + layerTracing.enable(); + layerTracing.writeToFile(outputLayersTracePath); + std::ofstream out(outputLayersTracePath, std::ios::binary | std::ios::app); - nsecs_t frameTime; - int64_t vsyncId; ALOGD("Generating %d transactions...", traceFile.entry_size()); for (int i = 0; i < traceFile.entry_size(); i++) { + // parse proto proto::TransactionTraceEntry entry = traceFile.entry(i); ALOGV(" Entry %04d/%04d for time=%" PRId64 " vsyncid=%" PRId64 - " layers +%d -%d transactions=%d", + " layers +%d -%d handles -%d transactions=%d", i, traceFile.entry_size(), entry.elapsed_realtime_nanos(), entry.vsync_id(), - entry.added_layers_size(), entry.removed_layers_size(), entry.transactions_size()); + entry.added_layers_size(), entry.destroyed_layers_size(), + entry.destroyed_layer_handles_size(), entry.transactions_size()); + std::vector> addedLayers; + addedLayers.reserve((size_t)entry.added_layers_size()); for (int j = 0; j < entry.added_layers_size(); j++) { - // create layers - TracingLayerCreationArgs tracingArgs; - parser.fromProto(entry.added_layers(j), tracingArgs); - - sp outHandle; - int32_t outLayerId; - LayerCreationArgs args(mFlinger.flinger(), nullptr /* client */, tracingArgs.name, - tracingArgs.flags, LayerMetadata()); - args.sequence = std::make_optional(tracingArgs.layerId); - - if (tracingArgs.mirrorFromId == -1) { - sp parentHandle = nullptr; - if ((tracingArgs.parentId != -1) && - (dataMapper->mLayerHandles.find(tracingArgs.parentId) == - dataMapper->mLayerHandles.end())) { - args.addToRoot = false; - } else { - parentHandle = dataMapper->getLayerHandle(tracingArgs.parentId); - } - mFlinger.createLayer(args, &outHandle, parentHandle, &outLayerId, - nullptr /* parentLayer */, nullptr /* outTransformHint */); - } else { - sp mirrorFromHandle = dataMapper->getLayerHandle(tracingArgs.mirrorFromId); - mFlinger.mirrorLayer(args, mirrorFromHandle, &outHandle, &outLayerId); - } - LOG_ALWAYS_FATAL_IF(outLayerId != tracingArgs.layerId, - "Could not create layer expected:%d actual:%d", tracingArgs.layerId, - outLayerId); - dataMapper->mLayerHandles[tracingArgs.layerId] = outHandle; + LayerCreationArgs args; + parser.fromProto(entry.added_layers(j), args); + ALOGV(" %s", args.getDebugString().c_str()); + addedLayers.emplace_back(std::make_unique(args)); } + std::vector transactions; + transactions.reserve((size_t)entry.transactions_size()); for (int j = 0; j < entry.transactions_size(); j++) { // apply transactions TransactionState transaction = parser.fromProto(entry.transactions(j)); - mFlinger.setTransactionState(transaction.frameTimelineInfo, transaction.states, - transaction.displays, transaction.flags, - transaction.applyToken, transaction.inputWindowCommands, - transaction.desiredPresentTime, - transaction.isAutoTimestamp, {}, - transaction.hasListenerCallbacks, - transaction.listenerCallbacks, transaction.id); + for (auto& resolvedComposerState : transaction.states) { + if (resolvedComposerState.state.what & layer_state_t::eInputInfoChanged) { + if (!resolvedComposerState.state.windowInfoHandle->getInfo()->inputConfig.test( + gui::WindowInfo::InputConfig::NO_INPUT_CHANNEL)) { + // create a fake token since the FE expects a valid token + resolvedComposerState.state.windowInfoHandle->editInfo()->token = + sp::make(); + } + } + } + transactions.emplace_back(std::move(transaction)); } - for (int j = 0; j < entry.removed_layer_handles_size(); j++) { - dataMapper->mLayerHandles.erase(entry.removed_layer_handles(j)); + for (int j = 0; j < entry.destroyed_layers_size(); j++) { + ALOGV(" destroyedHandles=%d", entry.destroyed_layers(j)); } - frameTime = entry.elapsed_realtime_nanos(); - vsyncId = entry.vsync_id(); - mFlinger.commit(frameTime, vsyncId); - } + std::vector destroyedHandles; + destroyedHandles.reserve((size_t)entry.destroyed_layer_handles_size()); + for (int j = 0; j < entry.destroyed_layer_handles_size(); j++) { + ALOGV(" destroyedHandles=%d", entry.destroyed_layer_handles(j)); + destroyedHandles.push_back(entry.destroyed_layer_handles(j)); + } + + bool displayChanged = entry.displays_changed(); + if (displayChanged) { + parser.fromProto(entry.displays(), displayInfos); + } + + // apply updates + lifecycleManager.addLayers(std::move(addedLayers)); + lifecycleManager.applyTransactions(transactions, /*ignoreUnknownHandles=*/true); + lifecycleManager.onHandlesDestroyed(destroyedHandles, /*ignoreUnknownHandles=*/true); + + if (lifecycleManager.getGlobalChanges().test( + frontend::RequestedLayerState::Changes::Hierarchy)) { + hierarchyBuilder.update(lifecycleManager.getLayers(), + lifecycleManager.getDestroyedLayers()); + } - flinger->stopLayerTracing(outputLayersTracePath); + frontend::LayerSnapshotBuilder::Args args{.root = hierarchyBuilder.getHierarchy(), + .layerLifecycleManager = lifecycleManager, + .displays = displayInfos, + .displayChanges = displayChanged, + .globalShadowSettings = globalShadowSettings, + .supportsBlur = supportsBlur, + .forceFullDamage = false, + .supportedLayerGenericMetadata = {}, + .genericLayerMetadataKeyMap = {}}; + snapshotBuilder.update(args); + + bool visibleRegionsDirty = lifecycleManager.getGlobalChanges().any( + frontend::RequestedLayerState::Changes::VisibleRegion | + frontend::RequestedLayerState::Changes::Hierarchy | + frontend::RequestedLayerState::Changes::Visibility); + + ALOGV(" layers:%04zu snapshots:%04zu changes:%s", lifecycleManager.getLayers().size(), + snapshotBuilder.getSnapshots().size(), + lifecycleManager.getGlobalChanges().string().c_str()); + + lifecycleManager.commitChanges(); + + LayersProto layersProto = LayerProtoFromSnapshotGenerator(snapshotBuilder, displayInfos, {}, + layerTracing.getFlags()) + .generate(hierarchyBuilder.getHierarchy()); + auto displayProtos = LayerProtoHelper::writeDisplayInfoToProto(displayInfos); + layerTracing.notify(visibleRegionsDirty, entry.elapsed_realtime_nanos(), entry.vsync_id(), + &layersProto, {}, &displayProtos); + layerTracing.appendToStream(out); + } + layerTracing.disable("", /*writeToFile=*/false); + out.close(); ALOGD("End of generating trace file. File written to %s", outputLayersTracePath); - dataMapper->mLayerHandles.clear(); return true; } -} // namespace android \ No newline at end of file +} // namespace android diff --git a/services/surfaceflinger/Tracing/tools/main.cpp b/services/surfaceflinger/Tracing/tools/main.cpp index f3cf42d7ab512b1bcbcdde4fc9bb7cc8f5e29e83..c440c19ccc4a6400a5c05165fb56df663d38307b 100644 --- a/services/surfaceflinger/Tracing/tools/main.cpp +++ b/services/surfaceflinger/Tracing/tools/main.cpp @@ -52,6 +52,7 @@ int main(int argc, char** argv) { ; ALOGD("Generating %s...", outputLayersTracePath); std::cout << "Generating " << outputLayersTracePath << "\n"; + if (!LayerTraceGenerator().generate(transactionTraceFile, outputLayersTracePath)) { std::cout << "Error: Failed to generate layers trace " << outputLayersTracePath; return -1; diff --git a/services/surfaceflinger/Tracing/tools/run.sh b/services/surfaceflinger/Tracing/tools/run.sh index baa93f1a1ca9a934a5f29ee6cd3e7480120bc053..307a4d8338f9e32a625ad53ff187de188cd676bb 100644 --- a/services/surfaceflinger/Tracing/tools/run.sh +++ b/services/surfaceflinger/Tracing/tools/run.sh @@ -5,7 +5,15 @@ set -ex # Build, push and run layertracegenerator $ANDROID_BUILD_TOP/build/soong/soong_ui.bash --make-mode layertracegenerator adb wait-for-device && adb push $OUT/system/bin/layertracegenerator /data/layertracegenerator -echo "Writing transaction trace to file" -adb shell service call SurfaceFlinger 1041 i32 0 -adb shell /data/layertracegenerator + +if [ -z "$1" ] + then + echo "Writing transaction trace to file" + adb shell service call SurfaceFlinger 1041 i32 0 + adb shell /data/layertracegenerator + else + echo "Pushing transaction trace to device" + adb push $1 /data/transaction_trace.winscope + adb shell /data/layertracegenerator /data/transaction_trace.winscope +fi adb pull /data/misc/wmtrace/layers_trace.winscope \ No newline at end of file diff --git a/services/surfaceflinger/TransactionCallbackInvoker.cpp b/services/surfaceflinger/TransactionCallbackInvoker.cpp index d2c2e29a2ff9b0f444b3629233ae2b5b4c182606..3587a726cd8fd5d3f06b15914a5fce3cfb2933eb 100644 --- a/services/surfaceflinger/TransactionCallbackInvoker.cpp +++ b/services/surfaceflinger/TransactionCallbackInvoker.cpp @@ -92,11 +92,6 @@ status_t TransactionCallbackInvoker::addCallbackHandles( return NO_ERROR; } -status_t TransactionCallbackInvoker::registerUnpresentedCallbackHandle( - const sp& handle) { - return addCallbackHandle(handle, std::vector()); -} - status_t TransactionCallbackInvoker::findOrCreateTransactionStats( const sp& listener, const std::vector& callbackIds, TransactionStats** outTransactionStats) { @@ -137,7 +132,6 @@ status_t TransactionCallbackInvoker::addCallbackHandle(const sp& sp currentFence = future.get().value_or(Fence::NO_FENCE); if (prevFence == nullptr && currentFence->getStatus() != Fence::Status::Invalid) { prevFence = std::move(currentFence); - handle->previousReleaseFence = prevFence; } else if (prevFence != nullptr) { // If both fences are signaled or both are unsignaled, we need to merge // them to get an accurate timestamp. @@ -147,8 +141,7 @@ status_t TransactionCallbackInvoker::addCallbackHandle(const sp& snprintf(fenceName, 32, "%.28s", handle->name.c_str()); sp mergedFence = Fence::merge(fenceName, prevFence, currentFence); if (mergedFence->isValid()) { - handle->previousReleaseFence = std::move(mergedFence); - prevFence = handle->previousReleaseFence; + prevFence = std::move(mergedFence); } } else if (currentFence->getStatus() == Fence::Status::Unsignaled) { // If one fence has signaled and the other hasn't, the unsignaled @@ -158,10 +151,11 @@ status_t TransactionCallbackInvoker::addCallbackHandle(const sp& // by this point, they will have both signaled and only the timestamp // will be slightly off; any dependencies after this point will // already have been met. - handle->previousReleaseFence = std::move(currentFence); + prevFence = std::move(currentFence); } } } + handle->previousReleaseFence = prevFence; handle->previousReleaseFences.clear(); FrameEventHistoryStats eventStats(handle->frameNumber, @@ -178,8 +172,8 @@ status_t TransactionCallbackInvoker::addCallbackHandle(const sp& return NO_ERROR; } -void TransactionCallbackInvoker::addPresentFence(const sp& presentFence) { - mPresentFence = presentFence; +void TransactionCallbackInvoker::addPresentFence(sp presentFence) { + mPresentFence = std::move(presentFence); } void TransactionCallbackInvoker::sendCallbacks(bool onCommitOnly) { diff --git a/services/surfaceflinger/TransactionCallbackInvoker.h b/services/surfaceflinger/TransactionCallbackInvoker.h index 81d79f0777fb696bb13406028a85011fbf207756..3074795f6295338bb51a40f741dffde69d0bce6d 100644 --- a/services/surfaceflinger/TransactionCallbackInvoker.h +++ b/services/surfaceflinger/TransactionCallbackInvoker.h @@ -19,6 +19,7 @@ #include #include #include +#include #include #include #include @@ -26,10 +27,10 @@ #include #include -#include #include #include #include +#include namespace android { @@ -48,7 +49,7 @@ public: std::vector> previousReleaseFences; std::variant> acquireTimeOrFence = -1; nsecs_t latchTime = -1; - uint32_t transformHint = 0; + std::optional transformHint = std::nullopt; uint32_t currentMaxAcquiredBufferCount = 0; std::shared_ptr gpuCompositionDoneFence{FenceTime::NO_FENCE}; CompositorTiming compositorTiming; @@ -65,12 +66,9 @@ public: status_t addOnCommitCallbackHandles(const std::deque>& handles, std::deque>& outRemainingHandles); - // Adds the Transaction CallbackHandle from a layer that does not need to be relatched and - // presented this frame. - status_t registerUnpresentedCallbackHandle(const sp& handle); void addEmptyTransaction(const ListenerCallbacks& listenerCallbacks); - void addPresentFence(const sp& presentFence); + void addPresentFence(sp); void sendCallbacks(bool onCommitOnly); void clearCompletedTransactions() { diff --git a/services/surfaceflinger/TransactionState.h b/services/surfaceflinger/TransactionState.h index 900d5669421f491bf37a94a74d5d1ab3a0e35ad0..7132a59016178d398b0e549ded1383da81b5e46e 100644 --- a/services/surfaceflinger/TransactionState.h +++ b/services/surfaceflinger/TransactionState.h @@ -20,60 +20,96 @@ #include #include #include +#include "FrontEnd/LayerCreationArgs.h" +#include "renderengine/ExternalTexture.h" #include #include namespace android { -class CountDownLatch; +enum TraverseBuffersReturnValues { + CONTINUE_TRAVERSAL, + STOP_TRAVERSAL, + DELETE_AND_CONTINUE_TRAVERSAL, +}; + +// Extends the client side composer state by resolving buffer. +class ResolvedComposerState : public ComposerState { +public: + ResolvedComposerState() = default; + ResolvedComposerState(ComposerState&& source) { state = std::move(source.state); } + std::shared_ptr externalTexture; + uint32_t layerId = UNASSIGNED_LAYER_ID; + uint32_t parentId = UNASSIGNED_LAYER_ID; + uint32_t relativeParentId = UNASSIGNED_LAYER_ID; + uint32_t touchCropId = UNASSIGNED_LAYER_ID; +}; struct TransactionState { TransactionState() = default; TransactionState(const FrameTimelineInfo& frameTimelineInfo, - const Vector& composerStates, + std::vector& composerStates, const Vector& displayStates, uint32_t transactionFlags, const sp& applyToken, const InputWindowCommands& inputWindowCommands, int64_t desiredPresentTime, bool isAutoTimestamp, - const client_cache_t& uncacheBuffer, int64_t postTime, uint32_t permissions, + std::vector uncacheBufferIds, int64_t postTime, bool hasListenerCallbacks, std::vector listenerCallbacks, - int originPid, int originUid, uint64_t transactionId) + int originPid, int originUid, uint64_t transactionId, + std::vector mergedTransactionIds) : frameTimelineInfo(frameTimelineInfo), - states(composerStates), + states(std::move(composerStates)), displays(displayStates), flags(transactionFlags), applyToken(applyToken), inputWindowCommands(inputWindowCommands), desiredPresentTime(desiredPresentTime), isAutoTimestamp(isAutoTimestamp), - buffer(uncacheBuffer), + uncacheBufferIds(std::move(uncacheBufferIds)), postTime(postTime), - permissions(permissions), hasListenerCallbacks(hasListenerCallbacks), listenerCallbacks(listenerCallbacks), originPid(originPid), originUid(originUid), - id(transactionId) {} + id(transactionId), + mergedTransactionIds(std::move(mergedTransactionIds)) {} // Invokes `void(const layer_state_t&)` visitor for matching layers. template void traverseStatesWithBuffers(Visitor&& visitor) const { - for (const auto& [state] : states) { - if (state.hasBufferChanges() && state.hasValidBuffer() && state.surface) { - visitor(state); + for (const auto& state : states) { + if (state.state.hasBufferChanges() && state.externalTexture && state.state.surface) { + visitor(state.state); } } } + template + void traverseStatesWithBuffersWhileTrue(Visitor&& visitor) { + for (auto state = states.begin(); state != states.end();) { + if (state->state.hasBufferChanges() && state->externalTexture && state->state.surface) { + int result = visitor(state->state, state->externalTexture); + if (result == STOP_TRAVERSAL) return; + if (result == DELETE_AND_CONTINUE_TRAVERSAL) { + state = states.erase(state); + continue; + } + } + state++; + } + } + // TODO(b/185535769): Remove FrameHint. Instead, reset the idle timer (of the relevant physical // display) on the main thread if commit leads to composite. Then, RefreshRateOverlay should be // able to setFrameRate once, rather than for each transaction. bool isFrameActive() const { if (!displays.empty()) return true; - for (const auto& [state] : states) { - if (state.frameRateCompatibility != ANATIVEWINDOW_FRAME_RATE_NO_VOTE) { + 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; } } @@ -82,66 +118,22 @@ struct TransactionState { } FrameTimelineInfo frameTimelineInfo; - Vector states; + std::vector states; Vector displays; uint32_t flags; sp applyToken; InputWindowCommands inputWindowCommands; int64_t desiredPresentTime; bool isAutoTimestamp; - client_cache_t buffer; + std::vector uncacheBufferIds; int64_t postTime; - uint32_t permissions; bool hasListenerCallbacks; std::vector listenerCallbacks; int originPid; int originUid; uint64_t id; - std::shared_ptr transactionCommittedSignal; - int64_t queueTime = 0; bool sentFenceTimeoutWarning = false; -}; - -class CountDownLatch { -public: - enum { - eSyncTransaction = 1 << 0, - eSyncInputWindows = 1 << 1, - }; - explicit CountDownLatch(uint32_t flags) : mFlags(flags) {} - - // True if there is no waiting condition after count down. - bool countDown(uint32_t flag) { - std::unique_lock lock(mMutex); - if (mFlags == 0) { - return true; - } - mFlags &= ~flag; - if (mFlags == 0) { - mCountDownComplete.notify_all(); - return true; - } - return false; - } - - // Return true if triggered. - bool wait_until(const std::chrono::nanoseconds& timeout) const { - std::unique_lock lock(mMutex); - const auto untilTime = std::chrono::system_clock::now() + timeout; - while (mFlags != 0) { - // Conditional variables can be woken up sporadically, so we check count - // to verify the wakeup was triggered by |countDown|. - if (std::cv_status::timeout == mCountDownComplete.wait_until(lock, untilTime)) { - return false; - } - } - return true; - } - -private: - uint32_t mFlags; - mutable std::condition_variable mCountDownComplete; - mutable std::mutex mMutex; + std::vector mergedTransactionIds; }; } // namespace android diff --git a/services/surfaceflinger/TunnelModeEnabledReporter.cpp b/services/surfaceflinger/TunnelModeEnabledReporter.cpp index 4497cafa587f6fadda2986fee79d67e73e28842e..bc9b8700751fdcc2f4952017bbb127981fc63181 100644 --- a/services/surfaceflinger/TunnelModeEnabledReporter.cpp +++ b/services/surfaceflinger/TunnelModeEnabledReporter.cpp @@ -59,7 +59,7 @@ void TunnelModeEnabledReporter::binderDied(const wp& who) { void TunnelModeEnabledReporter::addListener(const sp& listener) { sp asBinder = IInterface::asBinder(listener); - asBinder->linkToDeath(this); + asBinder->linkToDeath(sp::fromExisting(this)); bool tunnelModeEnabled = false; { std::scoped_lock lock(mMutex); diff --git a/services/surfaceflinger/Utils/Dumper.h b/services/surfaceflinger/Utils/Dumper.h new file mode 100644 index 0000000000000000000000000000000000000000..ee942177e57ba085ef604ad41f33ca65b0a460d8 --- /dev/null +++ b/services/surfaceflinger/Utils/Dumper.h @@ -0,0 +1,119 @@ +/* + * 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 + +#include + +namespace android::utils { + +// Dumps variables by appending their name and value to the output string. A variable is formatted +// as "name=value". If the name or value is empty, the format is "value" or "name=", respectively. +// A value of user-defined type T is stringified via `std::string to_string(const T&)`, which must +// be defined in the same namespace as T per the rules of ADL (argument-dependent lookup). +// +// TODO(b/249828573): Consolidate with +class Dumper { +public: + explicit Dumper(std::string& out) : mOut(out) {} + + void eol() { mOut += '\n'; } + + void dump(std::string_view name, std::string_view value = {}) { + using namespace std::string_view_literals; + + for (int i = mIndent; i-- > 0;) mOut += " "sv; + mOut += name; + if (!name.empty()) mOut += '='; + mOut += value; + eol(); + } + + void dump(std::string_view name, const std::string& value) { + dump(name, static_cast(value)); + } + + void dump(std::string_view name, bool value) { + using namespace std::string_view_literals; + dump(name, value ? "true"sv : "false"sv); + } + + template + void dump(std::string_view name, const std::optional& opt) { + if (opt) { + dump(name, *opt); + } else { + using namespace std::string_view_literals; + dump(name, "nullopt"sv); + } + } + + template + void dump(std::string_view name, const ftl::Optional& opt) { + dump(name, static_cast&>(opt)); + } + + template + void dump(std::string_view name, const T& value, const Ts&... rest) { + std::string string; + + constexpr bool kIsTuple = sizeof...(Ts) > 0; + if constexpr (kIsTuple) { + string += '{'; + } + + using std::to_string; + string += to_string(value); + + if constexpr (kIsTuple) { + string += ((", " + to_string(rest)) + ...); + string += '}'; + } + + dump(name, string); + } + + struct Indent { + explicit Indent(Dumper& dumper) : dumper(dumper) { dumper.mIndent++; } + ~Indent() { dumper.mIndent--; } + + Dumper& dumper; + }; + + struct Section { + Section(Dumper& dumper, std::string_view heading) : dumper(dumper) { + dumper.dump({}, heading); + indent.emplace(dumper); + } + + ~Section() { + indent.reset(); + dumper.eol(); + } + + Dumper& dumper; + std::optional indent; + }; + +private: + std::string& mOut; + int mIndent = 0; +}; + +} // namespace android::utils diff --git a/services/surfaceflinger/WindowInfosListenerInvoker.cpp b/services/surfaceflinger/WindowInfosListenerInvoker.cpp index 023402f7473ed8716cc094c05bd13acdbfef2b8c..7062a4e3a76d58b674d6c54f4a78e958921c2e54 100644 --- a/services/surfaceflinger/WindowInfosListenerInvoker.cpp +++ b/services/surfaceflinger/WindowInfosListenerInvoker.cpp @@ -14,133 +14,189 @@ * limitations under the License. */ -#include +#include +#include +#include #include +#include +#include +#include -#include "SurfaceFlinger.h" +#include "BackgroundExecutor.h" #include "WindowInfosListenerInvoker.h" +#undef ATRACE_TAG +#define ATRACE_TAG ATRACE_TAG_GRAPHICS + namespace android { using gui::DisplayInfo; using gui::IWindowInfosListener; using gui::WindowInfo; -struct WindowInfosListenerInvoker::WindowInfosReportedListener - : gui::BnWindowInfosReportedListener { - explicit WindowInfosReportedListener(WindowInfosListenerInvoker& invoker, size_t callbackCount, - bool shouldSync) - : mInvoker(invoker), mCallbacksPending(callbackCount), mShouldSync(shouldSync) {} +void WindowInfosListenerInvoker::addWindowInfosListener(sp listener, + gui::WindowInfosListenerInfo* outInfo) { + int64_t listenerId = mNextListenerId++; + outInfo->listenerId = listenerId; + outInfo->windowInfosPublisher = sp::fromExisting(this); + + BackgroundExecutor::getInstance().sendCallbacks( + {[this, listener = std::move(listener), listenerId]() { + ATRACE_NAME("WindowInfosListenerInvoker::addWindowInfosListener"); + sp asBinder = IInterface::asBinder(listener); + asBinder->linkToDeath(sp::fromExisting(this)); + mWindowInfosListeners.try_emplace(asBinder, + std::make_pair(listenerId, std::move(listener))); + }}); +} + +void WindowInfosListenerInvoker::removeWindowInfosListener( + const sp& listener) { + BackgroundExecutor::getInstance().sendCallbacks({[this, listener]() { + ATRACE_NAME("WindowInfosListenerInvoker::removeWindowInfosListener"); + sp asBinder = IInterface::asBinder(listener); + asBinder->unlinkToDeath(sp::fromExisting(this)); + mWindowInfosListeners.erase(asBinder); + }}); +} - binder::Status onWindowInfosReported() override { - mCallbacksPending--; - if (mCallbacksPending == 0) { - mInvoker.windowInfosReported(mShouldSync); +void WindowInfosListenerInvoker::binderDied(const wp& who) { + BackgroundExecutor::getInstance().sendCallbacks({[this, who]() { + ATRACE_NAME("WindowInfosListenerInvoker::binderDied"); + auto it = mWindowInfosListeners.find(who); + int64_t listenerId = it->second.first; + mWindowInfosListeners.erase(who); + + std::vector vsyncIds; + for (auto& [vsyncId, state] : mUnackedState) { + if (std::find(state.unackedListenerIds.begin(), state.unackedListenerIds.end(), + listenerId) != state.unackedListenerIds.end()) { + vsyncIds.push_back(vsyncId); + } } - return binder::Status::ok(); + + for (int64_t vsyncId : vsyncIds) { + ackWindowInfosReceived(vsyncId, listenerId); + } + }}); +} + +void WindowInfosListenerInvoker::windowInfosChanged( + gui::WindowInfosUpdate update, WindowInfosReportedListenerSet reportedListeners, + bool forceImmediateCall) { + if (!mDelayInfo) { + mDelayInfo = DelayInfo{ + .vsyncId = update.vsyncId, + .frameTime = update.timestamp, + }; } -private: - WindowInfosListenerInvoker& mInvoker; - std::atomic mCallbacksPending; - bool mShouldSync; -}; + // If there are unacked messages and this isn't a forced call, then return immediately. + // If a forced window infos change doesn't happen first, the update will be sent after + // the WindowInfosReportedListeners are called. If a forced window infos change happens or + // if there are subsequent delayed messages before this update is sent, then this message + // will be dropped and the listeners will only be called with the latest info. This is done + // to reduce the amount of binder memory used. + if (!mUnackedState.empty() && !forceImmediateCall) { + mDelayedUpdate = std::move(update); + mReportedListeners.merge(reportedListeners); + return; + } -WindowInfosListenerInvoker::WindowInfosListenerInvoker(SurfaceFlinger& flinger) - : mFlinger(flinger) {} + if (mDelayedUpdate) { + mDelayedUpdate.reset(); + } -void WindowInfosListenerInvoker::addWindowInfosListener(sp listener) { - sp asBinder = IInterface::asBinder(listener); - asBinder->linkToDeath(this); + if (CC_UNLIKELY(mWindowInfosListeners.empty())) { + mReportedListeners.merge(reportedListeners); + mDelayInfo.reset(); + return; + } - std::scoped_lock lock(mListenersMutex); - mWindowInfosListeners.try_emplace(asBinder, std::move(listener)); -} + reportedListeners.merge(mReportedListeners); + mReportedListeners.clear(); + + // Update mUnackedState to include the message we're about to send + auto [it, _] = mUnackedState.try_emplace(update.vsyncId, + UnackedState{.reportedListeners = + std::move(reportedListeners)}); + auto& unackedState = it->second; + for (auto& pair : mWindowInfosListeners) { + int64_t listenerId = pair.second.first; + unackedState.unackedListenerIds.push_back(listenerId); + } -void WindowInfosListenerInvoker::removeWindowInfosListener( - const sp& listener) { - sp asBinder = IInterface::asBinder(listener); + mDelayInfo.reset(); + updateMaxSendDelay(); - std::scoped_lock lock(mListenersMutex); - asBinder->unlinkToDeath(this); - mWindowInfosListeners.erase(asBinder); + // Call the listeners + for (auto& pair : mWindowInfosListeners) { + auto& [listenerId, listener] = pair.second; + auto status = listener->onWindowInfosChanged(update); + if (!status.isOk()) { + ackWindowInfosReceived(update.vsyncId, listenerId); + } + } } -void WindowInfosListenerInvoker::binderDied(const wp& who) { - std::scoped_lock lock(mListenersMutex); - mWindowInfosListeners.erase(who); +WindowInfosListenerInvoker::DebugInfo WindowInfosListenerInvoker::getDebugInfo() { + DebugInfo result; + BackgroundExecutor::getInstance().sendCallbacks({[&, this]() { + ATRACE_NAME("WindowInfosListenerInvoker::getDebugInfo"); + updateMaxSendDelay(); + result = mDebugInfo; + result.pendingMessageCount = mUnackedState.size(); + }}); + BackgroundExecutor::getInstance().flushQueue(); + return result; } -void WindowInfosListenerInvoker::windowInfosChanged(std::vector windowInfos, - std::vector displayInfos, - bool shouldSync, bool forceImmediateCall) { - auto callListeners = [this, windowInfos = std::move(windowInfos), - displayInfos = std::move(displayInfos)](bool shouldSync) mutable { - ftl::SmallVector, kStaticCapacity> windowInfosListeners; - { - std::scoped_lock lock(mListenersMutex); - for (const auto& [_, listener] : mWindowInfosListeners) { - windowInfosListeners.push_back(listener); - } - } - - auto reportedListener = - sp::make(*this, windowInfosListeners.size(), - shouldSync); +void WindowInfosListenerInvoker::updateMaxSendDelay() { + if (!mDelayInfo) { + return; + } + nsecs_t delay = TimePoint::now().ns() - mDelayInfo->frameTime; + if (delay > mDebugInfo.maxSendDelayDuration) { + mDebugInfo.maxSendDelayDuration = delay; + mDebugInfo.maxSendDelayVsyncId = VsyncId{mDelayInfo->vsyncId}; + } +} - for (const auto& listener : windowInfosListeners) { - auto status = - listener->onWindowInfosChanged(windowInfos, displayInfos, reportedListener); - if (!status.isOk()) { - reportedListener->onWindowInfosReported(); - } +binder::Status WindowInfosListenerInvoker::ackWindowInfosReceived(int64_t vsyncId, + int64_t listenerId) { + BackgroundExecutor::getInstance().sendCallbacks({[this, vsyncId, listenerId]() { + ATRACE_NAME("WindowInfosListenerInvoker::ackWindowInfosReceived"); + auto it = mUnackedState.find(vsyncId); + if (it == mUnackedState.end()) { + return; } - }; - - { - std::scoped_lock lock(mMessagesMutex); - // If there are unacked messages and this isn't a forced call, then return immediately. - // If a forced window infos change doesn't happen first, the update will be sent after - // the WindowInfosReportedListeners are called. If a forced window infos change happens or - // if there are subsequent delayed messages before this update is sent, then this message - // will be dropped and the listeners will only be called with the latest info. This is done - // to reduce the amount of binder memory used. - if (mActiveMessageCount > 0 && !forceImmediateCall) { - mWindowInfosChangedDelayed = std::move(callListeners); - mShouldSyncDelayed |= shouldSync; + + auto& state = it->second; + state.unackedListenerIds.unstable_erase(std::find(state.unackedListenerIds.begin(), + state.unackedListenerIds.end(), + listenerId)); + if (!state.unackedListenerIds.empty()) { return; } - mWindowInfosChangedDelayed = nullptr; - shouldSync |= mShouldSyncDelayed; - mShouldSyncDelayed = false; - mActiveMessageCount++; - } - callListeners(shouldSync); -} + WindowInfosReportedListenerSet reportedListeners{std::move(state.reportedListeners)}; + mUnackedState.erase(vsyncId); -void WindowInfosListenerInvoker::windowInfosReported(bool shouldSync) { - if (shouldSync) { - mFlinger.windowInfosReported(); - } + for (const auto& reportedListener : reportedListeners) { + sp asBinder = IInterface::asBinder(reportedListener); + if (asBinder->isBinderAlive()) { + reportedListener->onWindowInfosReported(); + } + } - std::function callListeners; - bool shouldSyncDelayed; - { - std::scoped_lock lock{mMessagesMutex}; - mActiveMessageCount--; - if (!mWindowInfosChangedDelayed || mActiveMessageCount > 0) { + if (!mDelayedUpdate || !mUnackedState.empty()) { return; } - - mActiveMessageCount++; - callListeners = std::move(mWindowInfosChangedDelayed); - mWindowInfosChangedDelayed = nullptr; - shouldSyncDelayed = mShouldSyncDelayed; - mShouldSyncDelayed = false; - } - - callListeners(shouldSyncDelayed); + gui::WindowInfosUpdate update{std::move(*mDelayedUpdate)}; + mDelayedUpdate.reset(); + windowInfosChanged(std::move(update), {}, false); + }}); + return binder::Status::ok(); } } // namespace android diff --git a/services/surfaceflinger/WindowInfosListenerInvoker.h b/services/surfaceflinger/WindowInfosListenerInvoker.h index 701f11efcd2f4d9e1b17e7a137a407b71f329699..f36b0edd7d100ff4d91b143957407ff4ba69a0f9 100644 --- a/services/surfaceflinger/WindowInfosListenerInvoker.h +++ b/services/surfaceflinger/WindowInfosListenerInvoker.h @@ -16,45 +16,71 @@ #pragma once -#include +#include +#include + +#include #include #include #include #include +#include +#include #include +#include "scheduler/VsyncId.h" + namespace android { -class SurfaceFlinger; +using WindowInfosReportedListenerSet = + std::unordered_set, + gui::SpHash>; -class WindowInfosListenerInvoker : public IBinder::DeathRecipient { +class WindowInfosListenerInvoker : public gui::BnWindowInfosPublisher, + public IBinder::DeathRecipient { public: - explicit WindowInfosListenerInvoker(SurfaceFlinger&); - - void addWindowInfosListener(sp); + void addWindowInfosListener(sp, gui::WindowInfosListenerInfo*); void removeWindowInfosListener(const sp& windowInfosListener); - void windowInfosChanged(std::vector, std::vector, - bool shouldSync, bool forceImmediateCall); + void windowInfosChanged(gui::WindowInfosUpdate update, + WindowInfosReportedListenerSet windowInfosReportedListeners, + bool forceImmediateCall); + + binder::Status ackWindowInfosReceived(int64_t, int64_t) override; + + struct DebugInfo { + VsyncId maxSendDelayVsyncId; + nsecs_t maxSendDelayDuration; + size_t pendingMessageCount; + }; + DebugInfo getDebugInfo(); protected: void binderDied(const wp& who) override; private: - struct WindowInfosReportedListener; - void windowInfosReported(bool shouldSync); + static constexpr size_t kStaticCapacity = 3; + std::atomic mNextListenerId{0}; + ftl::SmallMap, const std::pair>, + kStaticCapacity> + mWindowInfosListeners; - SurfaceFlinger& mFlinger; - std::mutex mListenersMutex; + std::optional mDelayedUpdate; + WindowInfosReportedListenerSet mReportedListeners; - static constexpr size_t kStaticCapacity = 3; - ftl::SmallMap, const sp, kStaticCapacity> - mWindowInfosListeners GUARDED_BY(mListenersMutex); + struct UnackedState { + ftl::SmallVector unackedListenerIds; + WindowInfosReportedListenerSet reportedListeners; + }; + ftl::SmallMap mUnackedState; - std::mutex mMessagesMutex; - uint32_t mActiveMessageCount GUARDED_BY(mMessagesMutex) = 0; - std::function mWindowInfosChangedDelayed GUARDED_BY(mMessagesMutex); - bool mShouldSyncDelayed; + DebugInfo mDebugInfo; + struct DelayInfo { + int64_t vsyncId; + nsecs_t frameTime; + }; + std::optional mDelayInfo; + void updateMaxSendDelay(); }; } // namespace android diff --git a/services/surfaceflinger/fuzzer/Android.bp b/services/surfaceflinger/fuzzer/Android.bp index b0b6bf14ab07daf1a912f46f39ae743990d3706b..f76a8d762ae187454822ab5d6023c5b23fa3dc9b 100644 --- a/services/surfaceflinger/fuzzer/Android.bp +++ b/services/surfaceflinger/fuzzer/Android.bp @@ -69,6 +69,7 @@ cc_defaults { "-Wno-unused-result", "-Wno-conversion", "-Wno-sign-compare", + "-Wno-unused-function", ], fuzz_config: { cc: [ @@ -127,3 +128,13 @@ cc_fuzz { "surfaceflinger_layer_fuzzer.cpp", ], } + +cc_fuzz { + name: "surfaceflinger_frametracer_fuzzer", + defaults: [ + "surfaceflinger_fuzz_defaults", + ], + srcs: [ + "surfaceflinger_frametracer_fuzzer.cpp", + ], +} diff --git a/services/surfaceflinger/fuzzer/README.md b/services/surfaceflinger/fuzzer/README.md index 78a7596ef3d7a428de6f314b09d882e5c282783b..a06c41b1393a1f2d9231c04d37fe66d15def9ad8 100644 --- a/services/surfaceflinger/fuzzer/README.md +++ b/services/surfaceflinger/fuzzer/README.md @@ -4,6 +4,7 @@ + [DisplayHardware](#DisplayHardware) + [Scheduler](#Scheduler) + [Layer](#Layer) ++ [FrameTracer](#FrameTracer) # Fuzzer for SurfaceFlinger @@ -77,9 +78,8 @@ You can find the possible values in the fuzzer's source code. Layer supports the following parameters: 1. Display Connection Types (parameter name: `fakeDisplay`) 2. State Sets (parameter name: `traverseInZOrder`) -3. State Subsets (parameter name: `prepareCompositionState`) -4. Disconnect modes (parameter name: `disconnect`) -5. Data Spaces (parameter name: `setDataspace`) +3. Disconnect modes (parameter name: `disconnect`) +4. Data Spaces (parameter name: `setDataspace`) You can find the possible values in the fuzzer's source code. @@ -93,3 +93,16 @@ You can find the possible values in the fuzzer's source code. $ adb sync data $ adb shell /data/fuzz/arm64/surfaceflinger_layer_fuzzer/surfaceflinger_layer_fuzzer ``` + +# Fuzzer for FrameTracer + +#### Steps to run +1. Build the fuzzer +``` + $ mm -j$(nproc) surfaceflinger_frametracer_fuzzer +``` +2. To run on device +``` + $ adb sync data + $ adb shell /data/fuzz/arm64/surfaceflinger_frametracer_fuzzer/surfaceflinger_frametracer_fuzzer +``` diff --git a/services/surfaceflinger/fuzzer/surfaceflinger_displayhardware_fuzzer.cpp b/services/surfaceflinger/fuzzer/surfaceflinger_displayhardware_fuzzer.cpp index a605a2fd9bdc820e9e6398e9bf749d2837abd971..9fac14ed4cf112d76bb94e5246ce548bae47f81b 100644 --- a/services/surfaceflinger/fuzzer/surfaceflinger_displayhardware_fuzzer.cpp +++ b/services/surfaceflinger/fuzzer/surfaceflinger_displayhardware_fuzzer.cpp @@ -116,7 +116,8 @@ static constexpr hal::HWConfigId kActiveConfig = 0; class DisplayHardwareFuzzer { public: DisplayHardwareFuzzer(const uint8_t* data, size_t size) : mFdp(data, size) { - mPhysicalDisplayId = SurfaceComposerClient::getInternalDisplayId().value(); + mPhysicalDisplayId = TestableSurfaceFlinger::getFirstDisplayId().value_or( + PhysicalDisplayId::fromPort(mFdp.ConsumeIntegral())); }; void process(); @@ -221,7 +222,7 @@ void DisplayHardwareFuzzer::getDeviceCompositionChanges(HalDisplayId halDisplayI std::optional outChanges; mHwc.getDeviceCompositionChanges(halDisplayID, mFdp.ConsumeBool() /*frameUsesClientComposition*/, - std::chrono::steady_clock::now(), FenceTime::NO_FENCE, + std::chrono::steady_clock::now(), mFdp.ConsumeIntegral(), &outChanges); } @@ -325,8 +326,7 @@ void DisplayHardwareFuzzer::invokeAidlComposer() { invokeComposerHal2_3(&composer, display, outLayer); invokeComposerHal2_4(&composer, display, outLayer); - composer.executeCommands(); - composer.resetCommands(); + composer.executeCommands(display); composer.destroyLayer(display, outLayer); composer.destroyVirtualDisplay(display); @@ -480,8 +480,8 @@ void DisplayHardwareFuzzer::invokeFrameBufferSurface() { BufferQueue::createBufferQueue(&bqProducer, &bqConsumer); sp surface = - new FramebufferSurface(mHwc, mPhysicalDisplayId, bqConsumer, getFuzzedSize() /*size*/, - getFuzzedSize() /*maxSize*/); + sp::make(mHwc, mPhysicalDisplayId, bqConsumer, + getFuzzedSize() /*size*/, getFuzzedSize() /*maxSize*/); surface->beginFrame(mFdp.ConsumeBool()); surface->prepareFrame(mFdp.PickValueInArray(kCompositionTypes)); @@ -497,15 +497,15 @@ void DisplayHardwareFuzzer::invokeVirtualDisplaySurface() { DisplayIdGenerator mGenerator; VirtualDisplayId VirtualDisplayId = mGenerator.generateId().value(); - sp mClient = new SurfaceComposerClient(); + sp mClient = sp::make(); sp mSurfaceControl = mClient->createSurface(String8("TestSurface"), 100, 100, PIXEL_FORMAT_RGBA_8888, ISurfaceComposerClient::eFXSurfaceBufferState, /*parent*/ nullptr); - sp mBlastBufferQueueAdapter = - new BLASTBufferQueue("TestBLASTBufferQueue", mSurfaceControl, 100, 100, - PIXEL_FORMAT_RGBA_8888); + auto mBlastBufferQueueAdapter = + sp::make("TestBLASTBufferQueue", mSurfaceControl, 100, 100, + PIXEL_FORMAT_RGBA_8888); sp sink = mBlastBufferQueueAdapter->getIGraphicBufferProducer(); sp bqProducer = mBlastBufferQueueAdapter->getIGraphicBufferProducer(); @@ -513,9 +513,9 @@ void DisplayHardwareFuzzer::invokeVirtualDisplaySurface() { BufferQueue::createBufferQueue(&bqProducer, &bqConsumer); BufferQueue::createBufferQueue(&sink, &bqConsumer); - sp surface = - new VirtualDisplaySurface(mHwc, VirtualDisplayId, sink, bqProducer, bqConsumer, - mFdp.ConsumeRandomLengthString().c_str() /*name*/); + auto surface = + sp::make(mHwc, VirtualDisplayId, sink, bqProducer, bqConsumer, + mFdp.ConsumeRandomLengthString().c_str() /*name*/); surface->beginFrame(mFdp.ConsumeBool()); surface->prepareFrame(mFdp.PickValueInArray(kCompositionTypes)); @@ -554,8 +554,7 @@ void DisplayHardwareFuzzer::invokeComposer() { mHwc.setClientTarget(halDisplayID, mFdp.ConsumeIntegral(), Fence::NO_FENCE, sp::make(), mFdp.PickValueInArray(kDataspaces)); - mHwc.presentAndGetReleaseFences(halDisplayID, std::chrono::steady_clock::now(), - FenceTime::NO_FENCE); + mHwc.presentAndGetReleaseFences(halDisplayID, std::chrono::steady_clock::now()); mHwc.setPowerMode(mPhysicalDisplayId, mFdp.PickValueInArray(kPowerModes)); @@ -565,7 +564,7 @@ void DisplayHardwareFuzzer::invokeComposer() { mHwc.getLayerReleaseFence(halDisplayID, layer); - mHwc.setOutputBuffer(halVirtualDisplayId, sp::make().get(), sp::make()); + mHwc.setOutputBuffer(halVirtualDisplayId, sp::make(), sp::make()); mHwc.clearReleaseFences(halDisplayID); diff --git a/services/surfaceflinger/fuzzer/surfaceflinger_displayhardware_fuzzer_utils.h b/services/surfaceflinger/fuzzer/surfaceflinger_displayhardware_fuzzer_utils.h index 6a6e3db73315e8be1df67cc25f75b75df72aa95c..1a951b34ac0f2be92612eaa803388e2493c76750 100644 --- a/services/surfaceflinger/fuzzer/surfaceflinger_displayhardware_fuzzer_utils.h +++ b/services/surfaceflinger/fuzzer/surfaceflinger_displayhardware_fuzzer_utils.h @@ -41,6 +41,7 @@ class SurfaceComposerClient; namespace android::hardware::graphics::composer::hal { +using aidl::android::hardware::graphics::composer3::RefreshRateChangedDebugData; using ::android::hardware::Return; using ::android::hardware::Void; using ::android::HWC2::ComposerCallback; @@ -99,6 +100,7 @@ struct TestHWC2ComposerCallback : public HWC2::ComposerCallback { void onComposerHalVsyncPeriodTimingChanged(HWDisplayId, const VsyncPeriodChangeTimeline&) {} void onComposerHalSeamlessPossible(HWDisplayId) {} void onComposerHalVsyncIdle(HWDisplayId) {} + void onRefreshRateChangedDebug(const RefreshRateChangedDebugData&) {} }; } // namespace android::hardware::graphics::composer::hal diff --git a/services/surfaceflinger/fuzzer/surfaceflinger_frametracer_fuzzer.cpp b/services/surfaceflinger/fuzzer/surfaceflinger_frametracer_fuzzer.cpp new file mode 100644 index 0000000000000000000000000000000000000000..a22a778989364a8d809ff4de10374d2ec309160e --- /dev/null +++ b/services/surfaceflinger/fuzzer/surfaceflinger_frametracer_fuzzer.cpp @@ -0,0 +1,132 @@ +/* + * 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. + * + */ + +#include +#include +#include + +namespace android::fuzz { + +using namespace google::protobuf; + +constexpr size_t kMaxStringSize = 100; +constexpr size_t kMinLayerIds = 1; +constexpr size_t kMaxLayerIds = 10; +constexpr int32_t kConfigDuration = 500; +constexpr int32_t kBufferSize = 1024; +constexpr int32_t kTimeOffset = 100000; + +class FrameTracerFuzzer { +public: + FrameTracerFuzzer(const uint8_t* data, size_t size) : mFdp(data, size) { + // Fuzzer is single-threaded, so no need to be thread-safe. + static bool wasInitialized = false; + if (!wasInitialized) { + perfetto::TracingInitArgs args; + args.backends = perfetto::kInProcessBackend; + perfetto::Tracing::Initialize(args); + wasInitialized = true; + } + mFrameTracer = std::make_unique(); + } + ~FrameTracerFuzzer() { mFrameTracer.reset(); } + void process(); + +private: + std::unique_ptr getTracingSessionForTest(); + void traceTimestamp(); + std::vector generateLayerIds(size_t numLayerIds); + void traceTimestamp(std::vector layerIds, size_t numLayerIds); + void traceFence(std::vector layerIds, size_t numLayerIds); + std::unique_ptr mFrameTracer = nullptr; + FuzzedDataProvider mFdp; + android::FenceToFenceTimeMap mFenceFactory; +}; + +std::unique_ptr FrameTracerFuzzer::getTracingSessionForTest() { + perfetto::TraceConfig cfg; + cfg.set_duration_ms(kConfigDuration); + cfg.add_buffers()->set_size_kb(kBufferSize); + auto* dsCfg = cfg.add_data_sources()->mutable_config(); + dsCfg->set_name(android::FrameTracer::kFrameTracerDataSource); + + auto tracingSession = perfetto::Tracing::NewTrace(perfetto::kInProcessBackend); + tracingSession->Setup(cfg); + return tracingSession; +} + +std::vector FrameTracerFuzzer::generateLayerIds(size_t numLayerIds) { + std::vector layerIds; + for (size_t i = 0; i < numLayerIds; ++i) { + layerIds.push_back(mFdp.ConsumeIntegral()); + } + return layerIds; +} + +void FrameTracerFuzzer::traceTimestamp(std::vector layerIds, size_t numLayerIds) { + int32_t layerId = layerIds.at(mFdp.ConsumeIntegralInRange(0, numLayerIds - 1)); + mFrameTracer->traceTimestamp(layerId, mFdp.ConsumeIntegral() /*bufferID*/, + mFdp.ConsumeIntegral() /*frameNumber*/, + mFdp.ConsumeIntegral() /*timestamp*/, + android::FrameTracer::FrameEvent::UNSPECIFIED, + mFdp.ConsumeIntegral() /*duration*/); +} + +void FrameTracerFuzzer::traceFence(std::vector layerIds, size_t numLayerIds) { + const nsecs_t signalTime = systemTime(); + const nsecs_t startTime = signalTime + kTimeOffset; + auto fence = mFenceFactory.createFenceTimeForTest(android::Fence::NO_FENCE); + mFenceFactory.signalAllForTest(android::Fence::NO_FENCE, signalTime); + int32_t layerId = layerIds.at(mFdp.ConsumeIntegralInRange(0, numLayerIds - 1)); + mFrameTracer->traceFence(layerId, mFdp.ConsumeIntegral() /*bufferID*/, + mFdp.ConsumeIntegral() /*frameNumber*/, fence, + android::FrameTracer::FrameEvent::ACQUIRE_FENCE, startTime); +} + +void FrameTracerFuzzer::process() { + mFrameTracer->registerDataSource(); + + auto tracingSession = getTracingSessionForTest(); + tracingSession->StartBlocking(); + + size_t numLayerIds = mFdp.ConsumeIntegralInRange(kMinLayerIds, kMaxLayerIds); + std::vector layerIds = generateLayerIds(numLayerIds); + + for (auto it = layerIds.begin(); it != layerIds.end(); ++it) { + mFrameTracer->traceNewLayer(*it /*layerId*/, + mFdp.ConsumeRandomLengthString(kMaxStringSize) /*layerName*/); + } + + traceTimestamp(layerIds, numLayerIds); + traceFence(layerIds, numLayerIds); + + mFenceFactory.signalAllForTest(android::Fence::NO_FENCE, systemTime()); + + tracingSession->StopBlocking(); + + for (auto it = layerIds.begin(); it != layerIds.end(); ++it) { + mFrameTracer->onDestroy(*it); + } +} + +extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) { + FrameTracerFuzzer frameTracerFuzzer(data, size); + frameTracerFuzzer.process(); + return 0; +} + +} // namespace android::fuzz diff --git a/services/surfaceflinger/fuzzer/surfaceflinger_fuzzer.cpp b/services/surfaceflinger/fuzzer/surfaceflinger_fuzzer.cpp index f25043cd6db278393bf6b5dc568e42b94f952a09..80943b5b63fcdba469d91067534c5aee12b755ac 100644 --- a/services/surfaceflinger/fuzzer/surfaceflinger_fuzzer.cpp +++ b/services/surfaceflinger/fuzzer/surfaceflinger_fuzzer.cpp @@ -103,7 +103,7 @@ static constexpr uint32_t kMaxCode = 1050; class SurfaceFlingerFuzzer { public: SurfaceFlingerFuzzer(const uint8_t *data, size_t size) : mFdp(data, size) { - mFlinger = mTestableFlinger.flinger(); + mFlinger = sp::fromExisting(mTestableFlinger.flinger()); }; void process(const uint8_t *data, size_t size); @@ -130,7 +130,7 @@ void SurfaceFlingerFuzzer::invokeFlinger() { mFlinger->maxFrameBufferAcquiredBuffers = mFdp.ConsumeIntegral(); mFlinger->maxGraphicsWidth = mFdp.ConsumeIntegral(); mFlinger->maxGraphicsHeight = mFdp.ConsumeIntegral(); - mFlinger->hasWideColorDisplay = mFdp.ConsumeBool(); + mTestableFlinger.mutableSupportsWideColor() = mFdp.ConsumeBool(); mFlinger->useContextPriority = mFdp.ConsumeBool(); mFlinger->defaultCompositionDataspace = mFdp.PickValueInArray(kDataspaces); @@ -150,8 +150,7 @@ void SurfaceFlingerFuzzer::invokeFlinger() { sp handle = defaultServiceManager()->checkService( String16(mFdp.ConsumeRandomLengthString().c_str())); - mFlinger->fromHandle(handle); - mFlinger->windowInfosReported(); + LayerHandle::getLayer(handle); mFlinger->disableExpensiveRendering(); } @@ -186,11 +185,12 @@ void SurfaceFlingerFuzzer::setTransactionState() { bool hasListenerCallbacks = mFdp.ConsumeBool(); std::vector listenerCallbacks{}; uint64_t transactionId = mFdp.ConsumeIntegral(); + std::vector mergedTransactionIds{}; mTestableFlinger.setTransactionState(FrameTimelineInfo{}, states, displays, flags, applyToken, InputWindowCommands{}, desiredPresentTime, isAutoTimestamp, - {}, hasListenerCallbacks, listenerCallbacks, - transactionId); + {}, hasListenerCallbacks, listenerCallbacks, transactionId, + mergedTransactionIds); } void SurfaceFlingerFuzzer::setDisplayStateLocked() { @@ -238,7 +238,8 @@ void SurfaceFlingerFuzzer::process(const uint8_t *data, size_t size) { mTestableFlinger.enableHalVirtualDisplays(mFdp.ConsumeBool()); - mTestableFlinger.commitTransactionsLocked(mFdp.ConsumeIntegral()); + FTL_FAKE_GUARD(kMainThreadContext, + mTestableFlinger.commitTransactionsLocked(mFdp.ConsumeIntegral())); mTestableFlinger.notifyPowerBoost(mFdp.ConsumeIntegral()); diff --git a/services/surfaceflinger/fuzzer/surfaceflinger_fuzzers_utils.h b/services/surfaceflinger/fuzzer/surfaceflinger_fuzzers_utils.h index c679b14d7b48df2314968d3f4931ab1161e4c0aa..9b9ac96cf71b9a6468d7841c2773947fa94c01ea 100644 --- a/services/surfaceflinger/fuzzer/surfaceflinger_fuzzers_utils.h +++ b/services/surfaceflinger/fuzzer/surfaceflinger_fuzzers_utils.h @@ -30,19 +30,16 @@ #include #include -#include "BufferQueueLayer.h" -#include "BufferStateLayer.h" -#include "ContainerLayer.h" #include "DisplayDevice.h" #include "DisplayHardware/ComposerHal.h" -#include "EffectLayer.h" #include "FrameTimeline/FrameTimeline.h" #include "FrameTracer/FrameTracer.h" +#include "FrontEnd/LayerHandle.h" #include "Layer.h" #include "NativeWindowSurface.h" #include "Scheduler/EventThread.h" #include "Scheduler/MessageQueue.h" -#include "Scheduler/RefreshRateConfigs.h" +#include "Scheduler/RefreshRateSelector.h" #include "Scheduler/VSyncTracker.h" #include "Scheduler/VsyncConfiguration.h" #include "Scheduler/VsyncController.h" @@ -50,9 +47,9 @@ #include "StartPropertySetThread.h" #include "SurfaceFlinger.h" #include "SurfaceFlingerDefaultFactory.h" -#include "SurfaceInterceptor.h" #include "ThreadContext.h" #include "TimeStats/TimeStats.h" +#include "surfaceflinger_scheduler_fuzzer.h" #include "renderengine/mock/RenderEngine.h" #include "scheduler/TimeKeeper.h" @@ -64,7 +61,6 @@ #include "tests/unittests/mock/MockFrameTimeline.h" #include "tests/unittests/mock/MockFrameTracer.h" #include "tests/unittests/mock/MockNativeWindowSurface.h" -#include "tests/unittests/mock/MockSurfaceInterceptor.h" #include "tests/unittests/mock/MockTimeStats.h" #include "tests/unittests/mock/MockVSyncTracker.h" #include "tests/unittests/mock/MockVsyncController.h" @@ -86,7 +82,6 @@ using types::V1_0::Transform; using types::V1_1::RenderIntent; using types::V1_2::ColorMode; using types::V1_2::Dataspace; -using types::V1_2::Hdr; using types::V1_2::PixelFormat; using V2_1::Config; @@ -155,14 +150,26 @@ static constexpr ui::PixelFormat kPixelFormats[] = {ui::PixelFormat::RGBA_8888, ui::PixelFormat::YCBCR_P010, ui::PixelFormat::HSV_888}; -FloatRect getFuzzedFloatRect(FuzzedDataProvider *fdp) { +inline VsyncId getFuzzedVsyncId(FuzzedDataProvider& fdp) { + return VsyncId{fdp.ConsumeIntegral()}; +} + +inline TimePoint getFuzzedTimePoint(FuzzedDataProvider& fdp) { + return TimePoint::fromNs(fdp.ConsumeIntegral()); +} + +inline Duration getFuzzedDuration(FuzzedDataProvider& fdp) { + return Duration::fromNs(fdp.ConsumeIntegral()); +} + +inline FloatRect getFuzzedFloatRect(FuzzedDataProvider* fdp) { return FloatRect(fdp->ConsumeFloatingPoint() /*left*/, fdp->ConsumeFloatingPoint() /*right*/, fdp->ConsumeFloatingPoint() /*top*/, fdp->ConsumeFloatingPoint() /*bottom*/); } -HdrMetadata getFuzzedHdrMetadata(FuzzedDataProvider *fdp) { +inline HdrMetadata getFuzzedHdrMetadata(FuzzedDataProvider* fdp) { HdrMetadata hdrMetadata; if (fdp->ConsumeBool()) { hdrMetadata.cta8613.maxContentLightLevel = fdp->ConsumeFloatingPoint(); @@ -194,9 +201,11 @@ struct FakePhaseOffsets : scheduler::VsyncConfiguration { static constexpr nsecs_t FAKE_PHASE_OFFSET_NS = 0; static constexpr auto FAKE_DURATION_OFFSET_NS = std::chrono::nanoseconds(0); - VsyncConfigSet getConfigsForRefreshRate(Fps) const override { return getCurrentConfigs(); } + scheduler::VsyncConfigSet getConfigsForRefreshRate(Fps) const override { + return getCurrentConfigs(); + } - VsyncConfigSet getCurrentConfigs() const override { + scheduler::VsyncConfigSet getCurrentConfigs() const override { return {{FAKE_PHASE_OFFSET_NS, FAKE_PHASE_OFFSET_NS, FAKE_DURATION_OFFSET_NS, FAKE_DURATION_OFFSET_NS}, {FAKE_PHASE_OFFSET_NS, FAKE_PHASE_OFFSET_NS, FAKE_DURATION_OFFSET_NS, @@ -215,30 +224,32 @@ namespace scheduler { class TestableScheduler : public Scheduler, private ICompositor { public: - TestableScheduler(const std::shared_ptr &refreshRateConfigs, - ISchedulerCallback &callback) + TestableScheduler(const std::shared_ptr& selectorPtr, + sp modulatorPtr, ISchedulerCallback& callback) : TestableScheduler(std::make_unique(), - std::make_unique(), refreshRateConfigs, - callback) {} + std::make_shared(), selectorPtr, + std::move(modulatorPtr), callback) {} TestableScheduler(std::unique_ptr controller, - std::unique_ptr tracker, - std::shared_ptr configs, ISchedulerCallback &callback) - : Scheduler(*this, callback, Feature::kContentDetection) { - mVsyncSchedule.emplace(VsyncSchedule(std::move(tracker), nullptr, std::move(controller))); - setRefreshRateConfigs(std::move(configs)); + VsyncSchedule::TrackerPtr tracker, + std::shared_ptr selectorPtr, + sp modulatorPtr, ISchedulerCallback& callback) + : Scheduler(*this, callback, Feature::kContentDetection, std::move(modulatorPtr)) { + const auto displayId = selectorPtr->getActiveMode().modePtr->getPhysicalDisplayId(); + registerDisplayInternal(displayId, std::move(selectorPtr), + std::shared_ptr( + new VsyncSchedule(displayId, std::move(tracker), + std::make_shared(), + std::move(controller)))); } ConnectionHandle createConnection(std::unique_ptr eventThread) { return Scheduler::createConnection(std::move(eventThread)); } - auto &mutablePrimaryHWVsyncEnabled() { return mPrimaryHWVsyncEnabled; } - auto &mutableHWVsyncAvailable() { return mHWVsyncAvailable; } - auto &mutableLayerHistory() { return mLayerHistory; } - auto refreshRateConfigs() { return holdRefreshRateConfigs(); } + auto refreshRateSelector() { return pacesetterSelectorPtr(); } void replaceTouchTimer(int64_t millis) { if (mTouchTimer) { @@ -266,14 +277,17 @@ public: mPolicy.cachedModeChangedParams.reset(); } - void onNonPrimaryDisplayModeChanged(ConnectionHandle handle, DisplayModePtr mode) { + void onNonPrimaryDisplayModeChanged(ConnectionHandle handle, const FrameRateMode &mode) { return Scheduler::onNonPrimaryDisplayModeChanged(handle, mode); } + using Scheduler::setVsyncConfig; + private: // ICompositor overrides: - bool commit(nsecs_t, int64_t, nsecs_t) override { return false; } - void composite(nsecs_t, int64_t) override {} + void configure() override {} + bool commit(TimePoint, VsyncId, TimePoint) override { return false; } + void composite(TimePoint, VsyncId) override {} void sample() override {} // MessageQueue overrides: @@ -286,13 +300,18 @@ private: namespace surfaceflinger::test { class Factory final : public surfaceflinger::Factory { + struct NoOpMessageQueue : android::impl::MessageQueue { + using android::impl::MessageQueue::MessageQueue; + void onFrameSignal(ICompositor&, VsyncId, TimePoint) override {} + }; + public: ~Factory() = default; - std::unique_ptr createHWComposer(const std::string &) override { return nullptr; } + std::unique_ptr createHWComposer(const std::string&) override { return nullptr; } - std::unique_ptr createMessageQueue(ICompositor &compositor) { - return std::make_unique(compositor); + std::unique_ptr createMessageQueue(ICompositor& compositor) { + return std::make_unique(compositor); } std::unique_ptr createVsyncConfiguration( @@ -301,27 +320,23 @@ public: } std::unique_ptr createScheduler( - const std::shared_ptr &, - scheduler::ISchedulerCallback &) { + const std::shared_ptr&, + scheduler::ISchedulerCallback&) { return nullptr; } - sp createSurfaceInterceptor() override { - return new android::impl::SurfaceInterceptor(); - } - sp createStartPropertySetThread(bool timestampPropertyValue) override { - return new StartPropertySetThread(timestampPropertyValue); + return sp::make(timestampPropertyValue); } sp createDisplayDevice(DisplayDeviceCreationArgs &creationArgs) override { - return new DisplayDevice(creationArgs); + return sp::make(creationArgs); } sp createGraphicBuffer(uint32_t width, uint32_t height, PixelFormat format, uint32_t layerCount, uint64_t usage, std::string requestorName) override { - return new GraphicBuffer(width, height, format, layerCount, usage, requestorName); + return sp::make(width, height, format, layerCount, usage, requestorName); } void createBufferQueue(sp *outProducer, @@ -334,18 +349,6 @@ public: mCreateBufferQueue(outProducer, outConsumer, consumerIsSurfaceFlinger); } - sp createMonitoredProducer(const sp &producer, - const sp &flinger, - const wp &layer) override { - return new MonitoredProducer(producer, flinger, layer); - } - - sp createBufferLayerConsumer(const sp &consumer, - renderengine::RenderEngine &renderEngine, - uint32_t textureName, Layer *layer) override { - return new BufferLayerConsumer(consumer, renderEngine, textureName, layer); - } - std::unique_ptr createNativeWindowSurface( const sp &producer) override { if (!mCreateNativeWindowSurface) return nullptr; @@ -356,20 +359,14 @@ public: return compositionengine::impl::createCompositionEngine(); } - sp createBufferQueueLayer(const LayerCreationArgs &) override { - return nullptr; - } - - sp createBufferStateLayer(const LayerCreationArgs &) override { - return nullptr; - } + sp createBufferStateLayer(const LayerCreationArgs &) override { return nullptr; } - sp createEffectLayer(const LayerCreationArgs &args) override { - return new EffectLayer(args); + sp createEffectLayer(const LayerCreationArgs &args) override { + return sp::make(args); } - sp createContainerLayer(const LayerCreationArgs &args) override { - return new ContainerLayer(args); + sp createLayerFE(const std::string &layerName) override { + return sp::make(layerName); } std::unique_ptr createFrameTracer() override { @@ -407,9 +404,8 @@ public: SurfaceFlinger *flinger() { return mFlinger.get(); } scheduler::TestableScheduler *scheduler() { return mScheduler; } - // Allow reading display state without locking, as if called on the SF main thread. - auto onInitializeDisplays() NO_THREAD_SAFETY_ANALYSIS { - return mFlinger->onInitializeDisplays(); + void initializeDisplays() { + FTL_FAKE_GUARD(kMainThreadContext, mFlinger->initializeDisplays()); } void setGlobalShadowSettings(FuzzedDataProvider *fdp) { @@ -430,7 +426,7 @@ public: void onPullAtom(FuzzedDataProvider *fdp) { const int32_t atomId = fdp->ConsumeIntegral(); - std::string pulledData = fdp->ConsumeRandomLengthString().c_str(); + std::vector pulledData = fdp->ConsumeRemainingBytes(); bool success = fdp->ConsumeBool(); mFlinger->onPullAtom(atomId, &pulledData, &success); } @@ -447,21 +443,18 @@ public: mFlinger->clearStatsLocked(dumpArgs, result); mFlinger->dumpTimeStats(dumpArgs, fdp->ConsumeBool(), result); - FTL_FAKE_GUARD(kMainThreadContext, mFlinger->logFrameStats()); + FTL_FAKE_GUARD(kMainThreadContext, + mFlinger->logFrameStats(TimePoint::fromNs(fdp->ConsumeIntegral()))); result = fdp->ConsumeRandomLengthString().c_str(); mFlinger->dumpFrameTimeline(dumpArgs, result); - result = fdp->ConsumeRandomLengthString().c_str(); - mFlinger->dumpStaticScreenStats(result); - result = fdp->ConsumeRandomLengthString().c_str(); mFlinger->dumpRawDisplayIdentificationData(dumpArgs, result); LayersProto layersProto = mFlinger->dumpDrawingStateProto(fdp->ConsumeIntegral()); mFlinger->dumpOffscreenLayersProto(layersProto); - LayersTraceProto layersTraceProto{}; - mFlinger->dumpDisplayProto(layersTraceProto); + mFlinger->dumpDisplayProto(); result = fdp->ConsumeRandomLengthString().c_str(); mFlinger->dumpHwc(result); @@ -469,10 +462,6 @@ public: mFlinger->calculateColorMatrix(fdp->ConsumeFloatingPoint()); mFlinger->updateColorMatrixLocked(); mFlinger->CheckTransactCodeCredentials(fdp->ConsumeIntegral()); - - const CountDownLatch transactionCommittedSignal(fdp->ConsumeIntegral()); - mFlinger->waitForSynchronousTransaction(transactionCommittedSignal); - mFlinger->signalSynchronousTransactions(fdp->ConsumeIntegral()); } void getCompositionPreference() { @@ -508,14 +497,14 @@ public: mFlinger->getDisplayState(display, &displayState); } - void getStaticDisplayInfo(sp &display) { + void getStaticDisplayInfo(int64_t displayId) { ui::StaticDisplayInfo staticDisplayInfo; - mFlinger->getStaticDisplayInfo(display, &staticDisplayInfo); + mFlinger->getStaticDisplayInfo(displayId, &staticDisplayInfo); } - void getDynamicDisplayInfo(sp &display) { + void getDynamicDisplayInfo(int64_t displayId) { android::ui::DynamicDisplayInfo dynamicDisplayInfo; - mFlinger->getDynamicDisplayInfo(display, &dynamicDisplayInfo); + mFlinger->getDynamicDisplayInfoFromId(displayId, &dynamicDisplayInfo); } void getDisplayNativePrimaries(sp &display) { android::ui::DisplayPrimaries displayPrimaries; @@ -523,36 +512,20 @@ public: } void getDesiredDisplayModeSpecs(sp &display) { - ui::DisplayModeId outDefaultMode; - bool outAllowGroupSwitching; - float outPrimaryRefreshRateMin; - float outPrimaryRefreshRateMax; - float outAppRequestRefreshRateMin; - float outAppRequestRefreshRateMax; - mFlinger->getDesiredDisplayModeSpecs(display, &outDefaultMode, &outAllowGroupSwitching, - &outPrimaryRefreshRateMin, &outPrimaryRefreshRateMax, - &outAppRequestRefreshRateMin, - &outAppRequestRefreshRateMax); - } - - void setVsyncConfig(FuzzedDataProvider *fdp) { - const scheduler::VsyncModulator::VsyncConfig vsyncConfig{}; - mFlinger->setVsyncConfig(vsyncConfig, fdp->ConsumeIntegral()); + gui::DisplayModeSpecs _; + mFlinger->getDesiredDisplayModeSpecs(display, &_); } - void updateCompositorTiming(FuzzedDataProvider *fdp) { - std::shared_ptr presentFenceTime = FenceTime::NO_FENCE; - mFlinger->updateCompositorTiming({}, fdp->ConsumeIntegral(), presentFenceTime); + // TODO(b/248317436): extend to cover all displays for multi-display devices + static std::optional getFirstDisplayId() { + std::vector ids = SurfaceComposerClient::getPhysicalDisplayIds(); + if (ids.empty()) return {}; + return ids.front(); } - void getCompositorTiming() { - CompositorTiming compositorTiming; - mFlinger->getCompositorTiming(&compositorTiming); - } - - sp fuzzBoot(FuzzedDataProvider *fdp) { + std::pair, int64_t> fuzzBoot(FuzzedDataProvider *fdp) { mFlinger->callingThreadHasUnscopedSurfaceFlingerAccess(fdp->ConsumeBool()); - mFlinger->createConnection(); + const sp client = sp::make(mFlinger); DisplayIdGenerator kGenerator; HalVirtualDisplayId halVirtualDisplayId = kGenerator.generateId().value(); @@ -561,14 +534,15 @@ public: ui::PixelFormat pixelFormat{}; mFlinger->getHwComposer().allocateVirtualDisplay(halVirtualDisplayId, uiSize, &pixelFormat); - PhysicalDisplayId physicalDisplayId = SurfaceComposerClient::getInternalDisplayId().value(); + PhysicalDisplayId physicalDisplayId = getFirstDisplayId().value_or( + PhysicalDisplayId::fromPort(fdp->ConsumeIntegral())); mFlinger->getHwComposer().allocatePhysicalDisplay(kHwDisplayId, physicalDisplayId); sp display = mFlinger->createDisplay(String8(fdp->ConsumeRandomLengthString().c_str()), fdp->ConsumeBool()); - onInitializeDisplays(); + initializeDisplays(); mFlinger->getPhysicalDisplayToken(physicalDisplayId); mFlinger->mStartPropertySetThread = @@ -576,36 +550,32 @@ public: mFlinger->bootFinished(); - return display; + return {display, physicalDisplayId.value}; } void fuzzSurfaceFlinger(const uint8_t *data, size_t size) { FuzzedDataProvider mFdp(data, size); - sp display = fuzzBoot(&mFdp); + auto [display, displayId] = fuzzBoot(&mFdp); sp bufferProducer = sp::make(); - mFlinger->authenticateSurfaceTexture(bufferProducer.get()); mFlinger->createDisplayEventConnection(); getDisplayStats(display); getDisplayState(display); - getStaticDisplayInfo(display); - getDynamicDisplayInfo(display); + getStaticDisplayInfo(displayId); + getDynamicDisplayInfo(displayId); getDisplayNativePrimaries(display); mFlinger->setAutoLowLatencyMode(display, mFdp.ConsumeBool()); mFlinger->setGameContentType(display, mFdp.ConsumeBool()); mFlinger->setPowerMode(display, mFdp.ConsumeIntegral()); - mFlinger->clearAnimationFrameStats(); overrideHdrTypes(display, &mFdp); onPullAtom(&mFdp); - mFlinger->injectVSync(mFdp.ConsumeIntegral()); - getCompositionPreference(); getDisplayedContentSample(display, &mFdp); getDesiredDisplayModeSpecs(display); @@ -620,19 +590,28 @@ public: mFlinger->binderDied(display); mFlinger->onFirstRef(); - mFlinger->commitTransactions(); - mFlinger->updateInputFlinger(); + mFlinger->updateInputFlinger(VsyncId{}, TimePoint{}); mFlinger->updateCursorAsync(); - setVsyncConfig(&mFdp); + mutableScheduler().setVsyncConfig({.sfOffset = mFdp.ConsumeIntegral(), + .appOffset = mFdp.ConsumeIntegral(), + .sfWorkDuration = getFuzzedDuration(mFdp), + .appWorkDuration = getFuzzedDuration(mFdp)}, + getFuzzedDuration(mFdp)); - mFlinger->flushTransactionQueues(0); + { + ftl::FakeGuard guard(kMainThreadContext); + + mFlinger->commitTransactions(); + mFlinger->flushTransactionQueues(getFuzzedVsyncId(mFdp)); + mFlinger->postComposition(systemTime()); + } mFlinger->setTransactionFlags(mFdp.ConsumeIntegral()); mFlinger->clearTransactionFlags(mFdp.ConsumeIntegral()); mFlinger->commitOffscreenLayers(); - mFlinger->frameIsEarly(mFdp.ConsumeIntegral(), mFdp.ConsumeIntegral()); + mFlinger->frameIsEarly(getFuzzedTimePoint(mFdp), getFuzzedVsyncId(mFdp)); mFlinger->computeLayerBounds(); mFlinger->startBootAnim(); @@ -643,14 +622,6 @@ public: mFlinger->getMaxAcquiredBufferCountForCurrentRefreshRate(mFdp.ConsumeIntegral()); - mFlinger->postComposition(); - - getCompositorTiming(); - - updateCompositorTiming(&mFdp); - - mFlinger->setCompositorTimingSnapped({}, mFdp.ConsumeIntegral()); - FTL_FAKE_GUARD(kMainThreadContext, mFlinger->postFrame()); mFlinger->calculateExpectedPresentTime({}); mFlinger->enableHalVirtualDisplays(mFdp.ConsumeBool()); @@ -661,7 +632,8 @@ public: } void setupRenderEngine(std::unique_ptr renderEngine) { - mFlinger->mCompositionEngine->setRenderEngine(std::move(renderEngine)); + mFlinger->mRenderEngine = std::move(renderEngine); + mFlinger->mCompositionEngine->setRenderEngine(mFlinger->mRenderEngine.get()); } void setupComposer(std::unique_ptr composer) { @@ -675,10 +647,10 @@ public: // The ISchedulerCallback argument can be nullptr for a no-op implementation. void setupScheduler(std::unique_ptr vsyncController, - std::unique_ptr vsyncTracker, + std::shared_ptr vsyncTracker, std::unique_ptr appEventThread, std::unique_ptr sfEventThread, - scheduler::ISchedulerCallback *callback = nullptr, + scheduler::ISchedulerCallback* callback = nullptr, bool hasMultipleModes = false) { constexpr DisplayModeId kModeId60{0}; DisplayModes modes = makeModes(mock::createDisplayMode(kModeId60, 60_Hz)); @@ -688,18 +660,20 @@ public: modes.try_emplace(kModeId90, mock::createDisplayMode(kModeId90, 90_Hz)); } - mRefreshRateConfigs = std::make_shared(modes, kModeId60); - const auto fps = mRefreshRateConfigs->getActiveMode()->getFps(); + mRefreshRateSelector = std::make_shared(modes, kModeId60); + const auto fps = mRefreshRateSelector->getActiveMode().modePtr->getFps(); mFlinger->mVsyncConfiguration = mFactory.createVsyncConfiguration(fps); - mFlinger->mVsyncModulator = sp::make( - mFlinger->mVsyncConfiguration->getCurrentConfigs()); + mFlinger->mRefreshRateStats = std::make_unique(*mFlinger->mTimeStats, fps, hal::PowerMode::OFF); + auto modulatorPtr = sp::make( + mFlinger->mVsyncConfiguration->getCurrentConfigs()); + mScheduler = new scheduler::TestableScheduler(std::move(vsyncController), - std::move(vsyncTracker), mRefreshRateConfigs, - *(callback ?: this)); + std::move(vsyncTracker), mRefreshRateSelector, + std::move(modulatorPtr), *(callback ?: this)); mFlinger->mAppConnectionHandle = mScheduler->createConnection(std::move(appEventThread)); mFlinger->mSfConnectionHandle = mScheduler->createConnection(std::move(sfEventThread)); @@ -740,9 +714,9 @@ public: void enableHalVirtualDisplays(bool enable) { mFlinger->enableHalVirtualDisplays(enable); } - auto commitTransactionsLocked(uint32_t transactionFlags) { + void commitTransactionsLocked(uint32_t transactionFlags) FTL_FAKE_GUARD(kMainThreadContext) { Mutex::Autolock lock(mFlinger->mStateLock); - return mFlinger->commitTransactionsLocked(transactionFlags); + mFlinger->commitTransactionsLocked(transactionFlags); } auto setDisplayStateLocked(const DisplayState &s) { @@ -758,28 +732,35 @@ public: return mFlinger->setPowerModeInternal(display, mode); } - auto &getTransactionQueue() { return mFlinger->mTransactionQueue; } - auto &getPendingTransactionQueue() { return mFlinger->mPendingTransactionQueues; } + auto &getTransactionQueue() { return mFlinger->mTransactionHandler.mLocklessTransactionQueue; } + auto &getPendingTransactionQueue() { + return mFlinger->mTransactionHandler.mPendingTransactionQueues; + } auto setTransactionState( - const FrameTimelineInfo &frameTimelineInfo, const Vector &states, - const Vector &displays, uint32_t flags, const sp &applyToken, - const InputWindowCommands &inputWindowCommands, int64_t desiredPresentTime, - bool isAutoTimestamp, const client_cache_t &uncacheBuffer, bool hasListenerCallbacks, - std::vector &listenerCallbacks, uint64_t transactionId) { + const FrameTimelineInfo& frameTimelineInfo, Vector& states, + const Vector& displays, uint32_t flags, const sp& applyToken, + const InputWindowCommands& inputWindowCommands, int64_t desiredPresentTime, + bool isAutoTimestamp, const std::vector& uncacheBuffers, + bool hasListenerCallbacks, std::vector& listenerCallbacks, + uint64_t transactionId, const std::vector& mergedTransactionIds) { return mFlinger->setTransactionState(frameTimelineInfo, states, displays, flags, applyToken, inputWindowCommands, desiredPresentTime, - isAutoTimestamp, uncacheBuffer, hasListenerCallbacks, - listenerCallbacks, transactionId); + isAutoTimestamp, uncacheBuffers, hasListenerCallbacks, + listenerCallbacks, transactionId, + mergedTransactionIds); } - auto flushTransactionQueues() { return mFlinger->flushTransactionQueues(0); }; + auto flushTransactionQueues() { + ftl::FakeGuard guard(kMainThreadContext); + return mFlinger->flushTransactionQueues(VsyncId{0}); + } auto onTransact(uint32_t code, const Parcel &data, Parcel *reply, uint32_t flags) { return mFlinger->onTransact(code, data, reply, flags); } - auto getGPUContextPriority() { return mFlinger->getGPUContextPriority(); } + auto getGpuContextPriority() { return mFlinger->getGpuContextPriority(); } auto calculateMaxAcquiredBufferCount(Fps refreshRate, std::chrono::nanoseconds presentLatency) const { @@ -789,35 +770,34 @@ public: /* Read-write access to private data to set up preconditions and assert * post-conditions. */ + auto& mutableSupportsWideColor() { return mFlinger->mSupportsWideColor; } + auto& mutableCurrentState() { return mFlinger->mCurrentState; } + auto& mutableDisplays() { return mFlinger->mDisplays; } + auto& mutableDrawingState() { return mFlinger->mDrawingState; } - auto &mutableCurrentState() { return mFlinger->mCurrentState; } - auto &mutableDisplays() { return mFlinger->mDisplays; } - auto &mutableDrawingState() { return mFlinger->mDrawingState; } - auto &mutableInterceptor() { return mFlinger->mInterceptor; } - - auto fromHandle(const sp &handle) { return mFlinger->fromHandle(handle); } + auto fromHandle(const sp &handle) { return LayerHandle::getLayer(handle); } ~TestableSurfaceFlinger() { mutableDisplays().clear(); mutableCurrentState().displays.clear(); mutableDrawingState().displays.clear(); - mutableInterceptor().clear(); mFlinger->mScheduler.reset(); mFlinger->mCompositionEngine->setHwComposer(std::unique_ptr()); - mFlinger->mCompositionEngine->setRenderEngine( - std::unique_ptr()); + mFlinger->mRenderEngine = std::unique_ptr(); + mFlinger->mCompositionEngine->setRenderEngine(mFlinger->mRenderEngine.get()); } private: - void setVsyncEnabled(bool) override {} - void requestDisplayMode(DisplayModePtr, DisplayModeEvent) override {} + void setVsyncEnabled(PhysicalDisplayId, bool) override {} + void requestDisplayModes(std::vector) override {} void kernelTimerChanged(bool) override {} void triggerOnFrameRateOverridesChanged() override {} surfaceflinger::test::Factory mFactory; - sp mFlinger = new SurfaceFlinger(mFactory, SurfaceFlinger::SkipInitialization); + sp mFlinger = + sp::make(mFactory, SurfaceFlinger::SkipInitialization); scheduler::TestableScheduler *mScheduler = nullptr; - std::shared_ptr mRefreshRateConfigs; + std::shared_ptr mRefreshRateSelector; }; } // namespace android diff --git a/services/surfaceflinger/fuzzer/surfaceflinger_layer_fuzzer.cpp b/services/surfaceflinger/fuzzer/surfaceflinger_layer_fuzzer.cpp index 34cf90628ae645a897d1edb23523f23a01f0ed1e..921cae4e416a7ddf6324a8707c427061769cfed5 100644 --- a/services/surfaceflinger/fuzzer/surfaceflinger_layer_fuzzer.cpp +++ b/services/surfaceflinger/fuzzer/surfaceflinger_layer_fuzzer.cpp @@ -14,13 +14,9 @@ * limitations under the License. * */ -#include #include #include -#include -#include #include -#include #include #include #include @@ -29,6 +25,7 @@ #include #include #include +#include #include #include @@ -46,6 +43,7 @@ public: void invokeEffectLayer(); LayerCreationArgs createLayerCreationArgs(TestableSurfaceFlinger* flinger, sp client); Rect getFuzzedRect(); + ui::Transform getFuzzedTransform(); FrameTimelineInfo getFuzzedFrameTimelineInfo(); private: @@ -58,9 +56,17 @@ Rect LayerFuzzer::getFuzzedRect() { mFdp.ConsumeIntegral() /*bottom*/); } +ui::Transform LayerFuzzer::getFuzzedTransform() { + return ui::Transform(mFdp.ConsumeIntegral() /*orientation*/, + mFdp.ConsumeIntegral() /*width*/, + mFdp.ConsumeIntegral() /*height*/); +} + FrameTimelineInfo LayerFuzzer::getFuzzedFrameTimelineInfo() { - return FrameTimelineInfo{.vsyncId = mFdp.ConsumeIntegral(), - .inputEventId = mFdp.ConsumeIntegral()}; + FrameTimelineInfo ftInfo; + ftInfo.vsyncId = mFdp.ConsumeIntegral(); + ftInfo.inputEventId = mFdp.ConsumeIntegral(); + return ftInfo; } LayerCreationArgs LayerFuzzer::createLayerCreationArgs(TestableSurfaceFlinger* flinger, @@ -77,15 +83,15 @@ LayerCreationArgs LayerFuzzer::createLayerCreationArgs(TestableSurfaceFlinger* f void LayerFuzzer::invokeEffectLayer() { TestableSurfaceFlinger flinger; - sp client = sp::make(flinger.flinger()); + sp client = sp::make(sp::fromExisting(flinger.flinger())); const LayerCreationArgs layerCreationArgs = createLayerCreationArgs(&flinger, client); - sp effectLayer = sp::make(layerCreationArgs); + sp effectLayer = sp::make(layerCreationArgs); effectLayer->setColor({(mFdp.ConsumeFloatingPointInRange(0, 255) /*x*/, mFdp.ConsumeFloatingPointInRange(0, 255) /*y*/, mFdp.ConsumeFloatingPointInRange(0, 255) /*z*/)}); effectLayer->setDataspace(mFdp.PickValueInArray(kDataspaces)); - sp parent = sp::make(layerCreationArgs); + sp parent = sp::make(layerCreationArgs); effectLayer->setChildrenDrawingParent(parent); const FrameTimelineInfo frameInfo = getFuzzedFrameTimelineInfo(); @@ -109,24 +115,25 @@ void LayerFuzzer::invokeEffectLayer() { void LayerFuzzer::invokeBufferStateLayer() { TestableSurfaceFlinger flinger; - sp client = sp::make(flinger.flinger()); - sp layer = - sp::make(createLayerCreationArgs(&flinger, client)); + sp client = sp::make(sp::fromExisting(flinger.flinger())); + sp layer = sp::make(createLayerCreationArgs(&flinger, client)); sp fence = sp::make(); const std::shared_ptr fenceTime = std::make_shared(fence); - const CompositorTiming compositor = {mFdp.ConsumeIntegral(), - mFdp.ConsumeIntegral(), - mFdp.ConsumeIntegral()}; + const CompositorTiming compositorTiming(mFdp.ConsumeIntegral(), + mFdp.ConsumeIntegral(), + mFdp.ConsumeIntegral(), + mFdp.ConsumeIntegral()); - layer->onLayerDisplayed(ftl::yield(fence).share()); - layer->onLayerDisplayed( - ftl::yield(base::unexpected(mFdp.ConsumeIntegral())).share()); + layer->onLayerDisplayed(ftl::yield(fence).share(), + ui::LayerStack::fromValue(mFdp.ConsumeIntegral())); + layer->onLayerDisplayed(ftl::yield( + base::unexpected(mFdp.ConsumeIntegral())) + .share(), + ui::LayerStack::fromValue(mFdp.ConsumeIntegral())); layer->releasePendingBuffer(mFdp.ConsumeIntegral()); - layer->finalizeFrameEventHistory(fenceTime, compositor); - layer->onPostComposition(nullptr, fenceTime, fenceTime, compositor); - layer->isBufferDue(mFdp.ConsumeIntegral()); + layer->onPostComposition(nullptr, fenceTime, fenceTime, compositorTiming); layer->setTransform(mFdp.ConsumeIntegral()); layer->setTransformToDisplayInverse(mFdp.ConsumeBool()); @@ -150,10 +157,9 @@ void LayerFuzzer::invokeBufferStateLayer() { layer->computeSourceBounds(getFuzzedFloatRect(&mFdp)); layer->fenceHasSignaled(); - layer->framePresentTimeIsCurrent(mFdp.ConsumeIntegral()); layer->onPreComposition(mFdp.ConsumeIntegral()); const std::vector> callbacks; - layer->setTransactionCompletedListeners(callbacks); + layer->setTransactionCompletedListeners(callbacks, mFdp.ConsumeBool()); std::shared_ptr texture = std::make_shared< renderengine::mock::FakeExternalTexture>(mFdp.ConsumeIntegral(), @@ -171,7 +177,8 @@ void LayerFuzzer::invokeBufferStateLayer() { {mFdp.ConsumeIntegral(), mFdp.ConsumeIntegral()} /*reqSize*/, mFdp.PickValueInArray(kDataspaces), mFdp.ConsumeBool(), - getFuzzedRect(), mFdp.ConsumeBool()); + mFdp.ConsumeBool(), getFuzzedTransform(), getFuzzedRect(), + mFdp.ConsumeBool()); layerArea.render([]() {} /*drawLayers*/); if (!ownsHandle) { diff --git a/services/surfaceflinger/fuzzer/surfaceflinger_scheduler_fuzzer.cpp b/services/surfaceflinger/fuzzer/surfaceflinger_scheduler_fuzzer.cpp index da60a6981bdd6405ff04be46154b5dcbb3867f05..f17d2e1cb40cbde4e8920ebe199fa8cde6f47b48 100644 --- a/services/surfaceflinger/fuzzer/surfaceflinger_scheduler_fuzzer.cpp +++ b/services/surfaceflinger/fuzzer/surfaceflinger_scheduler_fuzzer.cpp @@ -19,12 +19,17 @@ #include #include -#include "Scheduler/DispSyncSource.h" +#include + #include "Scheduler/OneShotTimer.h" +#include "Scheduler/RefreshRateSelector.h" #include "Scheduler/VSyncDispatchTimerQueue.h" #include "Scheduler/VSyncPredictor.h" #include "Scheduler/VSyncReactor.h" +#include "mock/MockVSyncDispatch.h" +#include "mock/MockVSyncTracker.h" + #include "surfaceflinger_fuzzers_utils.h" #include "surfaceflinger_scheduler_fuzzer.h" @@ -36,13 +41,14 @@ constexpr nsecs_t kVsyncPeriods[] = {(30_Hz).getPeriodNsecs(), (60_Hz).getPeriod (72_Hz).getPeriodNsecs(), (90_Hz).getPeriodNsecs(), (120_Hz).getPeriodNsecs()}; -constexpr auto kLayerVoteTypes = ftl::enum_range(); +constexpr auto kLayerVoteTypes = ftl::enum_range(); constexpr PowerMode kPowerModes[] = {PowerMode::ON, PowerMode::DOZE, PowerMode::OFF, PowerMode::DOZE_SUSPEND, PowerMode::ON_SUSPEND}; constexpr uint16_t kRandomStringLength = 256; constexpr std::chrono::duration kSyncPeriod(16ms); +constexpr PhysicalDisplayId DEFAULT_DISPLAY_ID = PhysicalDisplayId::fromPort(42u); template void dump(T* component, FuzzedDataProvider* fdp) { @@ -50,19 +56,19 @@ void dump(T* component, FuzzedDataProvider* fdp) { component->dump(res); } -class SchedulerFuzzer : private VSyncSource::Callback { +class SchedulerFuzzer { public: SchedulerFuzzer(const uint8_t* data, size_t size) : mFdp(data, size){}; void process(); private: void fuzzRefreshRateSelection(); - void fuzzRefreshRateConfigs(); + void fuzzRefreshRateSelector(); + void fuzzPresentLatencyTracker(); void fuzzVSyncModulator(); void fuzzVSyncPredictor(); void fuzzVSyncReactor(); void fuzzLayerHistory(); - void fuzzDispSyncSource(); void fuzzCallbackToken(scheduler::VSyncDispatchTimerQueue* dispatch); void fuzzVSyncDispatchTimerQueue(); void fuzzOneShotTimer(); @@ -71,8 +77,7 @@ private: FuzzedDataProvider mFdp; -protected: - void onVSyncEvent(nsecs_t /* when */, VSyncSource::VSyncData) {} + std::shared_ptr mVsyncSchedule; }; PhysicalDisplayId SchedulerFuzzer::getPhysicalDisplayId() { @@ -86,44 +91,30 @@ PhysicalDisplayId SchedulerFuzzer::getPhysicalDisplayId() { } void SchedulerFuzzer::fuzzEventThread() { + mVsyncSchedule = std::shared_ptr( + new scheduler::VsyncSchedule(getPhysicalDisplayId(), + std::make_shared(), + std::make_shared(), nullptr)); const auto getVsyncPeriod = [](uid_t /* uid */) { return kSyncPeriod.count(); }; std::unique_ptr thread = std::make_unique< - android::impl::EventThread>(std::move(std::make_unique()), nullptr, - nullptr, nullptr, getVsyncPeriod); + android::impl::EventThread>("fuzzer", mVsyncSchedule, nullptr, nullptr, getVsyncPeriod, + (std::chrono::nanoseconds)mFdp.ConsumeIntegral(), + (std::chrono::nanoseconds)mFdp.ConsumeIntegral()); thread->onHotplugReceived(getPhysicalDisplayId(), mFdp.ConsumeBool()); sp connection = - new EventThreadConnection(thread.get(), mFdp.ConsumeIntegral(), nullptr, - {} /*eventRegistration*/); + sp::make(thread.get(), mFdp.ConsumeIntegral(), + nullptr); thread->requestNextVsync(connection); thread->setVsyncRate(mFdp.ConsumeIntegral() /*rate*/, connection); thread->setDuration((std::chrono::nanoseconds)mFdp.ConsumeIntegral(), (std::chrono::nanoseconds)mFdp.ConsumeIntegral()); thread->registerDisplayEventConnection(connection); - thread->onScreenAcquired(); - thread->onScreenReleased(); + thread->enableSyntheticVsync(mFdp.ConsumeBool()); dump(thread.get(), &mFdp); } -void SchedulerFuzzer::fuzzDispSyncSource() { - std::unique_ptr vSyncDispatch = - std::make_unique(); - std::unique_ptr vSyncTracker = std::make_unique(); - std::unique_ptr dispSyncSource = std::make_unique< - scheduler::DispSyncSource>(*vSyncDispatch, *vSyncTracker, - (std::chrono::nanoseconds) - mFdp.ConsumeIntegral() /*workDuration*/, - (std::chrono::nanoseconds)mFdp.ConsumeIntegral() - /*readyDuration*/, - mFdp.ConsumeBool(), - mFdp.ConsumeRandomLengthString(kRandomStringLength).c_str()); - dispSyncSource->setVSyncEnabled(true); - dispSyncSource->setCallback(this); - dispSyncSource->setDuration((std::chrono::nanoseconds)mFdp.ConsumeIntegral(), 0ns); - dump(dispSyncSource.get(), &mFdp); -} - void SchedulerFuzzer::fuzzCallbackToken(scheduler::VSyncDispatchTimerQueue* dispatch) { scheduler::VSyncDispatch::CallbackToken tmp = dispatch->registerCallback( [&](auto, auto, auto) { @@ -142,7 +133,7 @@ void SchedulerFuzzer::fuzzCallbackToken(scheduler::VSyncDispatchTimerQueue* disp } void SchedulerFuzzer::fuzzVSyncDispatchTimerQueue() { - FuzzImplVSyncTracker stubTracker{mFdp.ConsumeIntegral()}; + auto stubTracker = std::make_shared(mFdp.ConsumeIntegral()); scheduler::VSyncDispatchTimerQueue mDispatch{std::make_unique(), stubTracker, mFdp.ConsumeIntegral() /*dispatchGroupThreshold*/, @@ -155,17 +146,17 @@ void SchedulerFuzzer::fuzzVSyncDispatchTimerQueue() { scheduler::VSyncDispatchTimerQueueEntry entry( "fuzz", [](auto, auto, auto) {}, mFdp.ConsumeIntegral() /*vSyncMoveThreshold*/); - entry.update(stubTracker, 0); + entry.update(*stubTracker, 0); entry.schedule({.workDuration = mFdp.ConsumeIntegral(), .readyDuration = mFdp.ConsumeIntegral(), .earliestVsync = mFdp.ConsumeIntegral()}, - stubTracker, 0); + *stubTracker, 0); entry.disarm(); entry.ensureNotRunning(); entry.schedule({.workDuration = mFdp.ConsumeIntegral(), .readyDuration = mFdp.ConsumeIntegral(), .earliestVsync = mFdp.ConsumeIntegral()}, - stubTracker, 0); + *stubTracker, 0); auto const wakeup = entry.wakeupTime(); auto const ready = entry.readyTime(); entry.callback(entry.executing(), *wakeup, *ready); @@ -179,7 +170,8 @@ void SchedulerFuzzer::fuzzVSyncPredictor() { uint16_t now = mFdp.ConsumeIntegral(); uint16_t historySize = mFdp.ConsumeIntegralInRange(1, UINT16_MAX); uint16_t minimumSamplesForPrediction = mFdp.ConsumeIntegralInRange(1, UINT16_MAX); - scheduler::VSyncPredictor tracker{mFdp.ConsumeIntegral() /*period*/, historySize, + scheduler::VSyncPredictor tracker{DEFAULT_DISPLAY_ID, + mFdp.ConsumeIntegral() /*period*/, historySize, minimumSamplesForPrediction, mFdp.ConsumeIntegral() /*outlierTolerancePercent*/}; uint16_t period = mFdp.ConsumeIntegral(); @@ -224,19 +216,19 @@ void SchedulerFuzzer::fuzzLayerHistory() { nsecs_t time2 = time1; uint8_t historySize = mFdp.ConsumeIntegral(); - sp layer1 = new FuzzImplLayer(flinger.flinger()); - sp layer2 = new FuzzImplLayer(flinger.flinger()); + sp layer1 = sp::make(flinger.flinger()); + sp layer2 = sp::make(flinger.flinger()); for (int i = 0; i < historySize; ++i) { - historyV1.record(layer1.get(), time1, time1, + historyV1.record(layer1->getSequence(), layer1->getLayerProps(), time1, time1, scheduler::LayerHistory::LayerUpdateType::Buffer); - historyV1.record(layer2.get(), time2, time2, + historyV1.record(layer2->getSequence(), layer2->getLayerProps(), time2, time2, scheduler::LayerHistory::LayerUpdateType::Buffer); time1 += mFdp.PickValueInArray(kVsyncPeriods); time2 += mFdp.PickValueInArray(kVsyncPeriods); } - historyV1.summarize(*scheduler->refreshRateConfigs(), time1); - historyV1.summarize(*scheduler->refreshRateConfigs(), time2); + historyV1.summarize(*scheduler->refreshRateSelector(), time1); + historyV1.summarize(*scheduler->refreshRateSelector(), time2); scheduler->createConnection(std::make_unique()); @@ -245,22 +237,26 @@ void SchedulerFuzzer::fuzzLayerHistory() { scheduler->setDuration(handle, (std::chrono::nanoseconds)mFdp.ConsumeIntegral(), (std::chrono::nanoseconds)mFdp.ConsumeIntegral()); - dump(scheduler, &mFdp); + std::string result = mFdp.ConsumeRandomLengthString(kRandomStringLength); + utils::Dumper dumper(result); + scheduler->dump(dumper); } void SchedulerFuzzer::fuzzVSyncReactor() { std::shared_ptr vSyncTracker = std::make_shared(); - scheduler::VSyncReactor reactor(std::make_unique( + scheduler::VSyncReactor reactor(DEFAULT_DISPLAY_ID, + std::make_unique( std::make_shared()), *vSyncTracker, mFdp.ConsumeIntegral() /*pendingLimit*/, false); - reactor.startPeriodTransition(mFdp.ConsumeIntegral()); - bool periodFlushed = mFdp.ConsumeBool(); + reactor.startPeriodTransition(mFdp.ConsumeIntegral(), mFdp.ConsumeBool()); + bool periodFlushed = false; // Value does not matter, since this is an out + // param from addHwVsyncTimestamp. reactor.addHwVsyncTimestamp(0, std::nullopt, &periodFlushed); reactor.addHwVsyncTimestamp(mFdp.ConsumeIntegral() /*newPeriod*/, std::nullopt, &periodFlushed); - sp fence = new Fence(memfd_create("fd", MFD_ALLOW_SEALING)); + sp fence = sp::make(memfd_create("fd", MFD_ALLOW_SEALING)); std::shared_ptr ft = std::make_shared(fence); vSyncTracker->addVsyncTimestamp(mFdp.ConsumeIntegral()); FenceTime::Snapshot snap(mFdp.ConsumeIntegral()); @@ -288,17 +284,14 @@ void SchedulerFuzzer::fuzzVSyncModulator() { }; using Schedule = scheduler::TransactionSchedule; using nanos = std::chrono::nanoseconds; - using VsyncModulator = scheduler::VsyncModulator; using FuzzImplVsyncModulator = scheduler::FuzzImplVsyncModulator; - const VsyncModulator::VsyncConfig early{SF_OFFSET_EARLY, APP_OFFSET_EARLY, - nanos(SF_DURATION_LATE), nanos(APP_DURATION_LATE)}; - const VsyncModulator::VsyncConfig earlyGpu{SF_OFFSET_EARLY_GPU, APP_OFFSET_EARLY_GPU, - nanos(SF_DURATION_EARLY), nanos(APP_DURATION_EARLY)}; - const VsyncModulator::VsyncConfig late{SF_OFFSET_LATE, APP_OFFSET_LATE, - nanos(SF_DURATION_EARLY_GPU), - nanos(APP_DURATION_EARLY_GPU)}; - const VsyncModulator::VsyncConfigSet offsets = {early, earlyGpu, late, - nanos(HWC_MIN_WORK_DURATION)}; + const scheduler::VsyncConfig early{SF_OFFSET_EARLY, APP_OFFSET_EARLY, nanos(SF_DURATION_LATE), + nanos(APP_DURATION_LATE)}; + const scheduler::VsyncConfig earlyGpu{SF_OFFSET_EARLY_GPU, APP_OFFSET_EARLY_GPU, + nanos(SF_DURATION_EARLY), nanos(APP_DURATION_EARLY)}; + const scheduler::VsyncConfig late{SF_OFFSET_LATE, APP_OFFSET_LATE, nanos(SF_DURATION_EARLY_GPU), + nanos(APP_DURATION_EARLY_GPU)}; + const scheduler::VsyncConfigSet offsets = {early, earlyGpu, late, nanos(HWC_MIN_WORK_DURATION)}; sp vSyncModulator = sp::make(offsets, scheduler::Now); (void)vSyncModulator->setVsyncConfigSet(offsets); @@ -319,14 +312,14 @@ void SchedulerFuzzer::fuzzRefreshRateSelection() { LayerCreationArgs args(flinger.flinger(), client, mFdp.ConsumeRandomLengthString(kRandomStringLength) /*name*/, mFdp.ConsumeIntegral() /*layerFlags*/, LayerMetadata()); - sp layer = new BufferQueueLayer(args); + sp layer = sp::make(args); layer->setFrameRateSelectionPriority(mFdp.ConsumeIntegral()); } -void SchedulerFuzzer::fuzzRefreshRateConfigs() { - using RefreshRateConfigs = scheduler::RefreshRateConfigs; - using LayerRequirement = RefreshRateConfigs::LayerRequirement; +void SchedulerFuzzer::fuzzRefreshRateSelector() { + using RefreshRateSelector = scheduler::RefreshRateSelector; + using LayerRequirement = RefreshRateSelector::LayerRequirement; using RefreshRateStats = scheduler::RefreshRateStats; const uint16_t minRefreshRate = mFdp.ConsumeIntegralInRange(1, UINT16_MAX >> 1); @@ -342,54 +335,75 @@ void SchedulerFuzzer::fuzzRefreshRateConfigs() { Fps::fromValue(static_cast(fps)))); } - RefreshRateConfigs refreshRateConfigs(displayModes, modeId); + RefreshRateSelector refreshRateSelector(displayModes, modeId); - const RefreshRateConfigs::GlobalSignals globalSignals = {.touch = false, .idle = false}; + const RefreshRateSelector::GlobalSignals globalSignals = {.touch = false, .idle = false}; std::vector layers = {{.weight = mFdp.ConsumeFloatingPoint()}}; - refreshRateConfigs.getBestRefreshRate(layers, globalSignals); + refreshRateSelector.getRankedFrameRates(layers, globalSignals); layers[0].name = mFdp.ConsumeRandomLengthString(kRandomStringLength); layers[0].ownerUid = mFdp.ConsumeIntegral(); layers[0].desiredRefreshRate = Fps::fromValue(mFdp.ConsumeFloatingPoint()); layers[0].vote = mFdp.PickValueInArray(kLayerVoteTypes.values); auto frameRateOverrides = - refreshRateConfigs.getFrameRateOverrides(layers, - Fps::fromValue( + refreshRateSelector.getFrameRateOverrides(layers, + Fps::fromValue( + mFdp.ConsumeFloatingPoint()), + globalSignals); + + { + ftl::FakeGuard guard(kMainThreadContext); + + refreshRateSelector.setPolicy( + RefreshRateSelector:: + DisplayManagerPolicy{modeId, + {Fps::fromValue(mFdp.ConsumeFloatingPoint()), + Fps::fromValue(mFdp.ConsumeFloatingPoint())}}); + refreshRateSelector.setPolicy( + RefreshRateSelector::OverridePolicy{modeId, + {Fps::fromValue( mFdp.ConsumeFloatingPoint()), - globalSignals); + Fps::fromValue( + mFdp.ConsumeFloatingPoint())}}); + refreshRateSelector.setPolicy(RefreshRateSelector::NoOverridePolicy{}); - refreshRateConfigs.setDisplayManagerPolicy( - {modeId, - {Fps::fromValue(mFdp.ConsumeFloatingPoint()), - Fps::fromValue(mFdp.ConsumeFloatingPoint())}}); - refreshRateConfigs.setActiveModeId(modeId); + refreshRateSelector.setActiveMode(modeId, + Fps::fromValue(mFdp.ConsumeFloatingPoint())); + } - RefreshRateConfigs::isFractionalPairOrMultiple(Fps::fromValue( - mFdp.ConsumeFloatingPoint()), - Fps::fromValue( - mFdp.ConsumeFloatingPoint())); - RefreshRateConfigs::getFrameRateDivisor(Fps::fromValue(mFdp.ConsumeFloatingPoint()), - Fps::fromValue(mFdp.ConsumeFloatingPoint())); + RefreshRateSelector::isFractionalPairOrMultiple(Fps::fromValue( + mFdp.ConsumeFloatingPoint()), + Fps::fromValue( + mFdp.ConsumeFloatingPoint())); + RefreshRateSelector::getFrameRateDivisor(Fps::fromValue(mFdp.ConsumeFloatingPoint()), + Fps::fromValue(mFdp.ConsumeFloatingPoint())); android::mock::TimeStats timeStats; RefreshRateStats refreshRateStats(timeStats, Fps::fromValue(mFdp.ConsumeFloatingPoint()), PowerMode::OFF); - const auto fpsOpt = displayModes.get(modeId, [](const auto& mode) { return mode->getFps(); }); + const auto fpsOpt = displayModes.get(modeId).transform( + [](const DisplayModePtr& mode) { return mode->getFps(); }); refreshRateStats.setRefreshRate(*fpsOpt); refreshRateStats.setPowerMode(mFdp.PickValueInArray(kPowerModes)); } +void SchedulerFuzzer::fuzzPresentLatencyTracker() { + scheduler::PresentLatencyTracker tracker; + tracker.trackPendingFrame(TimePoint::fromNs(mFdp.ConsumeIntegral()), + FenceTime::NO_FENCE); +} + void SchedulerFuzzer::process() { fuzzRefreshRateSelection(); - fuzzRefreshRateConfigs(); + fuzzRefreshRateSelector(); + fuzzPresentLatencyTracker(); fuzzVSyncModulator(); fuzzVSyncPredictor(); fuzzVSyncReactor(); fuzzLayerHistory(); - fuzzDispSyncSource(); fuzzEventThread(); fuzzVSyncDispatchTimerQueue(); fuzzOneShotTimer(); diff --git a/services/surfaceflinger/fuzzer/surfaceflinger_scheduler_fuzzer.h b/services/surfaceflinger/fuzzer/surfaceflinger_scheduler_fuzzer.h index 1a49ead2759cfa2e673b30a7e69ae2c6a76f9186..8061a8f2dc23fb0ab9351c09a43bab8e1eaaa932 100644 --- a/services/surfaceflinger/fuzzer/surfaceflinger_scheduler_fuzzer.h +++ b/services/surfaceflinger/fuzzer/surfaceflinger_scheduler_fuzzer.h @@ -27,7 +27,6 @@ #include "Clock.h" #include "Layer.h" #include "Scheduler/EventThread.h" -#include "Scheduler/RefreshRateConfigs.h" #include "Scheduler/Scheduler.h" #include "Scheduler/VSyncTracker.h" #include "Scheduler/VsyncModulator.h" @@ -76,23 +75,7 @@ public: bool isVisible() const override { return true; } - sp createClone() override { return nullptr; } -}; - -class FuzzImplVSyncSource : public VSyncSource { -public: - const char* getName() const override { return "fuzz"; } - - void setVSyncEnabled(bool /* enable */) override {} - - void setCallback(Callback* /* callback */) override {} - - void setDuration(std::chrono::nanoseconds /* workDuration */, - std::chrono::nanoseconds /* readyDuration */) override {} - - VSyncData getLatestVSyncData() const override { return {}; } - - void dump(std::string& /* result */) const override {} + sp createClone(uint32_t /* mirrorRootId */) override { return nullptr; } }; class FuzzImplVSyncTracker : public scheduler::VSyncTracker { @@ -117,6 +100,8 @@ public: return true; } + void setRenderRate(Fps) override {} + nsecs_t nextVSyncTime(nsecs_t timePoint) const { if (timePoint % mPeriod == 0) { return timePoint; @@ -144,6 +129,11 @@ public: return (scheduler::ScheduleResult)0; } + scheduler::ScheduleResult update(CallbackToken /* token */, + ScheduleTiming /* scheduleTiming */) override { + return (scheduler::ScheduleResult)0; + } + scheduler::CancelResult cancel(CallbackToken /* token */) override { return (scheduler::CancelResult)0; } diff --git a/services/surfaceflinger/layerproto/Android.bp b/services/surfaceflinger/layerproto/Android.bp index 973a4392a2afb6ad1ec42c175fdd705094a742b3..7287dd010370d4d63507d89059c48aa205476d25 100644 --- a/services/surfaceflinger/layerproto/Android.bp +++ b/services/surfaceflinger/layerproto/Android.bp @@ -44,10 +44,11 @@ cc_library { } java_library_static { - name: "layersprotosnano", + name: "layersprotoslite", host_supported: true, proto: { - type: "nano", + type: "lite", + include_dirs: ["external/protobuf/src"], }, srcs: ["*.proto"], sdk_version: "core_platform", @@ -56,7 +57,7 @@ java_library_static { jarjar_rules: "jarjar-rules.txt", }, host: { - static_libs: ["libprotobuf-java-nano"], + static_libs: ["libprotobuf-java-lite"], }, }, } diff --git a/services/surfaceflinger/layerproto/include/layerproto/LayerProtoParser.h b/services/surfaceflinger/layerproto/include/layerproto/LayerProtoParser.h index 52503bad3a75789f02baed0e77f4a22b21c6ac9c..cdc2706ee2e4aee23ef78c379c08790b9cfcc509 100644 --- a/services/surfaceflinger/layerproto/include/layerproto/LayerProtoParser.h +++ b/services/surfaceflinger/layerproto/include/layerproto/LayerProtoParser.h @@ -24,6 +24,8 @@ #include #include +using android::gui::LayerMetadata; + namespace android { namespace surfaceflinger { diff --git a/services/surfaceflinger/layerproto/layers.proto b/services/surfaceflinger/layerproto/layers.proto index 359830833878165aa2873bedffe1a15780b3f233..e9add2e1a41ea11daf5a7eeed3bf2f4e5b0edd7b 100644 --- a/services/surfaceflinger/layerproto/layers.proto +++ b/services/surfaceflinger/layerproto/layers.proto @@ -138,6 +138,8 @@ message LayerProto { float requested_corner_radius = 56; RectProto destination_frame = 57; + + uint32 original_id = 58; } message PositionProto { diff --git a/services/surfaceflinger/layerproto/layerstrace.proto b/services/surfaceflinger/layerproto/layerstrace.proto index 13647b669e2f7b493a2f2ee5a3676aa1091a7f10..804a4994eeebada0f37257bcc134d8a4b33dbc49 100644 --- a/services/surfaceflinger/layerproto/layerstrace.proto +++ b/services/surfaceflinger/layerproto/layerstrace.proto @@ -38,9 +38,13 @@ message LayersTraceFileProto { optional fixed64 magic_number = 1; /* Must be the first field, set to value in MagicNumber */ repeated LayersTraceProto entry = 2; + + /* offset between real-time clock and elapsed time clock in nanoseconds. + Calculated as: systemTime(SYSTEM_TIME_REALTIME) - systemTime(SYSTEM_TIME_MONOTONIC) */ + optional fixed64 real_to_elapsed_time_offset_nanos = 3; } -/* one window manager trace entry. */ +/* one layers trace entry. */ message LayersTraceProto { /* required: elapsed realtime in nanos since boot of when this entry was logged */ optional sfixed64 elapsed_realtime_nanos = 1; @@ -60,4 +64,6 @@ message LayersTraceProto { optional uint32 missed_entries = 6; repeated DisplayProto displays = 7; + + optional int64 vsync_id = 8; } diff --git a/services/surfaceflinger/layerproto/transactions.proto b/services/surfaceflinger/layerproto/transactions.proto index 4f99b192a3e716daf39c6df468828ea3d85dc42a..b0cee9b39817bf8c6b0dadcc562b9f87b595d5c4 100644 --- a/services/surfaceflinger/layerproto/transactions.proto +++ b/services/surfaceflinger/layerproto/transactions.proto @@ -36,6 +36,11 @@ message TransactionTraceFile { fixed64 magic_number = 1; /* Must be the first field, set to value in MagicNumber */ repeated TransactionTraceEntry entry = 2; + + /* offset between real-time clock and elapsed time clock in nanoseconds. + Calculated as: systemTime(SYSTEM_TIME_REALTIME) - systemTime(SYSTEM_TIME_MONOTONIC) */ + fixed64 real_to_elapsed_time_offset_nanos = 3; + uint32 version = 4; } message TransactionTraceEntry { @@ -43,18 +48,47 @@ message TransactionTraceEntry { int64 vsync_id = 2; repeated TransactionState transactions = 3; repeated LayerCreationArgs added_layers = 4; - repeated int32 removed_layers = 5; + repeated uint32 destroyed_layers = 5; repeated DisplayState added_displays = 6; repeated int32 removed_displays = 7; - repeated int32 removed_layer_handles = 8; + repeated uint32 destroyed_layer_handles = 8; + bool displays_changed = 9; + repeated DisplayInfo displays = 10; +} + +message DisplayInfo { + uint32 layer_stack = 1; + int32 display_id = 2; + int32 logical_width = 3; + int32 logical_height = 4; + Transform transform_inverse = 5; + Transform transform = 6; + bool receives_input = 7; + bool is_secure = 8; + bool is_primary = 9; + bool is_virtual = 10; + int32 rotation_flags = 11; + int32 transform_hint = 12; + } message LayerCreationArgs { - int32 layer_id = 1; + uint32 layer_id = 1; string name = 2; uint32 flags = 3; - int32 parent_id = 4; - int32 mirror_from_id = 5; + uint32 parent_id = 4; + uint32 mirror_from_id = 5; + bool add_to_root = 6; + uint32 layer_stack_to_mirror = 7; +} + +message Transform { + float dsdx = 1; + float dtdx = 2; + float dtdy = 3; + float dsdy = 4; + float tx = 5; + float ty = 6; } message TransactionState { @@ -66,11 +100,12 @@ message TransactionState { uint64 transaction_id = 6; repeated LayerState layer_changes = 7; repeated DisplayState display_changes = 8; + repeated uint64 merged_transaction_ids = 9; } // Keep insync with layer_state_t message LayerState { - int64 layer_id = 1; + uint32 layer_id = 1; // Changes are split into ChangesLsb and ChangesMsb. First 32 bits are in ChangesLsb // and the next 32 bits are in ChangesMsb. This is needed because enums have to be // 32 bits and there's no nice way to put 64bit constants into .proto files. @@ -78,7 +113,7 @@ message LayerState { eChangesLsbNone = 0; ePositionChanged = 0x00000001; eLayerChanged = 0x00000002; - eSizeChanged = 0x00000004; + // unused = 0x00000004; eAlphaChanged = 0x00000008; eMatrixChanged = 0x00000010; @@ -94,8 +129,7 @@ message LayerState { eReparent = 0x00008000; eColorChanged = 0x00010000; - eDestroySurface = 0x00020000; - eTransformChanged = 0x00040000; + eBufferTransformChanged = 0x00040000; eTransformToDisplayInverseChanged = 0x00080000; eCropChanged = 0x00100000; @@ -161,8 +195,8 @@ message LayerState { Matrix22 matrix = 11; float corner_radius = 12; uint32 background_blur_radius = 13; - int64 parent_id = 14; - int64 relative_parent_id = 15; + uint32 parent_id = 14; + uint32 relative_parent_id = 15; float alpha = 16; message Color3 { @@ -217,14 +251,6 @@ message LayerState { ColorTransformProto color_transform = 25; repeated BlurRegion blur_regions = 26; - message Transform { - float dsdx = 1; - float dtdx = 2; - float dtdy = 3; - float dsdy = 4; - float tx = 5; - float ty = 6; - } message WindowInfo { uint32 layout_params_flags = 1; int32 layout_params_type = 2; @@ -233,7 +259,7 @@ message LayerState { bool focusable = 5; bool has_wallpaper = 6; float global_scale_factor = 7; - int64 crop_layer_id = 8; + uint32 crop_layer_id = 8; bool replace_touchable_region_with_crop = 9; RectProto touchable_region_crop = 10; Transform transform = 11; diff --git a/services/surfaceflinger/main_surfaceflinger.cpp b/services/surfaceflinger/main_surfaceflinger.cpp index 883766b334cef9f4345cc92aa12f8126d956d3ec..cf23169eae34eed5bbf9a130897077e85cde093a 100644 --- a/services/surfaceflinger/main_surfaceflinger.cpp +++ b/services/surfaceflinger/main_surfaceflinger.cpp @@ -67,7 +67,7 @@ static void startDisplayService() { using android::frameworks::displayservice::V1_0::implementation::DisplayService; using android::frameworks::displayservice::V1_0::IDisplayService; - sp displayservice = new DisplayService(); + sp displayservice = sp::make(); status_t err = displayservice->registerAsService(); // b/141930622 @@ -91,7 +91,7 @@ int main(int, char**) { // Set uclamp.min setting on all threads, maybe an overkill but we want // to cover important threads like RenderEngine. if (SurfaceFlinger::setSchedAttr(true) != NO_ERROR) { - ALOGW("Couldn't set uclamp.min: %s\n", strerror(errno)); + ALOGW("Failed to set uclamp.min during boot: %s", strerror(errno)); } // The binder threadpool we start will inherit sched policy and priority @@ -148,14 +148,14 @@ int main(int, char**) { IServiceManager::DUMP_FLAG_PRIORITY_CRITICAL | IServiceManager::DUMP_FLAG_PROTO); // publish gui::ISurfaceComposer, the new AIDL interface - sp composerAIDL = new SurfaceComposerAIDL(flinger); + sp composerAIDL = sp::make(flinger); sm->addService(String16("SurfaceFlingerAIDL"), composerAIDL, false, IServiceManager::DUMP_FLAG_PRIORITY_CRITICAL | IServiceManager::DUMP_FLAG_PROTO); startDisplayService(); // dependency on SF getting registered above if (SurfaceFlinger::setSchedFifo(true) != NO_ERROR) { - ALOGW("Couldn't set to SCHED_FIFO: %s", strerror(errno)); + ALOGW("Failed to set SCHED_FIFO during boot: %s", strerror(errno)); } // run surface flinger in this thread diff --git a/services/surfaceflinger/sysprop/SurfaceFlingerProperties.sysprop b/services/surfaceflinger/sysprop/SurfaceFlingerProperties.sysprop index bcbe21a483c3a56b1192f11b4227230f9791eb45..689f51ad5b7a571422ffedf543fc1d35a526c7fb 100644 --- a/services/surfaceflinger/sysprop/SurfaceFlingerProperties.sysprop +++ b/services/surfaceflinger/sysprop/SurfaceFlingerProperties.sysprop @@ -470,4 +470,18 @@ prop { scope: Public access: Readonly prop_name: "ro.surface_flinger.ignore_hdr_camera_layers" -} \ No newline at end of file +} + +# When enabled, SurfaceFlinger will attempt to clear the per-layer HAL buffer cache slots for +# buffers when they are evicted from the app cache by using additional setLayerBuffer commands. +# Ideally, this behavior would always be enabled to reduce graphics memory consumption. However, +# Some HAL implementations may not support the additional setLayerBuffer commands used to clear +# the cache slots. +prop { + api_name: "clear_slots_with_set_layer_buffer" + type: Boolean + scope: Public + access: Readonly + prop_name: "ro.surface_flinger.clear_slots_with_set_layer_buffer" +} + diff --git a/services/surfaceflinger/sysprop/api/SurfaceFlingerProperties-current.txt b/services/surfaceflinger/sysprop/api/SurfaceFlingerProperties-current.txt index 348a4620384cdfe8dd15df5c3678788acc06dbef..9660ff3de6dca579cc45f4fd106cb317d6613829 100644 --- a/services/surfaceflinger/sysprop/api/SurfaceFlingerProperties-current.txt +++ b/services/surfaceflinger/sysprop/api/SurfaceFlingerProperties-current.txt @@ -1,5 +1,9 @@ props { module: "android.sysprop.SurfaceFlingerProperties" + prop { + api_name: "clear_slots_with_set_layer_buffer" + prop_name: "ro.surface_flinger.clear_slots_with_set_layer_buffer" + } prop { api_name: "color_space_agnostic_dataspace" type: Long diff --git a/services/surfaceflinger/tests/Android.bp b/services/surfaceflinger/tests/Android.bp index 6ecf78b6ff9baa110830edadc14c2d0854392cb0..62b539a8883f58515b7a5306b636454719a62bc4 100644 --- a/services/surfaceflinger/tests/Android.bp +++ b/services/surfaceflinger/tests/Android.bp @@ -23,7 +23,10 @@ package { cc_test { name: "SurfaceFlinger_test", - defaults: ["surfaceflinger_defaults"], + defaults: [ + "android.hardware.graphics.common-ndk_shared", + "surfaceflinger_defaults", + ], test_suites: ["device-tests"], srcs: [ "BootDisplayMode_test.cpp", @@ -34,11 +37,13 @@ cc_test { "DisplayConfigs_test.cpp", "DisplayEventReceiver_test.cpp", "EffectLayer_test.cpp", + "LayerBorder_test.cpp", "InvalidHandles_test.cpp", "LayerCallback_test.cpp", "LayerRenderTypeTransaction_test.cpp", "LayerState_test.cpp", "LayerTransaction_test.cpp", + "LayerTrustedPresentationListener_test.cpp", "LayerTypeAndRenderTypeTransaction_test.cpp", "LayerTypeTransaction_test.cpp", "LayerUpdate_test.cpp", @@ -52,18 +57,16 @@ cc_test { "SetFrameRateOverride_test.cpp", "SetGeometry_test.cpp", "Stress_test.cpp", - "SurfaceInterceptor_test.cpp", + "TextureFiltering_test.cpp", "VirtualDisplay_test.cpp", "WindowInfosListener_test.cpp", ], data: ["SurfaceFlinger_test.filter"], static_libs: [ - "libtrace_proto", "liblayers_proto", "android.hardware.graphics.composer@2.1", ], shared_libs: [ - "android.hardware.graphics.common-V4-ndk", "android.hardware.graphics.common@1.2", "libandroid", "libbase", @@ -128,7 +131,6 @@ cc_test { } subdirs = [ - "fakehwc", "hwc2", "unittests", "utils", diff --git a/services/surfaceflinger/tests/BootDisplayMode_test.cpp b/services/surfaceflinger/tests/BootDisplayMode_test.cpp index d70908e390b5b225e9ef296c48afa4e2e7d183ae..f2874ae0e130be154285a4348e8aa9d156ac623b 100644 --- a/services/surfaceflinger/tests/BootDisplayMode_test.cpp +++ b/services/surfaceflinger/tests/BootDisplayMode_test.cpp @@ -18,6 +18,7 @@ #include +#include #include #include #include @@ -25,27 +26,34 @@ namespace android { +using gui::aidl_utils::statusTFromBinderStatus; + TEST(BootDisplayModeTest, setBootDisplayMode) { - sp sf(ComposerService::getComposerService()); - sp sf_aidl(ComposerServiceAIDL::getComposerService()); - auto displayToken = SurfaceComposerClient::getInternalDisplayToken(); + sp sf(ComposerServiceAIDL::getComposerService()); + + const auto ids = SurfaceComposerClient::getPhysicalDisplayIds(); + ASSERT_FALSE(ids.empty()); + auto displayToken = SurfaceComposerClient::getPhysicalDisplayToken(ids.front()); bool bootModeSupport = false; - binder::Status status = sf_aidl->getBootDisplayModeSupport(&bootModeSupport); - ASSERT_NO_FATAL_FAILURE(status.transactionError()); + binder::Status status = sf->getBootDisplayModeSupport(&bootModeSupport); + ASSERT_NO_FATAL_FAILURE(statusTFromBinderStatus(status)); if (bootModeSupport) { - ASSERT_EQ(NO_ERROR, sf->setBootDisplayMode(displayToken, 0)); + status = sf->setBootDisplayMode(displayToken, 0); + ASSERT_EQ(NO_ERROR, statusTFromBinderStatus(status)); } } TEST(BootDisplayModeTest, clearBootDisplayMode) { sp sf(ComposerServiceAIDL::getComposerService()); - auto displayToken = SurfaceComposerClient::getInternalDisplayToken(); + const auto ids = SurfaceComposerClient::getPhysicalDisplayIds(); + ASSERT_FALSE(ids.empty()); + auto displayToken = SurfaceComposerClient::getPhysicalDisplayToken(ids.front()); bool bootModeSupport = false; binder::Status status = sf->getBootDisplayModeSupport(&bootModeSupport); - ASSERT_NO_FATAL_FAILURE(status.transactionError()); + ASSERT_NO_FATAL_FAILURE(statusTFromBinderStatus(status)); if (bootModeSupport) { status = sf->clearBootDisplayMode(displayToken); - ASSERT_EQ(NO_ERROR, status.transactionError()); + ASSERT_EQ(NO_ERROR, statusTFromBinderStatus(status)); } } diff --git a/services/surfaceflinger/tests/BufferGenerator.cpp b/services/surfaceflinger/tests/BufferGenerator.cpp index 47a150dd359bca2e61cf0b2ae0c6e8ba9bf014fb..d74bd559875a9555e1ebb70264d96471d228570f 100644 --- a/services/surfaceflinger/tests/BufferGenerator.cpp +++ b/services/surfaceflinger/tests/BufferGenerator.cpp @@ -70,12 +70,13 @@ public: consumer->setDefaultBufferSize(width, height); consumer->setDefaultBufferFormat(format); - mBufferItemConsumer = new BufferItemConsumer(consumer, GraphicBuffer::USAGE_HW_TEXTURE); + mBufferItemConsumer = + sp::make(consumer, GraphicBuffer::USAGE_HW_TEXTURE); - mListener = new BufferListener(consumer, callback); + mListener = sp::make(consumer, callback); mBufferItemConsumer->setFrameAvailableListener(mListener); - mSurface = new Surface(producer, true); + mSurface = sp::make(producer, true); } /* Used by Egl manager. The surface is never displayed. */ @@ -364,7 +365,7 @@ status_t BufferGenerator::get(sp* outBuffer, sp* outFence) *outBuffer = mGraphicBuffer; } if (outFence) { - *outFence = new Fence(mFence); + *outFence = sp::make(mFence); } else { close(mFence); } diff --git a/services/surfaceflinger/tests/Credentials_test.cpp b/services/surfaceflinger/tests/Credentials_test.cpp index 434c297cc978d9210d258ff63591c0c8d5008df4..69e9a169e3b490ebd7b9e89b6aa1320dda72ace6 100644 --- a/services/surfaceflinger/tests/Credentials_test.cpp +++ b/services/surfaceflinger/tests/Credentials_test.cpp @@ -18,22 +18,26 @@ #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wconversion" +#include #include -#include +#include #include #include #include #include -#include +#include #include #include #include #include #include "utils/ScreenshotUtils.h" +#include "utils/WindowInfosListenerUtils.h" namespace android { using Transaction = SurfaceComposerClient::Transaction; +using gui::LayerDebugInfo; +using gui::aidl_utils::statusTFromBinderStatus; using ui::ColorMode; namespace { @@ -67,12 +71,30 @@ protected: sp mVirtualSurfaceControl; void initClient() { - mComposerClient = new SurfaceComposerClient; + mComposerClient = sp::make(); ASSERT_EQ(NO_ERROR, mComposerClient->initCheck()); } + static sp getFirstDisplayToken() { + const auto ids = SurfaceComposerClient::getPhysicalDisplayIds(); + if (ids.empty()) { + return nullptr; + } + + return SurfaceComposerClient::getPhysicalDisplayToken(ids.front()); + } + + static std::optional getFirstDisplayId() { + const auto ids = SurfaceComposerClient::getPhysicalDisplayIds(); + if (ids.empty()) { + return std::nullopt; + } + + return ids.front().value; + } + void setupBackgroundSurface() { - mDisplay = SurfaceComposerClient::getInternalDisplayToken(); + mDisplay = getFirstDisplayToken(); ASSERT_FALSE(mDisplay == nullptr); ui::DisplayMode mode; @@ -149,45 +171,39 @@ TEST_F(CredentialsTest, ClientInitTest) { // Anyone else can init the client. { UIDFaker f(AID_BIN); - mComposerClient = new SurfaceComposerClient; + mComposerClient = sp::make(); ASSERT_NO_FATAL_FAILURE(initClient()); } } TEST_F(CredentialsTest, GetBuiltInDisplayAccessTest) { - std::function condition = [] { - return SurfaceComposerClient::getInternalDisplayToken() != nullptr; - }; + std::function condition = [] { return getFirstDisplayToken() != nullptr; }; // Anyone can access display information. - ASSERT_NO_FATAL_FAILURE(checkWithPrivileges(condition, true, true)); + ASSERT_NO_FATAL_FAILURE(checkWithPrivileges(condition, true, false)); } TEST_F(CredentialsTest, AllowedGetterMethodsTest) { // The following methods are tested with a UID that is not root, graphics, // or system, to show that anyone can access them. UIDFaker f(AID_BIN); - const auto display = SurfaceComposerClient::getInternalDisplayToken(); - ASSERT_TRUE(display != nullptr); - - ui::DisplayMode mode; - ASSERT_EQ(NO_ERROR, SurfaceComposerClient::getActiveDisplayMode(display, &mode)); - - Vector modes; + const auto id = getFirstDisplayId(); + ASSERT_TRUE(id); ui::DynamicDisplayInfo info; - ASSERT_EQ(NO_ERROR, SurfaceComposerClient::getDynamicDisplayInfo(display, &info)); + ASSERT_EQ(NO_ERROR, SurfaceComposerClient::getDynamicDisplayInfoFromId(*id, &info)); } TEST_F(CredentialsTest, GetDynamicDisplayInfoTest) { - const auto display = SurfaceComposerClient::getInternalDisplayToken(); + const auto id = getFirstDisplayId(); + ASSERT_TRUE(id); std::function condition = [=]() { ui::DynamicDisplayInfo info; - return SurfaceComposerClient::getDynamicDisplayInfo(display, &info); + return SurfaceComposerClient::getDynamicDisplayInfoFromId(*id, &info); }; ASSERT_NO_FATAL_FAILURE(checkWithPrivileges(condition, NO_ERROR, NO_ERROR)); } TEST_F(CredentialsTest, GetDisplayNativePrimariesTest) { - const auto display = SurfaceComposerClient::getInternalDisplayToken(); + const auto display = getFirstDisplayToken(); std::function condition = [=]() { ui::DisplayPrimaries primaries; return SurfaceComposerClient::getDisplayNativePrimaries(display, primaries); @@ -196,30 +212,19 @@ TEST_F(CredentialsTest, GetDisplayNativePrimariesTest) { } TEST_F(CredentialsTest, SetDesiredDisplayConfigsTest) { - const auto display = SurfaceComposerClient::getInternalDisplayToken(); - ui::DisplayModeId defaultMode; - bool allowGroupSwitching; - float primaryFpsMin; - float primaryFpsMax; - float appRequestFpsMin; - float appRequestFpsMax; - status_t res = - SurfaceComposerClient::getDesiredDisplayModeSpecs(display, &defaultMode, - &allowGroupSwitching, &primaryFpsMin, - &primaryFpsMax, &appRequestFpsMin, - &appRequestFpsMax); + const auto display = getFirstDisplayToken(); + gui::DisplayModeSpecs specs; + status_t res = SurfaceComposerClient::getDesiredDisplayModeSpecs(display, &specs); ASSERT_EQ(res, NO_ERROR); + gui::DisplayModeSpecs setSpecs; std::function condition = [=]() { - return SurfaceComposerClient::setDesiredDisplayModeSpecs(display, defaultMode, - allowGroupSwitching, primaryFpsMin, - primaryFpsMax, appRequestFpsMin, - appRequestFpsMax); + return SurfaceComposerClient::setDesiredDisplayModeSpecs(display, specs); }; ASSERT_NO_FATAL_FAILURE(checkWithPrivileges(condition, NO_ERROR, PERMISSION_DENIED)); } TEST_F(CredentialsTest, SetActiveColorModeTest) { - const auto display = SurfaceComposerClient::getInternalDisplayToken(); + const auto display = getFirstDisplayToken(); std::function condition = [=]() { return SurfaceComposerClient::setActiveColorMode(display, ui::ColorMode::NATIVE); }; @@ -271,7 +276,7 @@ TEST_F(CredentialsTest, CreateDisplayTest) { } TEST_F(CredentialsTest, CaptureTest) { - const auto display = SurfaceComposerClient::getInternalDisplayToken(); + const auto display = getFirstDisplayToken(); std::function condition = [=]() { sp outBuffer; DisplayCaptureArgs captureArgs; @@ -301,39 +306,45 @@ TEST_F(CredentialsTest, CaptureLayersTest) { */ TEST_F(CredentialsTest, GetLayerDebugInfo) { setupBackgroundSurface(); - sp sf(ComposerService::getComposerService()); + sp sf(ComposerServiceAIDL::getComposerService()); // Historically, only root and shell can access the getLayerDebugInfo which // is called when we call dumpsys. I don't see a reason why we should change this. std::vector outLayers; + binder::Status status = binder::Status::ok(); // Check with root. { UIDFaker f(AID_ROOT); - ASSERT_EQ(NO_ERROR, sf->getLayerDebugInfo(&outLayers)); + status = sf->getLayerDebugInfo(&outLayers); + ASSERT_EQ(NO_ERROR, statusTFromBinderStatus(status)); } // Check as a shell. { UIDFaker f(AID_SHELL); - ASSERT_EQ(NO_ERROR, sf->getLayerDebugInfo(&outLayers)); + status = sf->getLayerDebugInfo(&outLayers); + ASSERT_EQ(NO_ERROR, statusTFromBinderStatus(status)); } // Check as anyone else. { UIDFaker f(AID_BIN); - ASSERT_EQ(PERMISSION_DENIED, sf->getLayerDebugInfo(&outLayers)); + status = sf->getLayerDebugInfo(&outLayers); + ASSERT_EQ(PERMISSION_DENIED, statusTFromBinderStatus(status)); } } TEST_F(CredentialsTest, IsWideColorDisplayBasicCorrectness) { - const auto display = SurfaceComposerClient::getInternalDisplayToken(); + const auto display = getFirstDisplayToken(); ASSERT_FALSE(display == nullptr); bool result = false; status_t error = SurfaceComposerClient::isWideColorDisplay(display, &result); ASSERT_EQ(NO_ERROR, error); bool hasWideColorMode = false; + const auto id = getFirstDisplayId(); + ASSERT_TRUE(id); ui::DynamicDisplayInfo info; - SurfaceComposerClient::getDynamicDisplayInfo(display, &info); + SurfaceComposerClient::getDynamicDisplayInfoFromId(*id, &info); const auto& colorModes = info.supportedColorModes; for (ColorMode colorMode : colorModes) { switch (colorMode) { @@ -350,7 +361,7 @@ TEST_F(CredentialsTest, IsWideColorDisplayBasicCorrectness) { } TEST_F(CredentialsTest, IsWideColorDisplayWithPrivileges) { - const auto display = SurfaceComposerClient::getInternalDisplayToken(); + const auto display = getFirstDisplayToken(); ASSERT_FALSE(display == nullptr); std::function condition = [=]() { bool result = false; @@ -360,14 +371,66 @@ TEST_F(CredentialsTest, IsWideColorDisplayWithPrivileges) { } TEST_F(CredentialsTest, GetActiveColorModeBasicCorrectness) { - const auto display = SurfaceComposerClient::getInternalDisplayToken(); - ASSERT_FALSE(display == nullptr); + const auto id = getFirstDisplayId(); + ASSERT_TRUE(id); ui::DynamicDisplayInfo info; - SurfaceComposerClient::getDynamicDisplayInfo(display, &info); + SurfaceComposerClient::getDynamicDisplayInfoFromId(*id, &info); ColorMode colorMode = info.activeColorMode; ASSERT_NE(static_cast(BAD_VALUE), colorMode); } +TEST_F(CredentialsTest, TransactionPermissionTest) { + WindowInfosListenerUtils windowInfosListenerUtils; + std::string name = "Test Layer"; + sp token = sp::make(); + WindowInfo windowInfo; + windowInfo.name = name; + windowInfo.token = token; + sp surfaceControl = + mComposerClient->createSurface(String8(name.c_str()), 100, 100, PIXEL_FORMAT_RGBA_8888, + ISurfaceComposerClient::eFXSurfaceBufferState); + const Rect crop(0, 0, 100, 100); + { + UIDFaker f(AID_SYSTEM); + Transaction() + .setLayerStack(surfaceControl, ui::DEFAULT_LAYER_STACK) + .show(surfaceControl) + .setLayer(surfaceControl, INT32_MAX - 1) + .setCrop(surfaceControl, crop) + .setInputWindowInfo(surfaceControl, windowInfo) + .apply(); + } + + // Called from non privileged process + Transaction().setTrustedOverlay(surfaceControl, true); + { + UIDFaker f(AID_SYSTEM); + auto windowIsPresentAndNotTrusted = [&](const std::vector& windowInfos) { + auto foundWindowInfo = + WindowInfosListenerUtils::findMatchingWindowInfo(windowInfo, windowInfos); + if (!foundWindowInfo) { + return false; + } + return !foundWindowInfo->inputConfig.test(WindowInfo::InputConfig::TRUSTED_OVERLAY); + }; + windowInfosListenerUtils.waitForWindowInfosPredicate(windowIsPresentAndNotTrusted); + } + + { + UIDFaker f(AID_SYSTEM); + Transaction().setTrustedOverlay(surfaceControl, true); + auto windowIsPresentAndTrusted = [&](const std::vector& windowInfos) { + auto foundWindowInfo = + WindowInfosListenerUtils::findMatchingWindowInfo(windowInfo, windowInfos); + if (!foundWindowInfo) { + return false; + } + return foundWindowInfo->inputConfig.test(WindowInfo::InputConfig::TRUSTED_OVERLAY); + }; + windowInfosListenerUtils.waitForWindowInfosPredicate(windowIsPresentAndTrusted); + } +} + } // namespace android // TODO(b/129481165): remove the #pragma below and fix conversion issues diff --git a/services/surfaceflinger/tests/DisplayConfigs_test.cpp b/services/surfaceflinger/tests/DisplayConfigs_test.cpp index c58fe4831c1883e17c72386ae9502658157297f2..4be961bda1356782d17ec33645ff8f46d2b581e4 100644 --- a/services/surfaceflinger/tests/DisplayConfigs_test.cpp +++ b/services/surfaceflinger/tests/DisplayConfigs_test.cpp @@ -39,105 +39,71 @@ namespace android { */ class RefreshRateRangeTest : public ::testing::Test { private: - ui::DisplayModeId initialDefaultMode; - bool initialAllowGroupSwitching; - float initialPrimaryMin; - float initialPrimaryMax; - float initialAppRequestMin; - float initialAppRequestMax; + gui::DisplayModeSpecs mSpecs; protected: void SetUp() override { - mDisplayToken = SurfaceComposerClient::getInternalDisplayToken(); - status_t res = - SurfaceComposerClient::getDesiredDisplayModeSpecs(mDisplayToken, - &initialDefaultMode, - &initialAllowGroupSwitching, - &initialPrimaryMin, - &initialPrimaryMax, - &initialAppRequestMin, - &initialAppRequestMax); + const auto ids = SurfaceComposerClient::getPhysicalDisplayIds(); + ASSERT_FALSE(ids.empty()); + mDisplayId = ids.front().value; + mDisplayToken = SurfaceComposerClient::getPhysicalDisplayToken(ids.front()); + status_t res = SurfaceComposerClient::getDesiredDisplayModeSpecs(mDisplayToken, &mSpecs); ASSERT_EQ(res, NO_ERROR); } void TearDown() override { - status_t res = - SurfaceComposerClient::setDesiredDisplayModeSpecs(mDisplayToken, initialDefaultMode, - initialAllowGroupSwitching, - initialPrimaryMin, - initialPrimaryMax, - initialAppRequestMin, - initialAppRequestMax); + status_t res = SurfaceComposerClient::setDesiredDisplayModeSpecs(mDisplayToken, mSpecs); ASSERT_EQ(res, NO_ERROR); } void testSetAllowGroupSwitching(bool allowGroupSwitching); sp mDisplayToken; + uint64_t mDisplayId; }; TEST_F(RefreshRateRangeTest, setAllConfigs) { ui::DynamicDisplayInfo info; - status_t res = SurfaceComposerClient::getDynamicDisplayInfo(mDisplayToken, &info); + status_t res = + SurfaceComposerClient::getDynamicDisplayInfoFromId(static_cast(mDisplayId), + &info); const auto& modes = info.supportedDisplayModes; ASSERT_EQ(res, NO_ERROR); ASSERT_GT(modes.size(), 0); + gui::DisplayModeSpecs setSpecs; + setSpecs.allowGroupSwitching = false; for (size_t i = 0; i < modes.size(); i++) { - res = SurfaceComposerClient::setDesiredDisplayModeSpecs(mDisplayToken, modes[i].id, false, - modes[i].refreshRate, - modes[i].refreshRate, - modes[i].refreshRate, - modes[i].refreshRate); + setSpecs.defaultMode = modes[i].id; + setSpecs.primaryRanges.physical.min = modes[i].refreshRate; + setSpecs.primaryRanges.physical.max = modes[i].refreshRate; + setSpecs.primaryRanges.render = setSpecs.primaryRanges.physical; + setSpecs.appRequestRanges = setSpecs.primaryRanges; + res = SurfaceComposerClient::setDesiredDisplayModeSpecs(mDisplayToken, setSpecs); ASSERT_EQ(res, NO_ERROR); - ui::DisplayModeId defaultConfig; - bool allowGroupSwitching; - float primaryRefreshRateMin; - float primaryRefreshRateMax; - float appRequestRefreshRateMin; - float appRequestRefreshRateMax; - res = SurfaceComposerClient::getDesiredDisplayModeSpecs(mDisplayToken, &defaultConfig, - &allowGroupSwitching, - &primaryRefreshRateMin, - &primaryRefreshRateMax, - &appRequestRefreshRateMin, - &appRequestRefreshRateMax); + gui::DisplayModeSpecs getSpecs; + res = SurfaceComposerClient::getDesiredDisplayModeSpecs(mDisplayToken, &getSpecs); ASSERT_EQ(res, NO_ERROR); - ASSERT_EQ(defaultConfig, i); - ASSERT_EQ(allowGroupSwitching, false); - ASSERT_EQ(primaryRefreshRateMin, modes[i].refreshRate); - ASSERT_EQ(primaryRefreshRateMax, modes[i].refreshRate); - ASSERT_EQ(appRequestRefreshRateMin, modes[i].refreshRate); - ASSERT_EQ(appRequestRefreshRateMax, modes[i].refreshRate); + ASSERT_EQ(setSpecs, getSpecs); } } void RefreshRateRangeTest::testSetAllowGroupSwitching(bool allowGroupSwitching) { - status_t res = - SurfaceComposerClient::setDesiredDisplayModeSpecs(mDisplayToken, 0, allowGroupSwitching, - 0.f, 90.f, 0.f, 90.f); + gui::DisplayModeSpecs setSpecs; + setSpecs.defaultMode = 0; + setSpecs.allowGroupSwitching = allowGroupSwitching; + setSpecs.primaryRanges.physical.min = 0; + setSpecs.primaryRanges.physical.max = 90; + setSpecs.primaryRanges.render = setSpecs.primaryRanges.physical; + setSpecs.appRequestRanges = setSpecs.primaryRanges; + + status_t res = SurfaceComposerClient::setDesiredDisplayModeSpecs(mDisplayToken, setSpecs); ASSERT_EQ(res, NO_ERROR); - ui::DisplayModeId defaultConfig; - bool newAllowGroupSwitching; - float primaryRefreshRateMin; - float primaryRefreshRateMax; - float appRequestRefreshRateMin; - float appRequestRefreshRateMax; - - res = SurfaceComposerClient::getDesiredDisplayModeSpecs(mDisplayToken, &defaultConfig, - &newAllowGroupSwitching, - &primaryRefreshRateMin, - &primaryRefreshRateMax, - &appRequestRefreshRateMin, - &appRequestRefreshRateMax); + gui::DisplayModeSpecs getSpecs; + res = SurfaceComposerClient::getDesiredDisplayModeSpecs(mDisplayToken, &getSpecs); ASSERT_EQ(res, NO_ERROR); - ASSERT_EQ(defaultConfig, 0); - ASSERT_EQ(newAllowGroupSwitching, allowGroupSwitching); - ASSERT_EQ(primaryRefreshRateMin, 0.f); - ASSERT_EQ(primaryRefreshRateMax, 90.f); - ASSERT_EQ(appRequestRefreshRateMin, 0.f); - ASSERT_EQ(appRequestRefreshRateMax, 90.f); + ASSERT_EQ(setSpecs, getSpecs); } TEST_F(RefreshRateRangeTest, setAllowGroupSwitching) { diff --git a/services/surfaceflinger/tests/DisplayEventReceiver_test.cpp b/services/surfaceflinger/tests/DisplayEventReceiver_test.cpp index 0e54664f771a3365023d7945de155f43d210e481..4c26017241a3cd1fc0c441f78d5a1f891536d996 100644 --- a/services/surfaceflinger/tests/DisplayEventReceiver_test.cpp +++ b/services/surfaceflinger/tests/DisplayEventReceiver_test.cpp @@ -33,10 +33,16 @@ TEST_F(DisplayEventReceiverTest, getLatestVsyncEventData) { const VsyncEventData& vsyncEventData = parcelableVsyncEventData.vsync; EXPECT_NE(std::numeric_limits::max(), vsyncEventData.preferredFrameTimelineIndex); + EXPECT_GT(static_cast(vsyncEventData.frameTimelinesLength), 0) + << "Frame timelines length should be greater than 0"; + EXPECT_LE(static_cast(vsyncEventData.frameTimelinesLength), + VsyncEventData::kFrameTimelinesCapacity) + << "Frame timelines length should not exceed max capacity"; EXPECT_GT(vsyncEventData.frameTimelines[0].deadlineTimestamp, now) << "Deadline timestamp should be greater than frame time"; - for (size_t i = 0; i < VsyncEventData::kFrameTimelinesLength; i++) { - EXPECT_NE(FrameTimelineInfo::INVALID_VSYNC_ID, vsyncEventData.frameTimelines[i].vsyncId); + for (size_t i = 0; i < vsyncEventData.frameTimelinesLength; i++) { + EXPECT_NE(gui::FrameTimelineInfo::INVALID_VSYNC_ID, + vsyncEventData.frameTimelines[i].vsyncId); EXPECT_GT(vsyncEventData.frameTimelines[i].expectedPresentationTime, vsyncEventData.frameTimelines[i].deadlineTimestamp) << "Expected vsync timestamp should be greater than deadline"; @@ -51,4 +57,4 @@ TEST_F(DisplayEventReceiverTest, getLatestVsyncEventData) { } } -} // namespace android \ No newline at end of file +} // namespace android diff --git a/services/surfaceflinger/tests/EffectLayer_test.cpp b/services/surfaceflinger/tests/EffectLayer_test.cpp index 9fa0452915e5a3abaac6eccd098d9cb47599bb3d..52aa5027433f468d0121b183579eb50fe0650b4b 100644 --- a/services/surfaceflinger/tests/EffectLayer_test.cpp +++ b/services/surfaceflinger/tests/EffectLayer_test.cpp @@ -28,7 +28,9 @@ protected: LayerTransactionTest::SetUp(); ASSERT_EQ(NO_ERROR, mClient->initCheck()); - const auto display = SurfaceComposerClient::getInternalDisplayToken(); + const auto ids = SurfaceComposerClient::getPhysicalDisplayIds(); + ASSERT_FALSE(ids.empty()); + const auto display = SurfaceComposerClient::getPhysicalDisplayToken(ids.front()); ASSERT_FALSE(display == nullptr); mParentLayer = createColorLayer("Parent layer", Color::RED); @@ -177,7 +179,9 @@ TEST_F(EffectLayerTest, BlurEffectLayerIsVisible) { } TEST_F(EffectLayerTest, EffectLayerWithColorNoCrop) { - const auto display = SurfaceComposerClient::getInternalDisplayToken(); + const auto ids = SurfaceComposerClient::getPhysicalDisplayIds(); + ASSERT_FALSE(ids.empty()); + const auto display = SurfaceComposerClient::getPhysicalDisplayToken(ids.front()); ui::DisplayMode mode; ASSERT_EQ(NO_ERROR, SurfaceComposerClient::getActiveDisplayMode(display, &mode)); const ui::Size& resolution = mode.resolution; diff --git a/services/surfaceflinger/tests/IPC_test.cpp b/services/surfaceflinger/tests/IPC_test.cpp index ce94dab9070f6566e979b1f0021a8496d42f87cb..40a5d5757d4078c279d853d0de638d53693261db 100644 --- a/services/surfaceflinger/tests/IPC_test.cpp +++ b/services/surfaceflinger/tests/IPC_test.cpp @@ -136,7 +136,7 @@ public: } status_t initClient() override { - mClient = new SurfaceComposerClient; + mClient = sp::make(); auto err = mClient->initCheck(); return err; } @@ -221,10 +221,12 @@ public: ProcessState::self()->startThreadPool(); } void SetUp() { - mClient = new SurfaceComposerClient; + mClient = sp::make(); ASSERT_EQ(NO_ERROR, mClient->initCheck()); - mPrimaryDisplay = mClient->getInternalDisplayToken(); + const auto ids = SurfaceComposerClient::getPhysicalDisplayIds(); + ASSERT_FALSE(ids.empty()); + mPrimaryDisplay = SurfaceComposerClient::getPhysicalDisplayToken(ids.front()); ui::DisplayMode mode; mClient->getActiveDisplayMode(mPrimaryDisplay, &mode); mDisplayWidth = mode.resolution.getWidth(); diff --git a/services/surfaceflinger/tests/InvalidHandles_test.cpp b/services/surfaceflinger/tests/InvalidHandles_test.cpp index d192a2d83d3eeb4a07740eafa54946b9a7a1bedc..666ce76c6bbe5a37b1ee464091d3e41172b1fdc5 100644 --- a/services/surfaceflinger/tests/InvalidHandles_test.cpp +++ b/services/surfaceflinger/tests/InvalidHandles_test.cpp @@ -42,13 +42,13 @@ protected: sp mScc; sp mNotSc; void SetUp() override { - mScc = new SurfaceComposerClient; + mScc = sp::make(); ASSERT_EQ(NO_ERROR, mScc->initCheck()); mNotSc = makeNotSurfaceControl(); } sp makeNotSurfaceControl() { - return new SurfaceControl(mScc, new NotALayer(), nullptr, true); + return sp::make(mScc, sp::make(), 1, "#1"); } }; @@ -64,4 +64,4 @@ TEST_F(InvalidHandleTest, captureLayersInvalidHandle) { } // namespace android // TODO(b/129481165): remove the #pragma below and fix conversion issues -#pragma clang diagnostic pop // ignored "-Wconversion" \ No newline at end of file +#pragma clang diagnostic pop // ignored "-Wconversion" diff --git a/services/surfaceflinger/tests/LayerBorder_test.cpp b/services/surfaceflinger/tests/LayerBorder_test.cpp new file mode 100644 index 0000000000000000000000000000000000000000..00e134b4d28623d4fb16c90fd93ff7b0c3ec8d47 --- /dev/null +++ b/services/surfaceflinger/tests/LayerBorder_test.cpp @@ -0,0 +1,287 @@ +/* + * 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/LayerCallback_test.cpp b/services/surfaceflinger/tests/LayerCallback_test.cpp index 219db8c1486f8554bffd5abce02980ce4981639e..79886bde4501e49039dd22b4f7cc635b5f024c71 100644 --- a/services/surfaceflinger/tests/LayerCallback_test.cpp +++ b/services/surfaceflinger/tests/LayerCallback_test.cpp @@ -51,7 +51,7 @@ public: LayerTransactionTest::TearDown(); } - virtual sp createBufferStateLayer() { + virtual sp createLayerWithBuffer() { return createLayer(mClient, "test", 0, 0, ISurfaceComposerClient::eFXSurfaceBufferState); } @@ -164,7 +164,7 @@ public: TEST_F(LayerCallbackTest, BufferColor) { sp layer; - ASSERT_NO_FATAL_FAILURE(layer = createBufferStateLayer()); + ASSERT_NO_FATAL_FAILURE(layer = createLayerWithBuffer()); Transaction transaction; CallbackHelper callback; @@ -183,7 +183,7 @@ TEST_F(LayerCallbackTest, BufferColor) { TEST_F(LayerCallbackTest, NoBufferNoColor) { sp layer; - ASSERT_NO_FATAL_FAILURE(layer = createBufferStateLayer()); + ASSERT_NO_FATAL_FAILURE(layer = createLayerWithBuffer()); Transaction transaction; CallbackHelper callback; @@ -206,7 +206,7 @@ TEST_F(LayerCallbackTest, NoBufferNoColor) { TEST_F(LayerCallbackTest, BufferNoColor) { sp layer; - ASSERT_NO_FATAL_FAILURE(layer = createBufferStateLayer()); + ASSERT_NO_FATAL_FAILURE(layer = createLayerWithBuffer()); Transaction transaction; CallbackHelper callback; @@ -228,7 +228,7 @@ TEST_F(LayerCallbackTest, BufferNoColor) { TEST_F(LayerCallbackTest, NoBufferColor) { sp layer; - ASSERT_NO_FATAL_FAILURE(layer = createBufferStateLayer()); + ASSERT_NO_FATAL_FAILURE(layer = createLayerWithBuffer()); Transaction transaction; CallbackHelper callback; @@ -266,7 +266,7 @@ TEST_F(LayerCallbackTest, NoStateChange) { TEST_F(LayerCallbackTest, OffScreen) { sp layer; - ASSERT_NO_FATAL_FAILURE(layer = createBufferStateLayer()); + ASSERT_NO_FATAL_FAILURE(layer = createLayerWithBuffer()); Transaction transaction; CallbackHelper callback; @@ -288,8 +288,8 @@ TEST_F(LayerCallbackTest, OffScreen) { TEST_F(LayerCallbackTest, MergeBufferNoColor) { sp layer1, layer2; - ASSERT_NO_FATAL_FAILURE(layer1 = createBufferStateLayer()); - ASSERT_NO_FATAL_FAILURE(layer2 = createBufferStateLayer()); + ASSERT_NO_FATAL_FAILURE(layer1 = createLayerWithBuffer()); + ASSERT_NO_FATAL_FAILURE(layer2 = createLayerWithBuffer()); Transaction transaction1, transaction2; CallbackHelper callback1, callback2; @@ -322,8 +322,8 @@ TEST_F(LayerCallbackTest, MergeBufferNoColor) { TEST_F(LayerCallbackTest, MergeNoBufferColor) { sp layer1, layer2; - ASSERT_NO_FATAL_FAILURE(layer1 = createBufferStateLayer()); - ASSERT_NO_FATAL_FAILURE(layer2 = createBufferStateLayer()); + ASSERT_NO_FATAL_FAILURE(layer1 = createLayerWithBuffer()); + ASSERT_NO_FATAL_FAILURE(layer2 = createLayerWithBuffer()); Transaction transaction1, transaction2; CallbackHelper callback1, callback2; @@ -357,8 +357,8 @@ TEST_F(LayerCallbackTest, MergeNoBufferColor) { TEST_F(LayerCallbackTest, MergeOneBufferOneColor) { sp layer1, layer2; - ASSERT_NO_FATAL_FAILURE(layer1 = createBufferStateLayer()); - ASSERT_NO_FATAL_FAILURE(layer2 = createBufferStateLayer()); + ASSERT_NO_FATAL_FAILURE(layer1 = createLayerWithBuffer()); + ASSERT_NO_FATAL_FAILURE(layer2 = createLayerWithBuffer()); Transaction transaction1, transaction2; CallbackHelper callback1, callback2; @@ -392,8 +392,8 @@ TEST_F(LayerCallbackTest, MergeOneBufferOneColor) { } TEST_F(LayerCallbackTest, Merge_SameCallback) { sp layer1, layer2; - ASSERT_NO_FATAL_FAILURE(layer1 = createBufferStateLayer()); - ASSERT_NO_FATAL_FAILURE(layer2 = createBufferStateLayer()); + ASSERT_NO_FATAL_FAILURE(layer1 = createLayerWithBuffer()); + ASSERT_NO_FATAL_FAILURE(layer2 = createLayerWithBuffer()); Transaction transaction1, transaction2; CallbackHelper callback; @@ -418,7 +418,7 @@ TEST_F(LayerCallbackTest, Merge_SameCallback) { TEST_F(LayerCallbackTest, Merge_SameLayer) { sp layer; - ASSERT_NO_FATAL_FAILURE(layer = createBufferStateLayer()); + ASSERT_NO_FATAL_FAILURE(layer = createLayerWithBuffer()); Transaction transaction1, transaction2; CallbackHelper callback1, callback2; @@ -442,8 +442,8 @@ TEST_F(LayerCallbackTest, Merge_SameLayer) { } TEST_F(LayerCallbackTest, Merge_DifferentClients) { - sp client1(new SurfaceComposerClient), - client2(new SurfaceComposerClient); + sp client1(sp::make()), + client2(sp::make()); ASSERT_EQ(NO_ERROR, client1->initCheck()) << "failed to create SurfaceComposerClient"; ASSERT_EQ(NO_ERROR, client2->initCheck()) << "failed to create SurfaceComposerClient"; @@ -485,7 +485,7 @@ TEST_F(LayerCallbackTest, Merge_DifferentClients) { TEST_F(LayerCallbackTest, MultipleTransactions) { sp layer; - ASSERT_NO_FATAL_FAILURE(layer = createBufferStateLayer()); + ASSERT_NO_FATAL_FAILURE(layer = createLayerWithBuffer()); Transaction transaction; CallbackHelper callback; @@ -510,7 +510,7 @@ TEST_F(LayerCallbackTest, MultipleTransactions) { TEST_F(LayerCallbackTest, MultipleTransactions_NoStateChange) { sp layer; - ASSERT_NO_FATAL_FAILURE(layer = createBufferStateLayer()); + ASSERT_NO_FATAL_FAILURE(layer = createLayerWithBuffer()); Transaction transaction; CallbackHelper callback; @@ -541,7 +541,7 @@ TEST_F(LayerCallbackTest, MultipleTransactions_NoStateChange) { TEST_F(LayerCallbackTest, MultipleTransactions_SameStateChange) { sp layer; - ASSERT_NO_FATAL_FAILURE(layer = createBufferStateLayer()); + ASSERT_NO_FATAL_FAILURE(layer = createLayerWithBuffer()); Transaction transaction; CallbackHelper callback; @@ -579,8 +579,8 @@ TEST_F(LayerCallbackTest, MultipleTransactions_SameStateChange) { TEST_F(LayerCallbackTest, MultipleTransactions_Merge) { sp layer1, layer2; - ASSERT_NO_FATAL_FAILURE(layer1 = createBufferStateLayer()); - ASSERT_NO_FATAL_FAILURE(layer2 = createBufferStateLayer()); + ASSERT_NO_FATAL_FAILURE(layer1 = createLayerWithBuffer()); + ASSERT_NO_FATAL_FAILURE(layer2 = createLayerWithBuffer()); Transaction transaction1, transaction2; CallbackHelper callback1, callback2; @@ -620,8 +620,8 @@ TEST_F(LayerCallbackTest, MultipleTransactions_Merge) { } TEST_F(LayerCallbackTest, MultipleTransactions_Merge_DifferentClients) { - sp client1(new SurfaceComposerClient), - client2(new SurfaceComposerClient); + sp client1(sp::make()), + client2(sp::make()); ASSERT_EQ(NO_ERROR, client1->initCheck()) << "failed to create SurfaceComposerClient"; ASSERT_EQ(NO_ERROR, client2->initCheck()) << "failed to create SurfaceComposerClient"; @@ -669,8 +669,8 @@ TEST_F(LayerCallbackTest, MultipleTransactions_Merge_DifferentClients) { } TEST_F(LayerCallbackTest, MultipleTransactions_Merge_DifferentClients_NoStateChange) { - sp client1(new SurfaceComposerClient), - client2(new SurfaceComposerClient); + sp client1(sp::make()), + client2(sp::make()); ASSERT_EQ(NO_ERROR, client1->initCheck()) << "failed to create SurfaceComposerClient"; ASSERT_EQ(NO_ERROR, client2->initCheck()) << "failed to create SurfaceComposerClient"; @@ -730,8 +730,8 @@ TEST_F(LayerCallbackTest, MultipleTransactions_Merge_DifferentClients_NoStateCha } TEST_F(LayerCallbackTest, MultipleTransactions_Merge_DifferentClients_SameStateChange) { - sp client1(new SurfaceComposerClient), - client2(new SurfaceComposerClient); + sp client1(sp::make()), + client2(sp::make()); ASSERT_EQ(NO_ERROR, client1->initCheck()) << "failed to create SurfaceComposerClient"; ASSERT_EQ(NO_ERROR, client2->initCheck()) << "failed to create SurfaceComposerClient"; @@ -799,7 +799,7 @@ TEST_F(LayerCallbackTest, MultipleTransactions_Merge_DifferentClients_SameStateC // TODO (b/183181768): Fix & re-enable TEST_F(LayerCallbackTest, DISABLED_MultipleTransactions_SingleFrame) { sp layer; - ASSERT_NO_FATAL_FAILURE(layer = createBufferStateLayer()); + ASSERT_NO_FATAL_FAILURE(layer = createLayerWithBuffer()); Transaction transaction; CallbackHelper callback; @@ -823,7 +823,7 @@ TEST_F(LayerCallbackTest, DISABLED_MultipleTransactions_SingleFrame) { TEST_F(LayerCallbackTest, MultipleTransactions_SingleFrame_NoStateChange) { sp layer; - ASSERT_NO_FATAL_FAILURE(layer = createBufferStateLayer()); + ASSERT_NO_FATAL_FAILURE(layer = createLayerWithBuffer()); // Normal call to set up test Transaction transaction; @@ -858,7 +858,7 @@ TEST_F(LayerCallbackTest, MultipleTransactions_SingleFrame_NoStateChange) { TEST_F(LayerCallbackTest, MultipleTransactions_SingleFrame_SameStateChange) { sp layer; - ASSERT_NO_FATAL_FAILURE(layer = createBufferStateLayer()); + ASSERT_NO_FATAL_FAILURE(layer = createLayerWithBuffer()); // Normal call to set up test Transaction transaction; @@ -901,7 +901,7 @@ TEST_F(LayerCallbackTest, MultipleTransactions_SingleFrame_SameStateChange) { TEST_F(LayerCallbackTest, DesiredPresentTime) { sp layer; - ASSERT_NO_FATAL_FAILURE(layer = createBufferStateLayer()); + ASSERT_NO_FATAL_FAILURE(layer = createLayerWithBuffer()); Transaction transaction; CallbackHelper callback; @@ -925,7 +925,7 @@ TEST_F(LayerCallbackTest, DesiredPresentTime) { TEST_F(LayerCallbackTest, DesiredPresentTime_Multiple) { sp layer; - ASSERT_NO_FATAL_FAILURE(layer = createBufferStateLayer()); + ASSERT_NO_FATAL_FAILURE(layer = createLayerWithBuffer()); Transaction transaction; CallbackHelper callback1; @@ -971,7 +971,7 @@ TEST_F(LayerCallbackTest, DesiredPresentTime_Multiple) { // TODO (b/183181768): Fix & re-enable TEST_F(LayerCallbackTest, DISABLED_DesiredPresentTime_OutOfOrder) { sp layer; - ASSERT_NO_FATAL_FAILURE(layer = createBufferStateLayer()); + ASSERT_NO_FATAL_FAILURE(layer = createLayerWithBuffer()); Transaction transaction; CallbackHelper callback1; @@ -1015,7 +1015,7 @@ TEST_F(LayerCallbackTest, DISABLED_DesiredPresentTime_OutOfOrder) { TEST_F(LayerCallbackTest, DesiredPresentTime_Past) { sp layer; - ASSERT_NO_FATAL_FAILURE(layer = createBufferStateLayer()); + ASSERT_NO_FATAL_FAILURE(layer = createLayerWithBuffer()); Transaction transaction; CallbackHelper callback; @@ -1039,7 +1039,7 @@ TEST_F(LayerCallbackTest, DesiredPresentTime_Past) { TEST_F(LayerCallbackTest, ExpectedPresentTime) { sp layer; - ASSERT_NO_FATAL_FAILURE(layer = createBufferStateLayer()); + ASSERT_NO_FATAL_FAILURE(layer = createLayerWithBuffer()); Transaction transaction; CallbackHelper callback; @@ -1050,7 +1050,10 @@ TEST_F(LayerCallbackTest, ExpectedPresentTime) { } const Vsync vsync = waitForNextVsync(); - transaction.setFrameTimelineInfo({vsync.vsyncId, 0}); + FrameTimelineInfo ftInfo; + ftInfo.vsyncId = vsync.vsyncId; + ftInfo.inputEventId = 0; + transaction.setFrameTimelineInfo(ftInfo); transaction.apply(); ExpectedResult expected; @@ -1062,8 +1065,8 @@ TEST_F(LayerCallbackTest, ExpectedPresentTime) { // b202394221 TEST_F(LayerCallbackTest, EmptyBufferStateChanges) { sp bufferLayer, emptyBufferLayer; - ASSERT_NO_FATAL_FAILURE(bufferLayer = createBufferStateLayer()); - ASSERT_NO_FATAL_FAILURE(emptyBufferLayer = createBufferStateLayer()); + ASSERT_NO_FATAL_FAILURE(bufferLayer = createLayerWithBuffer()); + ASSERT_NO_FATAL_FAILURE(emptyBufferLayer = createLayerWithBuffer()); Transaction transaction; CallbackHelper callback; @@ -1117,7 +1120,7 @@ TEST_F(LayerCallbackTest, DISABLED_NonBufferLayerStateChanges) { TEST_F(LayerCallbackTest, CommitCallbackOffscreenLayer) { sp layer; - ASSERT_NO_FATAL_FAILURE(layer = createBufferStateLayer()); + ASSERT_NO_FATAL_FAILURE(layer = createLayerWithBuffer()); sp offscreenLayer = createSurface(mClient, "Offscreen Layer", 0, 0, PIXEL_FORMAT_RGBA_8888, ISurfaceComposerClient::eFXSurfaceBufferState, layer.get()); @@ -1148,7 +1151,7 @@ TEST_F(LayerCallbackTest, CommitCallbackOffscreenLayer) { TEST_F(LayerCallbackTest, TransactionCommittedCallback_BSL) { sp layer; - ASSERT_NO_FATAL_FAILURE(layer = createBufferStateLayer()); + ASSERT_NO_FATAL_FAILURE(layer = createLayerWithBuffer()); Transaction transaction; CallbackHelper callback; @@ -1221,4 +1224,75 @@ TEST_F(LayerCallbackTest, TransactionCommittedCallback_NoLayer) { EXPECT_NO_FATAL_FAILURE(waitForCallback(callback, expected, true)); } +TEST_F(LayerCallbackTest, SetNullBuffer) { + sp layer; + ASSERT_NO_FATAL_FAILURE(layer = createLayerWithBuffer()); + + Transaction transaction; + CallbackHelper callback; + int err = fillTransaction(transaction, &callback, layer, /*setBuffer=*/true, + /*setBackgroundColor=*/false); + if (err) { + GTEST_SUCCEED() << "test not supported"; + return; + } + transaction.apply(); + + { + ExpectedResult expected; + expected.addSurface(ExpectedResult::Transaction::PRESENTED, layer, + ExpectedResult::Buffer::ACQUIRED, + ExpectedResult::PreviousBuffer::NOT_RELEASED); + EXPECT_NO_FATAL_FAILURE(waitForCallback(callback, expected, true)); + } + + transaction.setBuffer(layer, nullptr); + transaction.addTransactionCompletedCallback(callback.function, callback.getContext()); + transaction.apply(); + + { + ExpectedResult expected; + expected.addSurface(ExpectedResult::Transaction::PRESENTED, layer, + ExpectedResult::Buffer::ACQUIRED_NULL, + ExpectedResult::PreviousBuffer::RELEASED); + EXPECT_NO_FATAL_FAILURE(waitForCallback(callback, expected, true)); + } + + err = fillTransaction(transaction, &callback, layer, /*setBuffer=*/true, + /*setBackgroundColor=*/false); + if (err) { + GTEST_SUCCEED() << "test not supported"; + return; + } + + transaction.apply(); + + { + ExpectedResult expected; + expected.addSurface(ExpectedResult::Transaction::PRESENTED, layer, + ExpectedResult::Buffer::ACQUIRED, + ExpectedResult::PreviousBuffer::NOT_RELEASED); + EXPECT_NO_FATAL_FAILURE(waitForCallback(callback, expected, true)); + } +} + +TEST_F(LayerCallbackTest, SetNullBufferOnLayerWithoutBuffer) { + sp layer; + ASSERT_NO_FATAL_FAILURE(layer = createLayerWithBuffer()); + + Transaction transaction; + transaction.setBuffer(layer, nullptr); + CallbackHelper callback; + transaction.addTransactionCompletedCallback(callback.function, callback.getContext()); + transaction.apply(); + + { + ExpectedResult expected; + expected.addSurface(ExpectedResult::Transaction::NOT_PRESENTED, layer, + ExpectedResult::Buffer::NOT_ACQUIRED, + ExpectedResult::PreviousBuffer::NOT_RELEASED); + EXPECT_NO_FATAL_FAILURE(waitForCallback(callback, expected, true)); + } +} + } // namespace android diff --git a/services/surfaceflinger/tests/LayerRenderTypeTransaction_test.cpp b/services/surfaceflinger/tests/LayerRenderTypeTransaction_test.cpp index 0e2bc3df0eb6e0699cc9d66b6edf403ee5cba362..b8068f79a4360ae9dd5be124aed84607e0c2c77e 100644 --- a/services/surfaceflinger/tests/LayerRenderTypeTransaction_test.cpp +++ b/services/surfaceflinger/tests/LayerRenderTypeTransaction_test.cpp @@ -328,7 +328,7 @@ TEST_P(LayerRenderTypeTransactionTest, SetTransparentRegionHintBasic_BufferState layer = createLayer("test", 32, 32, ISurfaceComposerClient::eFXSurfaceBufferState)); sp buffer = - new GraphicBuffer(32, 32, PIXEL_FORMAT_RGBA_8888, 1, kUsageFlags, "test"); + sp::make(32u, 32u, PIXEL_FORMAT_RGBA_8888, 1u, kUsageFlags, "test"); ASSERT_NO_FATAL_FAILURE( TransactionUtils::fillGraphicBufferColor(buffer, top, Color::TRANSPARENT)); @@ -352,7 +352,7 @@ TEST_P(LayerRenderTypeTransactionTest, SetTransparentRegionHintBasic_BufferState shot->expectColor(bottom, Color::BLACK); } - buffer = new GraphicBuffer(32, 32, PIXEL_FORMAT_RGBA_8888, 1, kUsageFlags, "test"); + buffer = sp::make(32u, 32u, PIXEL_FORMAT_RGBA_8888, 1u, kUsageFlags, "test"); ASSERT_NO_FATAL_FAILURE(TransactionUtils::fillGraphicBufferColor(buffer, top, Color::RED)); ASSERT_NO_FATAL_FAILURE( @@ -399,7 +399,7 @@ TEST_P(LayerRenderTypeTransactionTest, SetTransparentRegionHintOutOfBounds_Buffe .apply(); ASSERT_NO_FATAL_FAILURE( fillBufferQueueLayerColor(layerTransparent, Color::TRANSPARENT, 32, 32)); - ASSERT_NO_FATAL_FAILURE(fillBufferStateLayerColor(layerR, Color::RED, 32, 32)); + ASSERT_NO_FATAL_FAILURE(fillBufferLayerColor(layerR, Color::RED, 32, 32)); getScreenCapture()->expectColor(Rect(16, 16, 48, 48), Color::RED); } @@ -482,7 +482,7 @@ TEST_P(LayerRenderTypeTransactionTest, SetColorBasic) { } } -// RED: Color layer base color and BufferQueueLayer/BufferStateLayer fill +// RED: Color layer base color and Layer buffer fill // BLUE: prior background color // GREEN: final background color // BLACK: no color or fill @@ -516,7 +516,7 @@ void LayerRenderTypeTransactionTest::setBackgroundColorHelper(uint32_t layerType case ISurfaceComposerClient::eFXSurfaceBufferState: ASSERT_NO_FATAL_FAILURE(layer = createLayer("test", width, height, layerType)); if (bufferFill) { - ASSERT_NO_FATAL_FAILURE(fillBufferStateLayerColor(layer, fillColor, width, height)); + ASSERT_NO_FATAL_FAILURE(fillBufferLayerColor(layer, fillColor, width, height)); expectedColor = fillColor; } Transaction().setCrop(layer, Rect(0, 0, width, height)).apply(); @@ -544,6 +544,7 @@ void LayerRenderTypeTransactionTest::setBackgroundColorHelper(uint32_t layerType .apply(); { + SCOPED_TRACE("final color"); auto shot = screenshot(); shot->expectColor(Rect(0, 0, width, height), finalColor); shot->expectBorder(Rect(0, 0, width, height), Color::BLACK); @@ -832,7 +833,7 @@ TEST_P(LayerRenderTypeTransactionTest, SetCropBasic_BufferState) { sp layer; ASSERT_NO_FATAL_FAILURE( layer = createLayer("test", 32, 32, ISurfaceComposerClient::eFXSurfaceBufferState)); - ASSERT_NO_FATAL_FAILURE(fillBufferStateLayerColor(layer, Color::RED, 32, 32)); + ASSERT_NO_FATAL_FAILURE(fillBufferLayerColor(layer, Color::RED, 32, 32)); const Rect crop(8, 8, 24, 24); Transaction().setCrop(layer, crop).apply(); @@ -863,7 +864,7 @@ TEST_P(LayerRenderTypeTransactionTest, SetCropEmpty_BufferState) { sp layer; ASSERT_NO_FATAL_FAILURE( layer = createLayer("test", 32, 32, ISurfaceComposerClient::eFXSurfaceBufferState)); - ASSERT_NO_FATAL_FAILURE(fillBufferStateLayerColor(layer, Color::RED, 32, 32)); + ASSERT_NO_FATAL_FAILURE(fillBufferLayerColor(layer, Color::RED, 32, 32)); { SCOPED_TRACE("empty rect"); @@ -894,7 +895,7 @@ TEST_P(LayerRenderTypeTransactionTest, SetCropOutOfBounds_BufferState) { ASSERT_NO_FATAL_FAILURE( layer = createLayer("test", 32, 64, ISurfaceComposerClient::eFXSurfaceBufferState)); sp buffer = - new GraphicBuffer(32, 64, PIXEL_FORMAT_RGBA_8888, 1, kUsageFlags, "test"); + sp::make(32, 64, PIXEL_FORMAT_RGBA_8888, 1, kUsageFlags, "test"); TransactionUtils::fillGraphicBufferColor(buffer, Rect(0, 0, 32, 16), Color::BLUE); TransactionUtils::fillGraphicBufferColor(buffer, Rect(0, 16, 32, 64), Color::RED); @@ -944,7 +945,7 @@ TEST_P(LayerRenderTypeTransactionTest, SetCropWithTranslation_BufferState) { sp layer; ASSERT_NO_FATAL_FAILURE( layer = createLayer("test", 32, 32, ISurfaceComposerClient::eFXSurfaceBufferState)); - ASSERT_NO_FATAL_FAILURE(fillBufferStateLayerColor(layer, Color::RED, 32, 32)); + ASSERT_NO_FATAL_FAILURE(fillBufferLayerColor(layer, Color::RED, 32, 32)); const Rect crop(8, 8, 24, 24); Transaction().setPosition(layer, 32, 32).setCrop(layer, crop).apply(); @@ -972,7 +973,7 @@ TEST_P(LayerRenderTypeTransactionTest, SetFrameBasic_BufferState) { sp layer; ASSERT_NO_FATAL_FAILURE( layer = createLayer("test", 32, 32, ISurfaceComposerClient::eFXSurfaceBufferState)); - ASSERT_NO_FATAL_FAILURE(fillBufferStateLayerColor(layer, Color::RED, 32, 32)); + ASSERT_NO_FATAL_FAILURE(fillBufferLayerColor(layer, Color::RED, 32, 32)); const Rect frame(8, 8, 24, 24); Transaction t; @@ -988,7 +989,7 @@ TEST_P(LayerRenderTypeTransactionTest, SetFrameEmpty_BufferState) { sp layer; ASSERT_NO_FATAL_FAILURE( layer = createLayer("test", 32, 32, ISurfaceComposerClient::eFXSurfaceBufferState)); - ASSERT_NO_FATAL_FAILURE(fillBufferStateLayerColor(layer, Color::RED, 32, 32)); + ASSERT_NO_FATAL_FAILURE(fillBufferLayerColor(layer, Color::RED, 32, 32)); Transaction t; { @@ -1014,7 +1015,7 @@ TEST_P(LayerRenderTypeTransactionTest, SetFrameDefaultParentless_BufferState) { sp layer; ASSERT_NO_FATAL_FAILURE( layer = createLayer("test", 32, 32, ISurfaceComposerClient::eFXSurfaceBufferState)); - ASSERT_NO_FATAL_FAILURE(fillBufferStateLayerColor(layer, Color::RED, 10, 10)); + ASSERT_NO_FATAL_FAILURE(fillBufferLayerColor(layer, Color::RED, 10, 10)); // A layer with a buffer will have a computed size that matches the buffer size. auto shot = getScreenCapture(); @@ -1026,11 +1027,11 @@ TEST_P(LayerRenderTypeTransactionTest, SetFrameDefaultBSParent_BufferState) { sp parent, child; ASSERT_NO_FATAL_FAILURE( parent = createLayer("test", 32, 32, ISurfaceComposerClient::eFXSurfaceBufferState)); - ASSERT_NO_FATAL_FAILURE(fillBufferStateLayerColor(parent, Color::RED, 32, 32)); + ASSERT_NO_FATAL_FAILURE(fillBufferLayerColor(parent, Color::RED, 32, 32)); ASSERT_NO_FATAL_FAILURE( child = createLayer("test", 10, 10, ISurfaceComposerClient::eFXSurfaceBufferState)); - ASSERT_NO_FATAL_FAILURE(fillBufferStateLayerColor(child, Color::BLUE, 10, 10)); + ASSERT_NO_FATAL_FAILURE(fillBufferLayerColor(child, Color::BLUE, 10, 10)); Transaction().reparent(child, parent).apply(); @@ -1047,7 +1048,7 @@ TEST_P(LayerRenderTypeTransactionTest, SetFrameDefaultBQParent_BufferState) { ASSERT_NO_FATAL_FAILURE( child = createLayer("test", 10, 10, ISurfaceComposerClient::eFXSurfaceBufferState)); - ASSERT_NO_FATAL_FAILURE(fillBufferStateLayerColor(child, Color::BLUE, 10, 10)); + ASSERT_NO_FATAL_FAILURE(fillBufferLayerColor(child, Color::BLUE, 10, 10)); Transaction().reparent(child, parent).apply(); @@ -1061,7 +1062,7 @@ TEST_P(LayerRenderTypeTransactionTest, SetFrameUpdate_BufferState) { sp layer; ASSERT_NO_FATAL_FAILURE( layer = createLayer("test", 32, 32, ISurfaceComposerClient::eFXSurfaceBufferState)); - ASSERT_NO_FATAL_FAILURE(fillBufferStateLayerColor(layer, Color::RED, 32, 32)); + ASSERT_NO_FATAL_FAILURE(fillBufferLayerColor(layer, Color::RED, 32, 32)); std::this_thread::sleep_for(500ms); @@ -1080,9 +1081,9 @@ TEST_P(LayerRenderTypeTransactionTest, SetFrameOutsideBounds_BufferState) { child = createLayer("test", 10, 10, ISurfaceComposerClient::eFXSurfaceBufferState)); Transaction().reparent(child, parent).apply(); - ASSERT_NO_FATAL_FAILURE(fillBufferStateLayerColor(parent, Color::RED, 32, 32)); + ASSERT_NO_FATAL_FAILURE(fillBufferLayerColor(parent, Color::RED, 32, 32)); - ASSERT_NO_FATAL_FAILURE(fillBufferStateLayerColor(child, Color::BLUE, 10, 10)); + ASSERT_NO_FATAL_FAILURE(fillBufferLayerColor(child, Color::BLUE, 10, 10)); Rect childDst(0, 16, 32, 32); Transaction t; TransactionUtils::setFrame(t, child, Rect(0, 0, 10, 10), childDst); @@ -1099,7 +1100,7 @@ TEST_P(LayerRenderTypeTransactionTest, SetBufferBasic_BufferState) { ASSERT_NO_FATAL_FAILURE( layer = createLayer("test", 32, 32, ISurfaceComposerClient::eFXSurfaceBufferState)); - ASSERT_NO_FATAL_FAILURE(fillBufferStateLayerColor(layer, Color::RED, 32, 32)); + ASSERT_NO_FATAL_FAILURE(fillBufferLayerColor(layer, Color::RED, 32, 32)); auto shot = getScreenCapture(); shot->expectColor(Rect(0, 0, 32, 32), Color::RED); @@ -1111,7 +1112,7 @@ TEST_P(LayerRenderTypeTransactionTest, SetBufferMultipleBuffers_BufferState) { ASSERT_NO_FATAL_FAILURE( layer = createLayer("test", 32, 32, ISurfaceComposerClient::eFXSurfaceBufferState)); - ASSERT_NO_FATAL_FAILURE(fillBufferStateLayerColor(layer, Color::RED, 32, 32)); + ASSERT_NO_FATAL_FAILURE(fillBufferLayerColor(layer, Color::RED, 32, 32)); { SCOPED_TRACE("set buffer 1"); @@ -1120,7 +1121,7 @@ TEST_P(LayerRenderTypeTransactionTest, SetBufferMultipleBuffers_BufferState) { shot->expectBorder(Rect(0, 0, 32, 32), Color::BLACK); } - ASSERT_NO_FATAL_FAILURE(fillBufferStateLayerColor(layer, Color::BLUE, 32, 32)); + ASSERT_NO_FATAL_FAILURE(fillBufferLayerColor(layer, Color::BLUE, 32, 32)); { SCOPED_TRACE("set buffer 2"); @@ -1129,7 +1130,7 @@ TEST_P(LayerRenderTypeTransactionTest, SetBufferMultipleBuffers_BufferState) { shot->expectBorder(Rect(0, 0, 32, 32), Color::BLACK); } - ASSERT_NO_FATAL_FAILURE(fillBufferStateLayerColor(layer, Color::RED, 32, 32)); + ASSERT_NO_FATAL_FAILURE(fillBufferLayerColor(layer, Color::RED, 32, 32)); { SCOPED_TRACE("set buffer 3"); @@ -1148,7 +1149,7 @@ TEST_P(LayerRenderTypeTransactionTest, SetBufferMultipleLayers_BufferState) { ASSERT_NO_FATAL_FAILURE( layer2 = createLayer("test", 32, 32, ISurfaceComposerClient::eFXSurfaceBufferState)); - ASSERT_NO_FATAL_FAILURE(fillBufferStateLayerColor(layer1, Color::RED, 64, 64)); + ASSERT_NO_FATAL_FAILURE(fillBufferLayerColor(layer1, Color::RED, 64, 64)); { SCOPED_TRACE("set layer 1 buffer red"); @@ -1156,7 +1157,7 @@ TEST_P(LayerRenderTypeTransactionTest, SetBufferMultipleLayers_BufferState) { shot->expectColor(Rect(0, 0, 64, 64), Color::RED); } - ASSERT_NO_FATAL_FAILURE(fillBufferStateLayerColor(layer2, Color::BLUE, 32, 32)); + ASSERT_NO_FATAL_FAILURE(fillBufferLayerColor(layer2, Color::BLUE, 32, 32)); { SCOPED_TRACE("set layer 2 buffer blue"); @@ -1166,7 +1167,7 @@ TEST_P(LayerRenderTypeTransactionTest, SetBufferMultipleLayers_BufferState) { shot->expectColor(Rect(0, 32, 32, 64), Color::RED); } - ASSERT_NO_FATAL_FAILURE(fillBufferStateLayerColor(layer1, Color::GREEN, 64, 64)); + ASSERT_NO_FATAL_FAILURE(fillBufferLayerColor(layer1, Color::GREEN, 64, 64)); { SCOPED_TRACE("set layer 1 buffer green"); auto shot = getScreenCapture(); @@ -1175,7 +1176,7 @@ TEST_P(LayerRenderTypeTransactionTest, SetBufferMultipleLayers_BufferState) { shot->expectColor(Rect(0, 32, 32, 64), Color::GREEN); } - ASSERT_NO_FATAL_FAILURE(fillBufferStateLayerColor(layer2, Color::WHITE, 32, 32)); + ASSERT_NO_FATAL_FAILURE(fillBufferLayerColor(layer2, Color::WHITE, 32, 32)); { SCOPED_TRACE("set layer 2 buffer white"); @@ -1197,7 +1198,7 @@ TEST_P(LayerRenderTypeTransactionTest, SetBufferCaching_BufferState) { size_t idx = 0; for (auto& buffer : buffers) { - buffer = new GraphicBuffer(32, 32, PIXEL_FORMAT_RGBA_8888, 1, kUsageFlags, "test"); + buffer = sp::make(32u, 32u, PIXEL_FORMAT_RGBA_8888, 1u, kUsageFlags, "test"); Color color = colors[idx % colors.size()]; TransactionUtils::fillGraphicBufferColor(buffer, Rect(0, 0, 32, 32), color); idx++; @@ -1230,7 +1231,7 @@ TEST_P(LayerRenderTypeTransactionTest, SetBufferCaching_LeastRecentlyUsed_Buffer size_t idx = 0; for (auto& buffer : buffers) { - buffer = new GraphicBuffer(32, 32, PIXEL_FORMAT_RGBA_8888, 1, kUsageFlags, "test"); + buffer = sp::make(32u, 32u, PIXEL_FORMAT_RGBA_8888, 1u, kUsageFlags, "test"); Color color = colors[idx % colors.size()]; TransactionUtils::fillGraphicBufferColor(buffer, Rect(0, 0, 32, 32), color); idx++; @@ -1263,7 +1264,7 @@ TEST_P(LayerRenderTypeTransactionTest, SetBufferCaching_DestroyedBuffer_BufferSt size_t idx = 0; for (auto& buffer : buffers) { - buffer = new GraphicBuffer(32, 32, PIXEL_FORMAT_RGBA_8888, 1, kUsageFlags, "test"); + buffer = sp::make(32u, 32u, PIXEL_FORMAT_RGBA_8888, 1u, kUsageFlags, "test"); Color color = colors[idx % colors.size()]; TransactionUtils::fillGraphicBufferColor(buffer, Rect(0, 0, 32, 32), color); idx++; @@ -1344,7 +1345,7 @@ TEST_P(LayerRenderTypeTransactionTest, DISABLED_SetFenceBasic_BufferState) { layer = createLayer("test", 32, 32, ISurfaceComposerClient::eFXSurfaceBufferState)); sp buffer = - new GraphicBuffer(32, 32, PIXEL_FORMAT_RGBA_8888, 1, kUsageFlags, "test"); + sp::make(32u, 32u, PIXEL_FORMAT_RGBA_8888, 1u, kUsageFlags, "test"); TransactionUtils::fillGraphicBufferColor(buffer, Rect(0, 0, 32, 32), Color::RED); sp fence; @@ -1370,7 +1371,7 @@ TEST_P(LayerRenderTypeTransactionTest, SetFenceNull_BufferState) { layer = createLayer("test", 32, 32, ISurfaceComposerClient::eFXSurfaceBufferState)); sp buffer = - new GraphicBuffer(32, 32, PIXEL_FORMAT_RGBA_8888, 1, kUsageFlags, "test"); + sp::make(32u, 32u, PIXEL_FORMAT_RGBA_8888, 1u, kUsageFlags, "test"); TransactionUtils::fillGraphicBufferColor(buffer, Rect(0, 0, 32, 32), Color::RED); sp fence = Fence::NO_FENCE; @@ -1388,7 +1389,7 @@ TEST_P(LayerRenderTypeTransactionTest, SetDataspaceBasic_BufferState) { layer = createLayer("test", 32, 32, ISurfaceComposerClient::eFXSurfaceBufferState)); sp buffer = - new GraphicBuffer(32, 32, PIXEL_FORMAT_RGBA_8888, 1, kUsageFlags, "test"); + sp::make(32u, 32u, PIXEL_FORMAT_RGBA_8888, 1u, kUsageFlags, "test"); TransactionUtils::fillGraphicBufferColor(buffer, Rect(0, 0, 32, 32), Color::RED); Transaction().setBuffer(layer, buffer).setDataspace(layer, ui::Dataspace::UNKNOWN).apply(); @@ -1404,7 +1405,7 @@ TEST_P(LayerRenderTypeTransactionTest, SetHdrMetadataBasic_BufferState) { layer = createLayer("test", 32, 32, ISurfaceComposerClient::eFXSurfaceBufferState)); sp buffer = - new GraphicBuffer(32, 32, PIXEL_FORMAT_RGBA_8888, 1, kUsageFlags, "test"); + sp::make(32u, 32u, PIXEL_FORMAT_RGBA_8888, 1u, kUsageFlags, "test"); TransactionUtils::fillGraphicBufferColor(buffer, Rect(0, 0, 32, 32), Color::RED); HdrMetadata hdrMetadata; @@ -1422,7 +1423,7 @@ TEST_P(LayerRenderTypeTransactionTest, SetSurfaceDamageRegionBasic_BufferState) layer = createLayer("test", 32, 32, ISurfaceComposerClient::eFXSurfaceBufferState)); sp buffer = - new GraphicBuffer(32, 32, PIXEL_FORMAT_RGBA_8888, 1, kUsageFlags, "test"); + sp::make(32u, 32u, PIXEL_FORMAT_RGBA_8888, 1u, kUsageFlags, "test"); TransactionUtils::fillGraphicBufferColor(buffer, Rect(0, 0, 32, 32), Color::RED); Region region; @@ -1440,7 +1441,7 @@ TEST_P(LayerRenderTypeTransactionTest, SetApiBasic_BufferState) { layer = createLayer("test", 32, 32, ISurfaceComposerClient::eFXSurfaceBufferState)); sp buffer = - new GraphicBuffer(32, 32, PIXEL_FORMAT_RGBA_8888, 1, kUsageFlags, "test"); + sp::make(32u, 32u, PIXEL_FORMAT_RGBA_8888, 1u, kUsageFlags, "test"); TransactionUtils::fillGraphicBufferColor(buffer, Rect(0, 0, 32, 32), Color::RED); Transaction().setBuffer(layer, buffer).setApi(layer, NATIVE_WINDOW_API_CPU).apply(); @@ -1635,6 +1636,65 @@ TEST_P(LayerRenderTypeTransactionTest, SetColorTransformOnChildAndParent) { getScreenCapture()->expectColor(Rect(0, 0, 32, 32), expectedColor, tolerance); } } + +TEST_P(LayerRenderTypeTransactionTest, SetNullBuffer) { + const Rect bounds(0, 0, 32, 32); + sp layer; + ASSERT_NO_FATAL_FAILURE( + layer = createLayer("test", 32, 32, ISurfaceComposerClient::eFXSurfaceBufferState)); + + sp buffer = + sp::make(32u, 32u, PIXEL_FORMAT_RGBA_8888, 1u, kUsageFlags, "test"); + + ASSERT_NO_FATAL_FAILURE(TransactionUtils::fillGraphicBufferColor(buffer, bounds, Color::GREEN)); + Transaction().setBuffer(layer, buffer).apply(); + { + SCOPED_TRACE("before null buffer"); + auto shot = getScreenCapture(); + shot->expectColor(bounds, Color::GREEN); + } + + Transaction().setBuffer(layer, nullptr).apply(); + { + SCOPED_TRACE("null buffer removes buffer"); + auto shot = getScreenCapture(); + shot->expectColor(bounds, Color::BLACK); + } + + Transaction().setBuffer(layer, buffer).apply(); + { + SCOPED_TRACE("after null buffer"); + auto shot = getScreenCapture(); + shot->expectColor(bounds, Color::GREEN); + } +} + +TEST_P(LayerRenderTypeTransactionTest, SetNullBufferOnLayerWithoutBuffer) { + const Rect bounds(0, 0, 32, 32); + sp layer; + ASSERT_NO_FATAL_FAILURE( + layer = createLayer("test", 32, 32, ISurfaceComposerClient::eFXSurfaceBufferState)); + { + SCOPED_TRACE("starting state"); + auto shot = getScreenCapture(); + shot->expectColor(bounds, Color::BLACK); + } + + Transaction().setBuffer(layer, nullptr).apply(); + { + SCOPED_TRACE("null buffer has no effect"); + auto shot = getScreenCapture(); + shot->expectColor(bounds, Color::BLACK); + } + + Transaction().setBuffer(layer, nullptr).apply(); + { + SCOPED_TRACE("null buffer has no effect"); + auto shot = getScreenCapture(); + shot->expectColor(bounds, Color::BLACK); + } +} + } // namespace android // TODO(b/129481165): remove the #pragma below and fix conversion issues diff --git a/services/surfaceflinger/tests/LayerState_test.cpp b/services/surfaceflinger/tests/LayerState_test.cpp index 094b0ffc0f3ee2dbbb04006c6e0b88f711561c73..21813700a201b6dde2280568c3fa4f2419e666e8 100644 --- a/services/surfaceflinger/tests/LayerState_test.cpp +++ b/services/surfaceflinger/tests/LayerState_test.cpp @@ -35,7 +35,7 @@ TEST(LayerStateTest, ParcellingDisplayCaptureArgs) { args.frameScaleX = 2; args.frameScaleY = 4; args.captureSecureLayers = true; - args.displayToken = new BBinder(); + args.displayToken = sp::make(); args.width = 10; args.height = 20; args.useIdentityTransform = true; @@ -67,8 +67,8 @@ TEST(LayerStateTest, ParcellingLayerCaptureArgs) { args.frameScaleX = 2; args.frameScaleY = 4; args.captureSecureLayers = true; - args.layerHandle = new BBinder(); - args.excludeHandles = {new BBinder(), new BBinder()}; + args.layerHandle = sp::make(); + args.excludeHandles = {sp::make(), sp::make()}; args.childrenOnly = false; args.grayscale = true; @@ -90,13 +90,12 @@ TEST(LayerStateTest, ParcellingLayerCaptureArgs) { ASSERT_EQ(args.grayscale, args2.grayscale); } -TEST(LayerStateTest, ParcellingScreenCaptureResults) { +TEST(LayerStateTest, ParcellingScreenCaptureResultsWithFence) { ScreenCaptureResults results; - results.buffer = new GraphicBuffer(100, 200, PIXEL_FORMAT_RGBA_8888, 1, 0); - results.fence = new Fence(dup(fileno(tmpfile()))); + results.buffer = sp::make(100u, 200u, PIXEL_FORMAT_RGBA_8888, 1u, 0u); + results.fenceResult = sp::make(dup(fileno(tmpfile()))); results.capturedSecureLayers = true; results.capturedDataspace = ui::Dataspace::DISPLAY_P3; - results.result = BAD_VALUE; Parcel p; results.writeToParcel(&p); @@ -110,10 +109,41 @@ TEST(LayerStateTest, ParcellingScreenCaptureResults) { ASSERT_EQ(results.buffer->getWidth(), results2.buffer->getWidth()); ASSERT_EQ(results.buffer->getHeight(), results2.buffer->getHeight()); ASSERT_EQ(results.buffer->getPixelFormat(), results2.buffer->getPixelFormat()); - ASSERT_EQ(results.fence->isValid(), results2.fence->isValid()); + ASSERT_TRUE(results.fenceResult.ok()); + ASSERT_TRUE(results2.fenceResult.ok()); + ASSERT_EQ(results.fenceResult.value()->isValid(), results2.fenceResult.value()->isValid()); ASSERT_EQ(results.capturedSecureLayers, results2.capturedSecureLayers); ASSERT_EQ(results.capturedDataspace, results2.capturedDataspace); - ASSERT_EQ(results.result, results2.result); +} + +TEST(LayerStateTest, ParcellingScreenCaptureResultsWithNoFenceOrError) { + ScreenCaptureResults results; + + Parcel p; + results.writeToParcel(&p); + p.setDataPosition(0); + + ScreenCaptureResults results2; + results2.readFromParcel(&p); + + ASSERT_TRUE(results2.fenceResult.ok()); + ASSERT_EQ(results2.fenceResult.value(), Fence::NO_FENCE); +} + +TEST(LayerStateTest, ParcellingScreenCaptureResultsWithFenceError) { + ScreenCaptureResults results; + results.fenceResult = base::unexpected(BAD_VALUE); + + Parcel p; + results.writeToParcel(&p); + p.setDataPosition(0); + + ScreenCaptureResults results2; + results2.readFromParcel(&p); + + ASSERT_FALSE(results.fenceResult.ok()); + ASSERT_FALSE(results2.fenceResult.ok()); + ASSERT_EQ(results.fenceResult.error(), results2.fenceResult.error()); } } // namespace test diff --git a/services/surfaceflinger/tests/LayerTransactionTest.h b/services/surfaceflinger/tests/LayerTransactionTest.h index 6bd7920a62267b8a2ce7f54875040f5c47b9f6ac..badd5bebbc81ec94eb6ec109b6daeff6562a0d40 100644 --- a/services/surfaceflinger/tests/LayerTransactionTest.h +++ b/services/surfaceflinger/tests/LayerTransactionTest.h @@ -23,9 +23,11 @@ #include #include +#include #include #include #include +#include #include #include "BufferGenerator.h" @@ -39,13 +41,14 @@ using android::hardware::graphics::common::V1_1::BufferUsage; class LayerTransactionTest : public ::testing::Test { protected: void SetUp() override { - mClient = new SurfaceComposerClient; + mClient = sp::make(); ASSERT_EQ(NO_ERROR, mClient->initCheck()) << "failed to create SurfaceComposerClient"; ASSERT_NO_FATAL_FAILURE(SetUpDisplay()); - sp sf(ComposerService::getComposerService()); - ASSERT_NO_FATAL_FAILURE(sf->getColorManagement(&mColorManagementUsed)); + sp sf(ComposerServiceAIDL::getComposerService()); + binder::Status status = sf->getColorManagement(&mColorManagementUsed); + ASSERT_NO_FATAL_FAILURE(gui::aidl_utils::statusTFromBinderStatus(status)); mCaptureArgs.displayToken = mDisplay; } @@ -134,13 +137,16 @@ protected: postBufferQueueLayerBuffer(layer); } - virtual void fillBufferStateLayerColor(const sp& layer, const Color& color, - int32_t bufferWidth, int32_t bufferHeight) { + virtual void fillBufferLayerColor(const sp& layer, const Color& color, + int32_t bufferWidth, int32_t bufferHeight) { sp buffer = - new GraphicBuffer(bufferWidth, bufferHeight, PIXEL_FORMAT_RGBA_8888, 1, - BufferUsage::CPU_READ_OFTEN | BufferUsage::CPU_WRITE_OFTEN | - BufferUsage::COMPOSER_OVERLAY | BufferUsage::GPU_TEXTURE, - "test"); + sp::make(static_cast(bufferWidth), + static_cast(bufferHeight), 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, bufferWidth, bufferHeight), color); Transaction().setBuffer(layer, buffer).apply(); @@ -153,7 +159,7 @@ protected: fillBufferQueueLayerColor(layer, color, bufferWidth, bufferHeight); break; case ISurfaceComposerClient::eFXSurfaceBufferState: - fillBufferStateLayerColor(layer, color, bufferWidth, bufferHeight); + fillBufferLayerColor(layer, color, bufferWidth, bufferHeight); break; default: ASSERT_TRUE(false) << "unsupported layer type: " << mLayerType; @@ -206,10 +212,13 @@ protected: const Color& topRight, const Color& bottomLeft, const Color& bottomRight) { sp buffer = - new GraphicBuffer(bufferWidth, bufferHeight, PIXEL_FORMAT_RGBA_8888, 1, - BufferUsage::CPU_READ_OFTEN | BufferUsage::CPU_WRITE_OFTEN | - BufferUsage::COMPOSER_OVERLAY | BufferUsage::GPU_TEXTURE, - "test"); + sp::make(static_cast(bufferWidth), + static_cast(bufferHeight), PIXEL_FORMAT_RGBA_8888, + 1u, + BufferUsage::CPU_READ_OFTEN | BufferUsage::CPU_WRITE_OFTEN | + BufferUsage::COMPOSER_OVERLAY | + BufferUsage::GPU_TEXTURE, + "test"); ASSERT_TRUE(bufferWidth % 2 == 0 && bufferHeight % 2 == 0); @@ -224,7 +233,7 @@ protected: Rect(halfW, halfH, bufferWidth, bufferHeight), bottomRight); - Transaction().setBuffer(layer, buffer).setSize(layer, bufferWidth, bufferHeight).apply(); + Transaction().setBuffer(layer, buffer).apply(); } std::unique_ptr screenshot() { @@ -280,7 +289,9 @@ protected: private: void SetUpDisplay() { - mDisplay = mClient->getInternalDisplayToken(); + const auto ids = SurfaceComposerClient::getPhysicalDisplayIds(); + ASSERT_FALSE(ids.empty()); + mDisplay = SurfaceComposerClient::getPhysicalDisplayToken(ids.front()); ASSERT_FALSE(mDisplay == nullptr) << "failed to get display"; ui::DisplayMode mode; diff --git a/services/surfaceflinger/tests/LayerTransaction_test.cpp b/services/surfaceflinger/tests/LayerTransaction_test.cpp index ef992d6a4083a1a9e2a2a1eef44e17b64fec5098..cbd54e7aa98719afa413ff7a956ea500c7ea88a9 100644 --- a/services/surfaceflinger/tests/LayerTransaction_test.cpp +++ b/services/surfaceflinger/tests/LayerTransaction_test.cpp @@ -32,7 +32,7 @@ TEST_F(LayerTransactionTest, SetTransformToDisplayInverse_BufferState) { Transaction().setTransformToDisplayInverse(layer, false).apply(); - ASSERT_NO_FATAL_FAILURE(fillBufferStateLayerColor(layer, Color::GREEN, 32, 32)); + ASSERT_NO_FATAL_FAILURE(fillBufferLayerColor(layer, Color::GREEN, 32, 32)); Transaction().setTransformToDisplayInverse(layer, true).apply(); } @@ -80,7 +80,7 @@ TEST_F(LayerTransactionTest, DISABLED_BufferQueueLayerMergeDamageRegionWhenDropp sp layer; ASSERT_NO_FATAL_FAILURE(layer = createLayer("test", width, height)); const auto producer = layer->getIGraphicBufferProducer(); - const sp stubListener(new StubProducerListener); + const sp stubListener(sp::make()); IGraphicBufferProducer::QueueBufferOutput queueBufferOutput; ASSERT_EQ(OK, producer->connect(stubListener, NATIVE_WINDOW_API_CPU, true, &queueBufferOutput)); @@ -154,6 +154,36 @@ TEST_F(LayerTransactionTest, DISABLED_BufferQueueLayerMergeDamageRegionWhenDropp ASSERT_EQ(OK, producer->disconnect(NATIVE_WINDOW_API_CPU)); } + +// b/245052266 - we possible could support blur and a buffer at the same layer but +// might break existing assumptions at higher level. This test captures the current +// expectations. A layer drawing a buffer will not support blur. +TEST_F(LayerTransactionTest, BufferTakesPriorityOverBlur) { + sp layer; + ASSERT_NO_FATAL_FAILURE(layer = createLayer("test", 32, 32)); + ASSERT_NO_FATAL_FAILURE(fillBufferLayerColor(layer, Color::RED, 32, 32)); + Transaction().setBackgroundBlurRadius(layer, 5).apply(); + { + SCOPED_TRACE("BufferTakesPriorityOverBlur"); + const Rect rect(0, 0, 32, 32); + auto shot = screenshot(); + shot->expectColor(rect, Color::RED); + } +} + +TEST_F(LayerTransactionTest, BufferTakesPriorityOverColor) { + sp layer; + ASSERT_NO_FATAL_FAILURE(layer = createLayer("test", 32, 32)); + ASSERT_NO_FATAL_FAILURE(fillBufferLayerColor(layer, Color::RED, 32, 32)); + Transaction().setColor(layer, {Color::GREEN.r, Color::GREEN.g, Color::GREEN.b}).apply(); + { + SCOPED_TRACE("BufferTakesPriorityOverColor"); + const Rect rect(0, 0, 32, 32); + auto shot = screenshot(); + shot->expectColor(rect, Color::RED); + } +} + } // namespace android // TODO(b/129481165): remove the #pragma below and fix conversion issues diff --git a/services/surfaceflinger/tests/LayerTrustedPresentationListener_test.cpp b/services/surfaceflinger/tests/LayerTrustedPresentationListener_test.cpp new file mode 100644 index 0000000000000000000000000000000000000000..2be8d3b0135548cd66a0a35add8e0d7be67f6110 --- /dev/null +++ b/services/surfaceflinger/tests/LayerTrustedPresentationListener_test.cpp @@ -0,0 +1,287 @@ +/* + * 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 +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wconversion" + +#include +#include +#include +#include "TransactionTestHarnesses.h" + +namespace android { +struct PresentationCallbackHelper { + void callbackArrived(bool state) { + std::unique_lock l(mMutex); + mGotCallback = true; + mState = state; + mCondition.notify_all(); + } + bool awaitCallback() { + std::unique_lock l(mMutex); + mGotCallback = false; + mCondition.wait_for(l, 5000ms); + EXPECT_TRUE(mGotCallback); + return mState; + } + + bool mState; + bool mGotCallback; + std::mutex mMutex; + std::condition_variable mCondition; +}; + +TrustedPresentationThresholds thresh() { + TrustedPresentationThresholds thresholds; + thresholds.minAlpha = 1.0; + thresholds.minFractionRendered = 1.0; + thresholds.stabilityRequirementMs = 100; + return thresholds; +} + +class LayerTrustedPresentationListenerTest : public LayerTransactionTest { +public: + void SetUp() override { + LayerTransactionTest::SetUp(); + mainLayer = makeLayer(); + thresholds = thresh(); + } + + void TearDown() override { + LayerTransactionTest::TearDown(); + mCallback = nullptr; + t.reparent(mainLayer, nullptr).apply(); + mainLayer = nullptr; + } + + void thresholdsPrepared() { + t.show(mainLayer) + .setLayer(mainLayer, INT32_MAX) + .setTrustedPresentationCallback( + mainLayer, + [&](void* context, bool state) { + PresentationCallbackHelper* helper = + (PresentationCallbackHelper*)context; + helper->callbackArrived(state); + }, + thresholds, &pch, mCallback) + .setPosition(mainLayer, 100, 100) + .apply(); + } + + sp makeLayer() { + sp layer = + createLayer("test", 100, 100, ISurfaceComposerClient::eFXSurfaceBufferState, + mBlackBgSurface.get()); + fillBufferLayerColor(layer, Color::RED, 100, 100); + return layer; + } + sp mainLayer; + PresentationCallbackHelper pch; + SurfaceComposerClient::Transaction t; + TrustedPresentationThresholds thresholds; + sp mCallback; +}; + +// The layer is fully presented with the default test setup. +TEST_F(LayerTrustedPresentationListenerTest, callback_arrives) { + thresholdsPrepared(); + EXPECT_TRUE(pch.awaitCallback()); +} + +// A hidden layer can't be considered presented! +TEST_F(LayerTrustedPresentationListenerTest, hiding_layer_clears_state) { + thresholdsPrepared(); + EXPECT_TRUE(pch.awaitCallback()); + t.hide(mainLayer).apply(); + EXPECT_FALSE(pch.awaitCallback()); +} + +// A fully obscured layer can't be considered presented! +TEST_F(LayerTrustedPresentationListenerTest, obscuring_clears_state) { + thresholdsPrepared(); + EXPECT_TRUE(pch.awaitCallback()); + + auto otherLayer = makeLayer(); + t.show(otherLayer) + .setPosition(otherLayer, 100, 100) + .setLayer(otherLayer, INT32_MAX) + .setLayer(mainLayer, INT32_MAX - 1) + .apply(); + EXPECT_FALSE(pch.awaitCallback()); +} + +// Even if the layer obscuring us has an Alpha channel, we are still considered +// obscured. +TEST_F(LayerTrustedPresentationListenerTest, obscuring_with_transparency_clears_state) { + thresholdsPrepared(); + EXPECT_TRUE(pch.awaitCallback()); + + auto otherLayer = makeLayer(); + t.show(otherLayer) + .setPosition(otherLayer, 100, 100) + .setLayer(otherLayer, INT32_MAX) + .setFlags(otherLayer, 0, layer_state_t::eLayerOpaque) + .setLayer(mainLayer, INT32_MAX - 1) + .apply(); + EXPECT_FALSE(pch.awaitCallback()); +} + +// We can't be presented if our alpha is below the threshold. +TEST_F(LayerTrustedPresentationListenerTest, alpha_below_threshold) { + thresholdsPrepared(); + EXPECT_TRUE(pch.awaitCallback()); + t.setAlpha(mainLayer, 0.9).apply(); + EXPECT_FALSE(pch.awaitCallback()); + t.setAlpha(mainLayer, 1.0).apply(); + EXPECT_TRUE(pch.awaitCallback()); +} + +// Verify that the passed in threshold is actually respected! +TEST_F(LayerTrustedPresentationListenerTest, alpha_below_other_threshold) { + thresholds.minAlpha = 0.8; + thresholdsPrepared(); + EXPECT_TRUE(pch.awaitCallback()); + t.setAlpha(mainLayer, 0.8).apply(); + EXPECT_FALSE(pch.awaitCallback()); + t.setAlpha(mainLayer, 0.9).apply(); + EXPECT_TRUE(pch.awaitCallback()); +} + +// (86*86)/(100*100) = 0.73...so a crop of 86x86 is below the threshold +// (87*87)/(100*100) = 0.76...so a crop of 87x87 is above the threshold! +TEST_F(LayerTrustedPresentationListenerTest, crop_below_threshold) { + thresholds.minFractionRendered = 0.75; + thresholdsPrepared(); + EXPECT_TRUE(pch.awaitCallback()); + t.setCrop(mainLayer, Rect(0, 0, 86, 86)).apply(); + EXPECT_FALSE(pch.awaitCallback()); + t.setCrop(mainLayer, Rect(0, 0, 87, 87)).apply(); + EXPECT_TRUE(pch.awaitCallback()); +} + +TEST_F(LayerTrustedPresentationListenerTest, scale_below_threshold) { + thresholds.minFractionRendered = 0.64; + thresholdsPrepared(); + EXPECT_TRUE(pch.awaitCallback()); + // 0.8 = sqrt(0.64) + t.setMatrix(mainLayer, 0.79, 0, 0, 0.79).apply(); + EXPECT_FALSE(pch.awaitCallback()); + t.setMatrix(mainLayer, 0.81, 0, 0, 0.81).apply(); + EXPECT_TRUE(pch.awaitCallback()); +} + +TEST_F(LayerTrustedPresentationListenerTest, obscuring_with_threshold_1) { + thresholds.minFractionRendered = 0.75; + thresholdsPrepared(); + EXPECT_TRUE(pch.awaitCallback()); + + auto otherLayer = makeLayer(); + t.show(otherLayer) + .setPosition(otherLayer, 100, 100) + .setLayer(otherLayer, INT32_MAX) + .setLayer(mainLayer, INT32_MAX - 1) + .apply(); + EXPECT_FALSE(pch.awaitCallback()); + t.setMatrix(otherLayer, 0.49, 0, 0, 0.49).apply(); + EXPECT_TRUE(pch.awaitCallback()); + t.setMatrix(otherLayer, 0.51, 0, 0, 0.51).apply(); + EXPECT_FALSE(pch.awaitCallback()); +} + +TEST_F(LayerTrustedPresentationListenerTest, obscuring_with_threshold_2) { + thresholds.minFractionRendered = 0.9; + thresholdsPrepared(); + EXPECT_TRUE(pch.awaitCallback()); + + auto otherLayer = makeLayer(); + t.show(otherLayer) + .setPosition(otherLayer, 100, 100) + .setLayer(otherLayer, INT32_MAX) + .setLayer(mainLayer, INT32_MAX - 1) + .apply(); + EXPECT_FALSE(pch.awaitCallback()); + t.setMatrix(otherLayer, 0.3, 0, 0, 0.3).apply(); + EXPECT_TRUE(pch.awaitCallback()); + t.setMatrix(otherLayer, 0.33, 0, 0, 0.33).apply(); + EXPECT_FALSE(pch.awaitCallback()); +} + +TEST_F(LayerTrustedPresentationListenerTest, obscuring_with_alpha) { + thresholds.minFractionRendered = 0.9; + thresholdsPrepared(); + EXPECT_TRUE(pch.awaitCallback()); + + auto otherLayer = makeLayer(); + t.show(otherLayer) + .setPosition(otherLayer, 100, 100) + .setLayer(otherLayer, INT32_MAX) + .setLayer(mainLayer, INT32_MAX - 1) + .setAlpha(otherLayer, 0.01) + .apply(); + EXPECT_FALSE(pch.awaitCallback()); + t.setAlpha(otherLayer, 0.0).apply(); + EXPECT_TRUE(pch.awaitCallback()); +} + +TEST_F(LayerTrustedPresentationListenerTest, obscuring_with_display_overlay) { + auto otherLayer = makeLayer(); + t.show(otherLayer) + .setPosition(otherLayer, 100, 100) + .setLayer(otherLayer, INT32_MAX) + .setFlags(otherLayer, layer_state_t::eLayerSkipScreenshot, + layer_state_t::eLayerSkipScreenshot) + .setLayer(mainLayer, INT32_MAX - 1) + .show(mainLayer) + .setPosition(mainLayer, 100, 100) + .setTrustedPresentationCallback( + mainLayer, + [&](void* context, bool state) { + PresentationCallbackHelper* helper = (PresentationCallbackHelper*)context; + helper->callbackArrived(state); + }, + thresholds, &pch, mCallback) + .apply(); + EXPECT_TRUE(pch.awaitCallback()); +} + +TEST_F(LayerTrustedPresentationListenerTest, obscuring_with_non_overlapping_bounds) { + thresholds.minFractionRendered = 0.5; + auto otherLayer1 = makeLayer(); + auto otherLayer2 = makeLayer(); + t.show(otherLayer1) + .show(otherLayer2) + .setPosition(otherLayer1, 100, 25) + .setLayer(otherLayer1, INT32_MAX) + .setPosition(otherLayer2, 100, 175) + .setLayer(otherLayer2, INT32_MAX) + .setLayer(mainLayer, INT32_MAX - 1) + .show(mainLayer) + .setPosition(mainLayer, 100, 100) + .setTrustedPresentationCallback( + mainLayer, + [&](void* context, bool state) { + PresentationCallbackHelper* helper = (PresentationCallbackHelper*)context; + helper->callbackArrived(state); + }, + thresholds, &pch, mCallback) + .apply(); + + EXPECT_TRUE(pch.awaitCallback()); +} + +} // namespace android diff --git a/services/surfaceflinger/tests/LayerTypeAndRenderTypeTransaction_test.cpp b/services/surfaceflinger/tests/LayerTypeAndRenderTypeTransaction_test.cpp index 9cb617a9801ae08f2ab849768f22492330bc6396..f247c9f088d9c72f7068501b9a6a410b1df8ec9a 100644 --- a/services/surfaceflinger/tests/LayerTypeAndRenderTypeTransaction_test.cpp +++ b/services/surfaceflinger/tests/LayerTypeAndRenderTypeTransaction_test.cpp @@ -799,7 +799,7 @@ TEST_P(LayerTypeAndRenderTypeTransactionTest, SetBufferFormat) { sp surface = layer->getSurface(); sp buffer = - new GraphicBuffer(width, height, PIXEL_FORMAT_RGBX_8888, 1, kUsageFlags, "test"); + sp::make(width, height, PIXEL_FORMAT_RGBX_8888, 1, kUsageFlags, "test"); ASSERT_NO_FATAL_FAILURE( TransactionUtils::fillGraphicBufferColor(buffer, crop, Color::TRANSPARENT)); @@ -815,7 +815,7 @@ TEST_P(LayerTypeAndRenderTypeTransactionTest, SetBufferFormat) { shot->expectColor(crop, Color::BLACK); } - buffer = new GraphicBuffer(width, height, PIXEL_FORMAT_RGBA_8888, 1, kUsageFlags, "test"); + buffer = sp::make(width, height, PIXEL_FORMAT_RGBA_8888, 1, kUsageFlags, "test"); ASSERT_NO_FATAL_FAILURE( TransactionUtils::fillGraphicBufferColor(buffer, crop, Color::TRANSPARENT)); diff --git a/services/surfaceflinger/tests/LayerUpdate_test.cpp b/services/surfaceflinger/tests/LayerUpdate_test.cpp index e1a7ecc03b9e881c07d01b49bb6b7dcd13a50662..867eddbc4465ef17979d3aa7c6f234daa825ce04 100644 --- a/services/surfaceflinger/tests/LayerUpdate_test.cpp +++ b/services/surfaceflinger/tests/LayerUpdate_test.cpp @@ -33,7 +33,9 @@ protected: LayerTransactionTest::SetUp(); ASSERT_EQ(NO_ERROR, mClient->initCheck()); - const auto display = SurfaceComposerClient::getInternalDisplayToken(); + const auto ids = SurfaceComposerClient::getPhysicalDisplayIds(); + ASSERT_FALSE(ids.empty()); + const auto display = SurfaceComposerClient::getPhysicalDisplayToken(ids.front()); ASSERT_FALSE(display == nullptr); ui::DisplayMode mode; diff --git a/services/surfaceflinger/tests/MirrorLayer_test.cpp b/services/surfaceflinger/tests/MirrorLayer_test.cpp index a921aa810e6e0305cd8d1bec4bfddd4d6a2cc7e6..0ea08247320173dc3fa55703dc8fbb0c4f9a4710 100644 --- a/services/surfaceflinger/tests/MirrorLayer_test.cpp +++ b/services/surfaceflinger/tests/MirrorLayer_test.cpp @@ -18,6 +18,7 @@ #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wconversion" +#include #include #include "LayerTransactionTest.h" #include "utils/TransactionUtils.h" @@ -29,8 +30,10 @@ protected: virtual void SetUp() { LayerTransactionTest::SetUp(); ASSERT_EQ(NO_ERROR, mClient->initCheck()); + const auto ids = SurfaceComposerClient::getPhysicalDisplayIds(); + ASSERT_FALSE(ids.empty()); - const auto display = SurfaceComposerClient::getInternalDisplayToken(); + const auto display = SurfaceComposerClient::getPhysicalDisplayToken(ids.front()); ASSERT_FALSE(display == nullptr); mParentLayer = createColorLayer("Parent layer", Color::RED); @@ -117,15 +120,20 @@ TEST_F(MirrorLayerTest, MirrorColorLayer) { shot->expectColor(Rect(750, 750, 950, 950), Color::BLACK); } - // Remove child layer + if (base::GetBoolProperty("debug.sf.enable_legacy_frontend", true)) { + GTEST_SKIP() << "Skipping test because mirroring behavior changes with legacy frontend"; + } + + // Remove child layer and verify we can still mirror the layer when + // its offscreen. Transaction().reparent(mChildLayer, nullptr).apply(); { SCOPED_TRACE("Removed Child Layer"); auto shot = screenshot(); // Grandchild mirror - shot->expectColor(Rect(550, 550, 750, 750), Color::RED); + shot->expectColor(Rect(550, 550, 750, 750), Color::BLACK); // Child mirror - shot->expectColor(Rect(750, 750, 950, 950), Color::RED); + shot->expectColor(Rect(750, 750, 950, 950), Color::BLACK); } // Add grandchild layer to offscreen layer @@ -134,9 +142,9 @@ TEST_F(MirrorLayerTest, MirrorColorLayer) { SCOPED_TRACE("Added Grandchild Layer"); auto shot = screenshot(); // Grandchild mirror - shot->expectColor(Rect(550, 550, 750, 750), Color::RED); + shot->expectColor(Rect(550, 550, 750, 750), Color::WHITE); // Child mirror - shot->expectColor(Rect(750, 750, 950, 950), Color::RED); + shot->expectColor(Rect(750, 750, 950, 950), Color::BLACK); } // Add child layer @@ -193,14 +201,14 @@ TEST_F(MirrorLayerTest, MirrorBufferLayer) { shot->expectColor(Rect(750, 750, 950, 950), Color::GREEN); } - sp bufferStateLayer = - createLayer("BufferStateLayer", 200, 200, ISurfaceComposerClient::eFXSurfaceBufferState, + sp layer = + createLayer("Layer", 200, 200, ISurfaceComposerClient::eFXSurfaceBufferState, mChildLayer.get()); - fillBufferStateLayerColor(bufferStateLayer, Color::BLUE, 200, 200); - Transaction().show(bufferStateLayer).apply(); + fillBufferLayerColor(layer, Color::BLUE, 200, 200); + Transaction().show(layer).apply(); { - SCOPED_TRACE("Initial Mirror BufferStateLayer"); + SCOPED_TRACE("Initial Mirror Layer"); auto shot = screenshot(); // Buffer mirror shot->expectColor(Rect(550, 550, 750, 750), Color::BLUE); @@ -208,9 +216,9 @@ TEST_F(MirrorLayerTest, MirrorBufferLayer) { shot->expectColor(Rect(750, 750, 950, 950), Color::GREEN); } - fillBufferStateLayerColor(bufferStateLayer, Color::WHITE, 200, 200); + fillBufferLayerColor(layer, Color::WHITE, 200, 200); { - SCOPED_TRACE("Update BufferStateLayer"); + SCOPED_TRACE("Update Layer"); auto shot = screenshot(); // Buffer mirror shot->expectColor(Rect(550, 550, 750, 750), Color::WHITE); @@ -218,9 +226,9 @@ TEST_F(MirrorLayerTest, MirrorBufferLayer) { shot->expectColor(Rect(750, 750, 950, 950), Color::GREEN); } - Transaction().reparent(bufferStateLayer, nullptr).apply(); + Transaction().reparent(layer, nullptr).apply(); { - SCOPED_TRACE("Removed BufferStateLayer"); + SCOPED_TRACE("Removed Layer"); auto shot = screenshot(); // Buffer mirror shot->expectColor(Rect(550, 550, 750, 750), Color::GREEN); @@ -231,7 +239,10 @@ TEST_F(MirrorLayerTest, MirrorBufferLayer) { // Test that the mirror layer is initially offscreen. TEST_F(MirrorLayerTest, InitialMirrorState) { - const auto display = SurfaceComposerClient::getInternalDisplayToken(); + const auto ids = SurfaceComposerClient::getPhysicalDisplayIds(); + ASSERT_FALSE(ids.empty()); + + const auto display = SurfaceComposerClient::getPhysicalDisplayToken(ids.front()); ui::DisplayMode mode; SurfaceComposerClient::getActiveDisplayMode(display, &mode); const ui::Size& size = mode.resolution; @@ -275,7 +286,9 @@ TEST_F(MirrorLayerTest, InitialMirrorState) { // Test that a mirror layer can be screenshot when offscreen TEST_F(MirrorLayerTest, OffscreenMirrorScreenshot) { - const auto display = SurfaceComposerClient::getInternalDisplayToken(); + const auto ids = SurfaceComposerClient::getPhysicalDisplayIds(); + ASSERT_FALSE(ids.empty()); + const auto display = SurfaceComposerClient::getPhysicalDisplayToken(ids.front()); ui::DisplayMode mode; SurfaceComposerClient::getActiveDisplayMode(display, &mode); const ui::Size& size = mode.resolution; @@ -283,7 +296,7 @@ TEST_F(MirrorLayerTest, OffscreenMirrorScreenshot) { sp grandchild = createLayer("Grandchild layer", 50, 50, ISurfaceComposerClient::eFXSurfaceBufferState, mChildLayer.get()); - ASSERT_NO_FATAL_FAILURE(fillBufferStateLayerColor(grandchild, Color::BLUE, 50, 50)); + ASSERT_NO_FATAL_FAILURE(fillBufferLayerColor(grandchild, Color::BLUE, 50, 50)); Rect childBounds = Rect(50, 50, 450, 450); asTransaction([&](Transaction& t) { diff --git a/services/surfaceflinger/tests/MultiDisplayLayerBounds_test.cpp b/services/surfaceflinger/tests/MultiDisplayLayerBounds_test.cpp index 1ed6c65afba8a00325f22bf10b76f298ef1b9c77..15ff69641299c878d37cd7773fd438cd06d9c5b3 100644 --- a/services/surfaceflinger/tests/MultiDisplayLayerBounds_test.cpp +++ b/services/surfaceflinger/tests/MultiDisplayLayerBounds_test.cpp @@ -35,7 +35,9 @@ protected: LayerTransactionTest::SetUp(); ASSERT_EQ(NO_ERROR, mClient->initCheck()); - mMainDisplay = SurfaceComposerClient::getInternalDisplayToken(); + const auto ids = SurfaceComposerClient::getPhysicalDisplayIds(); + ASSERT_FALSE(ids.empty()); + mMainDisplay = SurfaceComposerClient::getPhysicalDisplayToken(ids.front()); SurfaceComposerClient::getDisplayState(mMainDisplay, &mMainDisplayState); SurfaceComposerClient::getActiveDisplayMode(mMainDisplay, &mMainDisplayMode); diff --git a/services/surfaceflinger/tests/RelativeZ_test.cpp b/services/surfaceflinger/tests/RelativeZ_test.cpp index 50a4092ddb058876f791f16efbbfaf77b2aebbf7..9cebf11b9ce86aaa85926e437c5f9191fede5d43 100644 --- a/services/surfaceflinger/tests/RelativeZ_test.cpp +++ b/services/surfaceflinger/tests/RelativeZ_test.cpp @@ -33,7 +33,9 @@ protected: LayerTransactionTest::SetUp(); ASSERT_EQ(NO_ERROR, mClient->initCheck()); - const auto display = SurfaceComposerClient::getInternalDisplayToken(); + const auto ids = SurfaceComposerClient::getPhysicalDisplayIds(); + ASSERT_FALSE(ids.empty()); + const auto display = SurfaceComposerClient::getPhysicalDisplayToken(ids.front()); ASSERT_FALSE(display == nullptr); // Back layer diff --git a/services/surfaceflinger/tests/ReleaseBufferCallback_test.cpp b/services/surfaceflinger/tests/ReleaseBufferCallback_test.cpp index a6d7f5834f4bc68f5adaca94d3db7c5d436a9104..c23fb9bd23fe19c113bf878129883727e8ffabdb 100644 --- a/services/surfaceflinger/tests/ReleaseBufferCallback_test.cpp +++ b/services/surfaceflinger/tests/ReleaseBufferCallback_test.cpp @@ -85,7 +85,8 @@ public: sp fence, CallbackHelper& callback, const ReleaseCallbackId& id, ReleaseBufferCallbackHelper& releaseCallback) { Transaction t; - t.setBuffer(layer, buffer, fence, id.framenumber, releaseCallback.getCallback()); + t.setBuffer(layer, buffer, fence, id.framenumber, 0 /* producerId */, + releaseCallback.getCallback()); t.addTransactionCompletedCallback(callback.function, callback.getContext()); t.apply(); } @@ -110,10 +111,10 @@ public: } static sp getBuffer() { - return new GraphicBuffer(32, 32, PIXEL_FORMAT_RGBA_8888, 1, - BufferUsage::CPU_READ_OFTEN | BufferUsage::CPU_WRITE_OFTEN | - BufferUsage::COMPOSER_OVERLAY, - "test"); + return sp::make(32u, 32u, PIXEL_FORMAT_RGBA_8888, 1u, + BufferUsage::CPU_READ_OFTEN | BufferUsage::CPU_WRITE_OFTEN | + BufferUsage::COMPOSER_OVERLAY, + "test"); } static uint64_t generateFrameNumber() { static uint64_t sFrameNumber = 0; @@ -301,7 +302,7 @@ TEST_F(ReleaseBufferCallbackTest, DISABLED_FrameDropping) { Transaction t; t.setBuffer(layer, firstBuffer, std::nullopt, firstBufferCallbackId.framenumber, - releaseCallback->getCallback()); + 0 /* producerId */, releaseCallback->getCallback()); t.addTransactionCompletedCallback(transactionCallback.function, transactionCallback.getContext()); t.setDesiredPresentTime(time); @@ -317,7 +318,7 @@ TEST_F(ReleaseBufferCallbackTest, DISABLED_FrameDropping) { sp secondBuffer = getBuffer(); ReleaseCallbackId secondBufferCallbackId(secondBuffer->getId(), generateFrameNumber()); t.setBuffer(layer, secondBuffer, std::nullopt, secondBufferCallbackId.framenumber, - releaseCallback->getCallback()); + 0 /* producerId */, releaseCallback->getCallback()); t.addTransactionCompletedCallback(transactionCallback.function, transactionCallback.getContext()); t.setDesiredPresentTime(time); @@ -332,8 +333,10 @@ TEST_F(ReleaseBufferCallbackTest, DISABLED_FrameDropping) { } TEST_F(ReleaseBufferCallbackTest, DISABLED_Merge_Different_Processes) { - sp firstCompletedListener = new TransactionCompletedListener(); - sp secondCompletedListener = new TransactionCompletedListener(); + sp firstCompletedListener = + sp::make(); + sp secondCompletedListener = + sp::make(); CallbackHelper callback1, callback2; @@ -360,7 +363,7 @@ TEST_F(ReleaseBufferCallbackTest, DISABLED_Merge_Different_Processes) { Transaction transaction1; transaction1.setBuffer(layer, secondBuffer, std::nullopt, secondBufferCallbackId.framenumber, - releaseCallback->getCallback()); + 0 /* producerId */, releaseCallback->getCallback()); transaction1.addTransactionCompletedCallback(callback1.function, callback1.getContext()); // Set a different TransactionCompletedListener to mimic a second process @@ -395,14 +398,14 @@ TEST_F(ReleaseBufferCallbackTest, DISABLED_SetBuffer_OverwriteBuffers) { // Create transaction with a buffer. Transaction transaction; transaction.setBuffer(layer, firstBuffer, std::nullopt, firstBufferCallbackId.framenumber, - releaseCallback->getCallback()); + 0 /* producerId */, releaseCallback->getCallback()); sp secondBuffer = getBuffer(); ReleaseCallbackId secondBufferCallbackId(secondBuffer->getId(), generateFrameNumber()); // Call setBuffer on the same transaction with a different buffer. transaction.setBuffer(layer, secondBuffer, std::nullopt, secondBufferCallbackId.framenumber, - releaseCallback->getCallback()); + 0 /* producerId */, releaseCallback->getCallback()); ASSERT_NO_FATAL_FAILURE(waitForReleaseBufferCallback(*releaseCallback, firstBufferCallbackId)); } @@ -417,7 +420,7 @@ TEST_F(ReleaseBufferCallbackTest, DISABLED_Merge_Transactions_OverwriteBuffers) // Create transaction with a buffer. Transaction transaction1; transaction1.setBuffer(layer, firstBuffer, std::nullopt, firstBufferCallbackId.framenumber, - releaseCallback->getCallback()); + 0 /* producerId */, releaseCallback->getCallback()); sp secondBuffer = getBuffer(); ReleaseCallbackId secondBufferCallbackId(secondBuffer->getId(), generateFrameNumber()); @@ -425,7 +428,7 @@ TEST_F(ReleaseBufferCallbackTest, DISABLED_Merge_Transactions_OverwriteBuffers) // Create a second transaction with a new buffer for the same layer. Transaction transaction2; transaction2.setBuffer(layer, secondBuffer, std::nullopt, secondBufferCallbackId.framenumber, - releaseCallback->getCallback()); + 0 /* producerId */, releaseCallback->getCallback()); // merge transaction1 into transaction2 so ensure we get a proper buffer release callback. transaction1.merge(std::move(transaction2)); @@ -433,8 +436,10 @@ TEST_F(ReleaseBufferCallbackTest, DISABLED_Merge_Transactions_OverwriteBuffers) } TEST_F(ReleaseBufferCallbackTest, DISABLED_MergeBuffers_Different_Processes) { - sp firstCompletedListener = new TransactionCompletedListener(); - sp secondCompletedListener = new TransactionCompletedListener(); + sp firstCompletedListener = + sp::make(); + sp secondCompletedListener = + sp::make(); TransactionCompletedListener::setInstance(firstCompletedListener); @@ -446,7 +451,7 @@ TEST_F(ReleaseBufferCallbackTest, DISABLED_MergeBuffers_Different_Processes) { Transaction transaction1; transaction1.setBuffer(layer, firstBuffer, std::nullopt, firstBufferCallbackId.framenumber, - releaseCallback->getCallback()); + 0 /* producerId */, releaseCallback->getCallback()); // Sent a second buffer to allow the first buffer to get released. sp secondBuffer = getBuffer(); @@ -454,7 +459,7 @@ TEST_F(ReleaseBufferCallbackTest, DISABLED_MergeBuffers_Different_Processes) { Transaction transaction2; transaction2.setBuffer(layer, secondBuffer, std::nullopt, secondBufferCallbackId.framenumber, - releaseCallback->getCallback()); + 0 /* producerId */, releaseCallback->getCallback()); // Set a different TransactionCompletedListener to mimic a second process TransactionCompletedListener::setInstance(secondCompletedListener); @@ -475,10 +480,11 @@ TEST_F(ReleaseBufferCallbackTest, SetBuffer_OverwriteBuffersWithNull) { // Create transaction with a buffer. Transaction transaction; transaction.setBuffer(layer, firstBuffer, std::nullopt, firstBufferCallbackId.framenumber, - releaseCallback->getCallback()); + 0 /* producerId */, releaseCallback->getCallback()); // Call setBuffer on the same transaction with a null buffer. - transaction.setBuffer(layer, nullptr, std::nullopt, 0, releaseCallback->getCallback()); + transaction.setBuffer(layer, nullptr, std::nullopt, 0, 0 /* producerId */, + releaseCallback->getCallback()); ASSERT_NO_FATAL_FAILURE(waitForReleaseBufferCallback(*releaseCallback, firstBufferCallbackId)); } diff --git a/services/surfaceflinger/tests/ScreenCapture_test.cpp b/services/surfaceflinger/tests/ScreenCapture_test.cpp index 6a7d8b8ca522d35d05bc95bb8c7bca24476908b5..013694fd479c90063b4c8c41748a847fe16db42b 100644 --- a/services/surfaceflinger/tests/ScreenCapture_test.cpp +++ b/services/surfaceflinger/tests/ScreenCapture_test.cpp @@ -30,7 +30,9 @@ protected: LayerTransactionTest::SetUp(); ASSERT_EQ(NO_ERROR, mClient->initCheck()); - const auto display = SurfaceComposerClient::getInternalDisplayToken(); + const auto ids = SurfaceComposerClient::getPhysicalDisplayIds(); + ASSERT_FALSE(ids.empty()); + const auto display = SurfaceComposerClient::getPhysicalDisplayToken(ids.front()); ASSERT_FALSE(display == nullptr); ui::DisplayMode mode; @@ -220,6 +222,14 @@ TEST_F(ScreenCaptureTest, CaptureLayerExclude) { mCapture->checkPixel(0, 0, 200, 200, 200); } +TEST_F(ScreenCaptureTest, CaptureLayerExcludeThroughDisplayArgs) { + mCaptureArgs.excludeHandles = {mFGSurfaceControl->getHandle()}; + ScreenCapture::captureDisplay(&mCapture, mCaptureArgs); + mCapture->expectBGColor(0, 0); + // Doesn't capture FG layer which is at 64, 64 + mCapture->expectBGColor(64, 64); +} + // Like the last test but verifies that children are also exclude. TEST_F(ScreenCaptureTest, CaptureLayerExcludeTree) { auto fgHandle = mFGSurfaceControl->getHandle(); @@ -371,7 +381,7 @@ TEST_F(ScreenCaptureTest, CaptureBufferLayerWithoutBufferFails) { ScreenCaptureResults captureResults; ASSERT_EQ(BAD_VALUE, ScreenCapture::captureLayers(args, captureResults)); - ASSERT_NO_FATAL_FAILURE(fillBufferStateLayerColor(child, Color::RED, 32, 32)); + ASSERT_NO_FATAL_FAILURE(fillBufferLayerColor(child, Color::RED, 32, 32)); SurfaceComposerClient::Transaction().apply(true); ASSERT_EQ(NO_ERROR, ScreenCapture::captureLayers(args, captureResults)); ScreenCapture sc(captureResults.buffer, captureResults.capturedHdrLayers); @@ -449,8 +459,8 @@ TEST_F(ScreenCaptureTest, CaptureCrop) { ISurfaceComposerClient::eFXSurfaceBufferState, redLayer.get()); - ASSERT_NO_FATAL_FAILURE(fillBufferStateLayerColor(redLayer, Color::RED, 60, 60)); - ASSERT_NO_FATAL_FAILURE(fillBufferStateLayerColor(blueLayer, Color::BLUE, 30, 30)); + ASSERT_NO_FATAL_FAILURE(fillBufferLayerColor(redLayer, Color::RED, 60, 60)); + ASSERT_NO_FATAL_FAILURE(fillBufferLayerColor(blueLayer, Color::BLUE, 30, 30)); SurfaceComposerClient::Transaction() .setLayer(redLayer, INT32_MAX - 1) @@ -484,8 +494,8 @@ TEST_F(ScreenCaptureTest, CaptureSize) { ISurfaceComposerClient::eFXSurfaceBufferState, redLayer.get()); - ASSERT_NO_FATAL_FAILURE(fillBufferStateLayerColor(redLayer, Color::RED, 60, 60)); - ASSERT_NO_FATAL_FAILURE(fillBufferStateLayerColor(blueLayer, Color::BLUE, 30, 30)); + ASSERT_NO_FATAL_FAILURE(fillBufferLayerColor(redLayer, Color::RED, 60, 60)); + ASSERT_NO_FATAL_FAILURE(fillBufferLayerColor(blueLayer, Color::BLUE, 30, 30)); SurfaceComposerClient::Transaction() .setLayer(redLayer, INT32_MAX - 1) @@ -519,7 +529,7 @@ TEST_F(ScreenCaptureTest, CaptureSize) { TEST_F(ScreenCaptureTest, CaptureInvalidLayer) { LayerCaptureArgs args; - args.layerHandle = new BBinder(); + args.layerHandle = sp::make(); ScreenCaptureResults captureResults; // Layer was deleted so captureLayers should fail with NAME_NOT_FOUND @@ -549,8 +559,8 @@ TEST_F(ScreenCaptureTest, CaptureSecureLayer) { ISurfaceComposerClient::eSecure | ISurfaceComposerClient::eFXSurfaceBufferState, redLayer.get()); - ASSERT_NO_FATAL_FAILURE(fillBufferStateLayerColor(redLayer, Color::RED, 60, 60)); - ASSERT_NO_FATAL_FAILURE(fillBufferStateLayerColor(secureLayer, Color::BLUE, 30, 30)); + ASSERT_NO_FATAL_FAILURE(fillBufferLayerColor(redLayer, Color::RED, 60, 60)); + ASSERT_NO_FATAL_FAILURE(fillBufferLayerColor(secureLayer, Color::BLUE, 30, 30)); auto redLayerHandle = redLayer->getHandle(); Transaction() @@ -803,7 +813,7 @@ TEST_F(ScreenCaptureTest, CaptureWithGrayscale) { ASSERT_NO_FATAL_FAILURE(layer = createLayer("test layer", 32, 32, ISurfaceComposerClient::eFXSurfaceBufferState, mBGSurfaceControl.get())); - ASSERT_NO_FATAL_FAILURE(fillBufferStateLayerColor(layer, Color::RED, 32, 32)); + ASSERT_NO_FATAL_FAILURE(fillBufferLayerColor(layer, Color::RED, 32, 32)); Transaction().show(layer).setLayer(layer, INT32_MAX).apply(); LayerCaptureArgs captureArgs; @@ -825,7 +835,7 @@ TEST_F(ScreenCaptureTest, CaptureWithGrayscale) { mCapture->expectColor(Rect(0, 0, 32, 32), Color{expectedColor, expectedColor, expectedColor, 255}, tolerance); - ASSERT_NO_FATAL_FAILURE(fillBufferStateLayerColor(layer, Color::BLUE, 32, 32)); + ASSERT_NO_FATAL_FAILURE(fillBufferLayerColor(layer, Color::BLUE, 32, 32)); ScreenCapture::captureLayers(&mCapture, captureArgs); expectedColor = luminance.b * 255; @@ -838,7 +848,7 @@ TEST_F(ScreenCaptureTest, CaptureOffscreen) { ASSERT_NO_FATAL_FAILURE(layer = createLayer("test layer", 32, 32, ISurfaceComposerClient::eFXSurfaceBufferState, mBGSurfaceControl.get())); - ASSERT_NO_FATAL_FAILURE(fillBufferStateLayerColor(layer, Color::RED, 32, 32)); + ASSERT_NO_FATAL_FAILURE(fillBufferLayerColor(layer, Color::RED, 32, 32)); Transaction().show(layer).hide(mFGSurfaceControl).reparent(layer, nullptr).apply(); @@ -865,7 +875,7 @@ TEST_F(ScreenCaptureTest, CaptureNonHdrLayer) { ASSERT_NO_FATAL_FAILURE(layer = createLayer("test layer", 32, 32, ISurfaceComposerClient::eFXSurfaceBufferState, mBGSurfaceControl.get())); - ASSERT_NO_FATAL_FAILURE(fillBufferStateLayerColor(layer, Color::BLACK, 32, 32)); + ASSERT_NO_FATAL_FAILURE(fillBufferLayerColor(layer, Color::BLACK, 32, 32)); Transaction() .show(layer) .setLayer(layer, INT32_MAX) @@ -885,7 +895,7 @@ TEST_F(ScreenCaptureTest, CaptureHdrLayer) { ASSERT_NO_FATAL_FAILURE(layer = createLayer("test layer", 32, 32, ISurfaceComposerClient::eFXSurfaceBufferState, mBGSurfaceControl.get())); - ASSERT_NO_FATAL_FAILURE(fillBufferStateLayerColor(layer, Color::BLACK, 32, 32)); + ASSERT_NO_FATAL_FAILURE(fillBufferLayerColor(layer, Color::BLACK, 32, 32)); Transaction() .show(layer) .setLayer(layer, INT32_MAX) diff --git a/services/surfaceflinger/tests/SetFrameRateOverride_test.cpp b/services/surfaceflinger/tests/SetFrameRateOverride_test.cpp index 4efec7738aa6c9ca8eccc5c298c800b685da38c9..e43ef952d60735b56bcf1e1baebec1d73c830d22 100644 --- a/services/surfaceflinger/tests/SetFrameRateOverride_test.cpp +++ b/services/surfaceflinger/tests/SetFrameRateOverride_test.cpp @@ -14,9 +14,9 @@ * limitations under the License. */ +#include #include #include -#include #include #include #include @@ -24,12 +24,14 @@ namespace android { namespace { using FrameRateOverride = DisplayEventReceiver::Event::FrameRateOverride; +using gui::ISurfaceComposer; class SetFrameRateOverrideTest : public ::testing::Test { protected: void SetUp() override { - const ISurfaceComposer::VsyncSource vsyncSource = ISurfaceComposer::eVsyncSourceApp; - const ISurfaceComposer::EventRegistrationFlags eventRegistration = { + const ISurfaceComposer::VsyncSource vsyncSource = + ISurfaceComposer::VsyncSource::eVsyncSourceApp; + const EventRegistrationFlags eventRegistration = { ISurfaceComposer::EventRegistration::frameRateOverride}; mDisplayEventReceiver = diff --git a/services/surfaceflinger/tests/Stress_test.cpp b/services/surfaceflinger/tests/Stress_test.cpp index e9b6ba0f64b5d844f85cbcbbc90d738bfa9c5c6f..03201f7937e832a3ae2c9e0a1539176f03a1f45e 100644 --- a/services/surfaceflinger/tests/Stress_test.cpp +++ b/services/surfaceflinger/tests/Stress_test.cpp @@ -32,7 +32,7 @@ namespace android { TEST(SurfaceFlingerStress, create_and_destroy) { auto do_stress = []() { - sp client = new SurfaceComposerClient; + sp client = sp::make(); ASSERT_EQ(NO_ERROR, client->initCheck()); for (int j = 0; j < 1000; j++) { auto surf = client->createSurface(String8("t"), 100, 100, diff --git a/services/surfaceflinger/tests/SurfaceInterceptor_test.cpp b/services/surfaceflinger/tests/SurfaceInterceptor_test.cpp deleted file mode 100644 index 7a98bc27a92d321c8e55c9ed82c021bb87e8fab4..0000000000000000000000000000000000000000 --- a/services/surfaceflinger/tests/SurfaceInterceptor_test.cpp +++ /dev/null @@ -1,967 +0,0 @@ -/* - * Copyright (C) 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. - */ - -// TODO(b/129481165): remove the #pragma below and fix conversion issues -#pragma clang diagnostic push -#pragma clang diagnostic ignored "-Wconversion" -#pragma clang diagnostic ignored "-Wextra" - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include -#include -#include - -namespace android { - -using Transaction = SurfaceComposerClient::Transaction; -using SurfaceChange = surfaceflinger::SurfaceChange; -using Trace = surfaceflinger::Trace; -using Increment = surfaceflinger::Increment; - -constexpr uint32_t BUFFER_UPDATES = 18; -constexpr uint32_t LAYER_UPDATE = INT_MAX - 2; -constexpr uint32_t SIZE_UPDATE = 134; -constexpr uint32_t STACK_UPDATE = 1; -constexpr int32_t RELATIVE_Z = 42; -constexpr float ALPHA_UPDATE = 0.29f; -constexpr float CORNER_RADIUS_UPDATE = 0.2f; -constexpr int BACKGROUND_BLUR_RADIUS_UPDATE = 24; -constexpr float POSITION_UPDATE = 121; -const Rect CROP_UPDATE(16, 16, 32, 32); -const float SHADOW_RADIUS_UPDATE = 35.0f; -std::vector BLUR_REGIONS_UPDATE; - -const String8 DISPLAY_NAME("SurfaceInterceptor Display Test"); -constexpr auto TEST_BG_SURFACE_NAME = "BG Interceptor Test Surface"; -constexpr auto TEST_FG_SURFACE_NAME = "FG Interceptor Test Surface"; -constexpr auto LAYER_NAME = "Layer Create and Delete Test"; - -constexpr auto DEFAULT_FILENAME = "/data/misc/wmtrace/transaction_trace.winscope"; - -// Fill an RGBA_8888 formatted surface with a single color. -static void fillSurfaceRGBA8(const sp& sc, uint8_t r, uint8_t g, uint8_t b) { - ANativeWindow_Buffer outBuffer; - sp s = sc->getSurface(); - ASSERT_TRUE(s != nullptr); - ASSERT_EQ(NO_ERROR, s->lock(&outBuffer, nullptr)); - uint8_t* img = reinterpret_cast(outBuffer.bits); - for (int y = 0; y < outBuffer.height; y++) { - for (int x = 0; x < outBuffer.width; x++) { - uint8_t* pixel = img + (4 * (y*outBuffer.stride + x)); - pixel[0] = r; - pixel[1] = g; - pixel[2] = b; - pixel[3] = 255; - } - } - ASSERT_EQ(NO_ERROR, s->unlockAndPost()); -} - -static status_t readProtoFile(Trace* trace) { - status_t err = NO_ERROR; - - int fd = open(DEFAULT_FILENAME, O_RDONLY); - { - google::protobuf::io::FileInputStream f(fd); - if (fd && !trace->ParseFromZeroCopyStream(&f)) { - err = PERMISSION_DENIED; - } - } - close(fd); - - return err; -} - -static void enableInterceptor() { - system("service call SurfaceFlinger 1020 i32 1 > /dev/null"); -} - -static void disableInterceptor() { - system("service call SurfaceFlinger 1020 i32 0 > /dev/null"); -} - -std::string getUniqueName(const std::string& name, const Increment& increment) { - return base::StringPrintf("%s#%d", name.c_str(), increment.surface_creation().id()); -} - -int32_t getSurfaceId(const Trace& capturedTrace, const std::string& surfaceName) { - int32_t layerId = 0; - for (const auto& increment : capturedTrace.increment()) { - if (increment.increment_case() == increment.kSurfaceCreation) { - if (increment.surface_creation().name() == getUniqueName(surfaceName, increment)) { - layerId = increment.surface_creation().id(); - } - } - } - return layerId; -} - -int32_t getDisplayId(const Trace& capturedTrace, const std::string& displayName) { - int32_t displayId = 0; - for (const auto& increment : capturedTrace.increment()) { - if (increment.increment_case() == increment.kDisplayCreation) { - if (increment.display_creation().name() == displayName) { - displayId = increment.display_creation().id(); - break; - } - } - } - return displayId; -} - -class SurfaceInterceptorTest : public ::testing::Test { -protected: - void SetUp() override { - // Allow SurfaceInterceptor write to /data - system("setenforce 0"); - - mComposerClient = new SurfaceComposerClient; - ASSERT_EQ(NO_ERROR, mComposerClient->initCheck()); - } - - void TearDown() override { - mComposerClient->dispose(); - mBGSurfaceControl.clear(); - mFGSurfaceControl.clear(); - mComposerClient.clear(); - system("setenforce 1"); - } - - sp mComposerClient; - sp mBGSurfaceControl; - sp mFGSurfaceControl; - int32_t mBGLayerId; - int32_t mFGLayerId; - -public: - using TestTransactionAction = void (SurfaceInterceptorTest::*)(Transaction&); - using TestAction = void (SurfaceInterceptorTest::*)(); - using TestBooleanVerification = bool (SurfaceInterceptorTest::*)(const Trace&); - using TestVerification = void (SurfaceInterceptorTest::*)(const Trace&); - - void setupBackgroundSurface(); - void preProcessTrace(const Trace& trace); - - // captureTest will enable SurfaceInterceptor, setup background surface, - // disable SurfaceInterceptor, collect the trace and process the trace for - // id of background surface before further verification. - void captureTest(TestTransactionAction action, TestBooleanVerification verification); - void captureTest(TestTransactionAction action, SurfaceChange::SurfaceChangeCase changeCase); - void captureTest(TestTransactionAction action, Increment::IncrementCase incrementCase); - void captureTest(TestAction action, TestBooleanVerification verification); - void captureTest(TestAction action, TestVerification verification); - void runInTransaction(TestTransactionAction action); - - // Verification of changes to a surface - bool positionUpdateFound(const SurfaceChange& change, bool foundPosition); - bool sizeUpdateFound(const SurfaceChange& change, bool foundSize); - bool alphaUpdateFound(const SurfaceChange& change, bool foundAlpha); - bool layerUpdateFound(const SurfaceChange& change, bool foundLayer); - bool cropUpdateFound(const SurfaceChange& change, bool foundCrop); - bool cornerRadiusUpdateFound(const SurfaceChange& change, bool foundCornerRadius); - bool backgroundBlurRadiusUpdateFound(const SurfaceChange& change, - bool foundBackgroundBlurRadius); - bool blurRegionsUpdateFound(const SurfaceChange& change, bool foundBlurRegions); - bool matrixUpdateFound(const SurfaceChange& change, bool foundMatrix); - bool scalingModeUpdateFound(const SurfaceChange& change, bool foundScalingMode); - bool transparentRegionHintUpdateFound(const SurfaceChange& change, bool foundTransparentRegion); - bool layerStackUpdateFound(const SurfaceChange& change, bool foundLayerStack); - bool hiddenFlagUpdateFound(const SurfaceChange& change, bool foundHiddenFlag); - bool opaqueFlagUpdateFound(const SurfaceChange& change, bool foundOpaqueFlag); - bool secureFlagUpdateFound(const SurfaceChange& change, bool foundSecureFlag); - bool reparentUpdateFound(const SurfaceChange& change, bool found); - bool relativeParentUpdateFound(const SurfaceChange& change, bool found); - bool shadowRadiusUpdateFound(const SurfaceChange& change, bool found); - bool trustedOverlayUpdateFound(const SurfaceChange& change, bool found); - bool surfaceUpdateFound(const Trace& trace, SurfaceChange::SurfaceChangeCase changeCase); - - // Find all of the updates in the single trace - void assertAllUpdatesFound(const Trace& trace); - - // Verification of creation and deletion of a surface - bool surfaceCreationFound(const Increment& increment, bool foundSurface); - bool surfaceDeletionFound(const Increment& increment, const int32_t targetId, - bool foundSurface); - bool displayCreationFound(const Increment& increment, bool foundDisplay); - bool displayDeletionFound(const Increment& increment, const int32_t targetId, - bool foundDisplay); - bool singleIncrementFound(const Trace& trace, Increment::IncrementCase incrementCase); - - // Verification of buffer updates - bool bufferUpdatesFound(const Trace& trace); - - // Perform each of the possible changes to a surface - void positionUpdate(Transaction&); - void sizeUpdate(Transaction&); - void alphaUpdate(Transaction&); - void layerUpdate(Transaction&); - void cropUpdate(Transaction&); - void cornerRadiusUpdate(Transaction&); - void backgroundBlurRadiusUpdate(Transaction&); - void blurRegionsUpdate(Transaction&); - void matrixUpdate(Transaction&); - void transparentRegionHintUpdate(Transaction&); - void layerStackUpdate(Transaction&); - void hiddenFlagUpdate(Transaction&); - void opaqueFlagUpdate(Transaction&); - void secureFlagUpdate(Transaction&); - void reparentUpdate(Transaction&); - void relativeParentUpdate(Transaction&); - void shadowRadiusUpdate(Transaction&); - void trustedOverlayUpdate(Transaction&); - void surfaceCreation(Transaction&); - void displayCreation(Transaction&); - void displayDeletion(Transaction&); - - void nBufferUpdates(); - void runAllUpdates(); - -private: - void captureInTransaction(TestTransactionAction action, Trace*); - void capture(TestAction action, Trace*); -}; - -void SurfaceInterceptorTest::captureInTransaction(TestTransactionAction action, Trace* outTrace) { - enableInterceptor(); - setupBackgroundSurface(); - runInTransaction(action); - disableInterceptor(); - ASSERT_EQ(NO_ERROR, readProtoFile(outTrace)); - preProcessTrace(*outTrace); -} - -void SurfaceInterceptorTest::capture(TestAction action, Trace* outTrace) { - enableInterceptor(); - setupBackgroundSurface(); - (this->*action)(); - disableInterceptor(); - ASSERT_EQ(NO_ERROR, readProtoFile(outTrace)); - preProcessTrace(*outTrace); -} - -void SurfaceInterceptorTest::setupBackgroundSurface() { - const auto display = SurfaceComposerClient::getInternalDisplayToken(); - ASSERT_FALSE(display == nullptr); - - ui::DisplayMode mode; - ASSERT_EQ(NO_ERROR, SurfaceComposerClient::getActiveDisplayMode(display, &mode)); - const ui::Size& resolution = mode.resolution; - - // Background surface - mBGSurfaceControl = - mComposerClient->createSurface(String8(TEST_BG_SURFACE_NAME), resolution.getWidth(), - resolution.getHeight(), PIXEL_FORMAT_RGBA_8888, 0); - ASSERT_TRUE(mBGSurfaceControl != nullptr); - ASSERT_TRUE(mBGSurfaceControl->isValid()); - - // Foreground surface - mFGSurfaceControl = - mComposerClient->createSurface(String8(TEST_FG_SURFACE_NAME), resolution.getWidth(), - resolution.getHeight(), PIXEL_FORMAT_RGBA_8888, 0); - ASSERT_TRUE(mFGSurfaceControl != nullptr); - ASSERT_TRUE(mFGSurfaceControl->isValid()); - - Transaction t; - t.setDisplayLayerStack(display, ui::DEFAULT_LAYER_STACK); - ASSERT_EQ(NO_ERROR, - t.setLayer(mBGSurfaceControl, INT_MAX - 3) - .show(mBGSurfaceControl) - .setLayer(mFGSurfaceControl, INT_MAX - 3) - .show(mFGSurfaceControl) - .apply()); -} - -void SurfaceInterceptorTest::preProcessTrace(const Trace& trace) { - mBGLayerId = getSurfaceId(trace, TEST_BG_SURFACE_NAME); - mFGLayerId = getSurfaceId(trace, TEST_FG_SURFACE_NAME); -} - -void SurfaceInterceptorTest::captureTest(TestTransactionAction action, - TestBooleanVerification verification) { - Trace capturedTrace; - captureInTransaction(action, &capturedTrace); - ASSERT_TRUE((this->*verification)(capturedTrace)); -} - -void SurfaceInterceptorTest::captureTest(TestTransactionAction action, - Increment::IncrementCase incrementCase) { - Trace capturedTrace; - captureInTransaction(action, &capturedTrace); - ASSERT_TRUE(singleIncrementFound(capturedTrace, incrementCase)); -} - -void SurfaceInterceptorTest::captureTest(TestTransactionAction action, - SurfaceChange::SurfaceChangeCase changeCase) { - Trace capturedTrace; - captureInTransaction(action, &capturedTrace); - ASSERT_TRUE(surfaceUpdateFound(capturedTrace, changeCase)); -} - -void SurfaceInterceptorTest::captureTest(TestAction action, TestBooleanVerification verification) { - Trace capturedTrace; - capture(action, &capturedTrace); - ASSERT_TRUE((this->*verification)(capturedTrace)); -} - -void SurfaceInterceptorTest::captureTest(TestAction action, TestVerification verification) { - Trace capturedTrace; - capture(action, &capturedTrace); - (this->*verification)(capturedTrace); -} - -void SurfaceInterceptorTest::runInTransaction(TestTransactionAction action) { - Transaction t; - (this->*action)(t); - t.apply(true); -} - -void SurfaceInterceptorTest::positionUpdate(Transaction& t) { - t.setPosition(mBGSurfaceControl, POSITION_UPDATE, POSITION_UPDATE); -} - -void SurfaceInterceptorTest::sizeUpdate(Transaction& t) { - t.setSize(mBGSurfaceControl, SIZE_UPDATE, SIZE_UPDATE); -} - -void SurfaceInterceptorTest::alphaUpdate(Transaction& t) { - t.setAlpha(mBGSurfaceControl, ALPHA_UPDATE); -} - -void SurfaceInterceptorTest::cornerRadiusUpdate(Transaction& t) { - t.setCornerRadius(mBGSurfaceControl, CORNER_RADIUS_UPDATE); -} - -void SurfaceInterceptorTest::backgroundBlurRadiusUpdate(Transaction& t) { - t.setBackgroundBlurRadius(mBGSurfaceControl, BACKGROUND_BLUR_RADIUS_UPDATE); -} - -void SurfaceInterceptorTest::blurRegionsUpdate(Transaction& t) { - BLUR_REGIONS_UPDATE.clear(); - BLUR_REGIONS_UPDATE.push_back(BlurRegion()); - t.setBlurRegions(mBGSurfaceControl, BLUR_REGIONS_UPDATE); -} - -void SurfaceInterceptorTest::layerUpdate(Transaction& t) { - t.setLayer(mBGSurfaceControl, LAYER_UPDATE); -} - -void SurfaceInterceptorTest::cropUpdate(Transaction& t) { - t.setCrop(mBGSurfaceControl, CROP_UPDATE); -} - -void SurfaceInterceptorTest::matrixUpdate(Transaction& t) { - t.setMatrix(mBGSurfaceControl, M_SQRT1_2, M_SQRT1_2, -M_SQRT1_2, M_SQRT1_2); -} - -void SurfaceInterceptorTest::transparentRegionHintUpdate(Transaction& t) { - Region region(CROP_UPDATE); - t.setTransparentRegionHint(mBGSurfaceControl, region); -} - -void SurfaceInterceptorTest::layerStackUpdate(Transaction& t) { - t.setLayerStack(mBGSurfaceControl, ui::LayerStack::fromValue(STACK_UPDATE)); -} - -void SurfaceInterceptorTest::hiddenFlagUpdate(Transaction& t) { - t.setFlags(mBGSurfaceControl, layer_state_t::eLayerHidden, layer_state_t::eLayerHidden); -} - -void SurfaceInterceptorTest::opaqueFlagUpdate(Transaction& t) { - t.setFlags(mBGSurfaceControl, layer_state_t::eLayerOpaque, layer_state_t::eLayerOpaque); -} - -void SurfaceInterceptorTest::secureFlagUpdate(Transaction& t) { - t.setFlags(mBGSurfaceControl, layer_state_t::eLayerSecure, layer_state_t::eLayerSecure); -} - -void SurfaceInterceptorTest::reparentUpdate(Transaction& t) { - t.reparent(mBGSurfaceControl, mFGSurfaceControl); -} - -void SurfaceInterceptorTest::relativeParentUpdate(Transaction& t) { - t.setRelativeLayer(mBGSurfaceControl, mFGSurfaceControl, RELATIVE_Z); -} - -void SurfaceInterceptorTest::shadowRadiusUpdate(Transaction& t) { - t.setShadowRadius(mBGSurfaceControl, SHADOW_RADIUS_UPDATE); -} - -void SurfaceInterceptorTest::trustedOverlayUpdate(Transaction& t) { - t.setTrustedOverlay(mBGSurfaceControl, true); -} - -void SurfaceInterceptorTest::displayCreation(Transaction&) { - sp testDisplay = SurfaceComposerClient::createDisplay(DISPLAY_NAME, false); - SurfaceComposerClient::destroyDisplay(testDisplay); -} - -void SurfaceInterceptorTest::displayDeletion(Transaction&) { - sp testDisplay = SurfaceComposerClient::createDisplay(DISPLAY_NAME, false); - SurfaceComposerClient::destroyDisplay(testDisplay); -} - -void SurfaceInterceptorTest::runAllUpdates() { - runInTransaction(&SurfaceInterceptorTest::positionUpdate); - runInTransaction(&SurfaceInterceptorTest::sizeUpdate); - runInTransaction(&SurfaceInterceptorTest::alphaUpdate); - runInTransaction(&SurfaceInterceptorTest::cornerRadiusUpdate); - runInTransaction(&SurfaceInterceptorTest::backgroundBlurRadiusUpdate); - runInTransaction(&SurfaceInterceptorTest::blurRegionsUpdate); - runInTransaction(&SurfaceInterceptorTest::layerUpdate); - runInTransaction(&SurfaceInterceptorTest::cropUpdate); - runInTransaction(&SurfaceInterceptorTest::matrixUpdate); - runInTransaction(&SurfaceInterceptorTest::transparentRegionHintUpdate); - runInTransaction(&SurfaceInterceptorTest::layerStackUpdate); - runInTransaction(&SurfaceInterceptorTest::hiddenFlagUpdate); - runInTransaction(&SurfaceInterceptorTest::opaqueFlagUpdate); - runInTransaction(&SurfaceInterceptorTest::secureFlagUpdate); - runInTransaction(&SurfaceInterceptorTest::reparentUpdate); - runInTransaction(&SurfaceInterceptorTest::relativeParentUpdate); - runInTransaction(&SurfaceInterceptorTest::shadowRadiusUpdate); - runInTransaction(&SurfaceInterceptorTest::trustedOverlayUpdate); -} - -void SurfaceInterceptorTest::surfaceCreation(Transaction&) { - mComposerClient->createSurface(String8(LAYER_NAME), SIZE_UPDATE, SIZE_UPDATE, - PIXEL_FORMAT_RGBA_8888, 0); -} - -void SurfaceInterceptorTest::nBufferUpdates() { - std::random_device rd; - std::mt19937_64 gen(rd()); - // This makes testing fun - std::uniform_int_distribution dis; - for (uint32_t i = 0; i < BUFFER_UPDATES; ++i) { - fillSurfaceRGBA8(mBGSurfaceControl, dis(gen), dis(gen), dis(gen)); - } -} - -bool SurfaceInterceptorTest::positionUpdateFound(const SurfaceChange& change, bool foundPosition) { - // There should only be one position transaction with x and y = POSITION_UPDATE - bool hasX(change.position().x() == POSITION_UPDATE); - bool hasY(change.position().y() == POSITION_UPDATE); - if (hasX && hasY && !foundPosition) { - foundPosition = true; - } else if (hasX && hasY && foundPosition) { - // Failed because the position update was found a second time - [] () { FAIL(); }(); - } - return foundPosition; -} - -bool SurfaceInterceptorTest::sizeUpdateFound(const SurfaceChange& change, bool foundSize) { - bool hasWidth(change.size().h() == SIZE_UPDATE); - bool hasHeight(change.size().w() == SIZE_UPDATE); - if (hasWidth && hasHeight && !foundSize) { - foundSize = true; - } else if (hasWidth && hasHeight && foundSize) { - [] () { FAIL(); }(); - } - return foundSize; -} - -bool SurfaceInterceptorTest::alphaUpdateFound(const SurfaceChange& change, bool foundAlpha) { - bool hasAlpha(change.alpha().alpha() == ALPHA_UPDATE); - if (hasAlpha && !foundAlpha) { - foundAlpha = true; - } else if (hasAlpha && foundAlpha) { - [] () { FAIL(); }(); - } - return foundAlpha; -} - -bool SurfaceInterceptorTest::cornerRadiusUpdateFound(const SurfaceChange &change, - bool foundCornerRadius) { - bool hasCornerRadius(change.corner_radius().corner_radius() == CORNER_RADIUS_UPDATE); - if (hasCornerRadius && !foundCornerRadius) { - foundCornerRadius = true; - } else if (hasCornerRadius && foundCornerRadius) { - [] () { FAIL(); }(); - } - return foundCornerRadius; -} - -bool SurfaceInterceptorTest::backgroundBlurRadiusUpdateFound(const SurfaceChange& change, - bool foundBackgroundBlur) { - bool hasBackgroundBlur(change.background_blur_radius().background_blur_radius() == - BACKGROUND_BLUR_RADIUS_UPDATE); - if (hasBackgroundBlur && !foundBackgroundBlur) { - foundBackgroundBlur = true; - } else if (hasBackgroundBlur && foundBackgroundBlur) { - []() { FAIL(); }(); - } - return foundBackgroundBlur; -} - -bool SurfaceInterceptorTest::blurRegionsUpdateFound(const SurfaceChange& change, - bool foundBlurRegions) { - bool hasBlurRegions(change.blur_regions().blur_regions_size() == BLUR_REGIONS_UPDATE.size()); - if (hasBlurRegions && !foundBlurRegions) { - foundBlurRegions = true; - } else if (hasBlurRegions && foundBlurRegions) { - []() { FAIL(); }(); - } - return foundBlurRegions; -} - -bool SurfaceInterceptorTest::layerUpdateFound(const SurfaceChange& change, bool foundLayer) { - bool hasLayer(change.layer().layer() == LAYER_UPDATE); - if (hasLayer && !foundLayer) { - foundLayer = true; - } else if (hasLayer && foundLayer) { - [] () { FAIL(); }(); - } - return foundLayer; -} - -bool SurfaceInterceptorTest::cropUpdateFound(const SurfaceChange& change, bool foundCrop) { - bool hasLeft(change.crop().rectangle().left() == CROP_UPDATE.left); - bool hasTop(change.crop().rectangle().top() == CROP_UPDATE.top); - bool hasRight(change.crop().rectangle().right() == CROP_UPDATE.right); - bool hasBottom(change.crop().rectangle().bottom() == CROP_UPDATE.bottom); - if (hasLeft && hasRight && hasTop && hasBottom && !foundCrop) { - foundCrop = true; - } else if (hasLeft && hasRight && hasTop && hasBottom && foundCrop) { - [] () { FAIL(); }(); - } - return foundCrop; -} - -bool SurfaceInterceptorTest::matrixUpdateFound(const SurfaceChange& change, bool foundMatrix) { - bool hasSx((float)change.matrix().dsdx() == (float)M_SQRT1_2); - bool hasTx((float)change.matrix().dtdx() == (float)M_SQRT1_2); - bool hasSy((float)change.matrix().dsdy() == (float)M_SQRT1_2); - bool hasTy((float)change.matrix().dtdy() == (float)-M_SQRT1_2); - if (hasSx && hasTx && hasSy && hasTy && !foundMatrix) { - foundMatrix = true; - } else if (hasSx && hasTx && hasSy && hasTy && foundMatrix) { - [] () { FAIL(); }(); - } - return foundMatrix; -} - -bool SurfaceInterceptorTest::transparentRegionHintUpdateFound(const SurfaceChange& change, - bool foundTransparentRegion) { - auto traceRegion = change.transparent_region_hint().region(0); - bool hasLeft(traceRegion.left() == CROP_UPDATE.left); - bool hasTop(traceRegion.top() == CROP_UPDATE.top); - bool hasRight(traceRegion.right() == CROP_UPDATE.right); - bool hasBottom(traceRegion.bottom() == CROP_UPDATE.bottom); - if (hasLeft && hasRight && hasTop && hasBottom && !foundTransparentRegion) { - foundTransparentRegion = true; - } else if (hasLeft && hasRight && hasTop && hasBottom && foundTransparentRegion) { - [] () { FAIL(); }(); - } - return foundTransparentRegion; -} - -bool SurfaceInterceptorTest::layerStackUpdateFound(const SurfaceChange& change, - bool foundLayerStack) { - bool hasLayerStackUpdate(change.layer_stack().layer_stack() == STACK_UPDATE); - if (hasLayerStackUpdate && !foundLayerStack) { - foundLayerStack = true; - } else if (hasLayerStackUpdate && foundLayerStack) { - [] () { FAIL(); }(); - } - return foundLayerStack; -} - -bool SurfaceInterceptorTest::hiddenFlagUpdateFound(const SurfaceChange& change, - bool foundHiddenFlag) { - bool hasHiddenFlag(change.hidden_flag().hidden_flag()); - if (hasHiddenFlag && !foundHiddenFlag) { - foundHiddenFlag = true; - } else if (hasHiddenFlag && foundHiddenFlag) { - [] () { FAIL(); }(); - } - return foundHiddenFlag; -} - -bool SurfaceInterceptorTest::opaqueFlagUpdateFound(const SurfaceChange& change, - bool foundOpaqueFlag) { - bool hasOpaqueFlag(change.opaque_flag().opaque_flag()); - if (hasOpaqueFlag && !foundOpaqueFlag) { - foundOpaqueFlag = true; - } else if (hasOpaqueFlag && foundOpaqueFlag) { - [] () { FAIL(); }(); - } - return foundOpaqueFlag; -} - -bool SurfaceInterceptorTest::secureFlagUpdateFound(const SurfaceChange& change, - bool foundSecureFlag) { - bool hasSecureFlag(change.secure_flag().secure_flag()); - if (hasSecureFlag && !foundSecureFlag) { - foundSecureFlag = true; - } else if (hasSecureFlag && foundSecureFlag) { - [] () { FAIL(); }(); - } - return foundSecureFlag; -} - -bool SurfaceInterceptorTest::reparentUpdateFound(const SurfaceChange& change, bool found) { - bool hasId(change.reparent().parent_id() == mFGLayerId); - if (hasId && !found) { - found = true; - } else if (hasId && found) { - []() { FAIL(); }(); - } - return found; -} - -bool SurfaceInterceptorTest::relativeParentUpdateFound(const SurfaceChange& change, bool found) { - bool hasId(change.relative_parent().relative_parent_id() == mFGLayerId); - if (hasId && !found) { - found = true; - } else if (hasId && found) { - []() { FAIL(); }(); - } - return found; -} - -bool SurfaceInterceptorTest::shadowRadiusUpdateFound(const SurfaceChange& change, - bool foundShadowRadius) { - bool hasShadowRadius(change.shadow_radius().radius() == SHADOW_RADIUS_UPDATE); - if (hasShadowRadius && !foundShadowRadius) { - foundShadowRadius = true; - } else if (hasShadowRadius && foundShadowRadius) { - []() { FAIL(); }(); - } - return foundShadowRadius; -} - -bool SurfaceInterceptorTest::trustedOverlayUpdateFound(const SurfaceChange& change, - bool foundTrustedOverlay) { - bool hasTrustedOverlay(change.trusted_overlay().is_trusted_overlay()); - if (hasTrustedOverlay && !foundTrustedOverlay) { - foundTrustedOverlay = true; - } else if (hasTrustedOverlay && foundTrustedOverlay) { - []() { FAIL(); }(); - } - return foundTrustedOverlay; -} - -bool SurfaceInterceptorTest::surfaceUpdateFound(const Trace& trace, - SurfaceChange::SurfaceChangeCase changeCase) { - bool foundUpdate = false; - for (const auto& increment : trace.increment()) { - if (increment.increment_case() == increment.kTransaction) { - for (const auto& change : increment.transaction().surface_change()) { - if (change.id() == mBGLayerId && change.SurfaceChange_case() == changeCase) { - switch (changeCase) { - case SurfaceChange::SurfaceChangeCase::kPosition: - // foundUpdate is sent for the tests to fail on duplicated increments - foundUpdate = positionUpdateFound(change, foundUpdate); - break; - case SurfaceChange::SurfaceChangeCase::kSize: - foundUpdate = sizeUpdateFound(change, foundUpdate); - break; - case SurfaceChange::SurfaceChangeCase::kAlpha: - foundUpdate = alphaUpdateFound(change, foundUpdate); - break; - case SurfaceChange::SurfaceChangeCase::kLayer: - foundUpdate = layerUpdateFound(change, foundUpdate); - break; - case SurfaceChange::SurfaceChangeCase::kCrop: - foundUpdate = cropUpdateFound(change, foundUpdate); - break; - case SurfaceChange::SurfaceChangeCase::kCornerRadius: - foundUpdate = cornerRadiusUpdateFound(change, foundUpdate); - break; - case SurfaceChange::SurfaceChangeCase::kBackgroundBlurRadius: - foundUpdate = backgroundBlurRadiusUpdateFound(change, foundUpdate); - break; - case SurfaceChange::SurfaceChangeCase::kBlurRegions: - foundUpdate = blurRegionsUpdateFound(change, foundUpdate); - break; - case SurfaceChange::SurfaceChangeCase::kMatrix: - foundUpdate = matrixUpdateFound(change, foundUpdate); - break; - case SurfaceChange::SurfaceChangeCase::kTransparentRegionHint: - foundUpdate = transparentRegionHintUpdateFound(change, foundUpdate); - break; - case SurfaceChange::SurfaceChangeCase::kLayerStack: - foundUpdate = layerStackUpdateFound(change, foundUpdate); - break; - case SurfaceChange::SurfaceChangeCase::kHiddenFlag: - foundUpdate = hiddenFlagUpdateFound(change, foundUpdate); - break; - case SurfaceChange::SurfaceChangeCase::kOpaqueFlag: - foundUpdate = opaqueFlagUpdateFound(change, foundUpdate); - break; - case SurfaceChange::SurfaceChangeCase::kSecureFlag: - foundUpdate = secureFlagUpdateFound(change, foundUpdate); - break; - case SurfaceChange::SurfaceChangeCase::kReparent: - foundUpdate = reparentUpdateFound(change, foundUpdate); - break; - case SurfaceChange::SurfaceChangeCase::kRelativeParent: - foundUpdate = relativeParentUpdateFound(change, foundUpdate); - break; - case SurfaceChange::SurfaceChangeCase::kShadowRadius: - foundUpdate = shadowRadiusUpdateFound(change, foundUpdate); - break; - case SurfaceChange::SurfaceChangeCase::kTrustedOverlay: - foundUpdate = trustedOverlayUpdateFound(change, foundUpdate); - break; - case SurfaceChange::SurfaceChangeCase::SURFACECHANGE_NOT_SET: - break; - } - } - } - } - } - return foundUpdate; -} - -void SurfaceInterceptorTest::assertAllUpdatesFound(const Trace& trace) { - ASSERT_TRUE(surfaceUpdateFound(trace, SurfaceChange::SurfaceChangeCase::kPosition)); - ASSERT_TRUE(surfaceUpdateFound(trace, SurfaceChange::SurfaceChangeCase::kSize)); - ASSERT_TRUE(surfaceUpdateFound(trace, SurfaceChange::SurfaceChangeCase::kAlpha)); - ASSERT_TRUE(surfaceUpdateFound(trace, SurfaceChange::SurfaceChangeCase::kLayer)); - ASSERT_TRUE(surfaceUpdateFound(trace, SurfaceChange::SurfaceChangeCase::kCrop)); - ASSERT_TRUE(surfaceUpdateFound(trace, SurfaceChange::SurfaceChangeCase::kMatrix)); - ASSERT_TRUE(surfaceUpdateFound(trace, SurfaceChange::SurfaceChangeCase::kTransparentRegionHint)); - ASSERT_TRUE(surfaceUpdateFound(trace, SurfaceChange::SurfaceChangeCase::kLayerStack)); - ASSERT_TRUE(surfaceUpdateFound(trace, SurfaceChange::SurfaceChangeCase::kHiddenFlag)); - ASSERT_TRUE(surfaceUpdateFound(trace, SurfaceChange::SurfaceChangeCase::kOpaqueFlag)); - ASSERT_TRUE(surfaceUpdateFound(trace, SurfaceChange::SurfaceChangeCase::kSecureFlag)); - ASSERT_TRUE(surfaceUpdateFound(trace, SurfaceChange::SurfaceChangeCase::kReparent)); - ASSERT_TRUE(surfaceUpdateFound(trace, SurfaceChange::SurfaceChangeCase::kRelativeParent)); -} - -bool SurfaceInterceptorTest::surfaceCreationFound(const Increment& increment, bool foundSurface) { - bool isMatch(increment.surface_creation().name() == getUniqueName(LAYER_NAME, increment)); - if (isMatch && !foundSurface) { - foundSurface = true; - } else if (isMatch && foundSurface) { - [] () { FAIL(); }(); - } - return foundSurface; -} - -bool SurfaceInterceptorTest::surfaceDeletionFound(const Increment& increment, - const int32_t targetId, bool foundSurface) { - bool isMatch(increment.surface_deletion().id() == targetId); - if (isMatch && !foundSurface) { - foundSurface = true; - } else if (isMatch && foundSurface) { - [] () { FAIL(); }(); - } - return foundSurface; -} - -bool SurfaceInterceptorTest::displayCreationFound(const Increment& increment, bool foundDisplay) { - bool isMatch(increment.display_creation().name() == DISPLAY_NAME.c_str() && - !increment.display_creation().is_secure()); - if (isMatch && !foundDisplay) { - foundDisplay = true; - } else if (isMatch && foundDisplay) { - [] () { FAIL(); }(); - } - return foundDisplay; -} - -bool SurfaceInterceptorTest::displayDeletionFound(const Increment& increment, - const int32_t targetId, bool foundDisplay) { - bool isMatch(increment.display_deletion().id() == targetId); - if (isMatch && !foundDisplay) { - foundDisplay = true; - } else if (isMatch && foundDisplay) { - [] () { FAIL(); }(); - } - return foundDisplay; -} - -bool SurfaceInterceptorTest::singleIncrementFound(const Trace& trace, - Increment::IncrementCase incrementCase) { - bool foundIncrement = false; - for (const auto& increment : trace.increment()) { - if (increment.increment_case() == incrementCase) { - int32_t targetId = 0; - switch (incrementCase) { - case Increment::IncrementCase::kSurfaceCreation: - foundIncrement = surfaceCreationFound(increment, foundIncrement); - break; - case Increment::IncrementCase::kSurfaceDeletion: - // Find the id of created surface. - targetId = getSurfaceId(trace, LAYER_NAME); - foundIncrement = surfaceDeletionFound(increment, targetId, foundIncrement); - break; - case Increment::IncrementCase::kDisplayCreation: - foundIncrement = displayCreationFound(increment, foundIncrement); - break; - case Increment::IncrementCase::kDisplayDeletion: - // Find the id of created display. - targetId = getDisplayId(trace, DISPLAY_NAME.c_str()); - foundIncrement = displayDeletionFound(increment, targetId, foundIncrement); - break; - default: - /* code */ - break; - } - } - } - return foundIncrement; -} - -bool SurfaceInterceptorTest::bufferUpdatesFound(const Trace& trace) { - uint32_t updates = 0; - for (const auto& inc : trace.increment()) { - if (inc.increment_case() == inc.kBufferUpdate && inc.buffer_update().id() == mBGLayerId) { - updates++; - } - } - return updates == BUFFER_UPDATES; -} - -TEST_F(SurfaceInterceptorTest, InterceptPositionUpdateWorks) { - captureTest(&SurfaceInterceptorTest::positionUpdate, - SurfaceChange::SurfaceChangeCase::kPosition); -} - -TEST_F(SurfaceInterceptorTest, InterceptSizeUpdateWorks) { - captureTest(&SurfaceInterceptorTest::sizeUpdate, SurfaceChange::SurfaceChangeCase::kSize); -} - -TEST_F(SurfaceInterceptorTest, InterceptAlphaUpdateWorks) { - captureTest(&SurfaceInterceptorTest::alphaUpdate, SurfaceChange::SurfaceChangeCase::kAlpha); -} - -TEST_F(SurfaceInterceptorTest, InterceptLayerUpdateWorks) { - captureTest(&SurfaceInterceptorTest::layerUpdate, SurfaceChange::SurfaceChangeCase::kLayer); -} - -TEST_F(SurfaceInterceptorTest, InterceptCropUpdateWorks) { - captureTest(&SurfaceInterceptorTest::cropUpdate, SurfaceChange::SurfaceChangeCase::kCrop); -} - -TEST_F(SurfaceInterceptorTest, InterceptCornerRadiusUpdateWorks) { - captureTest(&SurfaceInterceptorTest::cornerRadiusUpdate, - SurfaceChange::SurfaceChangeCase::kCornerRadius); -} - -TEST_F(SurfaceInterceptorTest, InterceptBackgroundBlurRadiusUpdateWorks) { - captureTest(&SurfaceInterceptorTest::backgroundBlurRadiusUpdate, - SurfaceChange::SurfaceChangeCase::kBackgroundBlurRadius); -} - -TEST_F(SurfaceInterceptorTest, InterceptBlurRegionsUpdateWorks) { - captureTest(&SurfaceInterceptorTest::blurRegionsUpdate, - SurfaceChange::SurfaceChangeCase::kBlurRegions); -} - -TEST_F(SurfaceInterceptorTest, InterceptMatrixUpdateWorks) { - captureTest(&SurfaceInterceptorTest::matrixUpdate, SurfaceChange::SurfaceChangeCase::kMatrix); -} - -TEST_F(SurfaceInterceptorTest, InterceptTransparentRegionHintUpdateWorks) { - captureTest(&SurfaceInterceptorTest::transparentRegionHintUpdate, - SurfaceChange::SurfaceChangeCase::kTransparentRegionHint); -} - -TEST_F(SurfaceInterceptorTest, InterceptLayerStackUpdateWorks) { - captureTest(&SurfaceInterceptorTest::layerStackUpdate, - SurfaceChange::SurfaceChangeCase::kLayerStack); -} - -TEST_F(SurfaceInterceptorTest, InterceptHiddenFlagUpdateWorks) { - captureTest(&SurfaceInterceptorTest::hiddenFlagUpdate, - SurfaceChange::SurfaceChangeCase::kHiddenFlag); -} - -TEST_F(SurfaceInterceptorTest, InterceptOpaqueFlagUpdateWorks) { - captureTest(&SurfaceInterceptorTest::opaqueFlagUpdate, - SurfaceChange::SurfaceChangeCase::kOpaqueFlag); -} - -TEST_F(SurfaceInterceptorTest, InterceptSecureFlagUpdateWorks) { - captureTest(&SurfaceInterceptorTest::secureFlagUpdate, - SurfaceChange::SurfaceChangeCase::kSecureFlag); -} - -TEST_F(SurfaceInterceptorTest, InterceptReparentUpdateWorks) { - captureTest(&SurfaceInterceptorTest::reparentUpdate, - SurfaceChange::SurfaceChangeCase::kReparent); -} - -TEST_F(SurfaceInterceptorTest, InterceptRelativeParentUpdateWorks) { - captureTest(&SurfaceInterceptorTest::relativeParentUpdate, - SurfaceChange::SurfaceChangeCase::kRelativeParent); -} - -TEST_F(SurfaceInterceptorTest, InterceptShadowRadiusUpdateWorks) { - captureTest(&SurfaceInterceptorTest::shadowRadiusUpdate, - SurfaceChange::SurfaceChangeCase::kShadowRadius); -} - -TEST_F(SurfaceInterceptorTest, InterceptTrustedOverlayUpdateWorks) { - captureTest(&SurfaceInterceptorTest::trustedOverlayUpdate, - SurfaceChange::SurfaceChangeCase::kTrustedOverlay); -} - -TEST_F(SurfaceInterceptorTest, InterceptAllUpdatesWorks) { - captureTest(&SurfaceInterceptorTest::runAllUpdates, - &SurfaceInterceptorTest::assertAllUpdatesFound); -} - -TEST_F(SurfaceInterceptorTest, InterceptSurfaceCreationWorks) { - captureTest(&SurfaceInterceptorTest::surfaceCreation, - Increment::IncrementCase::kSurfaceCreation); -} - -TEST_F(SurfaceInterceptorTest, InterceptDisplayCreationWorks) { - captureTest(&SurfaceInterceptorTest::displayCreation, - Increment::IncrementCase::kDisplayCreation); -} - -TEST_F(SurfaceInterceptorTest, InterceptDisplayDeletionWorks) { - enableInterceptor(); - runInTransaction(&SurfaceInterceptorTest::displayDeletion); - disableInterceptor(); - Trace capturedTrace; - ASSERT_EQ(NO_ERROR, readProtoFile(&capturedTrace)); - ASSERT_TRUE(singleIncrementFound(capturedTrace, Increment::IncrementCase::kDisplayDeletion)); -} - -// If the interceptor is enabled while buffer updates are being pushed, the interceptor should -// first create a snapshot of the existing displays and surfaces and then start capturing -// the buffer updates -TEST_F(SurfaceInterceptorTest, InterceptWhileBufferUpdatesWorks) { - setupBackgroundSurface(); - std::thread bufferUpdates(&SurfaceInterceptorTest::nBufferUpdates, this); - enableInterceptor(); - disableInterceptor(); - bufferUpdates.join(); - - Trace capturedTrace; - ASSERT_EQ(NO_ERROR, readProtoFile(&capturedTrace)); - const auto& firstIncrement = capturedTrace.mutable_increment(0); - ASSERT_EQ(firstIncrement->increment_case(), Increment::IncrementCase::kDisplayCreation); -} -} -// TODO(b/129481165): remove the #pragma below and fix conversion issues -#pragma clang diagnostic pop // ignored "-Wconversion -Wextra" diff --git a/services/surfaceflinger/tests/TextureFiltering_test.cpp b/services/surfaceflinger/tests/TextureFiltering_test.cpp new file mode 100644 index 0000000000000000000000000000000000000000..d0ab105414e98ca23406df34d67714a9d2ee213f --- /dev/null +++ b/services/surfaceflinger/tests/TextureFiltering_test.cpp @@ -0,0 +1,238 @@ +/* + * 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. + */ + +#include +#include +#include +#include +#include + +#include "LayerTransactionTest.h" + +namespace android { + +bool operator==(const Color& left, const Color& right) { + return left.a == right.a && left.r == right.r && left.g == right.g && left.b == right.b; +} + +class TextureFilteringTest : public LayerTransactionTest { +protected: + virtual void SetUp() { + LayerTransactionTest::SetUp(); + + mParent = createLayer("test-parent", 100, 100, + gui::ISurfaceComposerClient::eFXSurfaceContainer); + mLayer = createLayer("test-child", 100, 100, + gui::ISurfaceComposerClient::eFXSurfaceBufferState, mParent.get()); + sp buffer = + sp::make(static_cast(100), static_cast(100), + 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, 50, 100}, Color::RED); + TransactionUtils::fillGraphicBufferColor(buffer, Rect{50, 0, 100, 100}, Color::BLUE); + Transaction() + .setBuffer(mLayer, buffer) + .setDataspace(mLayer, ui::Dataspace::V0_SRGB) + .setLayer(mLayer, INT32_MAX) + .apply(); + } + + virtual void TearDown() { LayerTransactionTest::TearDown(); } + + void expectFiltered(Rect redRect, Rect blueRect) { + // Check that at least some of the pixels in the red rectangle aren't solid red + int redPixels = 0; + for (int x = redRect.left; x < redRect.right; x++) { + for (int y = redRect.top; y < redRect.bottom; y++) { + redPixels += mCapture->getPixelColor(static_cast(x), + static_cast(y)) == Color::RED; + } + } + ASSERT_LT(redPixels, redRect.getWidth() * redRect.getHeight()); + + // Check that at least some of the pixels in the blue rectangle aren't solid blue + int bluePixels = 0; + for (int x = blueRect.left; x < blueRect.right; x++) { + for (int y = blueRect.top; y < blueRect.bottom; y++) { + bluePixels += mCapture->getPixelColor(static_cast(x), + static_cast(y)) == Color::BLUE; + } + } + ASSERT_LT(bluePixels, blueRect.getWidth() * blueRect.getHeight()); + } + + sp mParent; + sp mLayer; + std::unique_ptr mCapture; +}; + +TEST_F(TextureFilteringTest, NoFiltering) { + gui::DisplayCaptureArgs captureArgs; + captureArgs.displayToken = mDisplay; + captureArgs.width = 100; + captureArgs.height = 100; + captureArgs.sourceCrop = Rect{100, 100}; + ScreenCapture::captureDisplay(&mCapture, captureArgs); + + mCapture->expectColor(Rect{0, 0, 50, 100}, Color::RED); + mCapture->expectColor(Rect{50, 0, 100, 100}, Color::BLUE); +} + +TEST_F(TextureFilteringTest, BufferCropNoFiltering) { + Transaction().setBufferCrop(mLayer, Rect{0, 0, 100, 100}).apply(); + + gui::DisplayCaptureArgs captureArgs; + captureArgs.displayToken = mDisplay; + captureArgs.width = 100; + captureArgs.height = 100; + captureArgs.sourceCrop = Rect{0, 0, 100, 100}; + ScreenCapture::captureDisplay(&mCapture, captureArgs); + + mCapture->expectColor(Rect{0, 0, 50, 100}, Color::RED); + mCapture->expectColor(Rect{50, 0, 100, 100}, Color::BLUE); +} + +// Expect filtering because the buffer is stretched to the layer's bounds. +TEST_F(TextureFilteringTest, BufferCropIsFiltered) { + Transaction().setBufferCrop(mLayer, Rect{25, 25, 75, 75}).apply(); + + gui::DisplayCaptureArgs captureArgs; + captureArgs.displayToken = mDisplay; + captureArgs.width = 100; + captureArgs.height = 100; + captureArgs.sourceCrop = Rect{0, 0, 100, 100}; + ScreenCapture::captureDisplay(&mCapture, captureArgs); + + expectFiltered({0, 0, 50, 100}, {50, 0, 100, 100}); +} + +// Expect filtering because the output source crop is stretched to the output buffer's size. +TEST_F(TextureFilteringTest, OutputSourceCropIsFiltered) { + gui::DisplayCaptureArgs captureArgs; + captureArgs.displayToken = mDisplay; + captureArgs.width = 100; + captureArgs.height = 100; + captureArgs.sourceCrop = Rect{25, 25, 75, 75}; + ScreenCapture::captureDisplay(&mCapture, captureArgs); + + expectFiltered({0, 0, 50, 100}, {50, 0, 100, 100}); +} + +// Expect filtering because the layer crop and output source crop are stretched to the output +// buffer's size. +TEST_F(TextureFilteringTest, LayerCropOutputSourceCropIsFiltered) { + Transaction().setCrop(mLayer, Rect{25, 25, 75, 75}).apply(); + + gui::DisplayCaptureArgs captureArgs; + captureArgs.displayToken = mDisplay; + captureArgs.width = 100; + captureArgs.height = 100; + captureArgs.sourceCrop = Rect{25, 25, 75, 75}; + ScreenCapture::captureDisplay(&mCapture, captureArgs); + + expectFiltered({0, 0, 50, 100}, {50, 0, 100, 100}); +} + +// Expect filtering because the layer is scaled up. +TEST_F(TextureFilteringTest, LayerCaptureWithScalingIsFiltered) { + LayerCaptureArgs captureArgs; + captureArgs.layerHandle = mLayer->getHandle(); + captureArgs.frameScaleX = 2; + captureArgs.frameScaleY = 2; + ScreenCapture::captureLayers(&mCapture, captureArgs); + + expectFiltered({0, 0, 100, 200}, {100, 0, 200, 200}); +} + +// Expect no filtering because the output buffer's size matches the source crop. +TEST_F(TextureFilteringTest, LayerCaptureOutputSourceCropNoFiltering) { + LayerCaptureArgs captureArgs; + captureArgs.layerHandle = mLayer->getHandle(); + captureArgs.sourceCrop = Rect{25, 25, 75, 75}; + ScreenCapture::captureLayers(&mCapture, captureArgs); + + mCapture->expectColor(Rect{0, 0, 25, 50}, Color::RED); + mCapture->expectColor(Rect{25, 0, 50, 50}, Color::BLUE); +} + +// Expect no filtering because the output buffer's size matches the source crop (with a cropped +// layer). +TEST_F(TextureFilteringTest, LayerCaptureWithCropNoFiltering) { + Transaction().setCrop(mLayer, Rect{10, 10, 90, 90}).apply(); + + LayerCaptureArgs captureArgs; + captureArgs.layerHandle = mLayer->getHandle(); + captureArgs.sourceCrop = Rect{25, 25, 75, 75}; + ScreenCapture::captureLayers(&mCapture, captureArgs); + + mCapture->expectColor(Rect{0, 0, 25, 50}, Color::RED); + mCapture->expectColor(Rect{25, 0, 50, 50}, Color::BLUE); +} + +// Expect no filtering because the output source crop and output buffer are the same size. +TEST_F(TextureFilteringTest, OutputSourceCropDisplayFrameMatchNoFiltering) { + gui::DisplayCaptureArgs captureArgs; + captureArgs.displayToken = mDisplay; + captureArgs.width = 50; + captureArgs.height = 50; + captureArgs.sourceCrop = Rect{25, 25, 75, 75}; + ScreenCapture::captureDisplay(&mCapture, captureArgs); + + mCapture->expectColor(Rect{0, 0, 25, 50}, Color::RED); + mCapture->expectColor(Rect{25, 0, 50, 50}, Color::BLUE); +} + +// Expect no filtering because the layer crop shouldn't scale the layer. +TEST_F(TextureFilteringTest, LayerCropDisplayFrameMatchNoFiltering) { + Transaction().setCrop(mLayer, Rect{25, 25, 75, 75}).apply(); + + gui::DisplayCaptureArgs captureArgs; + captureArgs.displayToken = mDisplay; + ScreenCapture::captureDisplay(&mCapture, captureArgs); + + mCapture->expectColor(Rect{25, 25, 50, 75}, Color::RED); + mCapture->expectColor(Rect{50, 25, 75, 75}, Color::BLUE); +} + +// Expect no filtering because the parent layer crop shouldn't scale the layer. +TEST_F(TextureFilteringTest, ParentCropNoFiltering) { + Transaction().setCrop(mParent, Rect{25, 25, 75, 75}).apply(); + + gui::DisplayCaptureArgs captureArgs; + captureArgs.displayToken = mDisplay; + ScreenCapture::captureDisplay(&mCapture, captureArgs); + + mCapture->expectColor(Rect{25, 25, 50, 75}, Color::RED); + mCapture->expectColor(Rect{50, 25, 75, 75}, Color::BLUE); +} + +// Expect no filtering because parent's position transform shouldn't scale the layer. +TEST_F(TextureFilteringTest, ParentHasTransformNoFiltering) { + Transaction().setPosition(mParent, 100, 100).apply(); + + LayerCaptureArgs captureArgs; + captureArgs.layerHandle = mParent->getHandle(); + captureArgs.sourceCrop = Rect{0, 0, 100, 100}; + ScreenCapture::captureLayers(&mCapture, captureArgs); + + mCapture->expectColor(Rect{0, 0, 50, 100}, Color::RED); + mCapture->expectColor(Rect{50, 0, 100, 100}, Color::BLUE); +} + +} // namespace android diff --git a/services/surfaceflinger/tests/TransactionTestHarnesses.h b/services/surfaceflinger/tests/TransactionTestHarnesses.h index 8ce63bc64c32e4ecefdfb3bbb81c02883d4c3ca9..797a64c7d751ce6dd2422c2a7df1321b347fee82 100644 --- a/services/surfaceflinger/tests/TransactionTestHarnesses.h +++ b/services/surfaceflinger/tests/TransactionTestHarnesses.h @@ -35,7 +35,10 @@ public: return mDelegate->screenshot(); case RenderPath::VIRTUAL_DISPLAY: - const auto displayToken = SurfaceComposerClient::getInternalDisplayToken(); + const auto ids = SurfaceComposerClient::getPhysicalDisplayIds(); + const auto displayToken = ids.empty() + ? nullptr + : SurfaceComposerClient::getPhysicalDisplayToken(ids.front()); ui::DisplayState displayState; SurfaceComposerClient::getDisplayState(displayToken, &displayState); @@ -53,11 +56,11 @@ public: consumer->setConsumerName(String8("Virtual disp consumer")); consumer->setDefaultBufferSize(resolution.getWidth(), resolution.getHeight()); - itemConsumer = new BufferItemConsumer(consumer, - // Sample usage bits from screenrecord - GRALLOC_USAGE_HW_VIDEO_ENCODER | - GRALLOC_USAGE_SW_READ_OFTEN); - sp listener = new BufferListener(this); + itemConsumer = sp::make(consumer, + // Sample usage bits from screenrecord + GRALLOC_USAGE_HW_VIDEO_ENCODER | + GRALLOC_USAGE_SW_READ_OFTEN); + sp listener = sp::make(this); itemConsumer->setFrameAvailableListener(listener); vDisplay = SurfaceComposerClient::createDisplay(String8("VirtualDisplay"), diff --git a/services/surfaceflinger/tests/VirtualDisplay_test.cpp b/services/surfaceflinger/tests/VirtualDisplay_test.cpp index 18e08062c06449374dc1fc44b032c9029eb2c5ca..f31f582ffd127979b780bbc9c8d3cf0da60e12b2 100644 --- a/services/surfaceflinger/tests/VirtualDisplay_test.cpp +++ b/services/surfaceflinger/tests/VirtualDisplay_test.cpp @@ -33,7 +33,7 @@ protected: consumer->setConsumerName(String8("Virtual disp consumer")); consumer->setDefaultBufferSize(100, 100); - mGLConsumer = new GLConsumer(consumer, GLConsumer::TEXTURE_EXTERNAL, true, false); + mGLConsumer = sp::make(consumer, GLConsumer::TEXTURE_EXTERNAL, true, false); } sp mProducer; @@ -55,7 +55,7 @@ TEST_F(VirtualDisplayTest, VirtualDisplayDestroyedSurfaceReuse) { // add another sync since we are deferring the display destruction t.apply(true); - sp surface = new Surface(mProducer); + sp surface = sp::make(mProducer); sp window(surface); ASSERT_EQ(NO_ERROR, native_window_api_connect(window.get(), NATIVE_WINDOW_API_EGL)); diff --git a/services/surfaceflinger/tests/WindowInfosListener_test.cpp b/services/surfaceflinger/tests/WindowInfosListener_test.cpp index bb522451a67b12db3d2585e81df738007ca2a1e2..ad9a674456e02e171a8bb173143917a91e0ee6ac 100644 --- a/services/surfaceflinger/tests/WindowInfosListener_test.cpp +++ b/services/surfaceflinger/tests/WindowInfosListener_test.cpp @@ -16,68 +16,40 @@ #include #include +#include #include +#include #include -#include "utils/TransactionUtils.h" +#include "utils/WindowInfosListenerUtils.h" namespace android { using Transaction = SurfaceComposerClient::Transaction; using gui::DisplayInfo; using gui::WindowInfo; +constexpr auto findMatchingWindowInfo = WindowInfosListenerUtils::findMatchingWindowInfo; + +using WindowInfosPredicate = std::function&)>; class WindowInfosListenerTest : public ::testing::Test { protected: void SetUp() override { seteuid(AID_SYSTEM); - mClient = new SurfaceComposerClient; - mWindowInfosListener = new SyncWindowInfosListener(); - mClient->addWindowInfosListener(mWindowInfosListener); + mClient = sp::make(); } - void TearDown() override { - mClient->removeWindowInfosListener(mWindowInfosListener); - seteuid(AID_ROOT); - } - - struct SyncWindowInfosListener : public gui::WindowInfosListener { - public: - void onWindowInfosChanged(const std::vector& windowInfos, - const std::vector&) override { - windowInfosPromise.set_value(windowInfos); - } - - std::vector waitForWindowInfos() { - std::future> windowInfosFuture = - windowInfosPromise.get_future(); - std::vector windowInfos = windowInfosFuture.get(); - windowInfosPromise = std::promise>(); - return windowInfos; - } - - private: - std::promise> windowInfosPromise; - }; + void TearDown() override { seteuid(AID_ROOT); } sp mClient; - sp mWindowInfosListener; -}; + WindowInfosListenerUtils mWindowInfosListenerUtils; -std::optional findMatchingWindowInfo(WindowInfo targetWindowInfo, - std::vector windowInfos) { - std::optional foundWindowInfo = std::nullopt; - for (WindowInfo windowInfo : windowInfos) { - if (windowInfo.token == targetWindowInfo.token) { - foundWindowInfo = std::make_optional<>(windowInfo); - break; - } + bool waitForWindowInfosPredicate(const WindowInfosPredicate& predicate) { + return mWindowInfosListenerUtils.waitForWindowInfosPredicate(std::move(predicate)); } - - return foundWindowInfo; -} +}; TEST_F(WindowInfosListenerTest, WindowInfoAddedAndRemoved) { std::string name = "Test Layer"; - sp token = new BBinder(); + sp token = sp::make(); WindowInfo windowInfo; windowInfo.name = name; windowInfo.token = token; @@ -92,20 +64,22 @@ TEST_F(WindowInfosListenerTest, WindowInfoAddedAndRemoved) { .setInputWindowInfo(surfaceControl, windowInfo) .apply(); - std::vector windowInfos = mWindowInfosListener->waitForWindowInfos(); - std::optional foundWindowInfo = findMatchingWindowInfo(windowInfo, windowInfos); - ASSERT_NE(std::nullopt, foundWindowInfo); + auto windowPresent = [&](const std::vector& windowInfos) { + return findMatchingWindowInfo(windowInfo, windowInfos); + }; + ASSERT_TRUE(waitForWindowInfosPredicate(windowPresent)); Transaction().reparent(surfaceControl, nullptr).apply(); - windowInfos = mWindowInfosListener->waitForWindowInfos(); - foundWindowInfo = findMatchingWindowInfo(windowInfo, windowInfos); - ASSERT_EQ(std::nullopt, foundWindowInfo); + auto windowNotPresent = [&](const std::vector& windowInfos) { + return !findMatchingWindowInfo(windowInfo, windowInfos); + }; + ASSERT_TRUE(waitForWindowInfosPredicate(windowNotPresent)); } TEST_F(WindowInfosListenerTest, WindowInfoChanged) { std::string name = "Test Layer"; - sp token = new BBinder(); + sp token = sp::make(); WindowInfo windowInfo; windowInfo.name = name; windowInfo.token = token; @@ -121,19 +95,30 @@ TEST_F(WindowInfosListenerTest, WindowInfoChanged) { .setInputWindowInfo(surfaceControl, windowInfo) .apply(); - std::vector windowInfos = mWindowInfosListener->waitForWindowInfos(); - std::optional foundWindowInfo = findMatchingWindowInfo(windowInfo, windowInfos); - ASSERT_NE(std::nullopt, foundWindowInfo); - ASSERT_TRUE(foundWindowInfo->touchableRegion.isEmpty()); + auto windowIsPresentAndTouchableRegionEmpty = [&](const std::vector& windowInfos) { + auto foundWindowInfo = findMatchingWindowInfo(windowInfo, windowInfos); + if (!foundWindowInfo) { + return false; + } + return foundWindowInfo->touchableRegion.isEmpty(); + }; + ASSERT_TRUE(waitForWindowInfosPredicate(windowIsPresentAndTouchableRegionEmpty)); - Rect touchableRegions(0, 0, 50, 50); - windowInfo.addTouchableRegion(Rect(0, 0, 50, 50)); + windowInfo.addTouchableRegion({0, 0, 50, 50}); Transaction().setInputWindowInfo(surfaceControl, windowInfo).apply(); - windowInfos = mWindowInfosListener->waitForWindowInfos(); - foundWindowInfo = findMatchingWindowInfo(windowInfo, windowInfos); - ASSERT_NE(std::nullopt, foundWindowInfo); - ASSERT_TRUE(foundWindowInfo->touchableRegion.hasSameRects(windowInfo.touchableRegion)); + auto windowIsPresentAndTouchableRegionMatches = + [&](const std::vector& windowInfos) { + auto foundWindowInfo = findMatchingWindowInfo(windowInfo, windowInfos); + if (!foundWindowInfo) { + return false; + } + + auto touchableRegion = + foundWindowInfo->transform.transform(foundWindowInfo->touchableRegion); + return touchableRegion.hasSameRects(windowInfo.touchableRegion); + }; + ASSERT_TRUE(waitForWindowInfosPredicate(windowIsPresentAndTouchableRegionMatches)); } } // namespace android diff --git a/services/surfaceflinger/tests/fakehwc/Android.bp b/services/surfaceflinger/tests/fakehwc/Android.bp deleted file mode 100644 index 704815de3a38c0db02d92836d1146e180c59f323..0000000000000000000000000000000000000000 --- a/services/surfaceflinger/tests/fakehwc/Android.bp +++ /dev/null @@ -1,63 +0,0 @@ -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: "sffakehwc_test", - defaults: ["surfaceflinger_defaults"], - test_suites: ["device-tests"], - srcs: [ - "FakeComposerClient.cpp", - "FakeComposerService.cpp", - "FakeComposerUtils.cpp", - "SFFakeHwc_test.cpp", - ], - require_root: true, - shared_libs: [ - "android.hardware.graphics.composer@2.1", - "android.hardware.graphics.composer@2.2", - "android.hardware.graphics.composer@2.3", - "android.hardware.graphics.composer@2.4", - "android.hardware.graphics.composer3-V1-ndk", - "android.hardware.graphics.mapper@2.0", - "android.hardware.graphics.mapper@3.0", - "android.hardware.graphics.mapper@4.0", - "android.hardware.power@1.3", - "android.hardware.power-V2-cpp", - "libbase", - "libbinder", - "libbinder_ndk", - "libcutils", - "libfmq", - "libgui", - "libhidlbase", - "liblayers_proto", - "liblog", - "libnativewindow", - "libsync", - "libtimestats", - "libui", - "libutils", - ], - static_libs: [ - "android.hardware.graphics.composer@2.1-resources", - "libaidlcommonsupport", - "libcompositionengine", - "libgmock", - "libperfetto_client_experimental", - "librenderengine", - "libtrace_proto", - "libaidlcommonsupport", - ], - header_libs: [ - "android.hardware.graphics.composer@2.4-command-buffer", - "android.hardware.graphics.composer@2.4-hal", - "android.hardware.graphics.composer3-command-buffer", - "libsurfaceflinger_headers", - ], -} diff --git a/services/surfaceflinger/tests/fakehwc/FakeComposerClient.cpp b/services/surfaceflinger/tests/fakehwc/FakeComposerClient.cpp deleted file mode 100644 index b38032d265f1d434c5d82c28404b20722c53e24e..0000000000000000000000000000000000000000 --- a/services/surfaceflinger/tests/fakehwc/FakeComposerClient.cpp +++ /dev/null @@ -1,927 +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. - */ - -// TODO(b/129481165): remove the #pragma below and fix conversion issues -#pragma clang diagnostic push -#pragma clang diagnostic ignored "-Wconversion" - -//#define LOG_NDEBUG 0 -#undef LOG_TAG -#define LOG_TAG "FakeComposer" - -#include "FakeComposerClient.h" - -#include - -#include - -#include - -#include -#include -#include -#include -#include -#include -#include -#include - -constexpr Config NULL_DISPLAY_CONFIG = static_cast(0); - -using namespace sftest; - -using android::Condition; -using android::Mutex; - -using Clock = std::chrono::steady_clock; -using TimePoint = std::chrono::time_point; - -namespace { - -// Internal state of a layer in the HWC API. -class LayerImpl { -public: - LayerImpl() = default; - - bool mValid = true; - RenderState mRenderState; - uint32_t mZ = 0; -}; - -// Struct for storing per frame rectangle state. Contains the render -// state shared to the test case. Basically a snapshot and a subset of -// LayerImpl sufficient to re-create the pixels of a layer for the -// frame. -struct FrameRect { -public: - FrameRect(Layer layer_, const RenderState& state, uint32_t z_) - : layer(layer_), renderState(state), z(z_) {} - - const Layer layer; - const RenderState renderState; - const uint32_t z; -}; - -// Collection of FrameRects forming one rendered frame. Could store -// related fences and other data in the future. -class Frame { -public: - Frame() = default; - std::vector> rectangles; -}; - -class DelayedEventGenerator { -public: - explicit DelayedEventGenerator(std::function onTimerExpired) - : mOnTimerExpired(onTimerExpired), mThread([this]() { loop(); }) {} - - ~DelayedEventGenerator() { - ALOGI("DelayedEventGenerator exiting."); - { - std::unique_lock lock(mMutex); - mRunning = false; - mWakeups.clear(); - mCondition.notify_one(); - } - mThread.join(); - ALOGI("DelayedEventGenerator exited."); - } - - void wakeAfter(std::chrono::nanoseconds waitTime) { - std::unique_lock lock(mMutex); - mWakeups.insert(Clock::now() + waitTime); - mCondition.notify_one(); - } - -private: - void loop() { - while (true) { - // Lock scope - { - std::unique_lock lock(mMutex); - mCondition.wait(lock, [this]() { return !mRunning || !mWakeups.empty(); }); - if (!mRunning && mWakeups.empty()) { - // This thread should only exit once the destructor has been called and all - // wakeups have been processed - return; - } - - // At this point, mWakeups will not be empty - - TimePoint target = *(mWakeups.begin()); - auto status = mCondition.wait_until(lock, target); - while (status == std::cv_status::no_timeout) { - // This was either a spurious wakeup or another wakeup was added, so grab the - // oldest point and wait again - target = *(mWakeups.begin()); - status = mCondition.wait_until(lock, target); - } - - // status must have been timeout, so we can finally clear this point - mWakeups.erase(target); - } - // Callback *without* locks! - mOnTimerExpired(); - } - } - - std::function mOnTimerExpired; - std::thread mThread; - std::mutex mMutex; - std::condition_variable mCondition; - bool mRunning = true; - std::set mWakeups; -}; - -} // namespace - -FakeComposerClient::FakeComposerClient() - : mEventCallback(nullptr), - mEventCallback_2_4(nullptr), - mCurrentConfig(NULL_DISPLAY_CONFIG), - mVsyncEnabled(false), - mLayers(), - mDelayedEventGenerator( - std::make_unique([this]() { this->requestVSync(); })), - mSurfaceComposer(nullptr) {} - -FakeComposerClient::~FakeComposerClient() {} - -bool FakeComposerClient::hasCapability(hwc2_capability_t /*capability*/) { - return false; -} - -std::string FakeComposerClient::dumpDebugInfo() { - return {}; -} - -void FakeComposerClient::registerEventCallback(EventCallback* callback) { - ALOGV("registerEventCallback"); - LOG_FATAL_IF(mEventCallback_2_4 != nullptr, - "already registered using registerEventCallback_2_4"); - - mEventCallback = callback; - if (mEventCallback) { - mEventCallback->onHotplug(PRIMARY_DISPLAY, IComposerCallback::Connection::CONNECTED); - } -} - -void FakeComposerClient::unregisterEventCallback() { - ALOGV("unregisterEventCallback"); - mEventCallback = nullptr; -} - -void FakeComposerClient::hotplugDisplay(Display display, IComposerCallback::Connection state) { - if (mEventCallback) { - mEventCallback->onHotplug(display, state); - } else if (mEventCallback_2_4) { - mEventCallback_2_4->onHotplug(display, state); - } -} - -void FakeComposerClient::refreshDisplay(Display display) { - if (mEventCallback) { - mEventCallback->onRefresh(display); - } else if (mEventCallback_2_4) { - mEventCallback_2_4->onRefresh(display); - } -} - -uint32_t FakeComposerClient::getMaxVirtualDisplayCount() { - ALOGV("getMaxVirtualDisplayCount"); - return 1; -} - -V2_1::Error FakeComposerClient::createVirtualDisplay(uint32_t /*width*/, uint32_t /*height*/, - V1_0::PixelFormat* /*format*/, - Display* /*outDisplay*/) { - ALOGV("createVirtualDisplay"); - return V2_1::Error::NONE; -} - -V2_1::Error FakeComposerClient::destroyVirtualDisplay(Display /*display*/) { - ALOGV("destroyVirtualDisplay"); - return V2_1::Error::NONE; -} - -V2_1::Error FakeComposerClient::createLayer(Display /*display*/, Layer* outLayer) { - ALOGV("createLayer"); - *outLayer = mLayers.size(); - auto newLayer = std::make_unique(); - mLayers.push_back(std::move(newLayer)); - return V2_1::Error::NONE; -} - -V2_1::Error FakeComposerClient::destroyLayer(Display /*display*/, Layer layer) { - ALOGV("destroyLayer"); - mLayers[layer]->mValid = false; - return V2_1::Error::NONE; -} - -V2_1::Error FakeComposerClient::getActiveConfig(Display display, Config* outConfig) { - ALOGV("getActiveConfig"); - if (mMockHal) { - return mMockHal->getActiveConfig(display, outConfig); - } - - // TODO Assert outConfig != nullptr - - // TODO This is my reading of the - // IComposerClient::getActiveConfig, but returning BAD_CONFIG - // seems to not fit SurfaceFlinger plans. See version 2 below. - // if (mCurrentConfig == NULL_DISPLAY_CONFIG) { - // return V2_1::Error::BAD_CONFIG; - // } - //*outConfig = mCurrentConfig; - *outConfig = 1; // Very special config for you my friend - return V2_1::Error::NONE; -} - -V2_1::Error FakeComposerClient::getClientTargetSupport(Display /*display*/, uint32_t /*width*/, - uint32_t /*height*/, - V1_0::PixelFormat /*format*/, - V1_0::Dataspace /*dataspace*/) { - ALOGV("getClientTargetSupport"); - return V2_1::Error::NONE; -} - -V2_1::Error FakeComposerClient::getColorModes(Display /*display*/, - hidl_vec* /*outModes*/) { - ALOGV("getColorModes"); - return V2_1::Error::NONE; -} - -V2_1::Error FakeComposerClient::getDisplayAttribute(Display display, Config config, - V2_1::IComposerClient::Attribute attribute, - int32_t* outValue) { - auto tmpError = - getDisplayAttribute_2_4(display, config, - static_cast(attribute), outValue); - return static_cast(tmpError); -} - -V2_1::Error FakeComposerClient::getDisplayConfigs(Display display, hidl_vec* outConfigs) { - ALOGV("getDisplayConfigs"); - if (mMockHal) { - return mMockHal->getDisplayConfigs(display, outConfigs); - } - - // TODO assert display == 1, outConfigs != nullptr - - outConfigs->resize(1); - (*outConfigs)[0] = 1; - - return V2_1::Error::NONE; -} - -V2_1::Error FakeComposerClient::getDisplayName(Display /*display*/, hidl_string* /*outName*/) { - ALOGV("getDisplayName"); - return V2_1::Error::NONE; -} - -V2_1::Error FakeComposerClient::getDisplayType(Display /*display*/, - IComposerClient::DisplayType* outType) { - ALOGV("getDisplayType"); - // TODO: This setting nothing on the output had no effect on initial trials. Is first display - // assumed to be physical? - *outType = static_cast(HWC2_DISPLAY_TYPE_PHYSICAL); - return V2_1::Error::NONE; -} - -V2_1::Error FakeComposerClient::getDozeSupport(Display /*display*/, bool* /*outSupport*/) { - ALOGV("getDozeSupport"); - return V2_1::Error::NONE; -} - -V2_1::Error FakeComposerClient::getHdrCapabilities(Display /*display*/, - hidl_vec* /*outTypes*/, - float* /*outMaxLuminance*/, - float* /*outMaxAverageLuminance*/, - float* /*outMinLuminance*/) { - ALOGV("getHdrCapabilities"); - return V2_1::Error::NONE; -} - -V2_1::Error FakeComposerClient::setActiveConfig(Display display, Config config) { - ALOGV("setActiveConfig"); - if (mMockHal) { - return mMockHal->setActiveConfig(display, config); - } - mCurrentConfig = config; - return V2_1::Error::NONE; -} - -V2_1::Error FakeComposerClient::setColorMode(Display /*display*/, V1_0::ColorMode /*mode*/) { - ALOGV("setColorMode"); - return V2_1::Error::NONE; -} - -V2_1::Error FakeComposerClient::setPowerMode(Display /*display*/, - V2_1::IComposerClient::PowerMode /*mode*/) { - ALOGV("setPowerMode"); - return V2_1::Error::NONE; -} - -V2_1::Error FakeComposerClient::setVsyncEnabled(Display /*display*/, - IComposerClient::Vsync enabled) { - mVsyncEnabled = (enabled == IComposerClient::Vsync::ENABLE); - ALOGV("setVsyncEnabled(%s)", mVsyncEnabled ? "ENABLE" : "DISABLE"); - return V2_1::Error::NONE; -} - -V2_1::Error FakeComposerClient::setColorTransform(Display /*display*/, const float* /*matrix*/, - int32_t /*hint*/) { - ALOGV("setColorTransform"); - return V2_1::Error::NONE; -} - -V2_1::Error FakeComposerClient::setClientTarget(Display /*display*/, buffer_handle_t /*target*/, - int32_t /*acquireFence*/, int32_t /*dataspace*/, - const std::vector& /*damage*/) { - ALOGV("setClientTarget"); - return V2_1::Error::NONE; -} - -V2_1::Error FakeComposerClient::setOutputBuffer(Display /*display*/, buffer_handle_t /*buffer*/, - int32_t /*releaseFence*/) { - ALOGV("setOutputBuffer"); - return V2_1::Error::NONE; -} - -V2_1::Error FakeComposerClient::validateDisplay( - Display /*display*/, std::vector* /*outChangedLayers*/, - std::vector* /*outCompositionTypes*/, - uint32_t* /*outDisplayRequestMask*/, std::vector* /*outRequestedLayers*/, - std::vector* /*outRequestMasks*/) { - ALOGV("validateDisplay"); - // TODO: Assume touching nothing means All Korrekt! - return V2_1::Error::NONE; -} - -V2_1::Error FakeComposerClient::acceptDisplayChanges(Display /*display*/) { - ALOGV("acceptDisplayChanges"); - // Didn't ask for changes because software is omnipotent. - return V2_1::Error::NONE; -} - -bool layerZOrdering(const std::unique_ptr& a, const std::unique_ptr& b) { - return a->z <= b->z; -} - -V2_1::Error FakeComposerClient::presentDisplay(Display /*display*/, int32_t* /*outPresentFence*/, - std::vector* /*outLayers*/, - std::vector* /*outReleaseFences*/) { - ALOGV("presentDisplay"); - // TODO Leaving layers and their fences out for now. Doing so - // means that we've already processed everything. Important to - // test that the fences are respected, though. (How?) - - std::unique_ptr